mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 10:04:33 +02:00
Compare commits
1486 commits
v0.11.0-al
...
main
Author | SHA1 | Date | |
---|---|---|---|
0e81bddedd | |||
e09b257936 | |||
fbd625cf5b | |||
eccd0e12a7 | |||
c2ff18b111 | |||
83d06259a7 | |||
bea6c086af | |||
b11073521b | |||
329ce9dc20 | |||
b0e46fb998 | |||
33f0d2a160 | |||
7811aae324 | |||
f5d0b90eda | |||
5f1597c27f | |||
bdd8888f1b | |||
e24a64ba25 | |||
ef4ea345fd | |||
ea27044d91 | |||
cb70ed8445 | |||
9fe59ffaa1 | |||
6fb092dd58 | |||
3cb329b379 | |||
8c224ab7dc | |||
577da699a8 | |||
b28ee0aa85 | |||
cc6b3eb958 | |||
ff6ef4b130 | |||
b0c0940e5d | |||
e57d3cf665 | |||
6ed283a5eb | |||
f308134276 | |||
bb8da6f5d8 | |||
aaa2084bfd | |||
9da3fb80f0 | |||
5ed11d771f | |||
adb0c82332 | |||
8d74566b6c | |||
363b90da07 | |||
34747356c9 | |||
03cc026965 | |||
6178454250 | |||
40f7337b6c | |||
0beb4aea23 | |||
6562a44175 | |||
d2b3f3a36e | |||
0f3611fb15 | |||
4d3a0832d5 | |||
24f41edb63 | |||
7714c7aeb6 | |||
6b4bdb4d7d | |||
cbc928f117 | |||
216799fe2b | |||
01b1977630 | |||
41d9642eb1 | |||
787cb2d862 | |||
fbf4e68d9a | |||
791e9ab9c6 | |||
98dada2af5 | |||
4f4c678eac | |||
f5de99a25b | |||
c8fa826e60 | |||
7bed6041f3 | |||
b58749a8a5 | |||
dd703b4bbd | |||
6b7146e02c | |||
2b32fb740c | |||
e4744a113f | |||
9eb3e3f889 | |||
6dc09c4d1b | |||
919b9c6eeb | |||
71c62a2f2c | |||
6d8f81ea89 | |||
de25c95a05 | |||
3a095a00ae | |||
69672482b3 | |||
c4ab8dfe4a | |||
09a87ab9d5 | |||
248bc596cc | |||
e5215a838e | |||
d48f457fd5 | |||
d5eea3c99c | |||
ec6b16b672 | |||
4de818f3a6 | |||
dc67b5632d | |||
6b7327df3d | |||
0bd6f6c9f9 | |||
20b1fe7e2d | |||
1599eb1515 | |||
61e861ee06 | |||
0e07697a98 | |||
11e51afe07 | |||
f63c597e80 | |||
454c4758d0 | |||
184caf57ab | |||
e65e3a984c | |||
e9ee0e600d | |||
dd964474ee | |||
6b693c6759 | |||
d2ee01479a | |||
9520bb4689 | |||
90790bd8e0 | |||
de82f649b1 | |||
d078394af4 | |||
a258f4b16f | |||
eeb72faf83 | |||
0240c6cd1d | |||
7e1d6f6100 | |||
01f3cefe55 | |||
659b1c0c56 | |||
1c6109a25a | |||
f6170604a6 | |||
ffb3db775f | |||
d90ac7cec0 | |||
34dcdd5d8a | |||
bab8aad126 | |||
d4275642d7 | |||
7ff95571c3 | |||
297be826dd | |||
e529bbb395 | |||
1b927ad183 | |||
8081a44e92 | |||
470503eb5c | |||
e221637394 | |||
14cbd46a88 | |||
2ee4776693 | |||
b2657bcbbd | |||
515f895ed3 | |||
6c9439ac70 | |||
2f21dc6abc | |||
2ea6a4fe13 | |||
84504f734f | |||
100c871cd9 | |||
e1b5872e26 | |||
744ed425c3 | |||
2b21aab26b | |||
c369037deb | |||
c8e6e11d84 | |||
05a274edeb | |||
bdaed0e873 | |||
6566412d64 | |||
25de300970 | |||
2f07441fc6 | |||
fd9fb81fa2 | |||
9706260933 | |||
4a34386c26 | |||
419f7732df | |||
59778165bf | |||
0c322bacf8 | |||
e7ce198fa1 | |||
dd4ca1903b | |||
956cede593 | |||
374f400f30 | |||
36890fe131 | |||
d3d09d4019 | |||
f307ed779f | |||
7ecccabcc0 | |||
33ad50fbe2 | |||
3574f76a79 | |||
0100104afb | |||
89f61c15fd | |||
7eb9fa1dc5 | |||
451ec404e9 | |||
e64d71b135 | |||
22f0226600 | |||
836f4b99d8 | |||
c64ea890d1 | |||
6c147106df | |||
6f64b61ea5 | |||
e284a58db9 | |||
8ccff438d0 | |||
cf06bbffea | |||
a6eec88337 | |||
f33222794b | |||
9db78d1031 | |||
0349cf1b7c | |||
fbcc49019f | |||
8b65b6240b | |||
98d8812571 | |||
c42ac793b9 | |||
2156a8fa8f | |||
ba5fbd3a71 | |||
5c232a2165 | |||
31a93b3183 | |||
fea294b26f | |||
f76dc5da14 | |||
f1971cdf4f | |||
1337a4a0c8 | |||
347a916a34 | |||
b1a1a40e97 | |||
4405293a90 | |||
d95bea09a8 | |||
b7563040ef | |||
30de19f729 | |||
2c24545e68 | |||
35276b01ef | |||
10be8db7d0 | |||
1cfae172bb | |||
4a3a0e4893 | |||
fde3de12d6 | |||
b002e7136e | |||
25bd486f8e | |||
9969b214d9 | |||
084c1143ee | |||
67a3ab31e7 | |||
91941eec7b | |||
6db6c486a3 | |||
d87579f9ac | |||
ee6ca66602 | |||
0213e538fa | |||
796d1c0566 | |||
47242fb33e | |||
70228054fd | |||
f8bff53eae | |||
2bfc8c345a | |||
7d33c0aad9 | |||
dbc959d06e | |||
3a84ec0bbb | |||
7a09f94767 | |||
25dedfde6c | |||
e90d1f7716 | |||
273914d714 | |||
44d24123b1 | |||
d7bd9c86a8 | |||
303ac0d6ef | |||
b52d55285c | |||
5ef97720b0 | |||
c329070606 | |||
7f86ab2ece | |||
5bee82a17c | |||
657a6a8967 | |||
404585ebe0 | |||
c26f9cb9ba | |||
7eaac1cc29 | |||
a07249213e | |||
b69e485f10 | |||
fc055671cb | |||
916c2a4a63 | |||
8d47d4890e | |||
b569cbc315 | |||
4f50b802ac | |||
0e1986b2da | |||
390817caa7 | |||
11a27bcbd7 | |||
877b517b72 | |||
9a8ebfef77 | |||
b017f39133 | |||
6dd78bf8fc | |||
fffc59b0a6 | |||
12d1010da9 | |||
3b505d24f2 | |||
fc9e848f1f | |||
f437b2a01b | |||
0738fe1454 | |||
321dda3fd9 | |||
37bc01e426 | |||
f11c61c8a6 | |||
acb2bb4e9b | |||
910690178e | |||
950eab71fe | |||
38c54b575a | |||
c15e489b1c | |||
923ab36668 | |||
21ab92f873 | |||
75c5227536 | |||
fa9d684853 | |||
a0525ef01b | |||
bb668a1ac9 | |||
8332690c67 | |||
139bdc7699 | |||
2ccabc28bc | |||
8525a7a321 | |||
045cb38a63 | |||
9734ce9a1c | |||
64e5e35b6f | |||
c3d558a07a | |||
a955d45518 | |||
7099d2e379 | |||
5093abc1dc | |||
704186c3c2 | |||
f239a1a3be | |||
fd63cc7fa7 | |||
74d79204ec | |||
ec34a690e9 | |||
17bce4b0db | |||
d38b3bc672 | |||
c89a809e6d | |||
d906d9ab5e | |||
a6c671e113 | |||
2f977e748b | |||
ba8a8bc7b9 | |||
3b52312150 | |||
f4b4f6b398 | |||
34101b8a20 | |||
3c9c115931 | |||
13e5f98b06 | |||
e58cc7a073 | |||
c650b3c94b | |||
6b0e06481c | |||
beb8317f94 | |||
4cf8097c83 | |||
96a300ada5 | |||
4b225ce6f4 | |||
3773ccb657 | |||
526911b54f | |||
dfdd639073 | |||
e658de3811 | |||
88c13c8129 | |||
ff624a4c9a | |||
c8ae7487f7 | |||
c33d23f326 | |||
8be087e4fc | |||
0e111e628b | |||
33465a1169 | |||
9e617b2da5 | |||
1371cadc02 | |||
8fefc11380 | |||
8ef8744ba1 | |||
0a9ce0d202 | |||
127f4db1a1 | |||
634cb35048 | |||
0502506b90 | |||
6670e0a6d1 | |||
1449869c0f | |||
cb8d17d006 | |||
d21100a14b | |||
6cce06eb2e | |||
6a58807227 | |||
01e4385c40 | |||
f526a9adb3 | |||
09a0bf0c0d | |||
1f38267626 | |||
8d62550775 | |||
8d7b961f4e | |||
463a1766f1 | |||
b5bb7acab6 | |||
e445d23968 | |||
77d95baf00 | |||
cf37622365 | |||
4f3b4b2762 | |||
243dc771fd | |||
a99bece107 | |||
d81305d019 | |||
34e4578633 | |||
4b1b351711 | |||
a25419275b | |||
8865897260 | |||
5ec2cd4dc6 | |||
127e5a4120 | |||
6c8e03c297 | |||
fb8e4b5170 | |||
eaffe29512 | |||
eae7edddb4 | |||
dbd0081cf6 | |||
b2f6464645 | |||
39b401f848 | |||
74f6482bb0 | |||
b7e0e20829 | |||
0f5736cdcc | |||
f003855e8a | |||
cb095ebae5 | |||
b389ca4e7f | |||
22f8ea1ce8 | |||
4ebcd1c7f6 | |||
f13f06c795 | |||
4a6e87efd7 | |||
ba6f07407c | |||
08c2b7809a | |||
d42b25d6eb | |||
c6eb7b71b2 | |||
d50b3b4db7 | |||
b619e8506a | |||
cf35520526 | |||
b7567ac33e | |||
7dc113948d | |||
81e9559dba | |||
cd4ccd5cf7 | |||
b70a209899 | |||
6a352e63b7 | |||
93eea0bd8b | |||
21ccd4dde1 | |||
b78d96bd66 | |||
87d7e73615 | |||
12b1f9e436 | |||
92039f63df | |||
79471dc151 | |||
a979b84339 | |||
1bba117db2 | |||
718ffd5ee7 | |||
d06cdd5649 | |||
9d44a47317 | |||
058eacd07a | |||
f4e11f190c | |||
d86d36a16e | |||
f8c3423018 | |||
03adceca23 | |||
e279f45229 | |||
8f5b4d67fe | |||
f5a3f96520 | |||
89faf52e57 | |||
4ccc509992 | |||
dde0a6e812 | |||
4aaf91d187 | |||
31363c9de2 | |||
1bbfa6f22b | |||
69a7705043 | |||
a66e875be2 | |||
b994a2ae4f | |||
041b477a4a | |||
20dd7cda2c | |||
9c33b28fdb | |||
70b73b0f7d | |||
eea52c55b6 | |||
b4bb4ef241 | |||
eec3f42ce0 | |||
ce87464d23 | |||
5f61991ab8 | |||
c067967d21 | |||
0fb6f40ee6 | |||
39d28291bd | |||
fdcaf7815a | |||
2e9344bba7 | |||
30bb0ba828 | |||
6104799d78 | |||
b1d25bdc44 | |||
5d4544f436 | |||
2685c7d5d5 | |||
beec229360 | |||
bc0e0fceb5 | |||
2a98731b5d | |||
eb3a74e146 | |||
01714c7691 | |||
5a9e7efd7b | |||
a847648b51 | |||
6928ab14cc | |||
7a9ee28bf7 | |||
67001ac842 | |||
5128a8c9f4 | |||
a37498d84e | |||
aa3034411d | |||
1f28074566 | |||
b62d3e3dc9 | |||
ac77e1edbd | |||
53fcc6cc56 | |||
90b0ff06e1 | |||
f4de64d3be | |||
53a34eaaa2 | |||
9d2332477f | |||
965dd5e29d | |||
83ecc2bca1 | |||
5402f4d9b1 | |||
e7cbb7b514 | |||
f83751cc80 | |||
857916285d | |||
26478ef0a4 | |||
47363b2521 | |||
a31fce3301 | |||
8d81e89d09 | |||
cd1a37ddbe | |||
552a5a9ac8 | |||
98f48295d0 | |||
f0a8b92d1b | |||
fadb14ad2f | |||
2e2444eb47 | |||
23d260a351 | |||
334bfa5281 | |||
9d9f54a006 | |||
49dc83553e | |||
c5b0378a80 | |||
236fb17376 | |||
10ab050346 | |||
a4aac80fac | |||
2fa50ccc8a | |||
9d86bb69bf | |||
789ae8ea79 | |||
ef79fc620a | |||
cc7dd28447 | |||
9020f78af9 | |||
a306914e8b | |||
03d1fb5dcb | |||
85dccb2962 | |||
c2387e0bf2 | |||
c64e68bc38 | |||
04c9dd31fa | |||
0de2b4ca44 | |||
2f0f7da8f4 | |||
c5aa759af8 | |||
6b61dbc3c4 | |||
e6f08d25cc | |||
fe4312007c | |||
4562f173d3 | |||
6bc3564a5e | |||
ef9ec3903a | |||
60453f5348 | |||
4778ff9a81 | |||
5117104049 | |||
abd06ac114 | |||
66e23be799 | |||
6f39c720dc | |||
7a95885009 | |||
0a84f35fdf | |||
b6aea495cb | |||
2d8416e34e | |||
05ca724374 | |||
8ae107393e | |||
ebca695503 | |||
e40007c755 | |||
9b407d5e96 | |||
318a3498bb | |||
1bf1b5a1c3 | |||
cefa80b647 | |||
9ce6f5726e | |||
64e115d4f3 | |||
49c34773d6 | |||
76ab882d0f | |||
518a739f21 | |||
467f015cf1 | |||
022d7ef258 | |||
b941eda41a | |||
33e2d63de1 | |||
eb3fb5e222 | |||
0c7fc3ebf2 | |||
d5567220f7 | |||
da9301c211 | |||
8b0ce23beb | |||
c2f9edae30 | |||
3a82e51a4f | |||
e47b644bf9 | |||
8778c17d88 | |||
de42481e24 | |||
cce39a161f | |||
3c353f51e3 | |||
fe01b064c3 | |||
7d79a3b74b | |||
8fb5f51ae0 | |||
f67f62f3ad | |||
64b5c2aa1a | |||
1c4402094a | |||
1a0e1fde1e | |||
95cf9c2056 | |||
6773fee6eb | |||
78f5c9d460 | |||
3713018f25 | |||
3eb09c5ac0 | |||
0ca8ee854a | |||
e75992869d | |||
e7431249e1 | |||
ef449d3ec3 | |||
d071075c55 | |||
640959ffa5 | |||
bcddc20f33 | |||
8736613277 | |||
f9c92fcbe9 | |||
c5214bc083 | |||
7b126afd35 | |||
f4326e196c | |||
667b0f7f15 | |||
6f39d434a7 | |||
ff34ef2d0d | |||
db07de5b2d | |||
fc38f79674 | |||
5ae2ec9d28 | |||
a1164eea60 | |||
87002745a5 | |||
f85e032e2f | |||
4a1b2849fa | |||
e63775f433 | |||
baae3a5bb4 | |||
543cbbdff8 | |||
9ad9e9b8c8 | |||
e23c2e5cdd | |||
cbfc141c89 | |||
a4dd5a25cf | |||
a1c92a0a03 | |||
eff0383202 | |||
80b21e6bef | |||
7159b0e86b | |||
22382a182b | |||
ad7bfb2e41 | |||
db64b34e09 | |||
0be22c33dd | |||
b5b1db99a1 | |||
da881d8bd2 | |||
8e78111b67 | |||
7cc5841d83 | |||
3e54a57e24 | |||
368afc0501 | |||
1a927f66c7 | |||
8fa3fbea56 | |||
20b9988bb9 | |||
1637b7bd59 | |||
15d6ba4fca | |||
a246a11d53 | |||
bd406b0f67 | |||
15b186f404 | |||
7c22d2f44b | |||
e3f158079c | |||
20c9c49b9c | |||
a26d152d92 | |||
1cfd8ab911 | |||
9f41476a85 | |||
a3821a14ad | |||
a94bad4a67 | |||
67211949ca | |||
dfd76eb1cd | |||
1694472d60 | |||
19e0848211 | |||
7285145071 | |||
c081261cfb | |||
750d6676d3 | |||
072450ecc4 | |||
366fad678d | |||
678c4ae09e | |||
c5f7238f5e | |||
ad40c595b6 | |||
366a871f86 | |||
2481bd4deb | |||
6877c61482 | |||
9be1685746 | |||
accde1fda3 | |||
5bc53c39f0 | |||
387a72f6f3 | |||
f4b5be5b49 | |||
dba2e5a3b2 | |||
d20e9e7494 | |||
57d69f88a1 | |||
3092fc5202 | |||
bdce742dcf | |||
d11e1fe0e1 | |||
cc92fcc214 | |||
b579cdb8c3 | |||
210ec62ba3 | |||
d673d20a01 | |||
06f8bc64ac | |||
ee1cff9588 | |||
bc286b36a2 | |||
3d9a9c90b2 | |||
7ce160da69 | |||
f56790dbc3 | |||
2ccf41f674 | |||
013fa6ab2e | |||
7ee4f6b61f | |||
05e44d7983 | |||
a2c53b5496 | |||
7f81de7ea2 | |||
966821cef8 | |||
a1b2258bd2 | |||
5a42033a89 | |||
e2eed4d428 | |||
be592e49ae | |||
01123e9998 | |||
60c1b14116 | |||
54ddb69083 | |||
02e88f70fe | |||
ef8b309fa6 | |||
e22736ae02 | |||
ff777b212c | |||
79130edbe9 | |||
27fc465a3a | |||
dc832b8edd | |||
ca789cfcdc | |||
996e2b4200 | |||
6495336400 | |||
9dacf64601 | |||
85b81bb0dd | |||
8967d2ac91 | |||
ebf7e821fe | |||
c5aaeb61e1 | |||
f4f4f33d91 | |||
86d818fccd | |||
4aeba647f2 | |||
7eb8242964 | |||
46f125ea17 | |||
98765ab2a4 | |||
471255ede8 | |||
566f74b88a | |||
b7633ff7d9 | |||
908f111128 | |||
e5c5e87449 | |||
64fc26ae8e | |||
3e85268f39 | |||
88bf8dbc7a | |||
f33b7f7bae | |||
0801d0ac4d | |||
ec13228efb | |||
0272fa3ee3 | |||
2680d5d365 | |||
bb1965481d | |||
a016d07466 | |||
e40ff05190 | |||
424f4738c5 | |||
cec7403491 | |||
884927cacd | |||
f1f9620c7a | |||
7549e34d45 | |||
ca46949653 | |||
90ba20411a | |||
8a164a6b89 | |||
06ec029ee7 | |||
ee0e3e4ad3 | |||
5ac7e22b0b | |||
9b0cbf7eea | |||
13064cea1a | |||
fae47151cb | |||
fbc319a09a | |||
ca7e7f6558 | |||
d91b7763e8 | |||
43fdd83178 | |||
98f1f3c5ca | |||
7cd1be6588 | |||
1d5a0970c6 | |||
cc60391d26 | |||
2db37adf21 | |||
137ac189fc | |||
23c60e80e8 | |||
a2ee95e53a | |||
0b43cc8e48 | |||
e91cf49f21 | |||
bfc0c88154 | |||
d30f071c99 | |||
67483c6dc4 | |||
53955a8107 | |||
2faf398e4b | |||
c81698a698 | |||
60a899c2ba | |||
38ce7a9843 | |||
fa658ae52e | |||
c9436ddf19 | |||
4e404c0de6 | |||
7b712b9369 | |||
5b39540ab5 | |||
ac5e87bdcf | |||
085fd0c668 | |||
9392d8f2e1 | |||
bc4f7e7ef1 | |||
3c3c6a9ae9 | |||
6ab29ad687 | |||
a9cdd75f95 | |||
55fed0a954 | |||
d5e7f92333 | |||
0fffe86f05 | |||
674dd2e383 | |||
6dd1c396fc | |||
d9f9c724ea | |||
00888c4b59 | |||
b73756541b | |||
25aa026f08 | |||
6830d9dcb2 | |||
1f38a59bb8 | |||
1027ea1b98 | |||
5d8554df08 | |||
2143a2a5b9 | |||
ecfc3ef643 | |||
534291671e | |||
026c929fc0 | |||
073966122f | |||
0a59c4f616 | |||
a3b48d2bd9 | |||
726385361e | |||
6a596f1087 | |||
169990ed58 | |||
e3d9e7e24c | |||
1db19df71b | |||
fda687308b | |||
8d704ff7f7 | |||
d919696d24 | |||
5558173181 | |||
377048b90a | |||
6f2c5ac5f3 | |||
91a0f90a1e | |||
9474220e45 | |||
64f09b1b41 | |||
f87d19ca4a | |||
d60e555193 | |||
28bf27b2f0 | |||
b6f5528b29 | |||
88606131a7 | |||
8815fff6a7 | |||
0da364828b | |||
8bc8cdffcc | |||
5786f1f1dd | |||
ff91eb888c | |||
a849c83e1e | |||
b07e96302a | |||
8a731e7604 | |||
8d70378fc9 | |||
7b09894a4b | |||
6f28f4ef32 | |||
5789c7a65f | |||
9b6647c29e | |||
02deab90b7 | |||
6042263523 | |||
ec99882497 | |||
4dfb36f572 | |||
de4eb9355b | |||
b2a83568e1 | |||
6b7df9d0a6 | |||
8b41139b48 | |||
e26d512951 | |||
19eb6ee6e9 | |||
436420f854 | |||
85689a4d37 | |||
32317b373f | |||
2f2f9d956a | |||
31c833135e | |||
0457297200 | |||
2de4d32cbb | |||
0d3ec7aa4b | |||
a7b02a400f | |||
00e46c02ef | |||
6f7166201f | |||
6d80d15859 | |||
681bc5703f | |||
7394e8b31b | |||
b3519eef56 | |||
ad6f913aab | |||
ada8072ee9 | |||
30996c03b6 | |||
4036eb8e9b | |||
1640f6c66e | |||
2f197225a9 | |||
d600d999d2 | |||
8b7f7c47aa | |||
4d59311f66 | |||
a970f96165 | |||
b5268ec4f0 | |||
22e98a816f | |||
c4dbe0caef | |||
16f37677d5 | |||
b03a882cfa | |||
b42c40dd78 | |||
a3d05f3521 | |||
fd33ccbe1e | |||
0014462c63 | |||
3bb9dd3991 | |||
62ea8fa9b2 | |||
02c9710b92 | |||
cefd1b3efc | |||
4b87d95670 | |||
864589b647 | |||
e357c6a1dc | |||
463c8c3829 | |||
55d8b554aa | |||
9bbdaa055a | |||
672ec7a4a1 | |||
c98b37db96 | |||
bef2e80acc | |||
ba846013e8 | |||
ed469ee035 | |||
48260f2c09 | |||
4eec0b969a | |||
3060940808 | |||
d17878b123 | |||
227bc40a00 | |||
ec06389507 | |||
4c3b3d79af | |||
94f23d47b7 | |||
e839453806 | |||
a0759139ec | |||
8f1894da50 | |||
18534a87aa | |||
d35860cc54 | |||
2189f99918 | |||
0368c57d87 | |||
ac790c118c | |||
214d9b1efc | |||
69c5050060 | |||
a30d42f11b | |||
2e02186ea6 | |||
90a5dba302 | |||
7823321559 | |||
cefaea2369 | |||
00585db9b8 | |||
aefca6e175 | |||
ee41286ff7 | |||
6e16a1bc07 | |||
ae1dc1ccb8 | |||
e1d04fe3ee | |||
7ed5e617f1 | |||
0ca35be688 | |||
2abe3da834 | |||
4ea01f39b4 | |||
edfe2f3064 | |||
5b89ab5e53 | |||
6980aa4e2f | |||
17504e194c | |||
ac9c46680d | |||
4f98929446 | |||
63cdd7759e | |||
474d1d0bac | |||
bb5ee9a374 | |||
00991eb93c | |||
41e13770b8 | |||
d90fd7c243 | |||
46eef7a3b5 | |||
e3bca5bdc7 | |||
73f6bba630 | |||
f28e47f441 | |||
d8df718645 | |||
9b820f6562 | |||
30710fbb7e | |||
bdc21d1c53 | |||
305aaf1626 | |||
929f470988 | |||
b4170f5329 | |||
c80917187e | |||
2b722c184a | |||
cfb26f88e4 | |||
740c319a36 | |||
031749218c | |||
6380fb7110 | |||
acf47f8f96 | |||
9612106717 | |||
bd4e9bc233 | |||
ed7dc955c8 | |||
ae6d69ac2e | |||
0527cc65f5 | |||
b4bbab67fc | |||
f60725ce98 | |||
49e25d6cc3 | |||
2206ed2e44 | |||
a8a6a183c6 | |||
5842519e2a | |||
71ad71d0bf | |||
14a6dab29e | |||
1b47882a1d | |||
a290c854d5 | |||
bb0fc79b4f | |||
c689b93ee1 | |||
9074e8e5eb | |||
0b35d393f5 | |||
a55e9ba9f8 | |||
7a9fa80a1f | |||
26d815067a | |||
c2d09902d7 | |||
d752314a67 | |||
462e4162c4 | |||
bf89828c75 | |||
1fab9f8e53 | |||
000702740f | |||
ad11332d16 | |||
54cf0bef3f | |||
83fc9c50fe | |||
ed1070be79 | |||
e7586ac967 | |||
8c69b74fd8 | |||
5e90c2e823 | |||
4c08d5d04e | |||
22912c9c1e | |||
a2edf4967a | |||
5a58ef97bb | |||
6877e950ad | |||
1917ec077d | |||
0aa3b7c249 | |||
3fd2c402af | |||
645b159d73 | |||
ce12679f2d | |||
e6782b6aac | |||
db579504f0 | |||
c06bb4abfa | |||
e569e352ad | |||
5c6f486efd | |||
fbc4e807cf | |||
1881079f4a | |||
7a5a2c1f63 | |||
c1852a4d5f | |||
aafa3cd5ba | |||
90730a01df | |||
04a9f72de1 | |||
a6630234d8 | |||
1e787d8c82 | |||
efb1a8e61b | |||
b29c539d5f | |||
e7f8dc73e0 | |||
dc17d4c034 | |||
f01cc38e64 | |||
7a88875309 | |||
6829a9df85 | |||
97bc309bb6 | |||
75dfa4d28a | |||
72658837c9 | |||
0290c1a28c | |||
2f4bb6378b | |||
fe70516788 | |||
038e6acc4b | |||
8eb81b8a36 | |||
53355ef4a0 | |||
539e838b2a | |||
d91d9cc90d | |||
0599ba1593 | |||
01801766b6 | |||
d126117bd3 | |||
ea409bd888 | |||
7619c5de8c | |||
2578b2eaf0 | |||
b960dea9c2 | |||
b7b51ad469 | |||
ab43314619 | |||
02062e3e18 | |||
ca1257bab7 | |||
4d0bef497b | |||
16786d4a7e | |||
32e1aa7edb | |||
63273b7f02 | |||
f4b00ce68a | |||
b901b8efaa | |||
bf4855aa63 | |||
06e79e627e | |||
6020b69527 | |||
2dbf6883a0 | |||
b5b5170a67 | |||
c27c6db199 | |||
d5a1a74f1a | |||
d24a67d813 | |||
f044b64ace | |||
c78d30f9ef | |||
bf31ca5891 | |||
99e6c86220 | |||
27e05d2d8e | |||
618bf3ae4b | |||
3e3d96a4bb | |||
85a13bd77e | |||
7d7dbf1ba0 | |||
f5415ce7cd | |||
e9c07731b7 | |||
705b8fa292 | |||
e57856d328 | |||
ead5169dfa | |||
c22122cdb4 | |||
00afc9d611 | |||
5202a93676 | |||
e398eaa413 | |||
018add39d2 | |||
8f2f464846 | |||
ff0e4c25a2 | |||
7c5cdc1290 | |||
47e5cf6958 | |||
5c582173f5 | |||
c97f3c158d | |||
eb0cf4a4a1 | |||
02efa9bc21 | |||
2e8fa11b37 | |||
d2eb4ffd09 | |||
12e5d2ff7a | |||
16af549274 | |||
1db9d1fca6 | |||
0f10d73a61 | |||
851cef2b9b | |||
c164faae21 | |||
766342a866 | |||
e3e486b5fc | |||
e0bd7c5e09 | |||
9872b52741 | |||
8883616e67 | |||
b5ee39b2d2 | |||
6dc0a739c4 | |||
a5c9014a15 | |||
750d01bc92 | |||
596fe53bb8 | |||
8670646ca7 | |||
dea132203b | |||
cf50e69e3a | |||
f96c566223 | |||
bfaa284837 | |||
e8a2a5d12a | |||
2d6af962b5 | |||
006dec976a | |||
395e505b84 | |||
dbd1cf1d98 | |||
5bb25c3d45 | |||
5c34ef40da | |||
1cf6d5914a | |||
2422f1dab5 | |||
01ae2748c8 | |||
22e2f2f08a | |||
28e7694545 | |||
a5ebf2d0fc | |||
8dfae3b35a | |||
06dd1637cd | |||
8c2c6a8ce0 | |||
eda5580f83 | |||
dd3695d07a | |||
6b79f826eb | |||
bed858117b | |||
019f5f7226 | |||
9d96e1ee37 | |||
53c9f2a668 | |||
e2c615b4ed | |||
e05ec46b66 | |||
d6c182964b | |||
73704ea206 | |||
0f08b6525f | |||
8847ffdd40 | |||
083a98263b | |||
5e6d343eda | |||
670a1703de | |||
5426cb3a1b | |||
81f6ce4636 | |||
b1d8810bcf | |||
d94399e07c | |||
0be61e75c4 | |||
d8ce1d456f | |||
dab78fe190 | |||
442a041952 | |||
225af9e8b5 | |||
129b7f6029 | |||
6670f36ad8 | |||
e1bb03f4d9 | |||
a804ff94af | |||
96efa8fb25 | |||
4210c8c035 | |||
a73904549e | |||
8792d3b0f2 | |||
36e71f44f7 | |||
ade05024cc | |||
50b92d813e | |||
7c73e131a5 | |||
2f1132cec5 | |||
fec578b1d7 | |||
4a650e66c1 | |||
477c93570c | |||
1104d6b2f1 | |||
ab7f03a9e6 | |||
3b33ce9a62 | |||
45c221b9fa | |||
daa8604c7b | |||
05a7bcfa42 | |||
2582972c34 | |||
a2130fab5e | |||
860c70649e | |||
a6d2a34b58 | |||
befdb15f41 | |||
c7d94b3b63 | |||
4f7972b546 | |||
03be0db844 | |||
60b68612ab | |||
c6871dbf52 | |||
312df72f8b | |||
851f1f6072 | |||
e861dde9c8 | |||
381c0d7813 | |||
51360b8458 | |||
b837c0f854 | |||
dc843276c2 | |||
07e6c33e89 | |||
108851fb78 | |||
5121cb5a14 | |||
145ac1533a | |||
0489db4292 | |||
2e4b06732e | |||
d84dc12fb0 | |||
f2befd29f2 | |||
5cc05d26e7 | |||
cbe05caea2 | |||
f912841f5a | |||
c684cd58e0 | |||
6682bf360a | |||
27209ff843 | |||
d7755b147a | |||
4e14eeecb7 | |||
ee709b32ef | |||
4318902f5a | |||
0e53b34ed6 | |||
c19586369e | |||
cf522bd41e | |||
2416a13812 | |||
fa0557da46 | |||
6e78b4d37c | |||
0916ef4ce7 | |||
924871859f | |||
be10ffd564 | |||
230aaf041d | |||
839787134c | |||
7a2d1be134 | |||
af80565acd | |||
1ed281e7c4 | |||
e8a126f299 | |||
5e7ceebe32 | |||
1ca3703420 | |||
c32b435cee | |||
6c8720d636 | |||
03738bcb8f | |||
d74dda0bc5 | |||
131ec6fa63 | |||
9d8c736917 | |||
d92c4a4a90 | |||
781a086c8f | |||
4af574cd43 | |||
d257f9fe90 | |||
c1f516babb | |||
237318b268 | |||
937c7b1f4b | |||
87f531a575 | |||
a5259bd463 | |||
ac34904ea9 | |||
c47ef5fa47 | |||
fa73706af5 | |||
5cb1e3f661 | |||
b74218abab | |||
ee0d559d94 | |||
cf0e9de0ea | |||
349ded60d1 | |||
1c7feccc3a | |||
52e646683c | |||
e04b5e4a89 | |||
da38532635 | |||
1dc06d0aa4 | |||
30a25b76fb | |||
5f8192d5cf | |||
1852458cf5 | |||
dba125fc87 | |||
7092a5d447 | |||
5e2f2b6958 | |||
9c0cc0a427 | |||
9627f269fb | |||
5301cd3b05 | |||
3aaf28cb3b | |||
a272b6fc2c | |||
2579d167e7 | |||
9c745167ac | |||
6487776bcd | |||
d5c05d90e4 | |||
1f7f6c4714 | |||
05a31be2e5 | |||
a3aa3f8bb2 | |||
9fdeeff921 | |||
f34f7d317e | |||
1a27458d14 | |||
8f2637c39f | |||
26be304699 | |||
e0c1ec1121 | |||
f1e84c2728 | |||
9130d97cae | |||
3cb9724d93 | |||
5962450be7 | |||
3c8e77a237 | |||
3349d09654 | |||
b76726c4a5 | |||
11ae37f3b9 | |||
aef44b1536 | |||
5a10a07e21 | |||
abb4d0000d | |||
87f2cbff2a | |||
78b545dff0 | |||
498a5c2e3c | |||
476597e792 | |||
4fe9f82b40 | |||
05f631e4b4 | |||
8b777a3ad2 | |||
ced26da1a1 | |||
a009f6dd3c | |||
bd2e173620 | |||
92c4831761 | |||
e95c9f1ff7 | |||
95cecd2834 | |||
9f7bdfbc2b | |||
42b206536d | |||
e82592bab7 | |||
722f1ef785 | |||
a7ef928d0f | |||
b73aedd079 | |||
7ba7a435ef | |||
4cf915c399 | |||
fbf6853c96 | |||
55a17b793b | |||
e7c976f5f4 | |||
e5779c84ae | |||
9e802db6d3 | |||
49430f65d2 | |||
8b49fc4f8e | |||
d8b7889401 | |||
3b9db2980c | |||
304b35da1f | |||
3ae0fc5b42 | |||
3616e131f7 | |||
7ee6707047 | |||
165fb9588b | |||
1dc766e70e | |||
c6104a9dbc | |||
24ad55081c | |||
119eb4ed88 | |||
a6aabd1bc4 | |||
bb73d897f9 | |||
7705723fe0 | |||
ec6a83ebd1 | |||
2eb47cf57f | |||
c170a938d1 | |||
368fe13201 | |||
282dc84364 | |||
c3d5494f4c | |||
2f7c3bc73c | |||
bf866a73e6 | |||
08f35903af | |||
0a8178508d | |||
a4f4b7b052 | |||
697cb7610a | |||
67a2bc8f98 | |||
0e21c33376 | |||
8bbfa57304 | |||
c38175f83f | |||
045fcb1e4b | |||
61636fe473 | |||
760585f9a6 | |||
3b5131d63e | |||
24cfc8f691 | |||
13ad59ae6e | |||
25a1e3b495 | |||
2a954830ce | |||
cc182059bd | |||
0102220271 | |||
8d99b0bfa2 | |||
28fcd69846 | |||
dcfff32fcc | |||
9a39d2246e | |||
d88d89373f | |||
22e92af2ac | |||
1c90711411 | |||
96e5e875b8 | |||
d018627e9e | |||
85044465aa | |||
3cbd8532b1 | |||
7e3e315f06 | |||
331031c0b9 | |||
7eb65dd91e | |||
39b28884bf | |||
8015ce88c6 | |||
d7fa0a6039 | |||
026f78eb82 | |||
b6da01969f | |||
a1a0486507 | |||
3afdcbe6c7 | |||
7b0d909c9a | |||
9a19c56af0 | |||
99043037d9 | |||
fd6e364e40 | |||
c9884b76ce | |||
6cc5a0f6c5 | |||
4e22335181 | |||
b6e8cb2df5 | |||
d0a2eca8be | |||
5f76a0d283 | |||
146abb3994 | |||
e95f9a1d6a | |||
0c23ca0561 | |||
e5766e1324 | |||
ae03bb6012 | |||
59188d71eb | |||
bc292a66e6 | |||
3582beb6a0 | |||
97dd0e96fb | |||
362e2e6829 | |||
0f6ee30389 | |||
5d7508dbed | |||
b0d2c195ae | |||
6268a4f5eb | |||
5632310b8c | |||
34ed96aec8 | |||
dfa3ad74fa | |||
28590809d3 | |||
cac628817d | |||
3f49b63930 | |||
e445fbb3a3 | |||
5d111cac5b | |||
d9ea2e941f | |||
4a47c3f58c | |||
b2a068dbfe | |||
a7b32369b6 | |||
49c56bd8b5 | |||
75581144bb | |||
648bfabbdf | |||
9cafc7b786 | |||
9e1720eb4f | |||
f59ccd5ae5 | |||
dff5b8e3cd | |||
be6035e159 | |||
1bb18e1e98 | |||
01c42e8d77 | |||
68e3d79260 | |||
24cab2285e | |||
270e0d06d9 | |||
6f28598e6f | |||
cd7b0f6045 | |||
4d3873a08f | |||
5a942492ef | |||
0ffdded569 | |||
f575488d82 | |||
6a97a354dd | |||
c68c952f13 | |||
44a49a84a3 | |||
66137f1f68 | |||
33094fa7a4 | |||
3e19428beb | |||
a48c7f08f1 | |||
b81bcb2c9f | |||
e30d71a85e | |||
36d6f061b2 | |||
5d09de0b62 | |||
70142708e2 | |||
0b50d7aa12 | |||
eada33df2f | |||
c32051054a | |||
e041772bb3 | |||
efae8fe06e | |||
4851d3113e | |||
3cf6374a96 | |||
dc2d7f454e | |||
36b53fe963 | |||
e2b8d3d846 | |||
48bddd4557 | |||
0863ce7829 | |||
5e1bd05146 | |||
d0591d2b86 | |||
1c82677063 | |||
df3f6f37cf | |||
d1754c01c0 | |||
c299c3e82b | |||
fc76844362 | |||
f9c281eb54 | |||
5b8a6201f5 | |||
12ef947272 | |||
e551091c74 | |||
bdc44aec9b | |||
31e2fbb421 | |||
6f04901aa2 | |||
55b03e8161 | |||
f78802fe7f | |||
38a5495b18 | |||
d4cf192266 | |||
d2c5c9b166 | |||
b457bb0d64 | |||
8c46578d52 | |||
c98999190e | |||
719e8f760d | |||
18faafc968 | |||
0f0efc1d40 | |||
96506de85a | |||
25193756e1 | |||
528f8431f7 | |||
7a8d1e9dd1 | |||
287826831b | |||
147b11e6fe | |||
706511ae7b | |||
9aa0b7c7a0 | |||
005519c6c1 | |||
5c33091257 | |||
cd8df0c57a | |||
204c34ba0c | |||
809c2ffa77 | |||
a6a685f6c1 | |||
f46366de2a | |||
00bdcff28d | |||
a9468a9fd5 | |||
86a38e94ef | |||
72702294c7 | |||
84613e6eb2 | |||
0a8cc3ade7 | |||
c417886b8b | |||
342213d2f2 | |||
3bcb21326c | |||
03e5fe5001 | |||
f7da708519 | |||
e41d6d6ea2 | |||
ea4d0f21f9 | |||
bd35c2c8bc | |||
cd0b2132fd | |||
8de7303543 | |||
ba95ef2d64 | |||
4d40b59303 | |||
5b093256fb | |||
31cbdbeaf0 | |||
e2da509898 | |||
ee3dc8f853 | |||
b7fed0c095 | |||
38caea01f5 | |||
9cb5134737 | |||
8f1d7d2918 | |||
a473199954 | |||
5fbb256969 | |||
ef3fa71982 | |||
053215a6bb | |||
4dc5a482e0 | |||
1efd6d5439 | |||
ef98046313 | |||
920e46754b | |||
6be61b8706 | |||
5d6f677a5e | |||
f153b1dbf1 |
18
.github/dependabot.yml
vendored
18
.github/dependabot.yml
vendored
|
@ -6,23 +6,19 @@ updates:
|
|||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
interval: "monthly"
|
||||
target-branch: "develop"
|
||||
|
||||
# Maintain dependencies for npm
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/gui"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
|
||||
# Maintain dependencies for npm
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/gui"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
target-branch: "ls-gui-single-pager"
|
||||
interval: "monthly"
|
||||
target-branch: "develop"
|
||||
|
||||
# Maintain dependencies for pip
|
||||
- package-ecosystem: "pip"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: "monthly"
|
||||
target-branch: "develop"
|
37
.github/workflows/build_gui.yml
vendored
Normal file
37
.github/workflows/build_gui.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
name: build_gui
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build_i686_x64_release:
|
||||
name: Build FreeDATA GUI
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
electron_parameters: "-p always"
|
||||
|
||||
- os: macos-latest
|
||||
electron_parameters: "-p always"
|
||||
|
||||
- os: windows-latest
|
||||
electron_parameters: "-p always --x64 --ia32"
|
||||
|
||||
steps:
|
||||
- name: Checkout code for ${{ matrix.platform.name }}
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: DJ2LS/FreeDATA
|
||||
- name: Electron Builder
|
||||
env: # Setting environment variables for the entire job
|
||||
GH_TOKEN: ${{ secrets.github_token }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
working-directory: gui
|
||||
run: |
|
||||
npm i
|
||||
npm run release
|
411
.github/workflows/build_multiplatform.yml
vendored
411
.github/workflows/build_multiplatform.yml
vendored
|
@ -1,411 +0,0 @@
|
|||
name: Build_Multiplatform
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
BUILD_AMD64:
|
||||
name: Build codec2 for x86/x64 devices
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, ubuntu-22.04, macos-latest, macos-12]
|
||||
platform: [{name: "native"}, {name: "Windows", file: "dll"}]
|
||||
architecture: [i686-w64-mingw32, x86_64-w64-mingw32]
|
||||
include:
|
||||
|
||||
- os: ubuntu-20.04
|
||||
libcodec2_name: libcodec2.so.1.2
|
||||
libcodec2_os_name: libcodec2_ubuntu-2004
|
||||
libcodec2_filetype: so
|
||||
generator: Unix Makefiles
|
||||
shell: bash
|
||||
|
||||
- os: ubuntu-22.04
|
||||
libcodec2_name: libcodec2.so.1.2
|
||||
libcodec2_os_name: libcodec2_ubuntu-2204
|
||||
libcodec2_filetype: so
|
||||
generator: Unix Makefiles
|
||||
shell: bash
|
||||
|
||||
- os: macos-latest
|
||||
libcodec2_name: libcodec2.1.2.dylib
|
||||
libcodec2_os_name: libcodec2_macos-latest
|
||||
libcodec2_filetype: dylib
|
||||
generator: Unix Makefiles
|
||||
shell: bash
|
||||
|
||||
- os: macos-12
|
||||
libcodec2_name: libcodec2.1.2.dylib
|
||||
libcodec2_os_name: libcodec2_macos-12
|
||||
libcodec2_filetype: dylib
|
||||
generator: Unix Makefiles
|
||||
shell: bash
|
||||
|
||||
|
||||
steps:
|
||||
- name: Build codec2 on ${{ matrix.os }} for ${{ matrix.platform.name }}
|
||||
if: ${{startsWith(matrix.platform.name, 'native') }}
|
||||
run: |
|
||||
git clone https://github.com/drowe67/codec2.git
|
||||
cd codec2
|
||||
mkdir build
|
||||
mkdir tempfiles
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release ../
|
||||
make
|
||||
mv src/${{ matrix.libcodec2_name }} ../tempfiles/libcodec2_${{ matrix.os }}_${{ matrix.platform.name }}.${{ matrix.libcodec2_filetype }}
|
||||
|
||||
- name: LIST ALL FILES ${{ github.workspace }}
|
||||
run: ls -R ${{ github.workspace }}
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{startsWith(matrix.platform.name, 'native') }}
|
||||
with:
|
||||
name: libcodec2_${{ matrix.os }}_${{ matrix.platform.name }}.${{ matrix.libcodec2_filetype }}
|
||||
# path: ${{ github.workspace }}/codec2/tempfiles/libcodec2_${{ matrix.os }}_${{ matrix.platform.name }}.${{ matrix.libcodec2_filetype }}
|
||||
path: ${{ github.workspace }}/codec2/tempfiles/
|
||||
|
||||
|
||||
- name: Build codec2 ${{ matrix.platform.name }} ${{ matrix.architecture }}
|
||||
if: ${{startsWith(matrix.os, 'ubuntu-20') && !startsWith(matrix.platform.name, 'native') }}
|
||||
run: |
|
||||
sudo apt install build-essential mingw-w64 g++-mingw-w64 make cmake
|
||||
git clone https://github.com/drowe67/codec2.git
|
||||
cd codec2
|
||||
mkdir tempfiles
|
||||
mkdir build_w32
|
||||
cd build_w32
|
||||
echo 'set(CMAKE_SYSTEM_NAME ${{ matrix.platform.name }})' > toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_C_COMPILER ${{ matrix.architecture }}-gcc)' >> toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_CXX_COMPILER ${{ matrix.architecture }}-g++)' >> toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_RC_COMPILER ${{ matrix.architecture }}-windres)' >> toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_FIND_ROOT_PATH /usr/${{ matrix.architecture }})' >> toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)' >> toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)' >> toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)' >> toolchain-ubuntu-mingw32.cmake
|
||||
echo 'set(CMAKE_SHARED_LINKER_FLAGS "-static-libgcc -static-libstdc++ -static")' >> toolchain-ubuntu-mingw32.cmake
|
||||
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=toolchain-ubuntu-mingw32.cmake ..
|
||||
make
|
||||
mv src/libcodec2.${{ matrix.platform.file }} ../tempfiles/libcodec2_${{ matrix.platform.name }}_${{ matrix.architecture }}.${{ matrix.platform.file }}
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{startsWith(matrix.os, 'ubuntu-20') && !startsWith(matrix.platform.name, 'native') }}
|
||||
with:
|
||||
name: libcodec2_${{ matrix.os }}_${{ matrix.platform.name }}_${{ matrix.architecture }}.${{ matrix.platform.file }}
|
||||
path: codec2/tempfiles/*
|
||||
|
||||
BUILD_ARM:
|
||||
# The host should always be linux
|
||||
runs-on: ubuntu-latest
|
||||
name: Build codec2 for ARM devices
|
||||
|
||||
# Run steps on a matrix of 2 arch/distro combinations
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: armv7
|
||||
distro: bullseye
|
||||
libcodec2_os_name: libcodec2_bullseye_armv7.so
|
||||
- arch: armv7
|
||||
distro: ubuntu_latest
|
||||
libcodec2_os_name: libcodec2_ubuntu_latest_armv7.so
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
- uses: uraimo/run-on-arch-action@v2
|
||||
name: Build artifact
|
||||
id: build
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
distro: ${{ matrix.distro }}
|
||||
|
||||
# Not required, but speeds up builds
|
||||
githubToken: ${{ github.token }}
|
||||
|
||||
# Create an artifacts directory
|
||||
setup: |
|
||||
mkdir -p "${PWD}/artifacts"
|
||||
|
||||
# Mount the artifacts directory as /artifacts in the container
|
||||
dockerRunArgs: |
|
||||
--volume "${PWD}/artifacts:/artifacts"
|
||||
|
||||
# Pass some environment variables to the container
|
||||
env: | # YAML, but pipe character is necessary
|
||||
artifact_name: ${{ matrix.libcodec2_os_name }}
|
||||
|
||||
# The shell to run commands with in the container
|
||||
shell: /bin/sh
|
||||
|
||||
# Install some dependencies in the container. This speeds up builds if
|
||||
# you are also using githubToken. Any dependencies installed here will
|
||||
# be part of the container image that gets cached, so subsequent
|
||||
# builds don't have to re-install them. The image layer is cached
|
||||
# publicly in your project's package repository, so it is vital that
|
||||
# no secrets are present in the container state or logs.
|
||||
install: |
|
||||
case "${{ matrix.distro }}" in
|
||||
ubuntu*|jessie|stretch|buster|bullseye)
|
||||
apt-get update -q -y
|
||||
apt-get install -q -y git build-essential cmake gcc g++
|
||||
cmake --version
|
||||
;;
|
||||
fedora*)
|
||||
dnf -y update
|
||||
dnf -y install git which make cmake gcc-c++ gcc
|
||||
cmake --version
|
||||
;;
|
||||
alpine*)
|
||||
apk update
|
||||
apk add git cmake gcc g++
|
||||
cmake --version
|
||||
;;
|
||||
esac
|
||||
|
||||
# Produce a binary artifact and place it in the mounted volume
|
||||
run: |
|
||||
|
||||
git clone https://github.com/drowe67/codec2.git
|
||||
cd codec2
|
||||
git checkout main
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ../
|
||||
make
|
||||
mv ./src/libcodec2.so.1.2 /artifacts/${artifact_name}
|
||||
|
||||
- name: Show recursive PWD/artifacts
|
||||
# Items placed in /artifacts in the container will be in
|
||||
# ${PWD}/artifacts on the host.
|
||||
run: ls -al "${PWD}/artifacts"
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.libcodec2_os_name }}
|
||||
#path: $GITHUB_WORKSPACE/codec2/artifacts/*
|
||||
path: artifacts/*
|
||||
|
||||
build_i686_x64_release:
|
||||
needs: [BUILD_AMD64]
|
||||
name: Build FreeDATA packages
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
zip_name: ubuntu_modem
|
||||
generator: Unix Makefiles
|
||||
daemon_binary_name: freedata-daemon
|
||||
modem_binary_name: freedata-modem
|
||||
electron_parameters: "-p always"
|
||||
|
||||
- os: macos-latest
|
||||
zip_name: macos_modem
|
||||
generator: Unix Makefiles
|
||||
daemon_binary_name: freedata-daemon
|
||||
modem_binary_name: freedata-modem
|
||||
electron_parameters: "-p always"
|
||||
|
||||
- os: windows-latest
|
||||
zip_name: windows_modem
|
||||
generator: Visual Studio 16 2019
|
||||
daemon_binary_name: freedata-daemon.exe
|
||||
modem_binary_name: freedata-modem.exe
|
||||
electron_parameters: "-p always --x64 --ia32"
|
||||
steps:
|
||||
- name: Checkout code for ${{ matrix.platform.name }}
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: DJ2LS/FreeDATA
|
||||
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.17
|
||||
|
||||
- name: Create modem/dist
|
||||
working-directory: modem
|
||||
run: |
|
||||
mkdir -p dist
|
||||
|
||||
- name: Create modem/dist/modem
|
||||
working-directory: modem
|
||||
run: |
|
||||
mkdir -p dist/modem
|
||||
|
||||
##- name: Download libcodec2 artifact Modem DIST
|
||||
## uses: actions/download-artifact@v3
|
||||
## with:
|
||||
## path: modem/dist/codec2
|
||||
|
||||
- name: create modem/lib/codec2
|
||||
working-directory: modem/lib/
|
||||
run: |
|
||||
mkdir codec2
|
||||
|
||||
- name: Download libcodec2 artifact Modem LIB
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: modem/lib/codec2
|
||||
|
||||
|
||||
- name: Install Linux dependencies
|
||||
# if: matrix.os == 'ubuntu-20.04'
|
||||
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
||||
run: |
|
||||
sudo apt install -y portaudio19-dev libhamlib-dev libhamlib-utils build-essential cmake python3-libhamlib2 patchelf
|
||||
|
||||
- name: Install MacOS pyAudio
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
run: |
|
||||
brew install portaudio
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install pyaudio
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
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: Build binaries macOS
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
working-directory: modem
|
||||
run: |
|
||||
# now build modem binaries
|
||||
pyinstaller -y freedata.spec
|
||||
# and to some final cleanup
|
||||
# cp -r -f dist/modem/* dist/
|
||||
# rm -r dist/modem
|
||||
|
||||
- name: Build binaries Linux and Windows
|
||||
if: ${{!startsWith(matrix.os, 'macos')}}
|
||||
working-directory: modem
|
||||
run: |
|
||||
# pyinstaller freedata.spec
|
||||
# python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --onefile daemon.py -o ${{ matrix.daemon_binary_name }}
|
||||
# python3 -m nuitka --enable-plugin=numpy --assume-yes-for-downloads --onefile main.py -o ${{ matrix.modem_binary_name }}
|
||||
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
|
||||
|
||||
- name: Copy binaries - Linux
|
||||
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
||||
working-directory: modem
|
||||
run: |
|
||||
cp -r -f daemon.dist/* dist/modem
|
||||
cp -r -f main.dist/* dist/modem
|
||||
|
||||
- name: Copy binaries - Windows
|
||||
if: ${{startsWith(matrix.os, 'windows')}}
|
||||
working-directory: modem
|
||||
# These are powershell aliases, not UNIX commands.
|
||||
run: |
|
||||
cp -r -Force daemon.dist/* dist/modem
|
||||
cp -r -Force main.dist/* dist/modem
|
||||
|
||||
- name: Rename modem binaries
|
||||
# we don't need renaming for pyinstaller builds as output name is defined
|
||||
if: ${{!startsWith(matrix.os, 'macos')}}
|
||||
working-directory: modem
|
||||
run: |
|
||||
mv dist/modem/daemon* dist/modem/${{ matrix.daemon_binary_name }}
|
||||
mv dist/modem/main* dist/modem/${{ matrix.modem_binary_name }}
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: modem/dist/modem
|
||||
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: Download Portaudio binaries Linux macOS
|
||||
if: ${{!startsWith(matrix.os, 'windows')}}
|
||||
working-directory: modem
|
||||
run: |
|
||||
if ! test -d "dist/modem/_sounddevice_data"; then
|
||||
git clone https://github.com/spatialaudio/portaudio-binaries dist/modem/_sounddevice_data/portaudio-binaries
|
||||
fi
|
||||
|
||||
- name: Download Portaudio binaries Windows
|
||||
if: ${{startsWith(matrix.os, 'windows')}}
|
||||
working-directory: modem
|
||||
run: |
|
||||
if(Test-Path -Path "dist/modem/_sounddevice_data"){
|
||||
echo "sounddevice folder already exists"
|
||||
} else {
|
||||
git clone https://github.com/spatialaudio/portaudio-binaries dist/modem/_sounddevice_data/portaudio-binaries
|
||||
}
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: cleanup on macos before code signing
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
run: |
|
||||
ls -l
|
||||
# find . -type d -name .git -exec rm -r {} \;
|
||||
find . -type d -o -name ".git" -delete
|
||||
|
||||
- name: Electron Builder
|
||||
env: # Setting environment variables for the entire job
|
||||
GH_TOKEN: ${{ secrets.github_token }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
working-directory: gui
|
||||
run: |
|
||||
npm i
|
||||
npm run build
|
||||
|
||||
- name: Compress Modem
|
||||
uses: thedoctor0/zip-release@master
|
||||
with:
|
||||
type: 'zip'
|
||||
filename: '${{ matrix.zip_name }}.zip'
|
||||
# directory: ./modem/dist/modem
|
||||
directory: ./modem/dist/modem
|
||||
path: .
|
||||
# exclusions: '*.git* /*node_modules/* .editorconfig'
|
||||
|
||||
- name: Release Modem
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
files: ./modem/dist/modem/${{ matrix.zip_name }}.zip
|
||||
#files: ./modem/dist/${{ matrix.zip_name }}.zip
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
#- name: Upload Modem artifacts
|
||||
# uses: actions/upload-artifact@v3
|
||||
# if: ${{!startsWith(github.ref, 'refs/tags/v')}}
|
||||
# with:
|
||||
# name: ${{ matrix.zip_name }}.zip
|
||||
# # path: ./modem/dist/modem/${{ matrix.zip_name }}.zip
|
||||
# path: ./modem/dist/modem/${{ matrix.zip_name }}.zip#
|
||||
|
||||
#- name: Upload App bundle artifacts
|
||||
# uses: actions/upload-artifact@v3
|
||||
# if: ${{!startsWith(github.ref, 'refs/tags/v')}}
|
||||
# with:
|
||||
# name: app_bundle_${{ matrix.os }}.zip
|
||||
# # path: ./modem/dist/modem/${{ matrix.zip_name }}.zip
|
||||
# path: ./gui/dist/*
|
69
.github/workflows/build_nsis_bundle.yml
vendored
Normal file
69
.github/workflows/build_nsis_bundle.yml
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
name: Build and Release NSIS Installer
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build-and-release:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Electron Builder
|
||||
working-directory: gui
|
||||
run: |
|
||||
npm i
|
||||
npm run build
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
|
||||
- uses: robinraju/release-downloader@v1.9
|
||||
with:
|
||||
repository: "Hamlib/Hamlib"
|
||||
fileName: " hamlib-w64-*.zip"
|
||||
latest: true
|
||||
extract: true
|
||||
out-file-path: "modem/lib/hamlib/"
|
||||
|
||||
- name: Build binaries
|
||||
working-directory: modem
|
||||
run: |
|
||||
python3 -m nuitka --remove-output --assume-yes-for-downloads --follow-imports --include-data-dir=lib=lib --include-data-files=lib/codec2/*=lib/codec2/ --include-data-files=config.ini.example=config.ini --standalone server.py --output-filename=freedata-server
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: Create installer
|
||||
uses: joncloud/makensis-action@v4.1
|
||||
with:
|
||||
script-file: "freedata-nsis-config.nsi"
|
||||
arguments: '/V3'
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'FreeDATA-Installer'
|
||||
path: ./FreeDATA-Installer.exe
|
||||
|
||||
- name: Upload Installer to Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
draft: true
|
||||
files: ./FreeDATA-Installer.exe
|
||||
tag_name: ${{ github.ref_name }}
|
||||
name: 'FreeDATA-Installer-${{ github.ref_name }}'
|
124
.github/workflows/build_server.yml
vendored
Normal file
124
.github/workflows/build_server.yml
vendored
Normal file
|
@ -0,0 +1,124 @@
|
|||
name: build_server
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build_i686_x64_release:
|
||||
name: Build FreeDATA packages
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-20.04
|
||||
zip_name: freedata-server_ubuntu
|
||||
generator: Unix Makefiles
|
||||
modem_binary_name: freedata-server
|
||||
|
||||
- os: macos-latest
|
||||
zip_name: freedata-server_macos
|
||||
generator: Unix Makefiles
|
||||
modem_binary_name: freedata-server
|
||||
|
||||
- os: windows-latest
|
||||
zip_name: freedata-server_windows
|
||||
generator: Visual Studio 16 2019
|
||||
modem_binary_name: freedata-server.exe
|
||||
|
||||
steps:
|
||||
- name: Checkout code for ${{ matrix.platform.name }}
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: DJ2LS/FreeDATA
|
||||
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install Linux dependencies
|
||||
# if: matrix.os == 'ubuntu-20.04'
|
||||
if: ${{startsWith(matrix.os, 'ubuntu')}}
|
||||
run: |
|
||||
sudo apt install -y portaudio19-dev libhamlib-dev libhamlib-utils build-essential cmake python3-libhamlib2 patchelf
|
||||
|
||||
- name: Install MacOS pyAudio
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
run: |
|
||||
brew install portaudio
|
||||
python -m pip install --upgrade pip
|
||||
pip3 install pyaudio
|
||||
export PYTHONPATH=/Library/Frameworks/Python.framework/Versions/3.11/lib/:$PYTHONPATH
|
||||
|
||||
- name: Install Python dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
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: Build binaries
|
||||
working-directory: modem
|
||||
run: |
|
||||
python3 -m nuitka --remove-output --assume-yes-for-downloads --follow-imports --include-data-dir=lib=lib --include-data-files=lib/codec2/*=lib/codec2/ --include-data-files=config.ini.example=config.ini --standalone server.py --output-filename=freedata-server
|
||||
|
||||
#- name: Download Portaudio binaries Linux macOS
|
||||
# if: ${{!startsWith(matrix.os, 'windows')}}
|
||||
# working-directory: modem
|
||||
# run: |
|
||||
# if ! test -d "server.dist/modem/_sounddevice_data"; then
|
||||
# git clone https://github.com/spatialaudio/portaudio-binaries dist/modem/_sounddevice_data/portaudio-binaries
|
||||
# fi
|
||||
|
||||
#- name: Download Portaudio binaries Windows
|
||||
# if: ${{startsWith(matrix.os, 'windows')}}
|
||||
# working-directory: modem
|
||||
# run: |
|
||||
# if(Test-Path -Path "server.dist/modem/_sounddevice_data"){
|
||||
# echo "sounddevice folder already exists"
|
||||
# } else {
|
||||
# git clone https://github.com/spatialaudio/portaudio-binaries dist/modem/_sounddevice_data/portaudio-binaries
|
||||
# }
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: cleanup on macos before code signing
|
||||
if: ${{startsWith(matrix.os, 'macos')}}
|
||||
run: |
|
||||
ls -l
|
||||
# find . -type d -name .git -exec rm -r {} \;
|
||||
find . -type d -o -name ".git" -delete
|
||||
|
||||
- name: Compress Modem
|
||||
uses: thedoctor0/zip-release@master
|
||||
with:
|
||||
type: 'zip'
|
||||
filename: '${{ matrix.zip_name }}.zip'
|
||||
directory: ./modem/server.dist
|
||||
path: .
|
||||
# exclusions: '*.git* /*node_modules/* .editorconfig'
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: '${{ matrix.zip_name }}'
|
||||
path: ./modem/server.dist/${{ matrix.zip_name }}.zip
|
||||
|
||||
- name: Release Modem
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
with:
|
||||
draft: true
|
||||
files: ./modem/server.dist/${{ matrix.zip_name }}.zip
|
||||
|
||||
- name: LIST ALL FILES
|
||||
run: ls -R
|
6
.github/workflows/codeql-analysis.yml
vendored
6
.github/workflows/codeql-analysis.yml
vendored
|
@ -45,7 +45,7 @@ jobs:
|
|||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
|
@ -56,7 +56,7 @@ jobs:
|
|||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
@ -70,4 +70,4 @@ jobs:
|
|||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@v3
|
||||
|
|
38
.github/workflows/gui_tests.yml
vendored
Normal file
38
.github/workflows/gui_tests.yml
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
name: GUI tests
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# The CMake configure and build commands are platform-agnostic and should work equally
|
||||
# well on Windows or Mac. You can convert this to a matrix build if you need
|
||||
# cross-platform coverage.
|
||||
# See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
# By default, GitHub will maximize the number of jobs run in parallel
|
||||
# depending on the available runners on GitHub-hosted virtual machines.
|
||||
# max-parallel: 8
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- node-version: "16"
|
||||
- node-version: "18"
|
||||
- node-version: "20"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Node.js, NPM and Yarn
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: gui
|
||||
run: |
|
||||
npm i
|
||||
|
||||
- name: GUI Test
|
||||
working-directory: gui
|
||||
run: |
|
||||
npm run test
|
|
@ -1,4 +1,4 @@
|
|||
name: CTest
|
||||
name: Modem tests
|
||||
|
||||
on: [push]
|
||||
|
||||
|
@ -16,41 +16,34 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- python-version: "3.7"
|
||||
#- python-version: "3.7" EOL
|
||||
- python-version: "3.8"
|
||||
- python-version: "3.9"
|
||||
- python-version: "3.10"
|
||||
- python-version: "3.11"
|
||||
#- python-version: "3.12-dev"
|
||||
#- python-version: "3.12" NOT YET SUPPORTED BY NUITKA!
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
|
||||
- name: Install packages
|
||||
- name: Install system packages
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install octave octave-common octave-signal sox portaudio19-dev python3-pyaudio
|
||||
pip3 install psutil crcengine ujson pyserial numpy structlog sounddevice pyaudio requests websocket-client
|
||||
pip3 install pytest pytest-rerunfailures
|
||||
sudo apt-get update || true
|
||||
sudo apt-get install octave octave-common octave-signal sox portaudio19-dev
|
||||
|
||||
- name: Build codec2
|
||||
- name: Install python packages
|
||||
shell: bash
|
||||
run: |
|
||||
git clone https://github.com/drowe67/codec2.git
|
||||
cd codec2
|
||||
mkdir -p build_linux && cd build_linux && cmake .. && make
|
||||
pip3 install -r requirements.txt
|
||||
|
||||
- name: run ctests
|
||||
- name: run config tests
|
||||
shell: bash
|
||||
working-directory: ${{github.workspace}}
|
||||
run: |
|
||||
mkdir build && cd build
|
||||
cmake -DCODEC2_BUILD_DIR=$GITHUB_WORKSPACE/codec2/build_linux ..
|
||||
ctest --output-on-failure
|
||||
python -m unittest discover tests
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -29,4 +29,7 @@ coverage.xml
|
|||
#Ignore gui build items
|
||||
/gui/dist
|
||||
/gui/release
|
||||
/gui/dist-electron
|
||||
/gui/dist-electron
|
||||
|
||||
#Ignore GUI config
|
||||
/gui/config/config.json
|
218
CMakeLists.txt
218
CMakeLists.txt
|
@ -1,218 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
project (FreeDATA)
|
||||
include(CTest)
|
||||
enable_testing()
|
||||
|
||||
# Find codec2
|
||||
if(CODEC2_BUILD_DIR)
|
||||
find_package(codec2 REQUIRED
|
||||
PATHS ${CODEC2_BUILD_DIR}
|
||||
NO_DEFAULT_PATH
|
||||
CONFIGS codec2.cmake
|
||||
)
|
||||
if(codec2_FOUND)
|
||||
message(STATUS "Codec2 library found in build tree.")
|
||||
endif()
|
||||
else()
|
||||
find_package(codec2 REQUIRED)
|
||||
endif()
|
||||
|
||||
# test variables
|
||||
set(FRAMESPERBURST 3)
|
||||
set(BURSTS 1)
|
||||
set(TESTFRAMES 3)
|
||||
|
||||
add_test(NAME audio_buffer
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../modem;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_audiobuffer.py")
|
||||
set_tests_properties(audio_buffer PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME resampler
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../modem;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_resample_48_8.py")
|
||||
set_tests_properties(resampler PROPERTIES PASS_REGULAR_EXPRESSION "PASS")
|
||||
|
||||
add_test(NAME modem_state_machine
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../modem;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_modem_states.py")
|
||||
set_tests_properties(modem_state_machine PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME modem_irs_iss
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../modem;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_modem.py")
|
||||
set_tests_properties(modem_irs_iss PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
# disabled this test as its actually broken since we introduced session IDs
|
||||
#add_test(NAME chat_text
|
||||
# COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
# export PYTHONPATH=../modem;
|
||||
# cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
# python3 test_chat_text.py")
|
||||
# set_tests_properties(chat_text PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME datac13_frames
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../modem;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_datac13.py")
|
||||
set_tests_properties(datac13_frames PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
# disabled this test as its actually broken since we introduced dataclasses
|
||||
#add_test(NAME datac13_frames_negative
|
||||
# COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
# export PYTHONPATH=../modem;
|
||||
# cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
# python3 test_datac13_negative.py")
|
||||
# set_tests_properties(datac13_frames_negative PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME helper_routines
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../modem;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_helpers.py")
|
||||
set_tests_properties(helper_routines PROPERTIES PASS_REGULAR_EXPRESSION "errors: 0")
|
||||
|
||||
add_test(NAME py_highsnr_stdio_P_P_multi
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../modem;
|
||||
export BURSTS=${BURSTS};
|
||||
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_highsnr_stdio_P_P_multi.py")
|
||||
set_tests_properties(py_highsnr_stdio_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC13: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}")
|
||||
|
||||
add_test(NAME py_highsnr_stdio_P_P_datacx
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../modem;
|
||||
export BURSTS=${BURSTS};
|
||||
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_highsnr_stdio_P_P_datacx.py")
|
||||
set_tests_properties(py_highsnr_stdio_P_P_datacx PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}")
|
||||
|
||||
add_test(NAME py_highsnr_stdio_P_C_datacx
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../modem;
|
||||
export BURSTS=${BURSTS};
|
||||
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_highsnr_stdio_P_C_datacx.py")
|
||||
set_tests_properties(py_highsnr_stdio_P_C_datacx PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD")
|
||||
|
||||
add_test(NAME py_highsnr_stdio_C_P_datacx
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
export PYTHONPATH=../modem;
|
||||
export BURSTS=${BURSTS};
|
||||
export FRAMESPERBURST=${FRAMESPERBURST};
|
||||
export TESTFRAMES=${TESTFRAMES};
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 test_highsnr_stdio_C_P_datacx.py")
|
||||
set_tests_properties(py_highsnr_stdio_C_P_datacx PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}")
|
||||
|
||||
add_test(NAME highsnr_stdio_P_C_single
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 util_tx.py --mode datac13 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} |
|
||||
sox -t .s16 -r 48000 -c 1 - -t .s16 -r 8000 -c 1 - |
|
||||
freedv_data_raw_rx datac13 - - --framesperburst ${FRAMESPERBURST} | hexdump -C")
|
||||
set_tests_properties(highsnr_stdio_P_C_single PROPERTIES PASS_REGULAR_EXPRESSION "HELLO WORLD")
|
||||
|
||||
add_test(NAME highsnr_stdio_C_P_single
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
freedv_data_raw_tx datac13 --testframes ${TESTFRAMES} --bursts ${BURSTS} --framesperburst ${FRAMESPERBURST} /dev/zero - |
|
||||
sox -t .s16 -r 8000 -c 1 - -t .s16 -r 48000 -c 1 - |
|
||||
python3 util_rx.py --mode datac13 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}")
|
||||
set_tests_properties(highsnr_stdio_C_P_single PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}")
|
||||
|
||||
add_test(NAME highsnr_stdio_P_P_single
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 util_tx.py --mode datac13 --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} |
|
||||
python3 util_rx.py --debug --mode datac13 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS}")
|
||||
set_tests_properties(highsnr_stdio_P_P_single PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: ${BURSTS} RECEIVED FRAMES: ${FRAMESPERBURST}")
|
||||
|
||||
add_test(NAME highsnr_stdio_P_P_multi
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
python3 util_multimode_tx.py --delay 500 --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} |
|
||||
python3 util_multimode_rx.py --framesperburst ${FRAMESPERBURST} --bursts ${BURSTS} --timeout 60")
|
||||
set_tests_properties(highsnr_stdio_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC13: ${BURSTS}/${FRAMESPERBURST} DATAC1: ${BURSTS}/${FRAMESPERBURST} DATAC3: ${BURSTS}/${FRAMESPERBURST}")
|
||||
|
||||
# These tests can't run on GitHub actions as we don't have a virtual sound card
|
||||
if(NOT DEFINED ENV{GITHUB_RUN_ID})
|
||||
|
||||
# uses aplay/arecord then pipe to Python
|
||||
add_test(NAME highsnr_virtual1_P_P_single_alsa
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
./test_virtual1.sh")
|
||||
set_tests_properties(highsnr_virtual1_P_P_single_alsa PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 5 RECEIVED FRAMES: 10 RX_ERRORS: 0")
|
||||
|
||||
# let Python do audio I/O
|
||||
add_test(NAME highsnr_virtual2_P_P_single
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
./test_virtual2.sh")
|
||||
set_tests_properties(highsnr_virtual2_P_P_single PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 3 RECEIVED FRAMES: 6 RX_ERRORS: 0")
|
||||
|
||||
# Multimode test with Python I/O
|
||||
add_test(NAME highsnr_virtual3_P_P_multi
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
./test_virtual_mm.sh")
|
||||
set_tests_properties(highsnr_virtual3_P_P_multi PROPERTIES PASS_REGULAR_EXPRESSION "DATAC13: 2/4 DATAC1: 2/4 DATAC3: 2/4")
|
||||
|
||||
# let Python do audio I/O via pyaudio callback mode
|
||||
add_test(NAME highsnr_virtual4_P_P_single_callback
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
./test_virtual3a.sh")
|
||||
set_tests_properties(highsnr_virtual4_P_P_single_callback PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 3 RECEIVED FRAMES: 6 RX_ERRORS: 0")
|
||||
|
||||
# let Python do audio I/O via pyaudio callback mode with code outside of callback
|
||||
add_test(NAME highsnr_virtual4_P_P_single_callback_outside
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
./test_virtual3b.sh")
|
||||
set_tests_properties(highsnr_virtual4_P_P_single_callback_outside PROPERTIES PASS_REGULAR_EXPRESSION "RECEIVED BURSTS: 3 RECEIVED FRAMES: 6 RX_ERRORS: 0")
|
||||
|
||||
# let Python do audio I/O via pyaudio callback mode with code outside of callback
|
||||
add_test(NAME highsnr_virtual5_P_P_multi_callback
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
./test_virtual4a.sh")
|
||||
set_tests_properties(highsnr_virtual5_P_P_multi_callback PROPERTIES PASS_REGULAR_EXPRESSION "DATAC13: 2/4 DATAC1: 2/4 DATAC3: 2/4")
|
||||
|
||||
# let Python do audio I/O via pyaudio callback mode with code outside of callback
|
||||
add_test(NAME highsnr_virtual5_P_P_multi_callback_outside
|
||||
COMMAND sh -c "export LD_LIBRARY_PATH=${CODEC2_BUILD_DIR}/src;
|
||||
PATH=$PATH:${CODEC2_BUILD_DIR}/src;
|
||||
cd ${CMAKE_CURRENT_SOURCE_DIR}/test;
|
||||
./test_virtual4b.sh")
|
||||
set_tests_properties(highsnr_virtual5_P_P_multi_callback_outside PROPERTIES PASS_REGULAR_EXPRESSION "DATAC13: 2/4 DATAC1: 2/4 DATAC3: 2/4")
|
||||
|
||||
endif()
|
||||
|
135
documentation/FreeDATA-protocols.md
Normal file
135
documentation/FreeDATA-protocols.md
Normal file
|
@ -0,0 +1,135 @@
|
|||
# FreeDATA - Protocols
|
||||
|
||||
## ARQ Sessions
|
||||
|
||||
An ARQ Session represents a reliable data transmission session from a sending station (A) to a receiving station (B). It uses automatic repeat request on top of different codec2 modes according to the transmission channel conditions.
|
||||
|
||||
So lets say A wants to send some data to B. A typical scenario would be like this:
|
||||
|
||||
```
|
||||
ISS->(1)IRS:<datac13> OPEN_REQ(session id, origin, dest)
|
||||
IRS->(1)ISS:<datac13> OPEN_ACK (session id, proto version, speed level, frames, snr)
|
||||
|
||||
ISS->(1)IRS:<datac13> INFO(id, total_bytes, total_crc)
|
||||
IRS->(1)ISS:<datac13> INFO_ACK(id, total_crc)
|
||||
|
||||
ISS->(1)IRS:BURST (ID, offset, payload),(ID, offset, payload),(ID, offset, payload)
|
||||
IRS->(1)ISS:BURST_ACK (ID, next_offset, speed level, frames, snr)
|
||||
|
||||
ISS-->(1)IRS:Lost BURST (total or part)
|
||||
IRS->(1)ISS:BURST_NACK (ID, next_offset, speed level, frames, snr)
|
||||
|
||||
ISS->(1)IRS:BURST (ID, offset, payload),(ID, offset, payload),(ID, offset, payload)
|
||||
IRS->(1)ISS:DATA ACK NACK (ID, next_offset, speed level, frames, snr)
|
||||
```
|
||||
|
||||
### Frame details
|
||||
|
||||
#### SESSION_OPEN_REQ
|
||||
|
||||
ISS sends this first
|
||||
|
||||
DATAC13 Mode (12 bytes)
|
||||
|
||||
| field | bytes |
|
||||
| --------------- | ----- |
|
||||
| session id | 1 |
|
||||
| origin | 6 |
|
||||
| destination_crc | 3 |
|
||||
|
||||
#### SESSION_OPEN_ACK
|
||||
|
||||
Sent by the IRS in response to a SESSION_OPEN_REQ
|
||||
|
||||
DATAC13 Mode (12 bytes)
|
||||
|
||||
| field | bytes |
|
||||
| ---------------- | ----- |
|
||||
| session id | 1 |
|
||||
| origin | 6 |
|
||||
| destination_crc | 3 |
|
||||
| protocol version | 1 |
|
||||
| snr | 1 |
|
||||
|
||||
#### SESSION_INFO
|
||||
|
||||
ISS sends this in response to a SESSION_OPEN_ACK
|
||||
|
||||
DATAC13 Mode (12 bytes)
|
||||
|
||||
| field | bytes |
|
||||
| ----------- | ----- |
|
||||
| session id | 1 |
|
||||
| total bytes | 4 |
|
||||
| total crc | 4 |
|
||||
| snr | 1 |
|
||||
|
||||
#### SESSION_INFO_ACK
|
||||
|
||||
IRS sends this in response to a SESSION_INFO
|
||||
|
||||
DATAC13 Mode (12 bytes)
|
||||
|
||||
| field | bytes |
|
||||
| ---------------- | ----- |
|
||||
| session id | 1 |
|
||||
| total crc | 4 |
|
||||
| snr | 1 |
|
||||
| speed level | 1 |
|
||||
| frames per burst | 1 |
|
||||
|
||||
#### Data Burst
|
||||
|
||||
ISS sends this to send data to IRS
|
||||
|
||||
Mode according to handshake speed level
|
||||
|
||||
Frames per burst according to handshake
|
||||
|
||||
##### Modulation
|
||||
|
||||
Each burst is composed of frames_per_burst frames:
|
||||
|
||||
|preamble|f1|f2|f3|...|postamble|
|
||||
|
||||
##### Each data frame
|
||||
|
||||
| field | bytes |
|
||||
| ---------- | ------------------------------ |
|
||||
| session id | 1 |
|
||||
| offset | 4 |
|
||||
| payload | (the remaining payload length) |
|
||||
|
||||
#### DATA_BURST_ACK
|
||||
|
||||
Sent by the IRS following successful decoding of burst.
|
||||
|
||||
| field | bytes |
|
||||
| --------------------- | ----- |
|
||||
| session id | 1 |
|
||||
| next offset | 4 |
|
||||
| next speed level | 1 |
|
||||
| next frames per burst | 1 |
|
||||
| snr | 1 |
|
||||
|
||||
#### DATA_BURST_NACK
|
||||
|
||||
Sent by the IRS following unsuccessful decoding of burst or timeout.
|
||||
|
||||
| field | bytes |
|
||||
| --------------------- | ----- |
|
||||
| session id | 1 |
|
||||
| next offset | 4 |
|
||||
| next speed level | 1 |
|
||||
| next frames per burst | 1 |
|
||||
| snr | 1 |
|
||||
|
||||
#### DATA ACK NACK
|
||||
|
||||
Sent by the IRS after receiving data with a state information.
|
||||
|
||||
| field | bytes |
|
||||
| ---------- | ----- |
|
||||
| session id | 1 |
|
||||
| state | 1 |
|
||||
| snr | 1 |
|
BIN
documentation/icon.ico
Normal file
BIN
documentation/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 183 KiB |
132
freedata-nsis-config.nsi
Normal file
132
freedata-nsis-config.nsi
Normal file
|
@ -0,0 +1,132 @@
|
|||
!include "MUI2.nsh"
|
||||
|
||||
; Request administrative rights
|
||||
RequestExecutionLevel admin
|
||||
|
||||
; The name and file name of the installer
|
||||
Name "FreeDATA Installer"
|
||||
OutFile "FreeDATA-Installer.exe"
|
||||
|
||||
; Default installation directory for the server
|
||||
InstallDir "$LOCALAPPDATA\FreeDATA"
|
||||
|
||||
; Registry key to store the installation directory
|
||||
InstallDirRegKey HKCU "Software\FreeDATA" "Install_Dir"
|
||||
|
||||
; Modern UI settings
|
||||
!define MUI_ABORTWARNING
|
||||
|
||||
; Installer interface settings
|
||||
!define MUI_ICON "documentation\icon.ico"
|
||||
!define MUI_UNICON "documentation\icon.ico" ; Icon for the uninstaller
|
||||
|
||||
; Define the welcome page text
|
||||
!define MUI_WELCOMEPAGE_TEXT "Welcome to the FreeDATA Setup Wizard. This wizard will guide you through the installation process."
|
||||
!define MUI_FINISHPAGE_TEXT "Folder: $INSTDIR"
|
||||
!define MUI_DIRECTORYPAGE_TEXT_TOP "Please select the installation folder. It's recommended to use the suggested one to avoid permission problems."
|
||||
|
||||
; Pages
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
!insertmacro MUI_PAGE_LICENSE "LICENSE"
|
||||
!insertmacro MUI_PAGE_COMPONENTS
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
||||
; Uninstaller
|
||||
!insertmacro MUI_UNPAGE_WELCOME
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
!insertmacro MUI_UNPAGE_INSTFILES
|
||||
!insertmacro MUI_UNPAGE_FINISH
|
||||
|
||||
; Language (you can choose and configure the language(s) you want)
|
||||
!insertmacro MUI_LANGUAGE "English"
|
||||
|
||||
|
||||
; Installer Sections
|
||||
Section "FreeData Server" SEC01
|
||||
; Set output path to the installation directory
|
||||
SetOutPath $INSTDIR\freedata-server
|
||||
|
||||
; Check if "config.ini" exists and back it up
|
||||
IfFileExists $INSTDIR\freedata-server\config.ini backupConfig
|
||||
|
||||
doneBackup:
|
||||
; Add your application files here
|
||||
File /r "modem\server.dist\*"
|
||||
|
||||
; Restore the original "config.ini" if it was backed up
|
||||
IfFileExists $INSTDIR\freedata-server\config.ini.bak restoreConfig
|
||||
|
||||
; Create a shortcut in the user's desktop
|
||||
CreateShortCut "$DESKTOP\FreeDATA Server.lnk" "$INSTDIR\freedata-server\freedata-server.exe"
|
||||
|
||||
; Create Uninstaller
|
||||
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
||||
|
||||
; Create a Start Menu directory
|
||||
CreateDirectory "$SMPROGRAMS\FreeDATA"
|
||||
|
||||
; Create shortcut in the Start Menu directory
|
||||
CreateShortCut "$SMPROGRAMS\FreeDATA\FreeDATA Server.lnk" "$INSTDIR\freedata-server\freedata-server.exe"
|
||||
|
||||
; Create an Uninstall shortcut
|
||||
CreateShortCut "$SMPROGRAMS\FreeDATA\Uninstall FreeDATA.lnk" "$INSTDIR\Uninstall.exe"
|
||||
|
||||
|
||||
; Backup "config.ini" before overwriting files
|
||||
backupConfig:
|
||||
Rename $INSTDIR\freedata-server\config.ini $INSTDIR\freedata-server\config.ini.bak
|
||||
Goto doneBackup
|
||||
|
||||
; Restore the original "config.ini"
|
||||
restoreConfig:
|
||||
Delete $INSTDIR\freedata-server\config.ini
|
||||
Rename $INSTDIR\freedata-server\config.ini.bak $INSTDIR\freedata-server\config.ini
|
||||
|
||||
|
||||
|
||||
SectionEnd
|
||||
|
||||
Section "FreeData x64 GUI" SEC02
|
||||
; Set output path to the GUI installation directory
|
||||
SetOutPath $INSTDIR\freedata-gui
|
||||
|
||||
; Add GUI files here
|
||||
File /r "gui\release\win-unpacked\*"
|
||||
|
||||
; Create a shortcut on the desktop for the GUI
|
||||
CreateShortCut "$DESKTOP\FreeDATA GUI.lnk" "$INSTDIR\freedata-gui\freedata.exe"
|
||||
|
||||
; Create a start menu shortcut
|
||||
CreateShortCut "$SMPROGRAMS\FreeDATA\FreeDATA GUI.lnk" "$INSTDIR\freedata-gui\freedata.exe"
|
||||
|
||||
; Create an Uninstall shortcut
|
||||
CreateShortCut "$SMPROGRAMS\FreeDATA\Uninstall FreeDATA.lnk" "$INSTDIR\Uninstall.exe"
|
||||
|
||||
SectionEnd
|
||||
|
||||
; Uninstaller Section
|
||||
Section "Uninstall"
|
||||
; Delete files and directories for the server
|
||||
Delete $INSTDIR\freedata-server\*.*
|
||||
RMDir /r $INSTDIR\freedata-server
|
||||
|
||||
; Delete files and directories for the GUI
|
||||
Delete $INSTDIR\freedata-gui\*.*
|
||||
RMDir /r $INSTDIR\freedata-gui
|
||||
|
||||
; Remove the desktop shortcuts
|
||||
Delete "$DESKTOP\FreeDATA Server.lnk"
|
||||
Delete "$DESKTOP\FreeDATA GUI.lnk"
|
||||
|
||||
; Remove Start Menu shortcuts
|
||||
Delete "$SMPROGRAMS\FreeDATA\*.*"
|
||||
RMDir "$SMPROGRAMS\FreeDATA"
|
||||
|
||||
; Attempt to delete the uninstaller itself
|
||||
Delete $EXEPATH
|
||||
|
||||
; Now remove the installation directory if it's empty
|
||||
RMDir /r $INSTDIR
|
||||
SectionEnd
|
10
gui/config/example.json
Normal file
10
gui/config/example.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"local": {
|
||||
"host": "127.0.0.1",
|
||||
"port": "5000",
|
||||
"spectrum": "waterfall",
|
||||
"wf_theme": 2,
|
||||
"update_channel": "alpha",
|
||||
"enable_sys_notification": false
|
||||
}
|
||||
}
|
|
@ -10,62 +10,52 @@
|
|||
"directories": {
|
||||
"output": "release"
|
||||
},
|
||||
|
||||
"asarUnpack": [
|
||||
"**/*.wav"
|
||||
],
|
||||
|
||||
|
||||
"files": [
|
||||
"dist",
|
||||
"dist-electron",
|
||||
],
|
||||
|
||||
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "../modem/dist/modem/",
|
||||
"to": "modem",
|
||||
"filter": [
|
||||
"**/*",
|
||||
"!**/.git"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
"mac": {
|
||||
"target": [
|
||||
"default"
|
||||
{
|
||||
"target": "default",
|
||||
//"arch": ["universal"],
|
||||
"arch": ["arm64", "x64"],
|
||||
|
||||
}
|
||||
],
|
||||
"arch": [
|
||||
"x64",
|
||||
"universal"
|
||||
],
|
||||
"notarize": "false",
|
||||
"icon": "build/icon.png",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "build/entitlements.plist",
|
||||
"entitlementsInherit": "build/entitlements.plist",
|
||||
"gatekeeperAssess": false,
|
||||
"artifactName": "${productName}-Mac-${version}-Installer.${ext}"
|
||||
"mergeASARs": true,
|
||||
"x64ArchFiles": "**/*",
|
||||
"artifactName": "${productName}-GUI-Mac-${version}.${ext}"
|
||||
},
|
||||
"win": {
|
||||
"icon": "build/icon.png",
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64"
|
||||
]
|
||||
"target": "portable",
|
||||
"arch": ["arm64", "x64"]
|
||||
}
|
||||
],
|
||||
"artifactName": "${productName}-Windows-${version}-Setup.${ext}"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"perMachine": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"deleteAppDataOnUninstall": true
|
||||
"artifactName": "${productName}-GUI-Windows-${version}.${ext}"
|
||||
},
|
||||
"linux": {
|
||||
"category": "Development",
|
||||
"target": [
|
||||
"AppImage"
|
||||
],
|
||||
"artifactName": "${productName}-${version}.${ext}"
|
||||
"artifactName": "${productName}-GUI-Linux-${version}.${ext}"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { app, BrowserWindow, shell, ipcMain } from "electron";
|
||||
import { release, platform } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { autoUpdater } from "electron-updater";
|
||||
import { release, platform } from "os";
|
||||
import { join, dirname } from "path";
|
||||
import { existsSync } from "fs";
|
||||
import { spawn } from "child_process";
|
||||
|
||||
|
@ -39,7 +38,7 @@ if (!app.requestSingleInstanceLock()) {
|
|||
// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
|
||||
|
||||
// set daemon process var
|
||||
var daemonProcess = null;
|
||||
var serverProcess = null;
|
||||
let win: BrowserWindow | null = null;
|
||||
// Here, you can also use other preload
|
||||
const preload = join(__dirname, "../preload/index.js");
|
||||
|
@ -55,8 +54,7 @@ async function createWindow() {
|
|||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
preload,
|
||||
backgroundThrottle: false,
|
||||
|
||||
backgroundThrottling: false,
|
||||
// Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
|
||||
// Consider using contextBridge.exposeInMainWorld
|
||||
// Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
|
||||
|
@ -75,9 +73,9 @@ async function createWindow() {
|
|||
}
|
||||
|
||||
// Test actively push message to the Electron-Renderer
|
||||
win.webContents.on("did-finish-load", () => {
|
||||
win?.webContents.send("main-process-message", new Date().toLocaleString());
|
||||
});
|
||||
//win.webContents.on("did-finish-load", () => {
|
||||
// win?.webContents.send("main-process-message", new Date().toLocaleString());
|
||||
//});
|
||||
|
||||
// Make all links open with the browser, not with the application
|
||||
win.webContents.setWindowOpenHandler(({ url }) => {
|
||||
|
@ -87,12 +85,7 @@ async function createWindow() {
|
|||
// win.webContents.on('will-navigate', (event, url) => { }) #344
|
||||
|
||||
win.once("ready-to-show", () => {
|
||||
//autoUpdater.logger = log.scope("updater");
|
||||
//autoUpdater.channel = config.update_channel;
|
||||
autoUpdater.autoInstallOnAppQuit = false;
|
||||
autoUpdater.autoDownload = true;
|
||||
autoUpdater.checkForUpdatesAndNotify();
|
||||
//autoUpdater.quitAndInstall();
|
||||
//
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -100,58 +93,68 @@ async function createWindow() {
|
|||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
|
||||
console.log(platform());
|
||||
//Generate daemon binary path
|
||||
var daemonPath = "";
|
||||
switch (platform().toLowerCase()) {
|
||||
case "darwin":
|
||||
case "linux":
|
||||
daemonPath = join(process.resourcesPath, "modem", "freedata-daemon");
|
||||
var serverPath = "";
|
||||
console.log(process.env);
|
||||
|
||||
break;
|
||||
// Attempt to find Installation Folder
|
||||
console.log(app.getAppPath());
|
||||
console.log(join(app.getAppPath(), "..", ".."));
|
||||
console.log(join(app.getAppPath(), "..", "..", ".."));
|
||||
|
||||
//var basePath = join(app.getAppPath(), '..', '..', '..') || join(process.env.PWD, '..') || join(process.env.INIT_CWD, '..') || join(process.env.DIST, '..', '..', '..');
|
||||
var basePath = join(app.getAppPath(), "..", "..", "..");
|
||||
switch (platform().toLowerCase()) {
|
||||
//case "darwin":
|
||||
//serverPath = join(basePath, "freedata-server", "freedata-server.exe");
|
||||
//serverProcess = spawn(serverPath, [], { detached: true });
|
||||
//serverProcess.unref(); // Allow the server process to continue running independently of the parent process
|
||||
// break;
|
||||
//case "linux":
|
||||
//serverPath = join(basePath, "freedata-server", "freedata-server.exe");
|
||||
//serverProcess = spawn(serverPath, [], { detached: true });
|
||||
//serverProcess.unref(); // Allow the server process to continue running independently of the parent process
|
||||
// break;
|
||||
case "win32":
|
||||
case "win64":
|
||||
daemonPath = join(process.resourcesPath, "modem", "freedata-daemon.exe");
|
||||
serverPath = join(basePath, "freedata-server", "freedata-server.exe");
|
||||
console.log(`Starting server with path: ${serverPath}`);
|
||||
serverProcess = spawn(
|
||||
"cmd.exe",
|
||||
["/c", "start", "cmd.exe", "/c", serverPath],
|
||||
{ shell: true },
|
||||
);
|
||||
console.log(`Started server | PID: ${serverProcess.pid}`);
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("Unhandled OS Platform: ", platform());
|
||||
serverProcess = null;
|
||||
serverPath = null;
|
||||
break;
|
||||
}
|
||||
|
||||
//Start daemon binary if it exists
|
||||
if (existsSync(daemonPath)) {
|
||||
console.log("Starting freedata-daemon binary");
|
||||
daemonProcess = spawn(daemonPath, [], {
|
||||
cwd: join(daemonPath, ".."),
|
||||
});
|
||||
// return process messages
|
||||
daemonProcess.on("error", (err) => {
|
||||
// daemonProcessLog.error(`error when starting daemon: ${err}`);
|
||||
console.log(err);
|
||||
});
|
||||
daemonProcess.on("message", () => {
|
||||
// daemonProcessLog.info(`${data}`);
|
||||
});
|
||||
daemonProcess.stdout.on("data", () => {
|
||||
// daemonProcessLog.info(`${data}`);
|
||||
});
|
||||
daemonProcess.stderr.on("data", () => {
|
||||
// daemonProcessLog.info(`${data}`);
|
||||
});
|
||||
daemonProcess.on("close", (code) => {
|
||||
// daemonProcessLog.warn(`daemonProcess exited with code ${code}`);
|
||||
});
|
||||
} else {
|
||||
daemonProcess = null;
|
||||
daemonPath = null;
|
||||
console.log("Daemon binary doesn't exist--normal for dev environments.");
|
||||
}
|
||||
serverProcess.on("error", (err) => {
|
||||
console.error("Failed to start server process:", err);
|
||||
serverProcess = null;
|
||||
serverPath = null;
|
||||
});
|
||||
serverProcess.stdout.on("data", (data) => {
|
||||
//console.log(`stdout: ${data}`);
|
||||
});
|
||||
|
||||
//)
|
||||
serverProcess.stderr.on("data", (data) => {
|
||||
console.error(`stderr: ${data}`);
|
||||
});
|
||||
});
|
||||
|
||||
app.on("before-quit", () => {
|
||||
close_sub_processes();
|
||||
});
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
win = null;
|
||||
if (process.platform !== "darwin") app.quit(close_sub_processes());
|
||||
if (process.platform !== "darwin") app.quit();
|
||||
});
|
||||
|
||||
app.on("second-instance", () => {
|
||||
|
@ -188,102 +191,33 @@ ipcMain.handle("open-win", (_, arg) => {
|
|||
}
|
||||
});
|
||||
|
||||
//restart and install udpate
|
||||
ipcMain.on("request-restart-and-install-update", (event, data) => {
|
||||
close_sub_processes();
|
||||
autoUpdater.quitAndInstall();
|
||||
});
|
||||
|
||||
// LISTENER FOR UPDATER EVENTS
|
||||
autoUpdater.on("update-available", (info) => {
|
||||
console.log("update available");
|
||||
|
||||
let arg = {
|
||||
status: "update-available",
|
||||
info: info,
|
||||
};
|
||||
win.webContents.send("action-updater", arg);
|
||||
});
|
||||
|
||||
autoUpdater.on("update-not-available", (info) => {
|
||||
console.log("update not available");
|
||||
let arg = {
|
||||
status: "update-not-available",
|
||||
info: info,
|
||||
};
|
||||
win.webContents.send("action-updater", arg);
|
||||
});
|
||||
|
||||
autoUpdater.on("update-downloaded", (info) => {
|
||||
console.log("update downloaded");
|
||||
let arg = {
|
||||
status: "update-downloaded",
|
||||
info: info,
|
||||
};
|
||||
win.webContents.send("action-updater", arg);
|
||||
// we need to call this at this point.
|
||||
// if an update is available and we are force closing the app
|
||||
// the entire screen crashes...
|
||||
//console.log('quit application and install update');
|
||||
//autoUpdater.quitAndInstall();
|
||||
});
|
||||
|
||||
autoUpdater.on("checking-for-update", () => {
|
||||
console.log("checking for update");
|
||||
let arg = {
|
||||
status: "checking-for-update",
|
||||
version: app.getVersion(),
|
||||
};
|
||||
win.webContents.send("action-updater", arg);
|
||||
});
|
||||
|
||||
autoUpdater.on("download-progress", (progress) => {
|
||||
let arg = {
|
||||
status: "download-progress",
|
||||
progress: progress,
|
||||
};
|
||||
win.webContents.send("action-updater", arg);
|
||||
});
|
||||
|
||||
autoUpdater.on("error", (error) => {
|
||||
console.log("update error");
|
||||
let arg = {
|
||||
status: "error",
|
||||
progress: error,
|
||||
};
|
||||
win.webContents.send("action-updater", arg);
|
||||
console.log("AUTO UPDATER : " + error);
|
||||
});
|
||||
|
||||
function close_sub_processes() {
|
||||
console.log("closing sub processes");
|
||||
console.log("Closing sub processes...");
|
||||
|
||||
// closing the modem binary if not closed when closing application and also our daemon which has been started by the gui
|
||||
try {
|
||||
if (daemonProcess != null) {
|
||||
daemonProcess.kill();
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
if (serverProcess != null) {
|
||||
try {
|
||||
console.log(`Killing server process with PID: ${serverProcess.pid}`);
|
||||
|
||||
console.log("closing modem and daemon");
|
||||
try {
|
||||
if (platform() == "win32" || platform() == "win64") {
|
||||
spawn("Taskkill", ["/IM", "freedata-modem.exe", "/F"]);
|
||||
spawn("Taskkill", ["/IM", "freedata-daemon.exe", "/F"]);
|
||||
}
|
||||
switch (platform().toLowerCase()) {
|
||||
//case "darwin":
|
||||
// process.kill(serverProcess.pid);
|
||||
// break;
|
||||
//case "linux":
|
||||
// process.kill(serverProcess.pid);
|
||||
// break;
|
||||
case "win32":
|
||||
// For Windows, use taskkill to ensure all child processes are also terminated
|
||||
spawn("taskkill", ["/pid", serverProcess.pid.toString(), "/f", "/t"]);
|
||||
break;
|
||||
|
||||
if (platform() == "linux") {
|
||||
spawn("pkill", ["-9", "freedata-modem"]);
|
||||
spawn("pkill", ["-9", "freedata-daemon"]);
|
||||
default:
|
||||
console.log("Unhandled OS Platform: ", platform());
|
||||
serverProcess = null;
|
||||
serverPath = null;
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error killing server process: ${error}`);
|
||||
}
|
||||
|
||||
if (platform() == "darwin") {
|
||||
spawn("pkill", ["-9", "freedata-modem"]);
|
||||
spawn("pkill", ["-9", "freedata-daemon"]);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,94 +111,4 @@ window.onmessage = (ev) => {
|
|||
ev.data.payload === "removeLoading" && removeLoading();
|
||||
};
|
||||
|
||||
setTimeout(removeLoading, 4999);
|
||||
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
// we are using this area for implementing the electron runUpdater
|
||||
// we need access to DOM for displaying updater results in GUI
|
||||
// close app, update and restart
|
||||
document
|
||||
.getElementById("update_and_install")
|
||||
.addEventListener("click", () => {
|
||||
ipcRenderer.send("request-restart-and-install-update");
|
||||
});
|
||||
});
|
||||
|
||||
// IPC ACTION FOR AUTO UPDATER
|
||||
ipcRenderer.on("action-updater", (event, arg) => {
|
||||
if (arg.status == "download-progress") {
|
||||
var progressinfo =
|
||||
"(" +
|
||||
Math.round(arg.progress.transferred / 1024) +
|
||||
"kB /" +
|
||||
Math.round(arg.progress.total / 1024) +
|
||||
"kB)" +
|
||||
" @ " +
|
||||
Math.round(arg.progress.bytesPerSecond / 1024) +
|
||||
"kByte/s";
|
||||
document.getElementById("UpdateProgressInfo").innerHTML = progressinfo;
|
||||
|
||||
document
|
||||
.getElementById("UpdateProgressBar")
|
||||
.setAttribute("aria-valuenow", arg.progress.percent);
|
||||
document
|
||||
.getElementById("UpdateProgressBar")
|
||||
.setAttribute("style", "width:" + arg.progress.percent + "%;");
|
||||
}
|
||||
|
||||
if (arg.status == "checking-for-update") {
|
||||
//document.title = document.title + ' - v' + arg.version;
|
||||
//updateTitle(
|
||||
// config.myCall,
|
||||
// config.tnc_host,
|
||||
// config.tnc_port,
|
||||
// " -v " + arg.version,
|
||||
//);
|
||||
document.getElementById("updater_status").innerHTML =
|
||||
'<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>';
|
||||
|
||||
document.getElementById("updater_status").className =
|
||||
"btn btn-secondary btn-sm";
|
||||
document.getElementById("update_and_install").style.display = "none";
|
||||
}
|
||||
if (arg.status == "update-downloaded") {
|
||||
document.getElementById("update_and_install").removeAttribute("style");
|
||||
document.getElementById("updater_status").innerHTML =
|
||||
'<i class="bi bi-cloud-download ms-1 me-1" style="color: white;"></i>';
|
||||
document.getElementById("updater_status").className =
|
||||
"btn btn-success btn-sm";
|
||||
|
||||
// HERE WE NEED TO RUN THIS SOMEHOW...
|
||||
//mainLog.info('quit application and install update');
|
||||
//autoUpdater.quitAndInstall();
|
||||
}
|
||||
if (arg.status == "update-not-available") {
|
||||
document.getElementById("updater_last_version").innerHTML =
|
||||
arg.info.releaseName;
|
||||
document.getElementById("updater_last_update").innerHTML =
|
||||
arg.info.releaseDate;
|
||||
document.getElementById("updater_release_notes").innerHTML =
|
||||
arg.info.releaseNotes;
|
||||
|
||||
document.getElementById("updater_status").innerHTML =
|
||||
'<i class="bi bi-check2-square ms-1 me-1" style="color: white;"></i>';
|
||||
document.getElementById("updater_status").className =
|
||||
"btn btn-success btn-sm";
|
||||
document.getElementById("update_and_install").style.display = "none";
|
||||
}
|
||||
if (arg.status == "update-available") {
|
||||
document.getElementById("updater_status").innerHTML =
|
||||
'<i class="bi bi-hourglass-split ms-1 me-1" style="color: white;"></i>';
|
||||
document.getElementById("updater_status").className =
|
||||
"btn btn-warning btn-sm";
|
||||
document.getElementById("update_and_install").style.display = "none";
|
||||
}
|
||||
|
||||
if (arg.status == "error") {
|
||||
document.getElementById("updater_status").innerHTML =
|
||||
'<i class="bi bi-exclamation-square ms-1 me-1" style="color: white;"></i>';
|
||||
document.getElementById("updater_status").className =
|
||||
"btn btn-danger btn-sm";
|
||||
document.getElementById("update_and_install").style.display = "none";
|
||||
}
|
||||
});
|
||||
setTimeout(removeLoading, 3999);
|
||||
|
|
101
gui/package.json
101
gui/package.json
|
@ -1,18 +1,20 @@
|
|||
{
|
||||
"name": "FreeDATA",
|
||||
"description": "FreeDATA",
|
||||
"description": "FreeDATA Client application for connecting to FreeDATA server",
|
||||
"private": true,
|
||||
"version": "0.11.0-alpha.2",
|
||||
"version": "0.14.5-alpha",
|
||||
"main": "dist-electron/main/index.js",
|
||||
"scripts": {
|
||||
"start": "git pull && npm i && vite",
|
||||
"start": "vite",
|
||||
"dev": "vite",
|
||||
"test": "vitest --run",
|
||||
"check": "vue-tsc --noEmit",
|
||||
"build": "vue-tsc --noEmit && vite build && electron-builder -p never",
|
||||
"release": "vue-tsc --noEmit && vite build && electron-builder -p onTag",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint --ext .js,.vue src",
|
||||
"lint-fix": "eslint --ext .js,.vue --fix src"
|
||||
"lint-fix": "eslint --ext .js,.vue --fix src",
|
||||
"install-deps": "npm install && npm update"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -31,53 +33,52 @@
|
|||
},
|
||||
"homepage": "https://freedata.app",
|
||||
"dependencies": {
|
||||
"@electron/notarize": "^2.1.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@vueuse/electron": "^10.4.1",
|
||||
"blob-util": "^2.0.2",
|
||||
"bootstrap": "^5.3.1",
|
||||
"bootstrap-icons": "^1.10.5",
|
||||
"bootswatch": "^5.3.1",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"chart.js": "^4.3.3",
|
||||
"chartjs-plugin-annotation": "^3.0.1",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-updater": "^6.1.6",
|
||||
"emoji-picker-element": "^1.18.3",
|
||||
"emoji-picker-element-data": "^1.4.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"mime": "^3.0.0",
|
||||
"pinia": "^2.1.6",
|
||||
"pouchdb": "^8.0.1",
|
||||
"pouchdb-browser": "^8.0.1",
|
||||
"pouchdb-find": "^8.0.1",
|
||||
"pouchdb-upsert": "^2.2.0",
|
||||
"qth-locator": "^2.1.0",
|
||||
"sass": "^1.66.1",
|
||||
"socket.io": "^4.7.2",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-chartjs": "^5.2.0",
|
||||
"vuemoji-picker": "^0.2.0"
|
||||
"@electron/notarize": "2.2.1",
|
||||
"@electron/universal": "2.0.1",
|
||||
"@popperjs/core": "2.11.8",
|
||||
"@vueuse/electron": "10.7.2",
|
||||
"blob-util": "2.0.2",
|
||||
"bootstrap": "5.3.2",
|
||||
"bootstrap-icons": "1.11.3",
|
||||
"browser-image-compression": "2.0.2",
|
||||
"chart.js": "4.4.1",
|
||||
"chartjs-plugin-annotation": "3.0.1",
|
||||
"electron-log": "5.1.1",
|
||||
"emoji-picker-element": "1.21.0",
|
||||
"emoji-picker-element-data": "1.6.0",
|
||||
"file-saver": "2.0.5",
|
||||
"gridstack": "10.0.1",
|
||||
"mime": "4.0.1",
|
||||
"nconf": "^0.12.1",
|
||||
"noto-color-emoji": "^1.0.1",
|
||||
"pinia": "2.1.7",
|
||||
"qth-locator": "2.1.0",
|
||||
"socket.io": "4.7.4",
|
||||
"uuid": "^9.0.1",
|
||||
"vue": "3.4.21",
|
||||
"vue-chartjs": "5.3.0",
|
||||
"vuemoji-picker": "0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||
"@vitejs/plugin-vue": "^4.4.0",
|
||||
"electron": "^27.0.0",
|
||||
"electron-builder": "^24.6.3",
|
||||
"eslint": "^8.50.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-config-standard-with-typescript": "^39.1.0",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-n": "^16.1.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^4.3.2",
|
||||
"vite-plugin-electron": "^0.14.0",
|
||||
"vite-plugin-electron-renderer": "^0.14.5",
|
||||
"vue": "^3.3.4",
|
||||
"vue-tsc": "^1.4.2"
|
||||
"@types/nconf": "^0.10.6",
|
||||
"@typescript-eslint/eslint-plugin": "6.21.0",
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"electron": "28.2.6",
|
||||
"electron-builder": "24.9.1",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-config-standard-with-typescript": "43.0.1",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-n": "16.6.2",
|
||||
"eslint-plugin-prettier": "5.1.3",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-vue": "9.22.0",
|
||||
"typescript": "5.3.3",
|
||||
"vite": "5.1.7",
|
||||
"vite-plugin-electron": "0.28.2",
|
||||
"vite-plugin-electron-renderer": "0.14.5",
|
||||
"vitest": "1.3.1",
|
||||
"vue": "3.4.21",
|
||||
"vue-tsc": "1.8.27"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -274,14 +274,16 @@ Spectrum.prototype.updateSpectrumRatio = function () {
|
|||
};
|
||||
|
||||
Spectrum.prototype.resize = function () {
|
||||
var width = this.canvas.clientWidth;
|
||||
var height = this.canvas.clientHeight;
|
||||
var width = this.parent.clientWidth;
|
||||
var height =this.parent.clientHeight;
|
||||
// little helper for setting height of clientHeight is not working as expected
|
||||
if (height == 0){
|
||||
var height = 250
|
||||
|
||||
}
|
||||
|
||||
if (width == 0){
|
||||
width=500;
|
||||
}
|
||||
|
||||
if (this.canvas.width != width || this.canvas.height != height) {
|
||||
this.canvas.width = width;
|
||||
|
@ -445,8 +447,8 @@ Spectrum.prototype.onKeypress = function (e) {
|
|||
|
||||
export function Spectrum(id, options) {
|
||||
|
||||
console.log("waterfall init....")
|
||||
console.log(document.getElementById(id))
|
||||
// console.log("waterfall init....")
|
||||
//console.log(document.getElementById(id))
|
||||
|
||||
// Handle options
|
||||
this.centerHz = options && options.centerHz ? options.centerHz : 1500;
|
||||
|
@ -473,7 +475,7 @@ export function Spectrum(id, options) {
|
|||
|
||||
// Create main canvas and adjust dimensions to match actual
|
||||
this.canvas = document.getElementById(id);
|
||||
|
||||
this.parent = this.canvas.parentElement;
|
||||
this.canvas.height = this.canvas.clientHeight;
|
||||
this.canvas.width = this.canvas.clientWidth;
|
||||
|
||||
|
|
|
@ -1,22 +1,128 @@
|
|||
<script setup lang="ts">
|
||||
import chat_navbar from "./chat_navbar.vue";
|
||||
// @ts-nocheck
|
||||
// disable typescript check beacuse of error with beacon histogram options
|
||||
|
||||
import chat_conversations from "./chat_conversations.vue";
|
||||
import chat_messages from "./chat_messages.vue";
|
||||
import chat_new_message from "./chat_new_message.vue";
|
||||
|
||||
//updateAllChat();
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
BarElement,
|
||||
} from "chart.js";
|
||||
|
||||
import { Bar } from "vue-chartjs";
|
||||
import { watch, nextTick, ref, computed } from "vue";
|
||||
import annotationPlugin from "chartjs-plugin-annotation";
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
BarElement,
|
||||
annotationPlugin,
|
||||
);
|
||||
|
||||
var beaconHistogramOptions = {
|
||||
type: "bar",
|
||||
bezierCurve: false, //remove curves from your plot
|
||||
scaleShowLabels: false, //remove labels
|
||||
tooltipEvents: [], //remove trigger from tooltips so they will'nt be show
|
||||
pointDot: false, //remove the points markers
|
||||
scaleShowGridLines: true, //set to false to remove the grids background
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
annotation: {
|
||||
annotations: [
|
||||
{
|
||||
type: "line",
|
||||
mode: "horizontal",
|
||||
scaleID: "y",
|
||||
value: 0,
|
||||
borderColor: "darkgrey", // Set the color to dark grey for the zero line
|
||||
borderWidth: 0.5, // Set the line width
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
scales: {
|
||||
x: {
|
||||
position: "bottom",
|
||||
display: false,
|
||||
min: -10,
|
||||
max: 15,
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
display: false,
|
||||
min: -5,
|
||||
max: 10,
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const beaconHistogramData = computed(() => ({
|
||||
labels: chat.beaconLabelArray,
|
||||
datasets: [
|
||||
{
|
||||
data: chat.beaconDataArray,
|
||||
tension: 0.1,
|
||||
borderColor: "rgb(0, 255, 0)",
|
||||
|
||||
backgroundColor: function (context) {
|
||||
var value = context.dataset.data[context.dataIndex];
|
||||
return value >= 0 ? "green" : "red";
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
const messagesContainer = ref(null);
|
||||
watch(
|
||||
() => chat.scrollTrigger,
|
||||
(newVal, oldVal) => {
|
||||
//console.log("Trigger changed from", oldVal, "to", newVal); // Debugging line
|
||||
nextTick(() => {
|
||||
if (messagesContainer.value) {
|
||||
messagesContainer.value.scrollTop =
|
||||
messagesContainer.value.scrollHeight;
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container-fluid m-0 p-0">
|
||||
<!------ chat navbar ---------------------------------------------------------------------->
|
||||
|
||||
<chat_navbar />
|
||||
|
||||
<div class="row h-100 ms-0 mt-0 me-1">
|
||||
<div class="col-3 m-0 p-0 h-100 bg-light">
|
||||
<!------Chats area ---------------------------------------------------------------------->
|
||||
<div class="container-fluid vh-100 overflow-scroll m-0 p-0">
|
||||
<div class="container-fluid vh-100 overflow-auto m-0 p-0">
|
||||
<chat_conversations />
|
||||
</div>
|
||||
<div class="h-100">
|
||||
|
@ -29,18 +135,36 @@ import chat_new_message from "./chat_new_message.vue";
|
|||
</div>
|
||||
</div>
|
||||
<div class="col-9 border-start vh-100 p-0">
|
||||
<!------messages area ---------------------------------------------------------------------->
|
||||
<div
|
||||
class="container overflow-auto"
|
||||
id="message-container"
|
||||
style="height: calc(100% - 200px)"
|
||||
>
|
||||
<chat_messages />
|
||||
<div class="d-flex flex-column vh-100">
|
||||
<!-- Top Navbar -->
|
||||
<nav class="navbar sticky-top z-0 bg-body-tertiary shadow">
|
||||
<div class="input-group mb-0 p-0 w-25">
|
||||
<button type="button" class="btn btn-outline-secondary" disabled>
|
||||
Beacons
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="form-floating border border-secondary-subtle border-1 rounded-end"
|
||||
>
|
||||
<Bar
|
||||
:data="beaconHistogramData"
|
||||
:options="beaconHistogramOptions"
|
||||
width="300"
|
||||
height="50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Chat Messages Area -->
|
||||
<div class="flex-grow-1 overflow-auto" ref="messagesContainer">
|
||||
<chat_messages />
|
||||
</div>
|
||||
|
||||
<chat_new_message />
|
||||
</div>
|
||||
|
||||
<!------ new message area ---------------------------------------------------------------------->
|
||||
|
||||
<chat_new_message />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,75 +4,87 @@ import pinia from "../store/index";
|
|||
setActivePinia(pinia);
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
import { getBeaconDataByCallsign } from "../js/api.js";
|
||||
|
||||
import {
|
||||
getNewMessagesByDXCallsign,
|
||||
resetIsNewMessage,
|
||||
} from "../js/chatHandler";
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
function chatSelected(callsign) {
|
||||
chat.selectedCallsign = callsign.toUpperCase();
|
||||
|
||||
// scroll message container to bottom
|
||||
var messageBody = document.getElementById("message-container");
|
||||
if (messageBody != null) {
|
||||
// needs sensible defaults
|
||||
messageBody.scrollTop = messageBody.scrollHeight - messageBody.clientHeight;
|
||||
}
|
||||
chat.triggerScrollToBottom();
|
||||
|
||||
if (getNewMessagesByDXCallsign(callsign)[1] > 0) {
|
||||
let messageArray = getNewMessagesByDXCallsign(callsign)[2];
|
||||
console.log(messageArray);
|
||||
processBeaconData(callsign);
|
||||
}
|
||||
|
||||
for (const key in messageArray) {
|
||||
resetIsNewMessage(messageArray[key].uuid, false);
|
||||
}
|
||||
}
|
||||
async function processBeaconData(callsign) {
|
||||
// fetch beacon data when selecting a callsign
|
||||
let beacons = await getBeaconDataByCallsign(callsign);
|
||||
chat.beaconLabelArray = beacons.map((entry) => entry.timestamp);
|
||||
chat.beaconDataArray = beacons.map((entry) => entry.snr);
|
||||
}
|
||||
|
||||
try {
|
||||
chat.beaconLabelArray = Object.values(
|
||||
chat.sorted_beacon_list[chat.selectedCallsign].timestamp,
|
||||
);
|
||||
chat.beaconDataArray = Object.values(
|
||||
chat.sorted_beacon_list[chat.selectedCallsign].snr,
|
||||
);
|
||||
} catch (e) {
|
||||
console.log("beacon data not fetched: " + e);
|
||||
chat.beaconLabelArray = [];
|
||||
chat.beaconDataArray = [];
|
||||
}
|
||||
function getDateTime(timestamp) {
|
||||
let date = new Date(timestamp);
|
||||
let hours = date.getHours().toString().padStart(2, "0");
|
||||
let minutes = date.getMinutes().toString().padStart(2, "0");
|
||||
let seconds = date.getSeconds().toString().padStart(2, "0");
|
||||
return `${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
const newChatCall = ref(null);
|
||||
|
||||
function newChat() {
|
||||
let callsign = this.newChatCall.value;
|
||||
callsign = callsign.toUpperCase().trim();
|
||||
if (callsign === "") return;
|
||||
this.newChatCall.value = "";
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="list-group m-0 p-0" id="chat-list-tab" role="chat-tablist">
|
||||
<template v-for="(item, key) in chat.callsign_list" :key="item.dxcallsign">
|
||||
<nav class="navbar sticky-top bg-body-tertiary shadow">
|
||||
<button
|
||||
class="btn btn-outline-primary w-100"
|
||||
data-bs-target="#newChatModal"
|
||||
data-bs-toggle="modal"
|
||||
>
|
||||
<i class="bi bi-pencil-square"> Start a new chat</i>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div
|
||||
class="list-group bg-body-tertiary m-0 p-1"
|
||||
id="chat-list-tab"
|
||||
role="chat-tablist"
|
||||
>
|
||||
<template
|
||||
v-for="(details, callsign, key) in chat.callsign_list"
|
||||
:key="callsign"
|
||||
>
|
||||
<a
|
||||
class="list-group-item list-group-item-action border-0 border-bottom rounded-0"
|
||||
class="list-group-item list-group-item-action list-group-item-secondary rounded-2 border-0 mb-2"
|
||||
:class="{ active: key == 0 }"
|
||||
:id="`list-chat-list-${item}`"
|
||||
:id="`list-chat-list-${callsign}`"
|
||||
data-bs-toggle="list"
|
||||
:href="`#list-${item}-messages`"
|
||||
:href="`#list-${callsign}-messages`"
|
||||
role="tab"
|
||||
aria-controls="list-{{item}}-messages"
|
||||
@click="chatSelected(item)"
|
||||
aria-controls="list-{{callsign}}-messages"
|
||||
@click="chatSelected(callsign)"
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
{{ item }}
|
||||
<span
|
||||
class="badge rounded-pill bg-danger"
|
||||
v-if="getNewMessagesByDXCallsign(item)[1] > 0"
|
||||
>
|
||||
{{ getNewMessagesByDXCallsign(item)[1] }} new messages
|
||||
</span>
|
||||
<div class="col-9 text-truncate">
|
||||
<strong>{{ callsign }}</strong>
|
||||
<br />
|
||||
<small> {{ details.body }} </small>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<small> {{ getDateTime(details.timestamp) }} </small>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary ms-2 border-0"
|
||||
data-bs-target="#deleteChatModal"
|
||||
data-bs-toggle="modal"
|
||||
@click="chatSelected(item)"
|
||||
@click="chatSelected(callsign)"
|
||||
>
|
||||
<i class="bi bi-three-dots-vertical"></i>
|
||||
</button>
|
||||
|
|
|
@ -15,22 +15,20 @@ import SentBroadcastMessage from "./chat_messages_broadcast_sent.vue"; // Import
|
|||
var prevChatMessageDay = "";
|
||||
|
||||
function getDateTime(timestampRaw) {
|
||||
var datetime = new Date(timestampRaw * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hourCycle: "h23",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
let date = new Date(timestampRaw);
|
||||
let year = date.getFullYear();
|
||||
let month = (date.getMonth() + 1).toString().padStart(2, "0"); // Months are zero-indexed
|
||||
let day = date.getDate().toString().padStart(2, "0");
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tab-content" id="nav-tabContent-chat-messages">
|
||||
<template v-for="(callsign, key) in chat.callsign_list">
|
||||
<div class="tab-content p-3" id="nav-tabContent-chat-messages">
|
||||
<template
|
||||
v-for="(details, callsign, key) in chat.callsign_list"
|
||||
:key="callsign"
|
||||
>
|
||||
<div
|
||||
class="tab-pane fade show"
|
||||
:class="{ active: key == 0 }"
|
||||
|
@ -38,38 +36,28 @@ function getDateTime(timestampRaw) {
|
|||
role="tabpanel"
|
||||
:aria-labelledby="`list-chat-list-${callsign}`"
|
||||
>
|
||||
<template
|
||||
v-for="item in chat.sorted_chat_list[callsign]"
|
||||
:key="item._id"
|
||||
>
|
||||
<template v-for="item in chat.sorted_chat_list[callsign]">
|
||||
<div v-if="prevChatMessageDay !== getDateTime(item.timestamp)">
|
||||
<div class="separator my-2">
|
||||
{{ (prevChatMessageDay = getDateTime(item.timestamp)) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="item.type === 'beacon' && item.status === 'received'">
|
||||
<!-- {{ item }} -->
|
||||
</div>
|
||||
|
||||
<div v-if="item.type === 'ping'">{{ item.snr }} dB ping received</div>
|
||||
|
||||
<div v-if="item.type === 'ping-ack'">
|
||||
{{ item.snr }} dB ping-ack received
|
||||
</div>
|
||||
|
||||
<div v-if="item.type === 'transmit'">
|
||||
<div v-if="item.direction === 'transmit'">
|
||||
<sent-message :message="item" />
|
||||
</div>
|
||||
<div v-else-if="item.type === 'received'">
|
||||
|
||||
<div v-else-if="item.direction === 'receive'">
|
||||
<received-message :message="item" />
|
||||
</div>
|
||||
<!--
|
||||
<div v-if="item.type === 'broadcast_transmit'">
|
||||
<sent-broadcast-message :message="item" />
|
||||
</div>
|
||||
<div v-else-if="item.type === 'broadcast_received'">
|
||||
<received-broadcast-message :message="item" />
|
||||
</div>
|
||||
-->
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -43,7 +43,8 @@
|
|||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
{{ message.percent }} % with {{ message.bytesperminute }} bpm
|
||||
{{ message.percent }} % with {{ message.bytesperminute }} bpm (
|
||||
{{ message.status }} )
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,16 +2,28 @@
|
|||
<div class="row justify-content-start mb-2">
|
||||
<div :class="messageWidthClass">
|
||||
<div class="card bg-light border-0 text-dark">
|
||||
<div class="card-header" v-if="getFileContent['filesize'] !== 0">
|
||||
<p class="card-text">
|
||||
{{ getFileContent["filename"] }} |
|
||||
{{ getFileContent["filesize"] }} Bytes |
|
||||
{{ getFileContent["filetype"] }}
|
||||
</p>
|
||||
<div
|
||||
v-for="attachment in message.attachments"
|
||||
:key="attachment.id"
|
||||
class="card-header"
|
||||
>
|
||||
<div class="btn-group w-100" role="group">
|
||||
<button class="btn btn-light text-truncate" disabled>
|
||||
{{ attachment.name }}
|
||||
</button>
|
||||
<button
|
||||
@click="
|
||||
downloadAttachment(attachment.hash_sha512, attachment.name)
|
||||
"
|
||||
class="btn btn-light w-25"
|
||||
>
|
||||
<i class="bi bi-download strong"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<p class="card-text">{{ message.msg }}</p>
|
||||
<p class="card-text">{{ message.body }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 bg-light border-top-0">
|
||||
|
@ -24,6 +36,7 @@
|
|||
<!-- Delete button outside of the card -->
|
||||
<div class="col-auto">
|
||||
<button
|
||||
disabled
|
||||
class="btn btn-outline-secondary border-0 me-1"
|
||||
@click="showMessageInfo"
|
||||
data-bs-target="#messageInfoModal"
|
||||
|
@ -32,14 +45,6 @@
|
|||
<i class="bi bi-info-circle"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="getFileContent['filesize'] !== 0"
|
||||
class="btn btn-outline-secondary border-0 me-1"
|
||||
@click="downloadAttachment"
|
||||
>
|
||||
<i class="bi bi-download"></i>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-outline-secondary border-0" @click="deleteMessage">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
|
@ -52,14 +57,13 @@ import {
|
|||
deleteMessageFromDB,
|
||||
requestMessageInfo,
|
||||
getMessageAttachment,
|
||||
} from "../js/chatHandler";
|
||||
} from "../js/messagesHandler";
|
||||
import { atob_FD } from "../js/freedata";
|
||||
|
||||
// pinia store setup
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
|
@ -70,50 +74,55 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
showMessageInfo() {
|
||||
requestMessageInfo(this.message._id);
|
||||
requestMessageInfo(this.message.id);
|
||||
//let infoModal = Modal.getOrCreateInstance(document.getElementById('messageInfoModal'))
|
||||
//console.log(this.infoModal)
|
||||
//this.infoModal.show()
|
||||
},
|
||||
deleteMessage() {
|
||||
deleteMessageFromDB(this.message._id);
|
||||
deleteMessageFromDB(this.message.id);
|
||||
},
|
||||
async downloadAttachment() {
|
||||
async downloadAttachment(hash_sha512, fileName) {
|
||||
try {
|
||||
// reset file store
|
||||
chat.downloadFileFromDB = [];
|
||||
const jsondata = await getMessageAttachment(hash_sha512);
|
||||
const byteCharacters = atob(jsondata.data);
|
||||
const byteArrays = [];
|
||||
|
||||
const attachment = await getMessageAttachment(this.message._id);
|
||||
const blob = new Blob([atob_FD(attachment[2])], {
|
||||
type: `${attachment[1]};charset=utf-8`,
|
||||
});
|
||||
window.focus();
|
||||
saveAs(blob, attachment[0]);
|
||||
for (let offset = 0; offset < byteCharacters.length; offset += 512) {
|
||||
const slice = byteCharacters.slice(offset, offset + 512);
|
||||
const byteNumbers = new Array(slice.length);
|
||||
for (let i = 0; i < slice.length; i++) {
|
||||
byteNumbers[i] = slice.charCodeAt(i);
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
byteArrays.push(byteArray);
|
||||
}
|
||||
|
||||
const blob = new Blob(byteArrays, { type: jsondata.type });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// Creating a temporary anchor element to download the file
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = url;
|
||||
anchor.download = fileName;
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
|
||||
// Cleanup
|
||||
document.body.removeChild(anchor);
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error("Failed to download attachment:", error);
|
||||
console.error("Failed to download the attachment:", error);
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
getFileContent() {
|
||||
try {
|
||||
var filename = Object.keys(this.message._attachments)[0];
|
||||
var filesize = this.message._attachments[filename]["length"];
|
||||
var filetype = filename.split(".")[1];
|
||||
|
||||
return { filename: filename, filesize: filesize, filetype: filetype };
|
||||
} catch (e) {
|
||||
console.log("file not loaded from database - empty?");
|
||||
// we are only checking against filesize for displaying attachments
|
||||
return { filesize: 0 };
|
||||
}
|
||||
},
|
||||
messageWidthClass() {
|
||||
// Calculate a Bootstrap grid class based on message length
|
||||
// Adjust the logic as needed to fit your requirements
|
||||
if (this.message.msg.length <= 50) {
|
||||
if (this.message.body.length <= 50) {
|
||||
return "col-4";
|
||||
} else if (this.message.msg.length <= 100) {
|
||||
} else if (this.message.body.length <= 100) {
|
||||
return "col-6";
|
||||
} else {
|
||||
return "col-9";
|
||||
|
@ -121,14 +130,11 @@ export default {
|
|||
},
|
||||
|
||||
getDateTime() {
|
||||
var datetime = new Date(this.message.timestamp * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
let date = new Date(this.message.timestamp);
|
||||
let hours = date.getHours().toString().padStart(2, "0");
|
||||
let minutes = date.getMinutes().toString().padStart(2, "0");
|
||||
let seconds = date.getSeconds().toString().padStart(2, "0");
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -2,14 +2,6 @@
|
|||
<div class="row justify-content-end mb-2">
|
||||
<!-- control area -->
|
||||
<div class="col-auto p-0 m-0">
|
||||
<button
|
||||
v-if="getFileContent['filesize'] !== 0"
|
||||
class="btn btn-outline-secondary border-0 me-1"
|
||||
@click="downloadAttachment"
|
||||
>
|
||||
<i class="bi bi-download"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-outline-secondary border-0 me-1"
|
||||
@click="repeatMessage"
|
||||
|
@ -18,6 +10,7 @@
|
|||
</button>
|
||||
|
||||
<button
|
||||
disabled
|
||||
class="btn btn-outline-secondary border-0 me-1"
|
||||
@click="showMessageInfo"
|
||||
data-bs-target="#messageInfoModal"
|
||||
|
@ -30,31 +23,53 @@
|
|||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- message area -->
|
||||
<div :class="messageWidthClass">
|
||||
<div class="card bg-primary text-white">
|
||||
<div class="card-header" v-if="getFileContent['filesize'] !== 0">
|
||||
<p class="card-text">
|
||||
{{ getFileContent["filename"] }} |
|
||||
{{ getFileContent["filesize"] }} Bytes |
|
||||
{{ getFileContent["filetype"] }}
|
||||
</p>
|
||||
<div class="card bg-secondary text-white">
|
||||
<div
|
||||
v-for="attachment in message.attachments"
|
||||
:key="attachment.id"
|
||||
class="card-header"
|
||||
>
|
||||
<div class="btn-group w-100" role="group">
|
||||
<button class="btn btn-light text-truncate" disabled>
|
||||
{{ attachment.name }}
|
||||
</button>
|
||||
<button
|
||||
@click="
|
||||
downloadAttachment(attachment.hash_sha512, attachment.name)
|
||||
"
|
||||
class="btn btn-light w-25"
|
||||
>
|
||||
<i class="bi bi-download strong"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<p class="card-text">{{ message.msg }}</p>
|
||||
<p class="card-text">{{ message.body }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 bg-primary border-top-0">
|
||||
<p class="text p-0 m-0 me-1 text-end">{{ getDateTime }}</p>
|
||||
<div class="card-footer p-0 bg-secondary border-top-0">
|
||||
<p class="text p-0 m-0 me-1 text-end">
|
||||
{{ message.status }} | {{ getDateTime }}
|
||||
</p>
|
||||
<!-- Display formatted timestamp in card-footer -->
|
||||
</div>
|
||||
|
||||
<div class="card-footer p-0 border-top-0" v-if="message.percent < 100">
|
||||
<div
|
||||
class="card-footer p-0 border-top-0"
|
||||
v-if="message.percent < 100 || message.status === 'failed'"
|
||||
>
|
||||
<div
|
||||
class="progress bg-secondary rounded-0 rounded-bottom"
|
||||
class="progress rounded-0 rounded-bottom"
|
||||
hidden
|
||||
:style="{ height: '10px' }"
|
||||
v-bind:class="{
|
||||
'bg-danger': message.status == 'failed',
|
||||
'bg-primary': message.status == 'transmitting',
|
||||
'bg-secondary': message.status == 'transmitted',
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped overflow-visible"
|
||||
|
@ -64,7 +79,8 @@
|
|||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
{{ message.percent }} % with {{ message.bytesperminute }} bpm
|
||||
{{ message.percent }} % with {{ message.bytesperminute }} bpm (
|
||||
{{ message.status }} )
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -81,13 +97,12 @@ import {
|
|||
deleteMessageFromDB,
|
||||
requestMessageInfo,
|
||||
getMessageAttachment,
|
||||
} from "../js/chatHandler";
|
||||
} from "../js/messagesHandler";
|
||||
|
||||
// pinia store setup
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
import { saveAs } from "file-saver";
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
|
@ -99,57 +114,60 @@ export default {
|
|||
|
||||
methods: {
|
||||
repeatMessage() {
|
||||
repeatMessageTransmission(this.message._id);
|
||||
repeatMessageTransmission(this.message.id);
|
||||
},
|
||||
|
||||
deleteMessage() {
|
||||
deleteMessageFromDB(this.message._id);
|
||||
deleteMessageFromDB(this.message.id);
|
||||
},
|
||||
showMessageInfo() {
|
||||
console.log("requesting message info.....");
|
||||
requestMessageInfo(this.message._id);
|
||||
requestMessageInfo(this.message.id);
|
||||
//let infoModal = Modal.getOrCreateInstance(document.getElementById('messageInfoModal'))
|
||||
//console.log(this.infoModal)
|
||||
//this.infoModal.show()
|
||||
},
|
||||
async downloadAttachment() {
|
||||
async downloadAttachment(hash_sha512, fileName) {
|
||||
try {
|
||||
// reset file store
|
||||
chat.downloadFileFromDB = [];
|
||||
const jsondata = await getMessageAttachment(hash_sha512);
|
||||
const byteCharacters = atob(jsondata.data);
|
||||
const byteArrays = [];
|
||||
|
||||
const attachment = await getMessageAttachment(this.message._id);
|
||||
const blob = new Blob([atob_FD(attachment[2])], {
|
||||
type: `${attachment[1]};charset=utf-8`,
|
||||
});
|
||||
saveAs(blob, attachment[0]);
|
||||
for (let offset = 0; offset < byteCharacters.length; offset += 512) {
|
||||
const slice = byteCharacters.slice(offset, offset + 512);
|
||||
const byteNumbers = new Array(slice.length);
|
||||
for (let i = 0; i < slice.length; i++) {
|
||||
byteNumbers[i] = slice.charCodeAt(i);
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
byteArrays.push(byteArray);
|
||||
}
|
||||
|
||||
const blob = new Blob(byteArrays, { type: jsondata.type });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// Creating a temporary anchor element to download the file
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = url;
|
||||
anchor.download = fileName;
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
|
||||
// Cleanup
|
||||
document.body.removeChild(anchor);
|
||||
URL.revokeObjectURL(url);
|
||||
} catch (error) {
|
||||
console.error("Failed to download attachment:", error);
|
||||
console.error("Failed to download the attachment:", error);
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
getFileContent() {
|
||||
var filename = Object.keys(this.message._attachments)[0];
|
||||
var filesize = this.message._attachments[filename]["length"];
|
||||
var filetype = filename.split(".")[1];
|
||||
|
||||
// ensure filesize is 0 for hiding message header if no data is available
|
||||
if (
|
||||
typeof filename === "undefined" ||
|
||||
filename === "" ||
|
||||
filename === "-"
|
||||
) {
|
||||
filesize = 0;
|
||||
}
|
||||
|
||||
return { filename: filename, filesize: filesize, filetype: filetype };
|
||||
},
|
||||
messageWidthClass() {
|
||||
// Calculate a Bootstrap grid class based on message length
|
||||
// Adjust the logic as needed to fit your requirements
|
||||
if (this.message.msg.length <= 50) {
|
||||
if (this.message.body.length <= 50) {
|
||||
return "col-4";
|
||||
} else if (this.message.msg.length <= 100) {
|
||||
} else if (this.message.body.length <= 100) {
|
||||
return "col-6";
|
||||
} else {
|
||||
return "col-9";
|
||||
|
@ -157,14 +175,11 @@ export default {
|
|||
},
|
||||
|
||||
getDateTime() {
|
||||
var datetime = new Date(this.message.timestamp * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
let date = new Date(this.message.timestamp);
|
||||
let hours = date.getHours().toString().padStart(2, "0");
|
||||
let minutes = date.getMinutes().toString().padStart(2, "0");
|
||||
let seconds = date.getSeconds().toString().padStart(2, "0");
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,195 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
import { getRxBuffer } from "../js/sock.js";
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
BarElement,
|
||||
} from "chart.js";
|
||||
|
||||
import { Bar } from "vue-chartjs";
|
||||
import { ref, computed } from "vue";
|
||||
import annotationPlugin from "chartjs-plugin-annotation";
|
||||
|
||||
const newChatCall = ref(null);
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
BarElement,
|
||||
annotationPlugin,
|
||||
);
|
||||
|
||||
var beaconHistogramOptions = {
|
||||
type: "bar",
|
||||
bezierCurve: false, //remove curves from your plot
|
||||
scaleShowLabels: false, //remove labels
|
||||
tooltipEvents: [], //remove trigger from tooltips so they will'nt be show
|
||||
pointDot: false, //remove the points markers
|
||||
scaleShowGridLines: true, //set to false to remove the grids background
|
||||
maintainAspectRatio: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
annotation: {
|
||||
annotations: [
|
||||
{
|
||||
type: "line",
|
||||
mode: "horizontal",
|
||||
scaleID: "y",
|
||||
value: 0,
|
||||
borderColor: "darkgrey", // Set the color to dark grey for the zero line
|
||||
borderWidth: 0.5, // Set the line width
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
scales: {
|
||||
x: {
|
||||
position: "bottom",
|
||||
display: false,
|
||||
min: -10,
|
||||
max: 15,
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
display: false,
|
||||
min: -5,
|
||||
max: 10,
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
//let dataArray = new Array(25).fill(0)
|
||||
//dataArray = dataArray.add([-3, 10, 8, 5, 3, 0, -5])
|
||||
//let dataArray1 = dataArray.shift(2)
|
||||
//console.log(dataArray1)
|
||||
//[-3, 10, 8, 5, 3, 0, -5]
|
||||
|
||||
try {
|
||||
chat.beaconLabelArray = Object.values(
|
||||
chat.sorted_beacon_list["DJ2LS-0"].timestamp,
|
||||
);
|
||||
chat.beaconDataArray = Object.values(chat.sorted_beacon_list["DJ2LS-0"].snr);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
const beaconHistogramData = computed(() => ({
|
||||
labels: chat.beaconLabelArray,
|
||||
datasets: [
|
||||
{
|
||||
data: chat.beaconDataArray,
|
||||
tension: 0.1,
|
||||
borderColor: "rgb(0, 255, 0)",
|
||||
|
||||
backgroundColor: function (context) {
|
||||
var value = context.dataset.data[context.dataIndex];
|
||||
return value >= 0 ? "green" : "red";
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
function newChat() {
|
||||
let callsign = this.newChatCall.value;
|
||||
callsign = callsign.toUpperCase();
|
||||
chat.callsign_list.add(callsign);
|
||||
this.newChatCall.value = "";
|
||||
}
|
||||
|
||||
function syncWithModem() {
|
||||
getRxBuffer();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="navbar bg-body-tertiary border-bottom">
|
||||
<div class="container">
|
||||
<div class="row w-100">
|
||||
<div class="col-3 p-0 me-2">
|
||||
<div class="input-group bottom-0 m-0 ms-1">
|
||||
<input
|
||||
class="form-control"
|
||||
maxlength="9"
|
||||
style="text-transform: uppercase"
|
||||
placeholder="callsign"
|
||||
@keypress.enter="newChat()"
|
||||
ref="newChatCall"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-success"
|
||||
id="createNewChatButton"
|
||||
type="button"
|
||||
title="Start a new chat (enter dx call sign first)"
|
||||
@click="newChat()"
|
||||
>
|
||||
new chat
|
||||
<i class="bi bi-pencil-square" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-5 ms-2 p-0">
|
||||
<!-- right side of chat nav bar-->
|
||||
|
||||
<div class="input-group mb-0 p-0 w-50">
|
||||
<button type="button" class="btn btn-outline-secondary" disabled>
|
||||
Beacons
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="form-floating border border-secondary-subtle border-1 rounded-end"
|
||||
>
|
||||
<Bar
|
||||
:data="beaconHistogramData"
|
||||
:options="beaconHistogramOptions"
|
||||
width="300"
|
||||
height="50"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-2 ms-2 p-0">
|
||||
<div class="input-group mb-0 p-0">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
@click="syncWithModem()"
|
||||
>
|
||||
Modem Sync {{ state.rx_buffer_length }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
|
@ -1,15 +1,10 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
|
||||
|
||||
import {saveSettingsToFile} from '../js/settingsHandler';
|
||||
|
||||
import { setActivePinia } from 'pinia';
|
||||
import pinia from '../store/index';
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from '../store/settingsStore.js';
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
import { useStateStore } from '../store/stateStore.js';
|
||||
const state = useStateStore(pinia);
|
||||
|
@ -22,8 +17,7 @@ import chat_navbar from './chat_navbar.vue'
|
|||
import chat_conversations from './chat_conversations.vue'
|
||||
import chat_messages from './chat_messages.vue'
|
||||
|
||||
import {updateAllChat, newMessage, newBroadcast} from '../js/chatHandler'
|
||||
|
||||
import { newMessage } from '../js/messagesHandler.ts'
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
|
@ -36,7 +30,7 @@ import {
|
|||
Legend
|
||||
} from 'chart.js'
|
||||
import { Line } from 'vue-chartjs'
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, nextTick } from 'vue';
|
||||
|
||||
|
||||
import { VuemojiPicker, EmojiClickEventDetail } from 'vuemoji-picker'
|
||||
|
@ -50,56 +44,98 @@ chat.inputText += detail.unicode
|
|||
const chatModuleMessage=ref(null);
|
||||
|
||||
|
||||
// Function to trigger the hidden file input
|
||||
function triggerFileInput() {
|
||||
fileInput.value.click();
|
||||
}
|
||||
|
||||
|
||||
// Use a ref for storing multiple files
|
||||
const selectedFiles = ref([]);
|
||||
const fileInput = ref(null);
|
||||
|
||||
function handleFileSelection(event) {
|
||||
// Reset previously selected files
|
||||
selectedFiles.value = [];
|
||||
|
||||
// Process each file
|
||||
for (let file of event.target.files) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
// Convert file content to base64
|
||||
const base64Content = btoa(reader.result); // Adjust this line if necessary
|
||||
selectedFiles.value.push({
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
type: file.type,
|
||||
content: base64Content, // Store base64 encoded content
|
||||
});
|
||||
};
|
||||
reader.readAsBinaryString(file); // Read the file content as binary string
|
||||
}
|
||||
}
|
||||
|
||||
function removeFile(index) {
|
||||
selectedFiles.value.splice(index, 1);
|
||||
// Check if the selectedFiles array is empty
|
||||
if (selectedFiles.value.length === 0) {
|
||||
// Reset the file input if there are no files left
|
||||
resetFile();
|
||||
}
|
||||
}
|
||||
|
||||
function transmitNewMessage() {
|
||||
// Check if a callsign is selected, default to the first one if not
|
||||
if (typeof(chat.selectedCallsign) === 'undefined') {
|
||||
chat.selectedCallsign = Object.keys(chat.callsign_list)[0];
|
||||
}
|
||||
|
||||
|
||||
function transmitNewMessage(){
|
||||
|
||||
chat.inputText = chat.inputText.trim();
|
||||
if (chat.inputText.length==0)
|
||||
return;
|
||||
|
||||
// Proceed only if there is text or files selected
|
||||
if (chat.inputText.length === 0 && selectedFiles.value.length === 0) return;
|
||||
|
||||
const attachments = selectedFiles.value.map(file => {
|
||||
return {
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
data: file.content
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
if (chat.selectedCallsign.startsWith("BC-")) {
|
||||
|
||||
newBroadcast(chat.selectedCallsign, chat.inputText)
|
||||
|
||||
// Handle broadcast message differently if needed
|
||||
return "new broadcast";
|
||||
} else {
|
||||
newMessage(chat.selectedCallsign, chat.inputText, chat.inputFile, chat.inputFileName, chat.inputFileSize, chat.inputFileType)
|
||||
// If there are attachments, send them along with the message
|
||||
if (attachments.length > 0) {
|
||||
newMessage(chat.selectedCallsign, chat.inputText, attachments);
|
||||
} else {
|
||||
// Send text only if no attachments are selected
|
||||
newMessage(chat.selectedCallsign, chat.inputText);
|
||||
}
|
||||
}
|
||||
// finally do a cleanup
|
||||
//chatModuleMessage.reset();
|
||||
|
||||
// Cleanup after sending message
|
||||
chat.inputText = '';
|
||||
chatModuleMessage.value="";
|
||||
// @ts-expect-error
|
||||
chatModuleMessage.value = "";
|
||||
resetFile()
|
||||
|
||||
}
|
||||
|
||||
function resetFile(event){
|
||||
chat.inputFileName = '-'
|
||||
chat.inputFileSize = '-'
|
||||
chat.inputFileType = '-'
|
||||
|
||||
if (fileInput.value) {
|
||||
fileInput.value.value = ''; // Reset the file input
|
||||
}
|
||||
// Clear the selected files array to reset the state of attachments
|
||||
selectedFiles.value = [];
|
||||
}
|
||||
|
||||
|
||||
function readFile(event) {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
console.log(reader.result);
|
||||
chat.inputFileName = event.target.files[0].name
|
||||
chat.inputFileSize = event.target.files[0].size
|
||||
chat.inputFileType = event.target.files[0].type
|
||||
|
||||
chat.inputFile = reader.result
|
||||
calculateTimeNeeded()
|
||||
|
||||
// String.fromCharCode.apply(null, Array.from(chatFile))
|
||||
|
||||
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(event.target.files[0]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -138,9 +174,9 @@ function calculateTimeNeeded(){
|
|||
return obj.snr === snrList[i].snr
|
||||
})
|
||||
|
||||
calculatedSpeedPerMinutePER0.push(chat.inputFileSize / result.bpm)
|
||||
calculatedSpeedPerMinutePER25.push(chat.inputFileSize / (result.bpm * 0.75))
|
||||
calculatedSpeedPerMinutePER75.push(chat.inputFileSize / (result.bpm * 0.25))
|
||||
calculatedSpeedPerMinutePER0.push(totalSize / result.bpm)
|
||||
calculatedSpeedPerMinutePER25.push(totalSize / (result.bpm * 0.75))
|
||||
calculatedSpeedPerMinutePER75.push(totalSize / (result.bpm * 0.25))
|
||||
|
||||
}
|
||||
|
||||
|
@ -167,22 +203,51 @@ const speedChartData = computed(() => ({
|
|||
|
||||
<template>
|
||||
|
||||
<div class="container-fluid mt-2 p-0">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="expand_textarea"
|
||||
class="btn-check"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<label
|
||||
class="btn d-flex justify-content-center"
|
||||
id="expand_textarea_label"
|
||||
for="expand_textarea"
|
||||
><i
|
||||
id="expand_textarea_button"
|
||||
class="bi bi-chevron-compact-up"
|
||||
></i
|
||||
></label>
|
||||
|
||||
<nav class="navbar sticky-bottom bg-body-tertiary border-top mb-5">
|
||||
<div class="container-fluid p-0">
|
||||
|
||||
|
||||
|
||||
<!-- Hidden file input -->
|
||||
<input type="file" multiple ref="fileInput" @change="handleFileSelection" style="display: none;" />
|
||||
|
||||
|
||||
|
||||
<div class="container-fluid px-0">
|
||||
<div class="d-flex flex-row overflow-auto bg-light">
|
||||
<div v-for="(file, index) in selectedFiles" :key="index" class="pe-2">
|
||||
<div class="card" style=" min-width: 10rem; max-width: 10rem;">
|
||||
<!-- Card Header with Remove Button -->
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span class="text-truncate">{{ file.name }}</span>
|
||||
<button class="btn btn-close" @click="removeFile(index)"></button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">...</p>
|
||||
</div>
|
||||
<div class="card-footer text-muted">
|
||||
{{ file.type }}
|
||||
</div>
|
||||
<div class="card-footer text-muted">
|
||||
{{ file.size }} bytes
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<Line :data="speedChartData" />
|
||||
-->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="input-group bottom-0 ms-2">
|
||||
|
||||
|
@ -200,10 +265,11 @@ const speedChartData = computed(() => ({
|
|||
|
||||
|
||||
<!-- trigger file selection modal -->
|
||||
<button type="button" class="btn btn-outline-secondary border-0 rounded-pill me-1" data-bs-toggle="modal" data-bs-target="#fileSelectionModal">
|
||||
<i class="bi bi-paperclip" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-outline-secondary border-0 rounded-pill me-1" @click="triggerFileInput">
|
||||
<i class="bi bi-paperclip" style="font-size: 1.2rem"></i>
|
||||
|
||||
</button>
|
||||
|
||||
<textarea
|
||||
class="form-control border rounded-pill"
|
||||
|
@ -231,76 +297,17 @@ const speedChartData = computed(() => ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- select file modal -->
|
||||
|
||||
<div
|
||||
class="modal fade"
|
||||
id="fileSelectionModal"
|
||||
tabindex="-1"
|
||||
aria-labelledby="fileSelectionModalLabel"
|
||||
aria-hidden="true"
|
||||
>
|
||||
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="staticBackdropLabel">File Attachment</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @click="resetFile"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
|
||||
<div class="alert alert-warning d-flex align-items-center" role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill ms-2 me-2"></i>
|
||||
<div>
|
||||
Transmission speed over HF channels is very limited!
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-group-text mb-3">
|
||||
<input class="" type="file" ref="doc" @change="readFile" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="btn-group me-2" role="group" aria-label="Basic outlined example">
|
||||
<button type="button" class="btn btn-secondary">Type</button>
|
||||
<button type="button" class="btn btn-secondary disabled">{{chat.inputFileType}}</button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group me-2" role="group" aria-label="Basic outlined example">
|
||||
<button type="button" class="btn btn-secondary">Size</button>
|
||||
<button type="button" class="btn btn-secondary disabled">{{chat.inputFileSize}}</button>
|
||||
</div>
|
||||
|
||||
<Line :data="speedChartData" />
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @click="resetFile">Reset</button>
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Append</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
<!-- Emoji Picker Modal -->
|
||||
<div class="modal fade" id="emojiPickerModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal fade" id="emojiPickerModal" aria-hidden="true" >
|
||||
<div class="modal-dialog modal-dialog-centered modal-sm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body p-0">
|
||||
<VuemojiPicker @emojiClick="handleEmojiClick" />
|
||||
<VuemojiPicker @emojiClick="handleEmojiClick"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
|
|
1027
gui/src/components/dynamic_components.vue
Normal file
1027
gui/src/components/dynamic_components.vue
Normal file
File diff suppressed because it is too large
Load diff
15
gui/src/components/grid/button.vue
Normal file
15
gui/src/components/grid/button.vue
Normal file
|
@ -0,0 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineProps(["btnText", "btnID"]);
|
||||
|
||||
function emitClick() {
|
||||
window.dispatchEvent(new CustomEvent("add-widget", { detail: props.btnID }));
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<button
|
||||
class="btn btn-small btn-outline-secondary mb-1"
|
||||
v-on:click="emitClick"
|
||||
>
|
||||
{{ btnText }}
|
||||
</button>
|
||||
</template>
|
17
gui/src/components/grid/grid_CQ.vue
Normal file
17
gui/src/components/grid/grid_CQ.vue
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { sendModemCQ } from "../../js/api.js";
|
||||
</script>
|
||||
<template>
|
||||
<div class="fill h-100" style="width: calc(100% - 24px)">
|
||||
<a
|
||||
class="btn btn-sm btn-secondary d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
|
||||
@click="sendModemCQ"
|
||||
title="Send a CQ call!"
|
||||
>CQ</a
|
||||
>
|
||||
</div>
|
||||
</template>
|
|
@ -1,69 +1,21 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { record_audio } from "../js/sock.js";
|
||||
|
||||
function startStopRecordAudio() {
|
||||
record_audio();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card mb-1">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-volume-up" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<strong class="fs-5">Audio</strong>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<button
|
||||
type="button"
|
||||
id="audioModalButton"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#audioModal"
|
||||
class="btn btn-sm btn-outline-secondary me-1"
|
||||
>
|
||||
Tune
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="startStopRecording"
|
||||
class="btn btn-sm"
|
||||
@click="startStopRecordAudio()"
|
||||
v-bind:class="{
|
||||
'btn-outline-secondary': state.audio_recording === 'False',
|
||||
'btn-secondary': state.audio_recording === 'True',
|
||||
}"
|
||||
>
|
||||
Record
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalAudioLevel"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#audioLevelHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card w-100 h-100">
|
||||
<div class="card-header p-0 mb-1">
|
||||
<i class="bi bi-volume-up" style="font-size: 1rem"></i>
|
||||
<strong>Audio</strong>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="container">
|
||||
<div class="card-body pt-0 pb-0">
|
||||
<div class="container-wide">
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="col-lg-6">
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-top"
|
||||
style="height: 22px"
|
||||
|
@ -122,7 +74,7 @@ function startStopRecordAudio() {
|
|||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm">
|
||||
<div class="col-lg-6">
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-top"
|
||||
style="height: 22px"
|
93
gui/src/components/grid/grid_active_broadcasts.vue
Normal file
93
gui/src/components/grid/grid_active_broadcasts.vue
Normal file
|
@ -0,0 +1,93 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { sendModemCQ, sendModemPing, setModemBeacon } from "../../js/api.js";
|
||||
|
||||
function transmitPing() {
|
||||
sendModemPing(dxcallPing.value.toUpperCase());
|
||||
}
|
||||
|
||||
function startStopBeacon() {
|
||||
if (state.beacon_state === true) {
|
||||
setModemBeacon(false);
|
||||
} else {
|
||||
setModemBeacon(true);
|
||||
}
|
||||
}
|
||||
var dxcallPing = ref("");
|
||||
window.addEventListener(
|
||||
"stationSelected",
|
||||
function (eventdata) {
|
||||
let evt = <CustomEvent>eventdata;
|
||||
dxcallPing.value = evt.detail;
|
||||
},
|
||||
false,
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
<div class="card-header p-0">
|
||||
<i class="bi bi-broadcast" style="font-size: 1.2rem"></i>
|
||||
<strong>Broadcasts</strong>
|
||||
</div>
|
||||
<div class="card-body overflow-auto p-0">
|
||||
<div class="input-group input-group-sm mb-0">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 6rem; min-width: 3rem; text-transform: uppercase"
|
||||
placeholder="DXcall"
|
||||
pattern="[A-Z]*"
|
||||
maxlength="11"
|
||||
aria-label="Input group"
|
||||
aria-describedby="btnGroupAddon"
|
||||
v-model="dxcallPing"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
id="sendPing"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Send a ping request to a remote station"
|
||||
@click="transmitPing()"
|
||||
>
|
||||
Ping
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary ms-1"
|
||||
id="sendCQ"
|
||||
type="button"
|
||||
title="Send a CQ to the world"
|
||||
@click="sendModemCQ()"
|
||||
>
|
||||
Call CQ
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="startBeacon"
|
||||
class="btn btn-sm ms-1"
|
||||
@click="startStopBeacon()"
|
||||
v-bind:class="{
|
||||
'btn-success': state.beacon_state === true,
|
||||
'btn-outline-secondary': state.beacon_state === false,
|
||||
}"
|
||||
title="Toggle beacon mode. The interval can be set in settings. While sending a beacon, you can receive ping requests and open a datachannel. If a datachannel is opened, the beacon pauses."
|
||||
>
|
||||
Toggle beacon
|
||||
</button>
|
||||
</div>
|
||||
<!-- end of row-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
105
gui/src/components/grid/grid_active_broadcasts_vert.vue
Normal file
105
gui/src/components/grid/grid_active_broadcasts_vert.vue
Normal file
|
@ -0,0 +1,105 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { sendModemCQ, sendModemPing, setModemBeacon } from "../../js/api.js";
|
||||
|
||||
function transmitPing() {
|
||||
sendModemPing(dxcallPing.value.toUpperCase());
|
||||
}
|
||||
|
||||
function startStopBeacon() {
|
||||
if (state.beacon_state === true) {
|
||||
setModemBeacon(false);
|
||||
} else {
|
||||
setModemBeacon(true);
|
||||
}
|
||||
}
|
||||
var dxcallPing = ref("");
|
||||
window.addEventListener(
|
||||
"stationSelected",
|
||||
function (eventdata) {
|
||||
let evt = <CustomEvent>eventdata;
|
||||
dxcallPing.value = evt.detail;
|
||||
},
|
||||
false,
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
<div class="card-header p-0">
|
||||
<i class="bi bi-broadcast" style="font-size: 1.2rem"></i>
|
||||
<strong>Broadcasts</strong>
|
||||
</div>
|
||||
<div class="card-body overflow-auto p-0">
|
||||
<div class="container text-center">
|
||||
<div class="row mb-2 mt-2">
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group w-100">
|
||||
<div class="form-floating">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="text-transform: uppercase"
|
||||
id="floatingInput"
|
||||
placeholder="dx-callsign"
|
||||
v-model="dxcallPing"
|
||||
maxlength="11"
|
||||
pattern="[A-Z]*"
|
||||
/>
|
||||
<label for="floatingInput">DX-Callsign</label>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
id="sendPing"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Send a ping request to a remote station"
|
||||
@click="transmitPing()"
|
||||
>
|
||||
<strong>Ping</strong>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="flexSwitchBeacon"
|
||||
v-model="state.beacon_state"
|
||||
@click="startStopBeacon()"
|
||||
/>
|
||||
<label class="form-check-label" for="flexSwitchBeacon"
|
||||
>Beacon</label
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary w-100"
|
||||
id="sendCQ"
|
||||
type="button"
|
||||
title="Send a CQ to the world"
|
||||
@click="sendModemCQ()"
|
||||
>
|
||||
<h3>CQ CQ CQ</h3>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
94
gui/src/components/grid/grid_active_heard_stations.vue
Normal file
94
gui/src/components/grid/grid_active_heard_stations.vue
Normal file
|
@ -0,0 +1,94 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
const { distance } = require("qth-locator");
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../../store/settingsStore.js";
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function getDateTime(timestampRaw) {
|
||||
var datetime = new Date(timestampRaw * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hourCycle: "h23",
|
||||
year: "2-digit",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
}
|
||||
|
||||
function getMaidenheadDistance(dxGrid) {
|
||||
try {
|
||||
return parseInt(distance(settings.remote.STATION.mygrid, dxGrid));
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
function pushToPing(origin)
|
||||
{
|
||||
window.dispatchEvent(new CustomEvent("stationSelected", {bubbles:true, detail: origin }));
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
<!--325px-->
|
||||
<div class="card-header p-0">
|
||||
<i class="bi bi-list-columns-reverse" style="font-size: 1.2rem"></i>
|
||||
<strong>Heard stations</strong>
|
||||
</div>
|
||||
|
||||
<div class="card-body overflow-auto p-0">
|
||||
<div class="table-responsive">
|
||||
<!-- START OF TABLE FOR HEARD STATIONS -->
|
||||
<table class="table table-sm table-striped" id="tblHeardStationList">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" id="thTime">Time</th>
|
||||
<th scope="col" id="thFreq">Freq</th>
|
||||
<th scope="col" id="thDxcall">DXCall</th>
|
||||
<th scope="col" id="thDxgrid">Grid</th>
|
||||
<th scope="col" id="thDist">Dist</th>
|
||||
<th scope="col" id="thType">Type</th>
|
||||
<th scope="col" id="thSnr">SNR</th>
|
||||
<!--<th scope="col">Off</th>-->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="gridHeardStations">
|
||||
<!--https://vuejs.org/guide/essentials/list.html-->
|
||||
<tr v-for="item in state.heard_stations" :key="item.origin" @click="pushToPing(item.origin)">
|
||||
<td>
|
||||
{{ getDateTime(item.timestamp) }}
|
||||
</td>
|
||||
<td>{{ item.frequency / 1000 }} kHz</td>
|
||||
<td>
|
||||
{{ item.origin }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.gridsquare }}
|
||||
</td>
|
||||
<td>{{ getMaidenheadDistance(item.gridsquare) }} km</td>
|
||||
<td>
|
||||
{{ item.activity_type }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.snr }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- END OF HEARD STATIONS TABLE -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
78
gui/src/components/grid/grid_active_heard_stations_mini.vue
Normal file
78
gui/src/components/grid/grid_active_heard_stations_mini.vue
Normal file
|
@ -0,0 +1,78 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
const { distance } = require("qth-locator");
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../../store/settingsStore.js";
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function getDateTime(timestampRaw) {
|
||||
var datetime = new Date(timestampRaw * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hourCycle: "h23",
|
||||
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
}
|
||||
|
||||
function getMaidenheadDistance(dxGrid) {
|
||||
try {
|
||||
return parseInt(distance(settings.remote.STATION.mygrid, dxGrid));
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
function pushToPing(origin)
|
||||
{
|
||||
window.dispatchEvent(new CustomEvent("stationSelected", {bubbles:true, detail: origin }));
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
<div class="card-header p-0">
|
||||
<i class="bi bi-list-columns-reverse" style="font-size: 1.2rem"></i>
|
||||
<strong>Heard stations</strong>
|
||||
</div>
|
||||
|
||||
<div class="card-body overflow-auto p-0">
|
||||
<div class="table-responsive">
|
||||
<!-- START OF TABLE FOR HEARD STATIONS -->
|
||||
<table class="table table-sm table-striped" id="tblHeardStationList">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" id="thTime">Time</th>
|
||||
<th scope="col" id="thDxcall">DXCall</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="miniHeardStations">
|
||||
<!--https://vuejs.org/guide/essentials/list.html-->
|
||||
<tr v-for="item in state.heard_stations" :key="item.origin" @click="pushToPing(item.origin)">
|
||||
<td>
|
||||
<span class="fs-6">{{ getDateTime(item.timestamp) }}</span>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<span>{{ item.origin }}</span>
|
||||
</td>
|
||||
<!--<td>{{ item.offset }}</td>-->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- END OF HEARD STATIONS TABLE -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
102
gui/src/components/grid/grid_active_rig_control.vue
Normal file
102
gui/src/components/grid/grid_active_rig_control.vue
Normal file
|
@ -0,0 +1,102 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
import { setRadioParametersFrequency, setRadioParametersMode, setRadioParametersRFLevel } from "../../js/api";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function set_radio_parameter_frequency(){
|
||||
setRadioParametersFrequency(state.new_frequency)
|
||||
}
|
||||
|
||||
function set_radio_parameter_mode(){
|
||||
setRadioParametersMode(state.mode)
|
||||
}
|
||||
|
||||
function set_radio_parameter_rflevel(){
|
||||
setRadioParametersRFLevel(state.rf_level)
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
<div class="card-header p-0">
|
||||
<i class="bi bi-house-door" style="font-size: 1.2rem"></i>
|
||||
<strong>Radio control</strong>
|
||||
</div>
|
||||
|
||||
<div class="card-body overflow-auto p-0">
|
||||
<div class="input-group input-group-sm bottom-0 m-0">
|
||||
<div class="me-2">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">QRG</span>
|
||||
<span class="input-group-text"
|
||||
>{{ state.frequency / 1000 }} kHz</span
|
||||
>
|
||||
|
||||
<button
|
||||
class="btn btn-secondary dropdown-toggle"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
type="button"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#offcanvasFrequency"
|
||||
aria-controls="offcanvasExample"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="me-2">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">Mode</span>
|
||||
<select
|
||||
class="form-control"
|
||||
v-model="state.mode"
|
||||
@click="set_radio_parameter_mode()"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>
|
||||
<option selected value="">---</option>
|
||||
<option value="USB">USB</option>
|
||||
<option value="USB-D">USB-D</option>
|
||||
<option value="PKTUSB">PKT-U</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="me-2">
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">% Power</span>
|
||||
<select
|
||||
class="form-control"
|
||||
v-model="state.rf_level"
|
||||
@click="set_radio_parameter_rflevel()"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>
|
||||
<option value="0">-</option>
|
||||
<option value="10">10</option>
|
||||
<option value="20">20</option>
|
||||
<option value="30">30</option>
|
||||
<option value="40">40</option>
|
||||
<option value="50">50</option>
|
||||
<option value="60">60</option>
|
||||
<option value="70">70</option>
|
||||
<option value="80">80</option>
|
||||
<option value="90">90</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
363
gui/src/components/grid/grid_active_stats.vue
Normal file
363
gui/src/components/grid/grid_active_stats.vue
Normal file
|
@ -0,0 +1,363 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
// reason for no check is, that we have some mixing of typescript and chart js which seems to be not to be fixed that easy
|
||||
import { ref, computed, onMounted, nextTick } from "vue";
|
||||
import { initWaterfall, setColormap } from "../../js/waterfallHandler.js";
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../../store/settingsStore.js";
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "chart.js";
|
||||
import { Line, Scatter } from "vue-chartjs";
|
||||
|
||||
const localSpectrumView = ref("waterfall");
|
||||
function selectStatsControl(item) {
|
||||
switch (item) {
|
||||
case "wf":
|
||||
localSpectrumView.value = "waterfall";
|
||||
break;
|
||||
case "scatter":
|
||||
localSpectrumView.value = "scatter";
|
||||
break;
|
||||
case "chart":
|
||||
localSpectrumView.value = "chart";
|
||||
break;
|
||||
default:
|
||||
localSpectrumView.value = "waterfall";
|
||||
}
|
||||
//saveSettingsToFile();
|
||||
}
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
);
|
||||
|
||||
// https://www.chartjs.org/docs/latest/samples/line/segments.html
|
||||
const skipped = (speedCtx, value) =>
|
||||
speedCtx.p0.skip || speedCtx.p1.skip ? value : undefined;
|
||||
const down = (speedCtx, value) =>
|
||||
speedCtx.p0.parsed.y > speedCtx.p1.parsed.y ? value : undefined;
|
||||
|
||||
var transmissionSpeedChartOptions = {
|
||||
//type: "line",
|
||||
responsive: true,
|
||||
animations: true,
|
||||
maintainAspectRatio: false,
|
||||
cubicInterpolationMode: "monotone",
|
||||
tension: 0.4,
|
||||
scales: {
|
||||
SNR: {
|
||||
type: "linear",
|
||||
ticks: { beginAtZero: false, color: "rgb(255, 99, 132)" },
|
||||
position: "right",
|
||||
},
|
||||
SPEED: {
|
||||
type: "linear",
|
||||
ticks: { beginAtZero: false, 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 } },
|
||||
},
|
||||
};
|
||||
|
||||
const transmissionSpeedChartData = computed(() => ({
|
||||
labels: state.arq_speed_list_timestamp,
|
||||
datasets: [
|
||||
{
|
||||
type: "line",
|
||||
label: "SNR[dB]",
|
||||
data: state.arq_speed_list_snr,
|
||||
borderColor: "rgb(75, 192, 192, 1.0)",
|
||||
pointRadius: 1,
|
||||
segment: {
|
||||
borderColor: (speedCtx) =>
|
||||
skipped(speedCtx, "rgb(0,0,0,0.4)") ||
|
||||
down(speedCtx, "rgb(192,75,75)"),
|
||||
borderDash: (speedCtx) => skipped(speedCtx, [3, 3]),
|
||||
},
|
||||
spanGaps: true,
|
||||
backgroundColor: "rgba(75, 192, 192, 0.2)",
|
||||
order: 1,
|
||||
yAxisID: "SNR",
|
||||
},
|
||||
{
|
||||
type: "bar",
|
||||
label: "Speed[bpm]",
|
||||
data: state.arq_speed_list_bpm,
|
||||
borderColor: "rgb(120, 100, 120, 1.0)",
|
||||
backgroundColor: "rgba(120, 100, 120, 0.2)",
|
||||
order: 0,
|
||||
yAxisID: "SPEED",
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
const scatterChartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
type: "linear",
|
||||
position: "bottom",
|
||||
grid: {
|
||||
display: true,
|
||||
lineWidth: 1, // Set the line width for x-axis grid lines
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
type: "linear",
|
||||
position: "left",
|
||||
grid: {
|
||||
display: true,
|
||||
lineWidth: 1, // Set the line width for y-axis grid lines
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// dummy data
|
||||
//state.scatter = [{"x":"166","y":"46"},{"x":"-193","y":"-139"},{"x":"-165","y":"-291"},{"x":"311","y":"-367"},{"x":"389","y":"199"},{"x":"78","y":"372"},{"x":"242","y":"-431"},{"x":"-271","y":"-248"},{"x":"28","y":"-130"},{"x":"-20","y":"187"},{"x":"74","y":"362"},{"x":"-316","y":"-229"},{"x":"-180","y":"261"},{"x":"321","y":"360"},{"x":"438","y":"-288"},{"x":"378","y":"-94"},{"x":"462","y":"-163"},{"x":"-265","y":"248"},{"x":"210","y":"314"},{"x":"230","y":"-320"},{"x":"261","y":"-244"},{"x":"-283","y":"-373"}]
|
||||
|
||||
const scatterChartData = computed(() => ({
|
||||
datasets: [
|
||||
{
|
||||
type: "scatter",
|
||||
fill: true,
|
||||
data: state.scatter,
|
||||
label: "Scatter",
|
||||
tension: 0.1,
|
||||
borderColor: "rgb(0, 255, 0)",
|
||||
},
|
||||
],
|
||||
}));
|
||||
var localSpectrum;
|
||||
//Define and generate a unique ID for canvas
|
||||
const localSpectrumID = ref("");
|
||||
localSpectrumID.value =
|
||||
"gridwfid-" + (Math.random() + 1).toString(36).substring(7);
|
||||
onMounted(() => {
|
||||
// This code will be executed after the component is mounted to the DOM
|
||||
// You can access DOM elements or perform other initialization here
|
||||
//const myElement = this.$refs.waterfall; // Access the DOM element with ref
|
||||
|
||||
// init waterfall
|
||||
localSpectrum = initWaterfall(localSpectrumID.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
<div class="card-header p-1">
|
||||
<div class="btn-group" role="group">
|
||||
<div
|
||||
class="list-group bg-body-tertiary list-group-horizontal"
|
||||
id="list-tab"
|
||||
role="tablist"
|
||||
>
|
||||
<a
|
||||
class="py-0 list-group-item list-group-item-dark list-group-item-action"
|
||||
data-bs-toggle="list"
|
||||
role="tab"
|
||||
aria-controls="list-waterfall"
|
||||
v-bind:class="{
|
||||
active: localSpectrumView == 'waterfall',
|
||||
}"
|
||||
@click="selectStatsControl('wf')"
|
||||
><strong><i class="bi bi-water"></i></strong
|
||||
></a>
|
||||
<a
|
||||
class="py-0 list-group-item list-group-item-dark list-group-item-action"
|
||||
data-bs-toggle="list"
|
||||
role="tab"
|
||||
aria-controls="list-scatter"
|
||||
v-bind:class="{
|
||||
active: localSpectrumView == 'scatter',
|
||||
}"
|
||||
@click="selectStatsControl('scatter')"
|
||||
><strong><i class="bi bi-border-outer"></i></strong
|
||||
></a>
|
||||
<a
|
||||
class="py-0 list-group-item list-group-item-dark list-group-item-action"
|
||||
data-bs-toggle="list"
|
||||
role="tab"
|
||||
aria-controls="list-chart"
|
||||
v-bind:class="{ active: localSpectrumView == 'chart' }"
|
||||
@click="selectStatsControl('chart')"
|
||||
><strong><i class="bi bi-graph-up-arrow"></i></strong
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group" role="group" aria-label="Busy indicators">
|
||||
<button
|
||||
class="btn btn-sm ms-1 p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy_slot[0] === true,
|
||||
'btn-outline-secondary': state.channel_busy_slot[0] === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S1
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy_slot[1] === true,
|
||||
'btn-outline-secondary': state.channel_busy_slot[1] === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S2
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy_slot[2] === true,
|
||||
'btn-outline-secondary': state.channel_busy_slot[2] === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S3
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy_slot[3] === true,
|
||||
'btn-outline-secondary': state.channel_busy_slot[3] === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S4
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy_slot[4] === true,
|
||||
'btn-outline-secondary': state.channel_busy_slot[4] === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S5
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
title="Recieving data: illuminates <strong class='text-success'>green</strong> if receiving codec2 data"
|
||||
v-bind:class="{
|
||||
'btn-success': state.is_codec2_traffic === true,
|
||||
'btn-outline-secondary': state.is_codec2_traffic === false,
|
||||
}"
|
||||
>
|
||||
data
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body w-100 h-100 overflow-auto p-2">
|
||||
<div class="tab-content h-100 w-100" id="nav-stats-tabContent">
|
||||
<div
|
||||
class="tab-pane fade h-100 w-100"
|
||||
v-bind:class="{
|
||||
'show active': localSpectrumView == 'waterfall',
|
||||
}"
|
||||
role="stats_tabpanel"
|
||||
aria-labelledby="list-waterfall-list"
|
||||
>
|
||||
<canvas v-bind:id="localSpectrumID" class="force-gpu"></canvas>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade h-100 w-100"
|
||||
v-bind:class="{
|
||||
'show active': localSpectrumView == 'scatter',
|
||||
}"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-scatter-list"
|
||||
>
|
||||
<Scatter :data="scatterChartData" :options="scatterChartOptions" />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade h-100 w-100"
|
||||
v-bind:class="{ 'show active': localSpectrumView == 'chart' }"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-chart-list"
|
||||
>
|
||||
<Line
|
||||
:data="transmissionSpeedChartData"
|
||||
:options="transmissionSpeedChartOptions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--278px-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
43
gui/src/components/grid/grid_activities.vue
Normal file
43
gui/src/components/grid/grid_activities.vue
Normal file
|
@ -0,0 +1,43 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function getDateTime(timestampRaw) {
|
||||
var datetime = new Date(timestampRaw * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hourCycle: "h23",
|
||||
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card h-100">
|
||||
<div class="card-header p-0">
|
||||
<i class="bi bi-card-list" style="font-size: 1.2rem"></i>
|
||||
<strong>Activity</strong>
|
||||
</div>
|
||||
<div class="card-body overflow-auto m-0 p-0" style="align-items: start">
|
||||
<div v-for="item in state.activities" :key="item[0]">
|
||||
<h6 style="text-align: start" class="mb-0">
|
||||
{{ item[1].origin }} -
|
||||
<span>{{ getDateTime(item[1].timestamp) }}</span>
|
||||
</h6>
|
||||
<p class="mb-2" style="text-align: start; font-size: smaller">
|
||||
{{ item[1].activity_type }} - {{ item[1].direction }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
36
gui/src/components/grid/grid_beacon.vue
Normal file
36
gui/src/components/grid/grid_beacon.vue
Normal file
|
@ -0,0 +1,36 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { setModemBeacon } from "../../js/api.js";
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function startStopBeacon() {
|
||||
if (state.beacon_state === true) {
|
||||
setModemBeacon(false);
|
||||
} else {
|
||||
setModemBeacon(true);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="fill h-100" style="width: calc(100% - 24px)">
|
||||
<a
|
||||
class="btn btn-sm btn-secondary d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
|
||||
@click="startStopBeacon"
|
||||
title="Enable/disable periodic beacons"
|
||||
>Beacon
|
||||
<span
|
||||
class=""
|
||||
role="status"
|
||||
v-bind:class="{
|
||||
'spinner-grow spinner-grow-sm': state.beacon_state === true,
|
||||
disabled: state.beacon_state === false,
|
||||
}"
|
||||
>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
61
gui/src/components/grid/grid_dbfs.vue
Normal file
61
gui/src/components/grid/grid_dbfs.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
</script>
|
||||
<template>
|
||||
<div class="progress mb-0 me-4 rounded-0 rounded-top" style="height: 22px">
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="dbfs_level"
|
||||
role="progressbar"
|
||||
:style="{ width: state.dbfs_level_percent + '%' }"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<p
|
||||
class="justify-content-center d-flex position-absolute w-100"
|
||||
id="dbfs_level_value"
|
||||
>
|
||||
{{ state.dbfs_level }} dBFS
|
||||
</p>
|
||||
</div>
|
||||
<div class="progress mb-0 me-4 rounded-0 rounded-bottom" style="height: 8px">
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 1%"
|
||||
aria-valuenow="1"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar bg-success"
|
||||
role="progressbar"
|
||||
style="width: 89%"
|
||||
aria-valuenow="50"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 20%"
|
||||
aria-valuenow="20"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-danger"
|
||||
role="progressbar"
|
||||
style="width: 29%"
|
||||
aria-valuenow="29"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
17
gui/src/components/grid/grid_frequency.vue
Normal file
17
gui/src/components/grid/grid_frequency.vue
Normal file
|
@ -0,0 +1,17 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
</script>
|
||||
<template>
|
||||
<a
|
||||
class="d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100 link-underline link-underline-opacity-0 link-underline-opacity-75-hover text-bg-light"
|
||||
data-bs-toggle="offcanvas"
|
||||
data-bs-target="#offcanvasFrequency"
|
||||
>
|
||||
{{ state.frequency / 1000 }} kHz
|
||||
</a>
|
||||
</template>
|
30
gui/src/components/grid/grid_mycall small.vue
Normal file
30
gui/src/components/grid/grid_mycall small.vue
Normal file
|
@ -0,0 +1,30 @@
|
|||
<script setup lang="ts">
|
||||
import { reactive, ref } from "vue";
|
||||
import { setActivePinia } from "pinia";
|
||||
import { setConfig } from "../../js/api";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore } from "../../store/settingsStore.js";
|
||||
</script>
|
||||
<template>
|
||||
<div class="w-100">
|
||||
<div class="input-group input-group-sm" style="width: calc(100% - 24px)">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
disabled
|
||||
style="min-width: 3em; background-color: transparent"
|
||||
v-model="settingsStore.remote.STATION.mycall"
|
||||
/>
|
||||
<span class="input-group-text">-</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
disabled
|
||||
style="min-width: 2em; max-width: 2.5em; background-color: transparent"
|
||||
v-model="settingsStore.remote.STATION.myssid"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
20
gui/src/components/grid/grid_mycall.vue
Normal file
20
gui/src/components/grid/grid_mycall.vue
Normal file
|
@ -0,0 +1,20 @@
|
|||
<script setup lang="ts">
|
||||
import { reactive, ref } from "vue";
|
||||
import { setActivePinia } from "pinia";
|
||||
import { setConfig } from "../../js/api";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore } from "../../store/settingsStore.js";
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
|
||||
>
|
||||
<h2>
|
||||
{{ settingsStore.remote.STATION.mycall }}-{{
|
||||
settingsStore.remote.STATION.myssid
|
||||
}}
|
||||
</h2>
|
||||
</div>
|
||||
</template>
|
53
gui/src/components/grid/grid_ping.vue
Normal file
53
gui/src/components/grid/grid_ping.vue
Normal file
|
@ -0,0 +1,53 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
import { sendModemPing } from "../../js/api.js";
|
||||
|
||||
const state = useStateStore(pinia);
|
||||
function transmitPing() {
|
||||
sendModemPing(dxcallPing.value.toUpperCase());
|
||||
}
|
||||
var dxcallPing = ref("");
|
||||
|
||||
window.addEventListener(
|
||||
"stationSelected",
|
||||
function (eventdata) {
|
||||
let evt = <CustomEvent>eventdata;
|
||||
dxcallPing.value = evt.detail;
|
||||
},
|
||||
false,
|
||||
);
|
||||
</script>
|
||||
<template>
|
||||
<div class="input-group" style="width: calc(100% - 24px)">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="min-width: 3rem; text-transform: uppercase; height: 31px"
|
||||
placeholder="DXcall"
|
||||
pattern="[A-Z]*"
|
||||
maxlength="11"
|
||||
aria-label="Input group"
|
||||
aria-describedby="btnGroupAddon"
|
||||
v-model="dxcallPing"
|
||||
/>
|
||||
<a
|
||||
class="btn btn-sm btn-secondary"
|
||||
style="max-width: 3em"
|
||||
id="sendPing"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Send a ping request to a remote station"
|
||||
@click="transmitPing()"
|
||||
>
|
||||
Ping
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
16
gui/src/components/grid/grid_ptt.vue
Normal file
16
gui/src/components/grid/grid_ptt.vue
Normal file
|
@ -0,0 +1,16 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
class="d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
|
||||
:class="state.ptt_state === true ? 'text-bg-warning' : 'text-bg-white'"
|
||||
>
|
||||
<h2>ON AIR</h2>
|
||||
</div>
|
||||
</template>
|
61
gui/src/components/grid/grid_s-meter.vue
Normal file
61
gui/src/components/grid/grid_s-meter.vue
Normal file
|
@ -0,0 +1,61 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
</script>
|
||||
<template>
|
||||
<div class="progress mb-0 me-4 rounded-0 rounded-top" style="height: 22px">
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="noise_level"
|
||||
role="progressbar"
|
||||
:style="{ width: state.s_meter_strength_percent + '%' }"
|
||||
aria-valuenow="{{state.s_meter_strength_percent}}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<p
|
||||
class="justify-content-center d-flex position-absolute w-100"
|
||||
id="noise_level_value"
|
||||
>
|
||||
S-Meter(dB): {{ state.s_meter_strength_raw }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="progress mb-0 me-4 rounded-0 rounded-bottom" style="height: 8px">
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 1%"
|
||||
aria-valuenow="1"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar bg-success"
|
||||
role="progressbar"
|
||||
style="width: 89%"
|
||||
aria-valuenow="50"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
role="progressbar"
|
||||
style="width: 20%"
|
||||
aria-valuenow="20"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-danger"
|
||||
role="progressbar"
|
||||
style="width: 29%"
|
||||
aria-valuenow="29"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
91
gui/src/components/grid/grid_scatter.vue
Normal file
91
gui/src/components/grid/grid_scatter.vue
Normal file
|
@ -0,0 +1,91 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
// reason for no check is, that we have some mixing of typescript and chart js which seems to be not to be fixed that easy
|
||||
import { computed, onMounted } from "vue";
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "chart.js";
|
||||
import { Scatter } from "vue-chartjs";
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
);
|
||||
|
||||
// https://www.chartjs.org/docs/latest/samples/line/segments.html
|
||||
const scatterChartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
type: "linear",
|
||||
position: "bottom",
|
||||
grid: {
|
||||
display: true,
|
||||
lineWidth: 1, // Set the line width for x-axis grid lines
|
||||
},
|
||||
ticks: {
|
||||
display: true,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
type: "linear",
|
||||
position: "left",
|
||||
grid: {
|
||||
display: true,
|
||||
lineWidth: 1, // Set the line width for y-axis grid lines
|
||||
},
|
||||
ticks: {
|
||||
display: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const scatterChartData = computed(() => ({
|
||||
datasets: [
|
||||
{
|
||||
type: "scatter",
|
||||
fill: true,
|
||||
data: state.scatter,
|
||||
label: "Scatter",
|
||||
tension: 0.1,
|
||||
borderColor: "rgb(0, 255, 0)",
|
||||
},
|
||||
],
|
||||
}));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-100 h-100">
|
||||
<Scatter :data="scatterChartData" :options="scatterChartOptions" />
|
||||
</div>
|
||||
<!--278px-->
|
||||
</template>
|
25
gui/src/components/grid/grid_stop.vue
Normal file
25
gui/src/components/grid/grid_stop.vue
Normal file
|
@ -0,0 +1,25 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { stopTransmission } from "../../js/api";
|
||||
|
||||
function stopAllTransmissions() {
|
||||
console.log("stopping transmissions");
|
||||
stopTransmission();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<a
|
||||
class="btn btn-outline-danger d-flex border justify-content-center align-items-center object-fill rounded w-100 h-100"
|
||||
id="stop_transmission_connection"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
@click="stopAllTransmissions()"
|
||||
title="Abort session and stop transmissions"
|
||||
>
|
||||
<i class="bi bi-sign-stop-fill h1"></i>
|
||||
</a>
|
||||
</template>
|
18
gui/src/components/grid/grid_tune.vue
Normal file
18
gui/src/components/grid/grid_tune.vue
Normal file
|
@ -0,0 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../../store/index";
|
||||
setActivePinia(pinia);
|
||||
</script>
|
||||
<template>
|
||||
<div class="fill h-100" style="width: calc(100% - 24px)">
|
||||
<a
|
||||
class="btn btn-sm btn-secondary d-flex justify-content-center align-items-center object-fill border rounded w-100 h-100"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#audioModal"
|
||||
title="Tune"
|
||||
>Tune</a
|
||||
>
|
||||
</div>
|
||||
</template>
|
|
@ -3,47 +3,23 @@ import { setActivePinia } from "pinia";
|
|||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import main_modals from "./main_modals.vue";
|
||||
import main_top_navbar from "./main_top_navbar.vue";
|
||||
import main_audio from "./main_audio.vue";
|
||||
import main_rig_control from "./main_rig_control.vue";
|
||||
import main_my_station from "./main_my_station.vue";
|
||||
import main_updater from "./main_updater.vue";
|
||||
import settings_view from "./settings.vue";
|
||||
import main_active_rig_control from "./main_active_rig_control.vue";
|
||||
import main_footer_navbar from "./main_footer_navbar.vue";
|
||||
|
||||
import main_active_stats from "./main_active_stats.vue";
|
||||
import main_active_broadcasts from "./main_active_broadcasts.vue";
|
||||
import main_active_heard_stations from "./main_active_heard_stations.vue";
|
||||
import main_active_audio_level from "./main_active_audio_level.vue";
|
||||
|
||||
import chat from "./chat.vue";
|
||||
import main_modem_healthcheck from "./main_modem_healthcheck.vue";
|
||||
import Dynamic_components from "./dynamic_components.vue";
|
||||
|
||||
import { stopTransmission } from "../js/sock.js";
|
||||
|
||||
const version = import.meta.env.PACKAGE_VERSION;
|
||||
|
||||
function stopAllTransmissions() {
|
||||
console.log("stopping transmissions");
|
||||
stopTransmission();
|
||||
}
|
||||
function openWebExternal(url) {
|
||||
open(url);
|
||||
}
|
||||
import { getFreedataMessages } from "../js/api";
|
||||
import { getRemote } from "../store/settingsStore.js";
|
||||
import { loadAllData } from "../js/eventHandler";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-------------------------------- INFO TOASTS ---------------->
|
||||
<div
|
||||
aria-live="polite"
|
||||
aria-atomic="true"
|
||||
class="position-relative"
|
||||
style="z-index: 500"
|
||||
>
|
||||
<div aria-live="polite" aria-atomic="true" class="position-relative z-3">
|
||||
<div
|
||||
class="toast-container position-absolute top-0 end-0 p-3"
|
||||
id="mainToastContainer"
|
||||
|
@ -52,7 +28,7 @@ function openWebExternal(url) {
|
|||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-auto bg-body-secondary border-end">
|
||||
<div class="col-1 p-0 bg-body-secondary border-end">
|
||||
<div
|
||||
class="d-flex flex-sm-column flex-row flex-nowrap align-items-center sticky-top"
|
||||
>
|
||||
|
@ -61,30 +37,35 @@ function openWebExternal(url) {
|
|||
id="main-list-tab"
|
||||
role="tablist"
|
||||
style="margin-top: 100px"
|
||||
@click="loadAllData"
|
||||
>
|
||||
<main_modem_healthcheck />
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-action active"
|
||||
id="list-modem-list"
|
||||
class="list-group-item list-group-item-dark list-group-item-action border-0 rounded-3 mb-2 active"
|
||||
id="list-grid-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-modem"
|
||||
href="#list-grid"
|
||||
role="tab"
|
||||
aria-controls="list-modem"
|
||||
title="Home"
|
||||
><i class="bi bi-house-door-fill h3"></i
|
||||
aria-controls="list-grid"
|
||||
title="Grid"
|
||||
><i class="bi bi-grid h3"></i
|
||||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-action"
|
||||
class="list-group-item list-group-item-dark list-group-item-action border-0 rounded-3 mb-2"
|
||||
id="list-chat-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-chat"
|
||||
role="tab"
|
||||
aria-controls="list-chat"
|
||||
title="Chat"
|
||||
@click="getFreedataMessages"
|
||||
><i class="bi bi-chat-text h3"></i
|
||||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-action d-none"
|
||||
class="list-group-item list-group-item-dark list-group-item-action d-none border-0 rounded-3 mb-2"
|
||||
id="list-mesh-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-mesh"
|
||||
|
@ -94,18 +75,7 @@ function openWebExternal(url) {
|
|||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-action mt-2 border"
|
||||
id="list-info-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-info"
|
||||
role="tab"
|
||||
aria-controls="list-info"
|
||||
title="About"
|
||||
><i class="bi bi-info h3"></i
|
||||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-action d-none"
|
||||
class="list-group-item list-group-item-dark list-group-item-action d-none border-0 rounded-3 mb-2"
|
||||
id="list-logger-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-logger"
|
||||
|
@ -113,44 +83,35 @@ function openWebExternal(url) {
|
|||
aria-controls="list-logger"
|
||||
><i class="bi bi-activity h3"></i
|
||||
></a>
|
||||
|
||||
<a
|
||||
class="list-group-item list-group-item-action rounded-bottom"
|
||||
class="list-group-item list-group-item-dark list-group-item-action border-0 rounded-3 mb-2"
|
||||
id="list-settings-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-settings"
|
||||
role="tab"
|
||||
aria-controls="list-settings"
|
||||
title="Settings"
|
||||
@click="loadAllData"
|
||||
><i class="bi bi-gear-wide-connected h3"></i
|
||||
></a>
|
||||
|
||||
<a
|
||||
class="btn border btn-outline-danger list-group-item mt-5"
|
||||
id="stop_transmission_connection"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
@click="stopAllTransmissions()"
|
||||
title="Abort session and stop transmissions"
|
||||
><i class="bi bi-sign-stop-fill h3"></i
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm min-vh-100 m-0 p-0">
|
||||
<div class="col-11 min-vh-100 m-0 p-0">
|
||||
<!-- content -->
|
||||
|
||||
<!-- TODO: Remove the top navbar entirely if not needed
|
||||
<main_top_navbar />
|
||||
-->
|
||||
|
||||
<div class="tab-content" id="nav-tabContent-settings">
|
||||
<div
|
||||
class="tab-pane fade show active"
|
||||
id="list-modem"
|
||||
class="tab-pane fade"
|
||||
id="list-home"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-modem-list"
|
||||
aria-labelledby="list-home-list"
|
||||
>
|
||||
<!-- TOP NAVBAR -->
|
||||
<main_top_navbar />
|
||||
|
||||
<div
|
||||
id="blurdiv"
|
||||
style="
|
||||
|
@ -163,53 +124,6 @@ function openWebExternal(url) {
|
|||
<!-------------------------------- MAIN AREA ---------------->
|
||||
|
||||
<!------------------------------------------------------------------------------------------>
|
||||
<div class="container p-3">
|
||||
<div
|
||||
class="row collapse multi-collapse show mt-4"
|
||||
id="collapseFirstRow"
|
||||
>
|
||||
<div class="col">
|
||||
<main_audio />
|
||||
</div>
|
||||
<div class="col">
|
||||
<main_rig_control />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row collapse multi-collapse show mt-4"
|
||||
id="collapseSecondRow"
|
||||
>
|
||||
<div class="col">
|
||||
<main_my_station />
|
||||
</div>
|
||||
<div class="col">
|
||||
<main_updater />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<div class="row collapse multi-collapse" id="collapseThirdRow">
|
||||
<main_active_rig_control />
|
||||
|
||||
<div class="col-5">
|
||||
<main_active_audio_level />
|
||||
</div>
|
||||
<div class="col">
|
||||
<main_active_broadcasts />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row collapse multi-collapse mt-3"
|
||||
id="collapseFourthRow"
|
||||
>
|
||||
<div class="col-5">
|
||||
<main_active_stats />
|
||||
</div>
|
||||
<div class="col">
|
||||
<main_active_heard_stations />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -348,116 +262,16 @@ function openWebExternal(url) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-info"
|
||||
class="tab-pane fade show active"
|
||||
id="list-grid"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-info-list"
|
||||
aria-labelledby="list-grid-list"
|
||||
>
|
||||
<h1 class="modal-title fs-5" id="aboutModalLabel">
|
||||
FreeDATA - {{ version }}
|
||||
</h1>
|
||||
|
||||
<h4 class="fs-5">modem version - {{ state.modem_version }}</h4>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row mt-2">
|
||||
<div
|
||||
class="btn-toolbar mx-auto"
|
||||
role="toolbar"
|
||||
aria-label="Toolbar with button groups"
|
||||
>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-sm bi bi-geo-alt btn-secondary me-2"
|
||||
id="openExplorer"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
@click="openWebExternal('https://explorer.freedata.app')"
|
||||
>
|
||||
Explorer map
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-2 bi bi-graph-up"
|
||||
id="btnStats"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
@click="
|
||||
openWebExternal('https://statistics.freedata.app/')
|
||||
"
|
||||
>
|
||||
Statistics
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-bookmarks me-2"
|
||||
id="fdWww"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="FreeDATA website"
|
||||
role="button"
|
||||
@click="openWebExternal('https://freedata.app')"
|
||||
>
|
||||
Website
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-github me-2"
|
||||
id="ghUrl"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="Github"
|
||||
role="button"
|
||||
@click="
|
||||
openWebExternal('https://github.com/dj2ls/freedata')
|
||||
"
|
||||
>
|
||||
Github
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-wikipedia me-2"
|
||||
id="wikiUrl"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="Wiki"
|
||||
role="button"
|
||||
@click="openWebExternal('https://wiki.freedata.app')"
|
||||
>
|
||||
Wiki
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-secondary bi bi-discord"
|
||||
id="discordUrl"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
title="Discord"
|
||||
role="button"
|
||||
@click="openWebExternal('https://discord.freedata.app')"
|
||||
>
|
||||
Discord
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-5">
|
||||
<h6>Special thanks to</h6>
|
||||
<hr />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-4" id="divContrib"></div>
|
||||
<div class="col-4" id="divContrib2"></div>
|
||||
<div class="col-4" id="divContrib3"></div>
|
||||
</div>
|
||||
</div>
|
||||
<Dynamic_components />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-chat"
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { sendCQ, sendPing, startBeacon, stopBeacon } from "../js/sock.js";
|
||||
|
||||
function transmitCQ() {
|
||||
sendCQ();
|
||||
}
|
||||
|
||||
function transmitPing() {
|
||||
sendPing((<HTMLInputElement>document.getElementById("dxCall")).value);
|
||||
}
|
||||
|
||||
function startStopBeacon() {
|
||||
switch (state.beacon_state) {
|
||||
case "False":
|
||||
startBeacon(settings.beacon_interval);
|
||||
|
||||
break;
|
||||
case "True":
|
||||
stopBeacon();
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card mb-1">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-broadcast" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<strong class="fs-5">Broadcasts</strong>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalBroadcasts"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#broadcastsHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="row">
|
||||
<div class="col-md-auto">
|
||||
<div class="input-group input-group-sm mb-0">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="max-width: 6rem; text-transform: uppercase"
|
||||
placeholder="DXcall"
|
||||
pattern="[A-Z]*"
|
||||
id="dxCall"
|
||||
maxlength="11"
|
||||
aria-label="Input group"
|
||||
aria-describedby="btnGroupAddon"
|
||||
/>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary ms-1"
|
||||
id="sendPing"
|
||||
type="button"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Send a ping request to a remote station"
|
||||
@click="transmitPing()"
|
||||
>
|
||||
Ping
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary ms-1"
|
||||
id="sendCQ"
|
||||
type="button"
|
||||
title="Send a CQ to the world"
|
||||
@click="transmitCQ()"
|
||||
>
|
||||
Call CQ
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="startBeacon"
|
||||
class="btn btn-sm ms-1"
|
||||
@click="startStopBeacon()"
|
||||
v-bind:class="{
|
||||
'btn-success': state.beacon_state === 'True',
|
||||
'btn-outline-secondary': state.beacon_state === 'False',
|
||||
}"
|
||||
title="Toggle beacon mode. The interval can be set in settings. While sending a beacon, you can receive ping requests and open a datachannel. If a datachannel is opened, the beacon pauses."
|
||||
>
|
||||
<i class="bi bi-soundwave"></i> Toggle beacon
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of row-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,105 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
const { distance } = require("qth-locator");
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function getDateTime(timestampRaw) {
|
||||
var datetime = new Date(timestampRaw * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hourCycle: "h23",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
},
|
||||
);
|
||||
return datetime;
|
||||
}
|
||||
|
||||
function getMaidenheadDistance(dxGrid) {
|
||||
try {
|
||||
return parseInt(distance(settings.mygrid, dxGrid));
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="card mb-1 h-100">
|
||||
<!--325px-->
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-auto">
|
||||
<i class="bi bi-list-columns-reverse" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<strong class="fs-5">Heard stations</strong>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalHeardStations"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#heardStationsHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0" style="overflow-y: overlay">
|
||||
<div class="table-responsive">
|
||||
<!-- START OF TABLE FOR HEARD STATIONS -->
|
||||
<table class="table table-sm" id="tblHeardStationList">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" id="thTime">
|
||||
<i id="hslSort" class="bi bi-sort-up"></i>Time
|
||||
</th>
|
||||
<th scope="col" id="thFreq">Frequency</th>
|
||||
<th> </th>
|
||||
<th scope="col" id="thDxcall">DXCall</th>
|
||||
<th scope="col" id="thDxgrid">DXGrid</th>
|
||||
<th scope="col" id="thDist">Distance</th>
|
||||
<th scope="col" id="thType">Type</th>
|
||||
<th scope="col" id="thSnr">SNR (rx/dx)</th>
|
||||
<!--<th scope="col">Off</th>-->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="heardstations">
|
||||
<!--https://vuejs.org/guide/essentials/list.html-->
|
||||
<tr v-for="item in state.heard_stations" :key="item.timestamp">
|
||||
<td>{{ getDateTime(item.timestamp) }}</td>
|
||||
<td>{{ item.frequency }}</td>
|
||||
<td> </td>
|
||||
<td>
|
||||
<span class="badge bg-secondary">{{ item.dxcallsign }}</span>
|
||||
</td>
|
||||
<td>{{ item.dxgrid }}</td>
|
||||
<td>{{ getMaidenheadDistance(item.dxgrid) }} km</td>
|
||||
<td>{{ item.datatype }}</td>
|
||||
<td>{{ item.snr }}</td>
|
||||
<!--<td>{{ item.offset }}</td>-->
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- END OF HEARD STATIONS TABLE -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,153 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { set_frequency, set_mode, set_rf_level } from "../js/sock.js";
|
||||
|
||||
function set_hamlib_frequency() {
|
||||
set_frequency(state.new_frequency);
|
||||
}
|
||||
|
||||
function set_hamlib_mode() {
|
||||
set_mode(state.mode);
|
||||
}
|
||||
|
||||
function set_hamlib_rf_level() {
|
||||
set_rf_level(state.rf_level);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-3">
|
||||
<div class="card mb-1">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-house-door" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<strong class="fs-5 me-2">Radio control</strong>
|
||||
<span
|
||||
class="badge"
|
||||
v-bind:class="{
|
||||
'text-bg-success': state.hamlib_status === 'connected',
|
||||
'text-bg-danger disabled':
|
||||
state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>{{ state.hamlib_status }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalStation"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#stationHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
disabled
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="input-group bottom-0 m-0">
|
||||
<div class="me-2">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">QRG</span>
|
||||
<span class="input-group-text">{{ state.frequency }} Hz</span>
|
||||
<span class="input-group-text">QSY</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
v-model="state.new_frequency"
|
||||
style="max-width: 8rem"
|
||||
pattern="[0-9]*"
|
||||
list="frequencyDataList"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
/>
|
||||
|
||||
<datalist id="frequencyDataList">
|
||||
<option selected value="7053000">40m | USB | EU, US</option>
|
||||
<option value="14093000">20m | USB | EU, US</option>
|
||||
<option value="21093000">15m | USB | EU, US</option>
|
||||
<option value="24908000">12m | USB | EU, US</option>
|
||||
<option value="28093000">10m | USB | EU, US</option>
|
||||
<option value="50308000">6m | USB | US</option>
|
||||
<option value="50616000">6m | USB | EU, US</option>
|
||||
</datalist>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-success"
|
||||
type="button"
|
||||
@click="set_hamlib_frequency"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>
|
||||
Apply
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="me-2">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Mode</span>
|
||||
<select
|
||||
class="form-control"
|
||||
v-model="state.mode"
|
||||
@click="set_hamlib_mode()"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>
|
||||
<option value="USB">USB</option>
|
||||
<option value="LSB">LSB</option>
|
||||
<option value="PKTUSB">PKT-U</option>
|
||||
<option value="PKTLSB">PKT-L</option>
|
||||
<option value="AM">AM</option>
|
||||
<option value="FM">FM</option>
|
||||
<option value="PKTFM">PKTFM</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="me-2">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">Power</span>
|
||||
<select
|
||||
class="form-control"
|
||||
v-model="state.rf_level"
|
||||
@click="set_hamlib_rf_level()"
|
||||
v-bind:class="{
|
||||
disabled: state.hamlib_status === 'disconnected',
|
||||
}"
|
||||
>
|
||||
<option value="0">-</option>
|
||||
<option value="10">10</option>
|
||||
<option value="20">20</option>
|
||||
<option value="30">30</option>
|
||||
<option value="40">40</option>
|
||||
<option value="50">50</option>
|
||||
<option value="60">60</option>
|
||||
<option value="70">70</option>
|
||||
<option value="80">80</option>
|
||||
<option value="90">90</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,396 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
// @ts-nocheck
|
||||
// reason for no check is, that we have some mixing of typescript and chart js which seems to be not to be fixed that easy
|
||||
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
} from "chart.js";
|
||||
import { Line, Scatter } from "vue-chartjs";
|
||||
import { computed } from "vue";
|
||||
|
||||
function selectStatsControl(obj) {
|
||||
switch (obj.delegateTarget.id) {
|
||||
case "list-waterfall-list":
|
||||
settings.spectrum = "waterfall";
|
||||
break;
|
||||
case "list-scatter-list":
|
||||
settings.spectrum = "scatter";
|
||||
break;
|
||||
case "list-chart-list":
|
||||
settings.spectrum = "chart";
|
||||
break;
|
||||
default:
|
||||
settings.spectrum = "waterfall";
|
||||
}
|
||||
saveSettingsToFile();
|
||||
}
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
);
|
||||
|
||||
// https://www.chartjs.org/docs/latest/samples/line/segments.html
|
||||
const skipped = (speedCtx, value) =>
|
||||
speedCtx.p0.skip || speedCtx.p1.skip ? value : undefined;
|
||||
const down = (speedCtx, value) =>
|
||||
speedCtx.p0.parsed.y > speedCtx.p1.parsed.y ? value : undefined;
|
||||
|
||||
var transmissionSpeedChartOptions = {
|
||||
//type: "line",
|
||||
responsive: true,
|
||||
animations: true,
|
||||
maintainAspectRatio: false,
|
||||
cubicInterpolationMode: "monotone",
|
||||
tension: 0.4,
|
||||
scales: {
|
||||
SNR: {
|
||||
type: "linear",
|
||||
ticks: { beginAtZero: false, color: "rgb(255, 99, 132)" },
|
||||
position: "right",
|
||||
},
|
||||
SPEED: {
|
||||
type: "linear",
|
||||
ticks: { beginAtZero: false, 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 } },
|
||||
},
|
||||
};
|
||||
|
||||
const transmissionSpeedChartData = computed(() => ({
|
||||
labels: state.arq_speed_list_timestamp,
|
||||
datasets: [
|
||||
{
|
||||
type: "line",
|
||||
label: "SNR[dB]",
|
||||
data: state.arq_speed_list_snr,
|
||||
borderColor: "rgb(75, 192, 192, 1.0)",
|
||||
pointRadius: 1,
|
||||
segment: {
|
||||
borderColor: (speedCtx) =>
|
||||
skipped(speedCtx, "rgb(0,0,0,0.4)") ||
|
||||
down(speedCtx, "rgb(192,75,75)"),
|
||||
borderDash: (speedCtx) => skipped(speedCtx, [3, 3]),
|
||||
},
|
||||
spanGaps: true,
|
||||
backgroundColor: "rgba(75, 192, 192, 0.2)",
|
||||
order: 1,
|
||||
yAxisID: "SNR",
|
||||
},
|
||||
{
|
||||
type: "bar",
|
||||
label: "Speed[bpm]",
|
||||
data: state.arq_speed_list_bpm,
|
||||
borderColor: "rgb(120, 100, 120, 1.0)",
|
||||
backgroundColor: "rgba(120, 100, 120, 0.2)",
|
||||
order: 0,
|
||||
yAxisID: "SPEED",
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
const scatterChartOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
type: "linear",
|
||||
position: "bottom",
|
||||
grid: {
|
||||
display: true,
|
||||
lineWidth: 1, // Set the line width for x-axis grid lines
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
type: "linear",
|
||||
position: "left",
|
||||
grid: {
|
||||
display: true,
|
||||
lineWidth: 1, // Set the line width for y-axis grid lines
|
||||
},
|
||||
ticks: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// dummy data
|
||||
//state.scatter = [{"x":"166","y":"46"},{"x":"-193","y":"-139"},{"x":"-165","y":"-291"},{"x":"311","y":"-367"},{"x":"389","y":"199"},{"x":"78","y":"372"},{"x":"242","y":"-431"},{"x":"-271","y":"-248"},{"x":"28","y":"-130"},{"x":"-20","y":"187"},{"x":"74","y":"362"},{"x":"-316","y":"-229"},{"x":"-180","y":"261"},{"x":"321","y":"360"},{"x":"438","y":"-288"},{"x":"378","y":"-94"},{"x":"462","y":"-163"},{"x":"-265","y":"248"},{"x":"210","y":"314"},{"x":"230","y":"-320"},{"x":"261","y":"-244"},{"x":"-283","y":"-373"}]
|
||||
|
||||
const scatterChartData = computed(() => ({
|
||||
datasets: [
|
||||
{
|
||||
type: "scatter",
|
||||
fill: true,
|
||||
data: state.scatter,
|
||||
label: "Scatter",
|
||||
tension: 0.1,
|
||||
borderColor: "rgb(0, 255, 0)",
|
||||
},
|
||||
],
|
||||
}));
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { initWaterfall } from "../js/waterfallHandler.js";
|
||||
|
||||
export default {
|
||||
mounted() {
|
||||
// This code will be executed after the component is mounted to the DOM
|
||||
// You can access DOM elements or perform other initialization here
|
||||
//const myElement = this.$refs.waterfall; // Access the DOM element with ref
|
||||
|
||||
// init waterfall
|
||||
initWaterfall();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card mb-1" style="height: calc(var(--variable-height) - 20px)">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-11">
|
||||
<div class="btn-group" role="group">
|
||||
<div
|
||||
class="list-group list-group-horizontal"
|
||||
id="list-tab"
|
||||
role="tablist"
|
||||
>
|
||||
<a
|
||||
class="py-1 list-group-item list-group-item-action"
|
||||
id="list-waterfall-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-waterfall"
|
||||
role="tab"
|
||||
aria-controls="list-waterfall"
|
||||
v-bind:class="{ active: settings.spectrum === 'waterfall' }"
|
||||
@click="selectStatsControl($event)"
|
||||
><strong><i class="bi bi-water"></i></strong
|
||||
></a>
|
||||
<a
|
||||
class="py-1 list-group-item list-group-item-action"
|
||||
id="list-scatter-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-scatter"
|
||||
role="tab"
|
||||
aria-controls="list-scatter"
|
||||
v-bind:class="{ active: settings.spectrum === 'scatter' }"
|
||||
@click="selectStatsControl($event)"
|
||||
><strong><i class="bi bi-border-outer"></i></strong
|
||||
></a>
|
||||
<a
|
||||
class="py-1 list-group-item list-group-item-action"
|
||||
id="list-chart-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-chart"
|
||||
role="tab"
|
||||
aria-controls="list-chart"
|
||||
v-bind:class="{ active: settings.spectrum === 'chart' }"
|
||||
@click="selectStatsControl($event)"
|
||||
><strong><i class="bi bi-graph-up-arrow"></i></strong
|
||||
></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group" role="group" aria-label="Busy indicators">
|
||||
<button
|
||||
class="btn btn-sm ms-1 p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.getChannelBusySlotState(0) === true,
|
||||
'btn-outline-secondary':
|
||||
state.getChannelBusySlotState(0) === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S1
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.getChannelBusySlotState(1) === true,
|
||||
'btn-outline-secondary':
|
||||
state.getChannelBusySlotState(1) === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S2
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.getChannelBusySlotState(2) === true,
|
||||
'btn-outline-secondary':
|
||||
state.getChannelBusySlotState(2) === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S3
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.getChannelBusySlotState(3) === true,
|
||||
'btn-outline-secondary':
|
||||
state.getChannelBusySlotState(3) === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S4
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.getChannelBusySlotState(4) === true,
|
||||
'btn-outline-secondary':
|
||||
state.getChannelBusySlotState(4) === false,
|
||||
}"
|
||||
title="Channel busy state: <strong class='text-success'>not busy</strong> / <strong class='text-danger'>busy </strong>"
|
||||
>
|
||||
S5
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm p-1 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
title="Recieving data: illuminates <strong class='text-success'>green</strong> if receiving codec2 data"
|
||||
v-bind:class="{
|
||||
'btn-success': state.is_codec2_traffic === 'True',
|
||||
'btn-outline-secondary': state.is_codec2_traffic === 'False',
|
||||
}"
|
||||
>
|
||||
data
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalWaterfall"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#waterfallHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-1">
|
||||
<div class="tab-content" id="nav-stats-tabContent">
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
v-bind:class="{ 'show active': settings.spectrum === 'waterfall' }"
|
||||
id="list-waterfall"
|
||||
role="stats_tabpanel"
|
||||
aria-labelledby="list-waterfall-list"
|
||||
>
|
||||
<canvas
|
||||
ref="waterfall"
|
||||
id="waterfall"
|
||||
style="position: relative; z-index: 2"
|
||||
class="force-gpu h-100 w-100"
|
||||
></canvas>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
v-bind:class="{ 'show active': settings.spectrum === 'scatter' }"
|
||||
id="list-scatter"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-scatter-list"
|
||||
>
|
||||
<Scatter :data="scatterChartData" :options="scatterChartOptions" />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
v-bind:class="{ 'show active': settings.spectrum === 'chart' }"
|
||||
id="list-chart"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-chart-list"
|
||||
>
|
||||
<Line
|
||||
:data="transmissionSpeedChartData"
|
||||
:options="transmissionSpeedChartOptions"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--278px-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,61 +0,0 @@
|
|||
<script setup>
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useAudioStore } from "../store/audioStore.js";
|
||||
const audio = useAudioStore(pinia);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card mb-0">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-volume-up" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<strong class="fs-5">Audio devices</strong>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalAudio"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#audioHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-2" style="height: 100px">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-mic-fill" style="font-size: 1rem"></i>
|
||||
</span>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="audio_input_selectbox"
|
||||
aria-label=".form-select-sm"
|
||||
v-html="audio.getInputDevices()"
|
||||
></select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-volume-up" style="font-size: 1rem"></i>
|
||||
</span>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="audio_output_selectbox"
|
||||
aria-label=".form-select-sm"
|
||||
v-html="audio.getOutputDevices()"
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -8,47 +8,52 @@ const state = useStateStore(pinia);
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<nav
|
||||
class="navbar fixed-bottom navbar-expand-xl bg-body-tertiary border-top p-2"
|
||||
style="margin-left: 87px"
|
||||
>
|
||||
<div class="col">
|
||||
<div class="btn-toolbar" role="toolbar" style="margin-left: 2px">
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
v-bind:class="{
|
||||
'bg-danger': state.ptt_state === 'True',
|
||||
'bg-secondary': state.ptt_state === 'False',
|
||||
}"
|
||||
id="ptt_state"
|
||||
type="button"
|
||||
title="Rig PTT state"
|
||||
style="pointer-events: auto"
|
||||
disabled
|
||||
>
|
||||
<i class="bi bi-broadcast-pin" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
id="busy_state"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'bg-danger': state.busy_state === 'BUSY',
|
||||
'bg-secondary': state.busy_state === 'IDLE',
|
||||
}"
|
||||
title="Modem state"
|
||||
disabled
|
||||
style="pointer-events: auto"
|
||||
>
|
||||
<i class="bi bi-cpu" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-1">123</div>
|
||||
<div class="col-11">
|
||||
<nav
|
||||
class="navbar fixed-bottom navbar-expand-xl bg-body-tertiary border-top p-2"
|
||||
>
|
||||
<div class="col">
|
||||
<div class="btn-toolbar" role="toolbar">
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
v-bind:class="{
|
||||
'btn-danger': state.ptt_state == true,
|
||||
'btn-secondary': state.ptt_state == false,
|
||||
}"
|
||||
id="ptt_state"
|
||||
type="button"
|
||||
style="pointer-events: auto"
|
||||
disabled
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
data-bs-title="PTT trigger state"
|
||||
>
|
||||
<i class="bi bi-broadcast-pin" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
id="busy_state"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-danger': state.busy_state === true,
|
||||
'btn-secondary': state.busy_state === false,
|
||||
}"
|
||||
data-bs-title="Modem state"
|
||||
disabled
|
||||
style="pointer-events: auto"
|
||||
>
|
||||
<i class="bi bi-cpu" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
<!--
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
id="arq_session"
|
||||
|
@ -58,31 +63,36 @@ const state = useStateStore(pinia);
|
|||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'bg-secondary': state.arq_session_state === 'disconnected',
|
||||
'bg-warning': state.arq_session_state === 'connected',
|
||||
'btn-secondary': state.arq_session_state === 'disconnected',
|
||||
'btn-warning': state.arq_session_state === 'connected',
|
||||
}"
|
||||
disabled
|
||||
style="pointer-events: auto"
|
||||
title="Session state"
|
||||
data-bs-title="Session state"
|
||||
>
|
||||
<i class="bi bi-arrow-left-right" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
|
||||
-->
|
||||
<!--
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
id="arq_state"
|
||||
type="button"
|
||||
title="Data channel state"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-title="Data channel state"
|
||||
v-bind:class="{
|
||||
'bg-secondary': state.arq_state === 'False',
|
||||
'bg-warning': state.arq_state === 'True',
|
||||
'btn-secondary': state.arq_state === 'False',
|
||||
'btn-warning': state.arq_state === 'True',
|
||||
}"
|
||||
disabled
|
||||
style="pointer-events: auto"
|
||||
>
|
||||
<i class="bi bi-file-earmark-binary" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
<!--
|
||||
-->
|
||||
<!--
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-1"
|
||||
id="rigctld_state"
|
||||
|
@ -96,156 +106,174 @@ const state = useStateStore(pinia);
|
|||
<i class="bi bi-usb-symbol" style="font-size: 0.8rem"></i>
|
||||
</button>
|
||||
-->
|
||||
|
||||
<!--
|
||||
<button
|
||||
class="btn btn-sm disabled me-3"
|
||||
class="btn btn-sm btn-secondary disabled me-3"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
v-bind:class="{
|
||||
'btn-warning': state.channel_busy === 'True',
|
||||
'btn-secondary': state.channel_busy === 'False',
|
||||
'btn-warning': state.channel_busy === true,
|
||||
'btn-secondary': state.channel_busy === false,
|
||||
}"
|
||||
style="pointer-events: auto"
|
||||
title="Channel busy"
|
||||
data-bs-title="Channel busy"
|
||||
>
|
||||
<i class="bi bi-hourglass"></i>
|
||||
</button>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-4 disabled"
|
||||
type="button"
|
||||
title="What's the frequency, Kenneth?"
|
||||
style="pointer-events: auto"
|
||||
>
|
||||
{{ parseInt(state.frequency) / 1000 }} KHz
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-4 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-title="What's the frequency, Kenneth?"
|
||||
style="pointer-events: auto"
|
||||
>
|
||||
{{ state.frequency / 1000 }} kHz
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-0"
|
||||
type="button"
|
||||
title="Speed level"
|
||||
>
|
||||
<i class="bi bi-speedometer2" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-0"
|
||||
type="button"
|
||||
title="Speed level"
|
||||
>
|
||||
<i class="bi bi-speedometer2" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-4 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
>
|
||||
<i
|
||||
class="bi"
|
||||
style="font-size: 1rem"
|
||||
v-bind:class="{
|
||||
'bi-reception-0': state.speed_level == 0,
|
||||
'bi-reception-1': state.speed_level == 1,
|
||||
'bi-reception-2': state.speed_level == 2,
|
||||
'bi-reception-3': state.speed_level == 3,
|
||||
'bi-reception-4': state.speed_level == 4,
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-0"
|
||||
type="button"
|
||||
title="Bytes transfered"
|
||||
>
|
||||
<i class="bi bi-file-earmark-binary" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-4 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
data-bs-titel="speed level"
|
||||
>
|
||||
<i
|
||||
class="bi"
|
||||
style="font-size: 1rem"
|
||||
v-bind:class="{
|
||||
'bi-reception-0': state.speed_level == 0,
|
||||
'bi-reception-1': state.speed_level == 1,
|
||||
'bi-reception-2': state.speed_level == 2,
|
||||
'bi-reception-3': state.speed_level == 3,
|
||||
'bi-reception-4': state.speed_level == 4,
|
||||
}"
|
||||
></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-0"
|
||||
type="button"
|
||||
title="Bytes transfered"
|
||||
>
|
||||
<i
|
||||
class="bi bi-file-earmark-binary"
|
||||
style="font-size: 1rem"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-4 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
>
|
||||
{{ state.arq_total_bytes }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-0"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
title="Current or last connected with station"
|
||||
>
|
||||
<i class="bi bi-file-earmark-binary" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-4 disabled"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
data-bs-title="total bytes of transmission"
|
||||
>
|
||||
{{ state.arq_total_bytes }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group btn-group-sm me-1" role="group">
|
||||
<button
|
||||
class="btn btn-sm btn-secondary me-0"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
data-bs-title="Current or last connected with station"
|
||||
>
|
||||
<i
|
||||
class="bi bi-file-earmark-binary"
|
||||
style="font-size: 1rem"
|
||||
></i>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-sm btn-secondary disabled me-1"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
>
|
||||
{{ state.dxcallsign }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div style="margin-right: 2px">
|
||||
<div
|
||||
class="progress w-100 rounded-0 rounded-top"
|
||||
style="height: 20px; min-width: 200px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="transmission_progress"
|
||||
role="progressbar"
|
||||
:style="{ width: state.arq_transmission_percent + '%' }"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<p
|
||||
class="justify-content-center m-0 d-flex position-absolute w-100 text-dark"
|
||||
>
|
||||
{{ state.arq_seconds_until_finish }}s left
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="progress mb-0 rounded-0 rounded-bottom"
|
||||
style="height: 10px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
id="transmission_timeleft"
|
||||
role="progressbar"
|
||||
:style="{ width: state.arq_seconds_until_timeout_percent + '%' }"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
<p
|
||||
class="justify-content-center m-0 d-flex position-absolute w-100 text-dark"
|
||||
>
|
||||
timeout in {{ state.arq_seconds_until_timeout }}s
|
||||
</p>
|
||||
<button
|
||||
class="btn btn-sm btn-secondary disabled me-1"
|
||||
type="button"
|
||||
data-bs-placement="top"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
data-bs-title="the dxcallsign of the connected station"
|
||||
>
|
||||
{{ state.dxcallsign }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div style="margin-right: 2px">
|
||||
<div
|
||||
class="progress w-100"
|
||||
style="height: 20px; min-width: 200px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-primary force-gpu"
|
||||
id="transmission_progress"
|
||||
role="progressbar"
|
||||
:style="{ width: state.arq_transmission_percent + '%' }"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
></div>
|
||||
<p
|
||||
class="justify-content-center m-0 d-flex position-absolute w-100 text-dark"
|
||||
>
|
||||
Message Progress
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
hidden
|
||||
class="progress mb-0 rounded-0 rounded-bottom"
|
||||
style="height: 10px"
|
||||
>
|
||||
<div
|
||||
class="progress-bar progress-bar-striped bg-warning"
|
||||
id="transmission_timeleft"
|
||||
role="progressbar"
|
||||
:style="{
|
||||
width: state.arq_seconds_until_timeout_percent + '%',
|
||||
}"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
<p
|
||||
class="justify-content-center m-0 d-flex position-absolute w-100 text-dark"
|
||||
>
|
||||
timeout in {{ state.arq_seconds_until_timeout }}s
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</template>
|
||||
ww
|
||||
|
|
|
@ -5,30 +5,24 @@ import { setActivePinia } from "pinia";
|
|||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chat = useChatStore(pinia);
|
||||
|
||||
import {
|
||||
deleteChatByCallsign,
|
||||
getNewMessagesByDXCallsign,
|
||||
} from "../js/chatHandler";
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
import { sendModemTestFrame } from "../js/api";
|
||||
import main_startup_check from "./main_startup_check.vue";
|
||||
import { newMessage, deleteCallsignFromDB } from "../js/messagesHandler.ts";
|
||||
|
||||
import { sendTestFrame, setTxAudioLevel } from "../js/sock.js";
|
||||
function newChat() {
|
||||
let newCallsign = chat.newChatCallsign.toUpperCase();
|
||||
newMessage(newCallsign, chat.newChatMessage);
|
||||
|
||||
function tuneAudio() {
|
||||
sendTestFrame();
|
||||
}
|
||||
|
||||
function set_audio_level() {
|
||||
setTxAudioLevel(state.audio_level);
|
||||
chat.newChatCallsign = "";
|
||||
chat.newChatMessage = "";
|
||||
}
|
||||
|
||||
function deleteChat() {
|
||||
//console.log(chat.selectedCallsign)
|
||||
deleteChatByCallsign(chat.selectedCallsign);
|
||||
deleteCallsignFromDB(chat.selectedCallsign);
|
||||
}
|
||||
|
||||
import {
|
||||
|
@ -119,6 +113,8 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<main_startup_check />
|
||||
|
||||
<!-- updater release notes-->
|
||||
<div
|
||||
class="modal fade"
|
||||
|
@ -186,16 +182,12 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({
|
|||
<span class="input-group-text" id="basic-addon1"
|
||||
>Total Messages</span
|
||||
>
|
||||
<span class="input-group-text" id="basic-addon1">{{
|
||||
getNewMessagesByDXCallsign(chat.selectedCallsign)[0]
|
||||
}}</span>
|
||||
<span class="input-group-text" id="basic-addon1">...</span>
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text" id="basic-addon1">New Messages</span>
|
||||
<span class="input-group-text" id="basic-addon1">{{
|
||||
getNewMessagesByDXCallsign(chat.selectedCallsign)[1]
|
||||
}}</span>
|
||||
<span class="input-group-text" id="basic-addon1">...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
@ -321,6 +313,86 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="modal fade"
|
||||
ref="modalEle"
|
||||
id="newChatModal"
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="deleteChatModalLabel">
|
||||
Start a new chat
|
||||
</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info" role="alert">
|
||||
1. Enter destination callsign
|
||||
<br />
|
||||
2. Enter a first message
|
||||
<br />
|
||||
3. Pressing "START NEW CHAT"
|
||||
</div>
|
||||
|
||||
<div class="form-floating mb-3">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="floatingInputDestination"
|
||||
placeholder="dxcallsign / destination"
|
||||
maxlength="9"
|
||||
style="text-transform: uppercase"
|
||||
@keypress.enter="newChat()"
|
||||
v-model="chat.newChatCallsign"
|
||||
/>
|
||||
<label for="floatingInputDestination"
|
||||
>dxcallsign / destination</label
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-floating">
|
||||
<textarea
|
||||
class="form-control"
|
||||
placeholder="Your first message"
|
||||
id="floatingTextareaNewChatMessage"
|
||||
style="height: 100px"
|
||||
v-model="chat.newChatMessage"
|
||||
></textarea>
|
||||
<label for="floatingTextareaNewChatMessage">First message</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-secondary"
|
||||
data-bs-dismiss="modal"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-outline-success"
|
||||
id="createNewChatButton"
|
||||
type="button"
|
||||
data-bs-dismiss="modal"
|
||||
title="Start a new chat (enter dx call sign first)"
|
||||
@click="newChat()"
|
||||
>
|
||||
START NEW CHAT
|
||||
<i class="bi bi-pencil-square" style="font-size: 1.2rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HELP MODALS AUDIO -->
|
||||
<div
|
||||
class="modal fade"
|
||||
|
@ -925,7 +997,7 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({
|
|||
<button
|
||||
type="button"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
@click="tuneAudio"
|
||||
@click="sendModemTestFrame()"
|
||||
>
|
||||
Tune
|
||||
</button>
|
||||
|
@ -1181,30 +1253,53 @@ const transmissionSpeedChartDataMessageInfo = computed(() => ({
|
|||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info" role="alert">
|
||||
Adjust audio levels. Value in dB. Default is <strong>0</strong>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">Test-Frame</span>
|
||||
<button
|
||||
type="button"
|
||||
id="sendTestFrame"
|
||||
@click="sendTestFrame()"
|
||||
@click="sendModemTestFrame()"
|
||||
class="btn btn-danger"
|
||||
>
|
||||
Transmit
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">TX Level</span>
|
||||
<span class="input-group-text">{{ state.audio_level }}</span>
|
||||
<span class="input-group-text">RX Level</span>
|
||||
<span class="input-group-text">{{
|
||||
settings.remote.AUDIO.rx_audio_level
|
||||
}}</span>
|
||||
<span class="input-group-text w-75">
|
||||
<input
|
||||
type="range"
|
||||
class="form-range"
|
||||
min="0"
|
||||
max="250"
|
||||
min="-30"
|
||||
max="20"
|
||||
step="1"
|
||||
id="audioLevelRX"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.AUDIO.rx_audio_level"
|
||||
/></span>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">TX Level</span>
|
||||
<span class="input-group-text">{{
|
||||
settings.remote.AUDIO.tx_audio_level
|
||||
}}</span>
|
||||
<span class="input-group-text w-75">
|
||||
<input
|
||||
type="range"
|
||||
class="form-range"
|
||||
min="-30"
|
||||
max="20"
|
||||
step="1"
|
||||
id="audioLevelTX"
|
||||
@click="set_audio_level()"
|
||||
v-model="state.audio_level"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.AUDIO.tx_audio_level"
|
||||
/></span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
27
gui/src/components/main_modem_healthcheck.vue
Normal file
27
gui/src/components/main_modem_healthcheck.vue
Normal file
|
@ -0,0 +1,27 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { getOverallHealth } from "../js/eventHandler.js";
|
||||
</script>
|
||||
<template>
|
||||
<a
|
||||
class="btn border btn-outline-secondary list-group-item mb-5"
|
||||
data-bs-html="false"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#modemCheck"
|
||||
title="Check FreeDATA status"
|
||||
:class="
|
||||
getOverallHealth() > 4
|
||||
? 'bg-danger'
|
||||
: getOverallHealth() < 2
|
||||
? ''
|
||||
: 'bg-warning'
|
||||
"
|
||||
><i class="bi bi-activity h3"></i>
|
||||
</a>
|
||||
</template>
|
|
@ -1,124 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
function saveSettings() {
|
||||
saveSettingsToFile();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card mb-1">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-house-door" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-10">
|
||||
<strong class="fs-5">My station</strong>
|
||||
</div>
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalStation"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#stationHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="row">
|
||||
<div class="col-md-auto">
|
||||
<div
|
||||
class="input-group input-group-sm mb-0"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Enter your callsign and save it"
|
||||
>
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-person-bounding-box" style="font-size: 1rem"></i>
|
||||
</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="width: 5rem; text-transform: uppercase"
|
||||
placeholder="callsign"
|
||||
pattern="[A-Z]*"
|
||||
id="myCall"
|
||||
maxlength="8"
|
||||
aria-label="Input group"
|
||||
aria-describedby="btnGroupAddon"
|
||||
v-model="settings.mycall"
|
||||
@input="saveSettings"
|
||||
/>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="myCallSSID"
|
||||
v-model="settings.myssid"
|
||||
@change="saveSettings"
|
||||
>
|
||||
<option selected value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
<option value="13">13</option>
|
||||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<div
|
||||
class="input-group input-group-sm mb-0"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Enter your gridsquare and save it"
|
||||
>
|
||||
<span class="input-group-text">
|
||||
<i class="bi bi-house-fill" style="font-size: 1rem"></i>
|
||||
</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"
|
||||
v-model="settings.mygrid"
|
||||
@input="saveSettings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- end of row-->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,276 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { startRigctld, stopRigctld } from "../js/daemon";
|
||||
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function startStopRigctld() {
|
||||
switch (state.rigctld_started) {
|
||||
case "stopped":
|
||||
|
||||
settings.hamlib_deviceport = (<HTMLInputElement>document.getElementById("hamlib_deviceport")).value;
|
||||
|
||||
startRigctld();
|
||||
|
||||
break;
|
||||
case "running":
|
||||
stopRigctld();
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
function selectRadioControl() {
|
||||
// @ts-expect-error
|
||||
switch (event.target.id) {
|
||||
case "list-rig-control-none-list":
|
||||
settings.radiocontrol = "disabled";
|
||||
break;
|
||||
case "list-rig-control-rigctld-list":
|
||||
settings.radiocontrol = "rigctld";
|
||||
break;
|
||||
case "list-rig-control-tci-list":
|
||||
settings.radiocontrol = "tci";
|
||||
break;
|
||||
default:
|
||||
console.log("default=!==");
|
||||
settings.radiocontrol = "disabled";
|
||||
}
|
||||
saveSettingsToFile();
|
||||
}
|
||||
|
||||
|
||||
function testHamlib(){
|
||||
|
||||
console.log("not yet implemented")
|
||||
alert("not yet implemented")
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card mb-0">
|
||||
<div class="card-header p-1">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-projector" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<strong class="fs-5">Rig control</strong>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<div
|
||||
class="list-group list-group-horizontal"
|
||||
id="rig-control-list-tab"
|
||||
role="rig-control-tablist"
|
||||
>
|
||||
<a
|
||||
class="py-1 ps-1 pe-1 list-group-item list-group-item-action"
|
||||
id="list-rig-control-none-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-rig-control-none"
|
||||
role="tab"
|
||||
aria-controls="list-rig-control-none"
|
||||
v-bind:class="{ active: settings.radiocontrol === 'disabled' }"
|
||||
@click="selectRadioControl()"
|
||||
>None/Vox</a
|
||||
>
|
||||
<a
|
||||
class="py-1 ps-1 pe-1 list-group-item list-group-item-action"
|
||||
id="list-rig-control-rigctld-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-rig-control-rigctld"
|
||||
role="tab"
|
||||
aria-controls="list-rig-control-rigctld"
|
||||
v-bind:class="{ active: settings.radiocontrol === 'rigctld' }"
|
||||
@click="selectRadioControl()"
|
||||
>Rigctld</a
|
||||
>
|
||||
<a
|
||||
class="py-1 ps-1 pe-1 list-group-item list-group-item-action"
|
||||
id="list-rig-control-tci-list"
|
||||
data-bs-toggle="list"
|
||||
href="#list-rig-control-tci"
|
||||
role="tab"
|
||||
aria-controls="list-rig-control-tci"
|
||||
v-bind:class="{ active: settings.radiocontrol === 'tci' }"
|
||||
@click="selectRadioControl()"
|
||||
>TCI</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalRigControl"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#rigcontrolHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2" style="height: 100px">
|
||||
<div class="tab-content" id="rig-control-nav-tabContent">
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
v-bind:class="{ 'show active': settings.radiocontrol === 'disabled' }"
|
||||
id="list-rig-control-none"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-rig-control-none-list"
|
||||
>
|
||||
<p class="small">
|
||||
Modem will not utilize rig control and features will be limited. While
|
||||
functional; it is recommended to configure hamlib.
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-rig-control-rigctld"
|
||||
v-bind:class="{ 'show active': settings.radiocontrol === 'rigctld' }"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-rig-control-rigctld-list"
|
||||
>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
|
||||
<span class="input-group-text"> Radio port </span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="hamlib_deviceport"
|
||||
style="width: 7rem"
|
||||
v-html="settings.getSerialDevices()"
|
||||
>
|
||||
|
||||
</select>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">Rigctld service</span>
|
||||
<button
|
||||
class="btn btn-outline-success"
|
||||
type="button"
|
||||
id="hamlib_rigctld_start"
|
||||
@click="startStopRigctld"
|
||||
>
|
||||
Start
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-outline-danger"
|
||||
type="button"
|
||||
id="hamlib_rigctld_stop"
|
||||
@click="startStopRigctld"
|
||||
|
||||
>
|
||||
Stop
|
||||
</button>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Status"
|
||||
id="hamlib_rigctld_status"
|
||||
aria-label="State"
|
||||
aria-describedby="basic-addon1"
|
||||
v-model="state.rigctld_started"
|
||||
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="testHamlib"
|
||||
class="btn btn-sm btn-outline-secondary ms-1"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
@click="testHamlib"
|
||||
title="Test your hamlib settings and toggle PTT once. Button will become <strong class='text-success'>green</strong> on success and <strong class='text-danger'>red</strong> if fails."
|
||||
>
|
||||
PTT Test
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="list-rig-control-tci"
|
||||
v-bind:class="{ 'show active': settings.radiocontrol === 'tci' }"
|
||||
role="tabpanel"
|
||||
aria-labelledby="list-rig-control-tci-list"
|
||||
>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">TCI</span>
|
||||
<span class="input-group-text">Address</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="tci IP"
|
||||
id="tci_ip"
|
||||
aria-label="Device IP"
|
||||
v-model="settings.tci_ip"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text">Port</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="tci port"
|
||||
id="tci_port"
|
||||
aria-label="Device Port"
|
||||
v-model="settings.tci_port"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RADIO CONTROL DISABLED -->
|
||||
<div id="radio-control-disabled"></div>
|
||||
|
||||
<!-- RADIO CONTROL RIGCTLD -->
|
||||
<div id="radio-control-rigctld"></div>
|
||||
<!-- RADIO CONTROL TCI-->
|
||||
<div id="radio-control-tci"></div>
|
||||
<!-- RADIO CONTROL HELP -->
|
||||
<div id="radio-control-help">
|
||||
<!--
|
||||
<strong>VOX:</strong> Use rig control mode 'none'
|
||||
<br />
|
||||
<strong>HAMLIB locally:</strong> configure in settings, then
|
||||
start/stop service.
|
||||
<br />
|
||||
<strong>HAMLIB remotely:</strong> Enter IP/Port, connection
|
||||
happens automatically.
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
<!--<div class="card-footer text-muted small" id="hamlib_info_field">
|
||||
Define Modem rig control mode (none/hamlib)
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</template>
|
431
gui/src/components/main_startup_check.vue
Normal file
431
gui/src/components/main_startup_check.vue
Normal file
|
@ -0,0 +1,431 @@
|
|||
<script setup lang="ts">
|
||||
import { Modal } from "bootstrap";
|
||||
import { onMounted } from "vue";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
import { sendModemCQ } from "../js/api.js";
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { useAudioStore } from "../store/audioStore";
|
||||
const audioStore = useAudioStore();
|
||||
import { useSerialStore } from "../store/serialStore";
|
||||
const serialStore = useSerialStore();
|
||||
|
||||
import {
|
||||
getVersion,
|
||||
setConfig,
|
||||
startModem,
|
||||
stopModem,
|
||||
getModemState,
|
||||
} from "../js/api";
|
||||
|
||||
const version = import.meta.env.PACKAGE_VERSION;
|
||||
|
||||
// start modemCheck modal once on startup
|
||||
onMounted(() => {
|
||||
getVersion().then((res) => {
|
||||
state.modem_version = res;
|
||||
});
|
||||
new Modal("#modemCheck", {}).show();
|
||||
});
|
||||
|
||||
function getModemStateLocal() {
|
||||
// Returns active/inactive if modem is running for modem status label
|
||||
if (state.is_modem_running == true) return "Active";
|
||||
else return "Inactive";
|
||||
}
|
||||
function getNetworkState() {
|
||||
// Returns active/inactive if modem is running for modem status label
|
||||
if (state.modem_connection === "connected") return "Connected";
|
||||
else return "Disconnected";
|
||||
}
|
||||
|
||||
function getRigControlStuff() {
|
||||
switch (settings.remote.RADIO.control) {
|
||||
case "disabled":
|
||||
return true;
|
||||
case "rigctld":
|
||||
case "rigctld_bundle":
|
||||
case "tci":
|
||||
return state.radio_status;
|
||||
default:
|
||||
console.error(
|
||||
"Unknown radio control mode " + settings.remote.RADIO.control,
|
||||
);
|
||||
return "Unknown control type" + settings.remote.RADIO.control;
|
||||
}
|
||||
}
|
||||
|
||||
function testHamlib() {
|
||||
sendModemCQ();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="modal modal-lg fade"
|
||||
id="modemCheck"
|
||||
data-bs-backdrop="static"
|
||||
data-bs-keyboard="false"
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5">Modem check</h1>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close"
|
||||
data-bs-dismiss="modal"
|
||||
aria-label="Close"
|
||||
></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="accordion" id="startupCheckAccordion">
|
||||
<!-- Network Section -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-target="#networkStatusCollapse"
|
||||
data-bs-toggle="collapse"
|
||||
>
|
||||
Network
|
||||
<span
|
||||
class="badge ms-2 bg-success"
|
||||
:class="
|
||||
state.modem_connection === 'connected'
|
||||
? 'bg-success'
|
||||
: 'bg-danger'
|
||||
"
|
||||
>{{ getNetworkState() }}</span
|
||||
>
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
id="networkStatusCollapse"
|
||||
class="accordion-collapse collapse"
|
||||
>
|
||||
<div class="accordion-body">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-25">Modem port</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="modem port (def 5000)"
|
||||
id="modem_port"
|
||||
maxlength="5"
|
||||
max="65534"
|
||||
min="1025"
|
||||
v-model="settings.local.port"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-25">Modem host</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="modem host (default 127.0.0.1)"
|
||||
id="modem_port"
|
||||
v-model="settings.local.host"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Modem Section -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-target="#modemStatusCollapse"
|
||||
data-bs-toggle="collapse"
|
||||
>
|
||||
Modem
|
||||
<span
|
||||
class="badge ms-2"
|
||||
:class="
|
||||
state.is_modem_running === true
|
||||
? 'bg-success'
|
||||
: 'bg-danger'
|
||||
"
|
||||
>{{ getModemStateLocal() }}</span
|
||||
>
|
||||
</button>
|
||||
</h2>
|
||||
<div id="modemStatusCollapse" class="accordion-collapse collapse">
|
||||
<div class="accordion-body">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50"
|
||||
>Manual modem restart</label
|
||||
>
|
||||
<label class="input-group-text">
|
||||
<button
|
||||
type="button"
|
||||
id="startModem"
|
||||
class="btn btn-sm btn-outline-success"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Start the Modem. Please set your audio and radio settings first!"
|
||||
@click="startModem"
|
||||
v-bind:class="{
|
||||
disabled: state.is_modem_running === true,
|
||||
}"
|
||||
>
|
||||
<i class="bi bi-play-fill"></i>
|
||||
</button> </label
|
||||
><label class="input-group-text">
|
||||
<button
|
||||
type="button"
|
||||
id="stopModem"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Stop the Modem."
|
||||
@click="stopModem"
|
||||
v-bind:class="{
|
||||
disabled: state.is_modem_running === false,
|
||||
}"
|
||||
>
|
||||
<i class="bi bi-stop-fill"></i>
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Audio Input Device -->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50"
|
||||
>Audio Input device</label
|
||||
>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.AUDIO.input_device"
|
||||
>
|
||||
<option
|
||||
v-for="device in audioStore.audioInputs"
|
||||
:value="device.id"
|
||||
>
|
||||
{{ device.name }} [{{ device.api }}]
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Audio Output Device -->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50"
|
||||
>Audio Output device</label
|
||||
>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.AUDIO.output_device"
|
||||
>
|
||||
<option
|
||||
v-for="device in audioStore.audioOutputs"
|
||||
:value="device.id"
|
||||
>
|
||||
{{ device.name }} [{{ device.api }}]
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Radio Control Section -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-target="#radioControlCollapse"
|
||||
data-bs-toggle="collapse"
|
||||
>
|
||||
Radio control
|
||||
<span
|
||||
class="badge ms-2"
|
||||
:class="
|
||||
getRigControlStuff() === true ? 'bg-success' : 'bg-danger'
|
||||
"
|
||||
>{{
|
||||
getRigControlStuff() === true ? "Online" : "Offline"
|
||||
}}</span
|
||||
>
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
id="radioControlCollapse"
|
||||
class="accordion-collapse collapse"
|
||||
>
|
||||
<div class="accordion-body">
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px"
|
||||
>Rig control method</span
|
||||
>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="rigcontrol_radiocontrol"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.RADIO.control"
|
||||
>
|
||||
<option selected value="disabled">
|
||||
Disabled (no rig control; use with VOX)
|
||||
</option>
|
||||
<option selected value="rigctld">
|
||||
Rigctld (external Hamlib)
|
||||
</option>
|
||||
<option selected value="rigctld_bundle">
|
||||
Rigctld (internal Hamlib)
|
||||
</option>
|
||||
<option selected value="tci">TCI</option>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
:class="
|
||||
settings.remote.RADIO.control == 'rigctld_bundle'
|
||||
? ''
|
||||
: 'd-none'
|
||||
"
|
||||
>
|
||||
<!-- Shown when rigctld is selected-->
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px"
|
||||
>Radio port</span
|
||||
>
|
||||
|
||||
<select
|
||||
@change="onChange"
|
||||
v-model="settings.remote.RADIO.serial_port"
|
||||
class="form-select form-select-sm"
|
||||
>
|
||||
<option
|
||||
v-for="device in serialStore.serialDevices"
|
||||
:value="device.port"
|
||||
:key="device.port"
|
||||
>
|
||||
{{ device.description }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-25">Rigctld Test</label>
|
||||
|
||||
<label class="input-group-text">
|
||||
<button
|
||||
type="button"
|
||||
id="testHamlib"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
data-bs-placement="bottom"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="true"
|
||||
@click="testHamlib"
|
||||
title="Test your hamlib settings and toggle PTT once. Button will become <strong class='text-success'>green</strong> on success and <strong class='text-danger'>red</strong> if fails."
|
||||
>
|
||||
Send a CQ
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
:class="
|
||||
settings.remote.RADIO.control == 'tci' ? '' : 'd-none'
|
||||
"
|
||||
>
|
||||
<!-- Shown when tci is selected-->
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-25">TCI IP address</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="TCI IP"
|
||||
id="rigcontrol_tci_ip"
|
||||
aria-label="Device IP"
|
||||
v-model="settings.remote.TCI.tci_ip"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-25">TCI port</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="TCI port"
|
||||
id="rigcontrol_tci_port"
|
||||
aria-label="Device Port"
|
||||
v-model="settings.remote.TCI.tci_port"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Version Section -->
|
||||
<div class="accordion-item">
|
||||
<h2 class="accordion-header">
|
||||
<button
|
||||
class="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-target="#versionCheckCollapse"
|
||||
data-bs-toggle="collapse"
|
||||
>
|
||||
Version
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
id="versionCheckCollapse"
|
||||
class="accordion-collapse collapse"
|
||||
>
|
||||
<div class="accordion-body">
|
||||
<button
|
||||
class="btn btn-secondary btn-sm ms-1 me-1"
|
||||
type="button"
|
||||
disabled
|
||||
>
|
||||
GUI version | {{ version }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="btn btn-secondary btn-sm ms-1 me-1"
|
||||
type="button"
|
||||
disabled
|
||||
>
|
||||
Modem version | {{ state.modem_version }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">
|
||||
Continue
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,5 +1,4 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
@ -7,122 +6,21 @@ setActivePinia(pinia);
|
|||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
import { useAudioStore } from "../store/audioStore.js";
|
||||
const audioStore = useAudioStore(pinia);
|
||||
|
||||
import { startModem, stopModem } from "../js/daemon";
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
function startStopModem() {
|
||||
switch (state.modem_running_state) {
|
||||
case "stopped":
|
||||
|
||||
let startupInputDeviceValue = parseInt((<HTMLSelectElement>document.getElementById("audio_input_selectbox")).value);
|
||||
let startupOutputDeviceValue = parseInt((<HTMLSelectElement>document.getElementById("audio_output_selectbox")).value);
|
||||
|
||||
let startupInputDeviceIndex = (<HTMLSelectElement>document.getElementById("audio_input_selectbox")).selectedIndex;
|
||||
let startupOutputDeviceIndex = (<HTMLSelectElement>document.getElementById("audio_output_selectbox")).selectedIndex;
|
||||
|
||||
|
||||
audioStore.startupInputDevice = startupInputDeviceValue
|
||||
audioStore.startupOutputDevice = startupOutputDeviceValue
|
||||
|
||||
// get full name of audio device
|
||||
settings.rx_audio = (<HTMLSelectElement>document.getElementById("audio_input_selectbox")).options[startupInputDeviceIndex].text;
|
||||
settings.tx_audio = (<HTMLSelectElement>document.getElementById("audio_output_selectbox")).options[startupOutputDeviceIndex].text;
|
||||
|
||||
|
||||
saveSettingsToFile();
|
||||
|
||||
startModem();
|
||||
|
||||
break;
|
||||
case "running":
|
||||
stopModem();
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="navbar bg-body-tertiary border-bottom">
|
||||
<nav class="navbar bg-body-tertiary border-bottom z-0">
|
||||
<div class="mx-auto">
|
||||
<span class="badge bg-secondary me-4"
|
||||
>Modem location | {{ settings.modem_host }}</span
|
||||
>
|
||||
|
||||
<span class="badge bg-secondary me-4"
|
||||
>Service | {{ state.modem_running_state }}</span
|
||||
>
|
||||
|
||||
<div class="btn-group" role="group"></div>
|
||||
<div class="btn-group me-4" role="group">
|
||||
<button
|
||||
type="button"
|
||||
id="startModem"
|
||||
class="btn btn-sm btn-outline-success"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Start the Modem. Please set your audio and radio settings first!"
|
||||
@click="startStopModem()"
|
||||
v-bind:class="{ disabled: state.modem_running_state === 'running' }"
|
||||
>
|
||||
<i class="bi bi-play-fill"></i>
|
||||
<span class="ms-2">start modem</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="stopModem"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Stop the Modem."
|
||||
@click="startStopModem()"
|
||||
v-bind:class="{ disabled: state.modem_running_state === 'stopped' }"
|
||||
>
|
||||
<i class="bi bi-stop-fill"></i>
|
||||
<span class="ms-2">stop modem</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalStartStopModem"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#startStopModemHelpModal"
|
||||
class="btn me-4 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
<span class="badge bg-secondary me-4">
|
||||
Modem Connection {{ state.modem_connection }}
|
||||
</span>
|
||||
<span class="badge bg-secondary me-4">
|
||||
Modem {{ state.is_modem_running }}
|
||||
</span>
|
||||
<span class="badge bg-secondary me-4">
|
||||
RIG Control {{ state.rigctld_started }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="btn-toolbar" role="toolbar">
|
||||
|
||||
<span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-trigger="hover" data-bs-html="false"
|
||||
title="View the received files. This is currently under development!">
|
||||
|
||||
|
||||
<button class="btn btn-sm btn-primary me-2" data-bs-toggle="offcanvas" data-bs-target="#receivedFilesSidebar" id="openReceivedFiles" type="button" > <strong>Files </strong>
|
||||
<i class="bi bi-file-earmark-arrow-up-fill" style="font-size: 1rem; color: white;"></i>
|
||||
<i class="bi bi-file-earmark-arrow-down-fill" style="font-size: 1rem; color: white;"></i>
|
||||
</button>
|
||||
</span> <span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-trigger="hover" data-bs-html="false" title="Send files through HF. This is currently under development!">
|
||||
<button class="btn btn-sm btn-primary me-2" id="openDataModule" data-bs-toggle="offcanvas" data-bs-target="#transmitFileSidebar" type="button" style="display: None;"> <strong>TX File </strong>
|
||||
<i class="bi bi-file-earmark-arrow-up-fill" style="font-size: 1rem; color: white;"></i>
|
||||
</button>
|
||||
|
||||
</span> <span data-bs-placement="bottom" data-bs-toggle="tooltip" data-bs-trigger="hover" data-bs-html="true"
|
||||
title="Settings and Info">
|
||||
|
||||
</span>
|
||||
</div>
|
||||
--></nav>
|
||||
</nav>
|
||||
</template>
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card mb-0">
|
||||
<div class="card-header p-1 d-flex">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-1">
|
||||
<i class="bi bi-cloud-download" style="font-size: 1.2rem"></i>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<strong class="fs-5">Updater</strong>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<div class="progress w-100 ms-1 m-1">
|
||||
<div
|
||||
class="progress-bar"
|
||||
style="width: 0%"
|
||||
role="progressbar"
|
||||
id="UpdateProgressBar"
|
||||
aria-valuenow="0"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
<span id="UpdateProgressInfo"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-1 text-end">
|
||||
<button
|
||||
type="button"
|
||||
id="openHelpModalUpdater"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#updaterHelpModal"
|
||||
class="btn m-0 p-0 border-0"
|
||||
>
|
||||
<i class="bi bi-question-circle" style="font-size: 1rem"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2 mb-1">
|
||||
<button
|
||||
class="btn btn-secondary btn-sm ms-1 me-1"
|
||||
id="updater_channel"
|
||||
type="button"
|
||||
disabled
|
||||
>
|
||||
{{ settings.update_channel }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary btn-sm ms-1"
|
||||
id="updater_status"
|
||||
type="button"
|
||||
disabled
|
||||
>
|
||||
...
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary btn-sm ms-1"
|
||||
id="updater_changelog"
|
||||
type="button"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#updaterReleaseNotes"
|
||||
>
|
||||
Changelog
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary btn-sm ms-1"
|
||||
id="update_and_install"
|
||||
type="button"
|
||||
style="display: none"
|
||||
>
|
||||
Install & Restart
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,7 +1,8 @@
|
|||
<script setup lang="ts">
|
||||
import settings_station from "./settings_station.vue";
|
||||
import settings_gui from "./settings_gui.vue";
|
||||
import settings_chat from "./settings_chat.vue";
|
||||
import settings_hamlib from "./settings_hamlib.vue";
|
||||
import settings_rigcontrol from "./settings_rigcontrol.vue";
|
||||
import settings_modem from "./settings_modem.vue";
|
||||
import settings_web from "./settings_web.vue";
|
||||
import settings_exp from "./settings_exp.vue";
|
||||
|
@ -14,156 +15,188 @@ import settings_exp from "./settings_exp.vue";
|
|||
aria-labelledby="list-settings-list"
|
||||
>
|
||||
<div class="container">
|
||||
<div class="badge text-bg-warning ms-3">
|
||||
<i class="bi bi-exclamation-triangle"></i> Please restart the modem
|
||||
after changing settings!
|
||||
</div>
|
||||
<!-- SETTINGS Nav tabs -->
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link active"
|
||||
id="gui-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#gui"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="home"
|
||||
aria-selected="true"
|
||||
>
|
||||
GUI
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="chat-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#chat"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="home"
|
||||
aria-selected="true"
|
||||
>
|
||||
Chat
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="hamlib-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#hamlib"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="profile"
|
||||
aria-selected="false"
|
||||
>
|
||||
Hamlib
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="modem-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#modem"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="profile"
|
||||
aria-selected="false"
|
||||
>
|
||||
Modem
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="web-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#web"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="messages"
|
||||
aria-selected="false"
|
||||
>
|
||||
Web
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="experiments-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#experiments"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="settings"
|
||||
aria-selected="false"
|
||||
>
|
||||
Exp
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="card text-center">
|
||||
<div class="card-header">
|
||||
<!-- SETTINGS Nav tabs -->
|
||||
<ul class="nav nav-tabs card-header-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link active"
|
||||
id="station-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#station"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="home"
|
||||
aria-selected="true"
|
||||
>
|
||||
Station
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="gui-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#gui"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="home"
|
||||
aria-selected="true"
|
||||
>
|
||||
GUI
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="chat-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#chat"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="home"
|
||||
aria-selected="true"
|
||||
>
|
||||
Chat
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="rigcontrol-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#rigcontrol"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="profile"
|
||||
aria-selected="false"
|
||||
>
|
||||
Rig Control
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<!-- SETTINGS Nav Tab panes -->
|
||||
<div class="tab-content mt-1">
|
||||
<!-- GUI tab contents-->
|
||||
<div
|
||||
class="tab-pane active"
|
||||
id="gui"
|
||||
role="tabpanel"
|
||||
aria-labelledby="gui-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_gui />
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="modem-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#modem"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="profile"
|
||||
aria-selected="false"
|
||||
>
|
||||
Modem
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="web-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#web"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="messages"
|
||||
aria-selected="false"
|
||||
>
|
||||
Web
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button
|
||||
class="nav-link"
|
||||
id="experiments-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#experiments"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="settings"
|
||||
aria-selected="false"
|
||||
>
|
||||
Exp
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
class="card-body overflow-auto"
|
||||
style="height: calc(100vh - 105px)"
|
||||
>
|
||||
<!-- SETTINGS Nav Tab panes -->
|
||||
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="chat"
|
||||
role="tabpanel"
|
||||
aria-labelledby="chat-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_chat />
|
||||
</div>
|
||||
<!-- Station tab contents-->
|
||||
<div class="tab-content">
|
||||
<div
|
||||
class="tab-pane active"
|
||||
id="station"
|
||||
role="tabpanel"
|
||||
aria-labelledby="station-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_station />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="hamlib"
|
||||
role="tabpanel"
|
||||
aria-labelledby="hamlib-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_hamlib />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="modem"
|
||||
role="tabpanel"
|
||||
aria-labelledby="modem-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_modem />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="web"
|
||||
role="tabpanel"
|
||||
aria-labelledby="web-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_web />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="experiments"
|
||||
role="tabpanel"
|
||||
aria-labelledby="experiments-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_exp />
|
||||
<!-- GUI tab contents-->
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="gui"
|
||||
role="tabpanel"
|
||||
aria-labelledby="gui-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_gui />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="chat"
|
||||
role="tabpanel"
|
||||
aria-labelledby="chat-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_chat />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="rigcontrol"
|
||||
role="tabpanel"
|
||||
aria-labelledby="rigcontrol-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_rigcontrol />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="modem"
|
||||
role="tabpanel"
|
||||
aria-labelledby="modem-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_modem />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="web"
|
||||
role="tabpanel"
|
||||
aria-labelledby="web-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_web />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane"
|
||||
id="experiments"
|
||||
role="tabpanel"
|
||||
aria-labelledby="experiments-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_exp />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,128 +1,28 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
import { setConfig } from "../js/api";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
function saveSettings() {
|
||||
saveSettingsToFile();
|
||||
}
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable "is typing"</label>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable message auto repeat</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<div class="form-check form-switch form-check-inline ms-2">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enable_is_writing"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_is_writing"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
disabled
|
||||
@change="onChange"
|
||||
v-model="settings.remote.MESSAGES.enable_auto_repeat"
|
||||
/>
|
||||
<label class="form-check-label" for="GraphicsSwitch"
|
||||
>Additional broadcast burst</label
|
||||
<label class="form-check-label" for="enableMessagesAutoRepeatSwitch"
|
||||
>Re-send message on beacon</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Allow requesting "user profile"</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enable_request_profile"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_request_profile"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50"
|
||||
>Allow requesting "shared folder"</label
|
||||
>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enable_request_shared_folder"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_request_shared_folder"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Shared folder path</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control w-50"
|
||||
id="shared_folder_path"
|
||||
@change="saveSettings"
|
||||
v-model="settings.shared_folder_path"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50"
|
||||
>Enable auto retry on Beacon or Ping
|
||||
</label>
|
||||
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enable_auto_retry"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_auto_retry"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-50">message retry attempts</span>
|
||||
<select
|
||||
class="form-select form-select-sm w-50"
|
||||
id="max_retry_attempts"
|
||||
@change="saveSettings"
|
||||
v-model="settings.max_retry_attempts"
|
||||
disabled
|
||||
>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
</select>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,58 +1,14 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
import { setConfig } from "../js/api";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
function saveSettings() {
|
||||
saveSettingsToFile();
|
||||
}
|
||||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable autotune</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline ms-2">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="autoTuneSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.auto_tune"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
/>
|
||||
<label class="form-check-label" for="autoTuneSwitch"
|
||||
>adjust ALC on TX</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable FSK mode</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline ms-2">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="fskModeSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_fsk"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
disabled
|
||||
/>
|
||||
<label class="form-check-label" for="fskModeSwitch"
|
||||
>not available, yet</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable MESH protocol</label>
|
||||
<label class="input-group-text w-50">
|
||||
|
@ -61,10 +17,8 @@ function saveSettings() {
|
|||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="enableMeshSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_mesh_features"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
@change="setConfig"
|
||||
v-model="settings.remote.MESH.enable_protocol"
|
||||
/>
|
||||
<label class="form-check-label" for="enableMeshSwitch"
|
||||
>experimental! REALLY!</label
|
||||
|
@ -72,24 +26,6 @@ function saveSettings() {
|
|||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Database maintenance</label>
|
||||
<label class="input-group-text w-50">
|
||||
<button
|
||||
class="btn btn-outline-secondary btn-sm w-50"
|
||||
id="btnCleanDB"
|
||||
type="button"
|
||||
disabled
|
||||
>
|
||||
Clean</button
|
||||
>
|
||||
<div
|
||||
class="spinner-border text-warning invisible"
|
||||
role="status"
|
||||
id="divCleanDBSpinner"
|
||||
></div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="center">
|
||||
<div class="badge text-bg-danger">
|
||||
<i class="bi bi-shield-exclamation"></i> These options may not work and
|
||||
|
|
|
@ -1,65 +1,25 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
import { setColormap } from "../js/waterfallHandler";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
|
||||
function saveSettings() {
|
||||
saveSettingsToFile();
|
||||
//saveSettingsToFile();
|
||||
setColormap();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-50">GUI theme</span>
|
||||
<select
|
||||
class="form-select form-select-sm w-50"
|
||||
id="theme_selector"
|
||||
@change="saveSettings"
|
||||
v-model="settings.theme"
|
||||
disabled
|
||||
>
|
||||
<option value="default_light">Default (light)</option>
|
||||
<option value="default_dark">Default (dark)</option>
|
||||
<option value="default_auto">Default (auto)</option>
|
||||
<option value="cerulean">Cerulean</option>
|
||||
<option value="cosmo">Cosmo</option>
|
||||
<option value="cyborg">Cyborg</option>
|
||||
<option value="darkly">Darkly</option>
|
||||
<option value="flatly">Flatly</option>
|
||||
<option value="journal">Journal</option>
|
||||
<option value="litera">Litera</option>
|
||||
<option value="lumen">Lumen</option>
|
||||
<option value="lux">Lux</option>
|
||||
<option value="materia">Materia</option>
|
||||
<option value="minty">Minty</option>
|
||||
<option value="morph">Morhp</option>
|
||||
<option value="pulse">Pulse</option>
|
||||
<option value="quartz">Quartz</option>
|
||||
<option value="sandstone">Sandstone</option>
|
||||
<option value="simplex">Simplex</option>
|
||||
<option value="sketchy">Sketchy</option>
|
||||
<option value="slate">Slate</option>
|
||||
<option value="solar">Solar</option>
|
||||
<option value="spacelab">Spacelab</option>
|
||||
<option value="superhero">Superhero</option>
|
||||
<option value="united">United</option>
|
||||
<option value="vapor">Vapor</option>
|
||||
<option value="yeti">Yeti</option>
|
||||
<option value="zephyr">Zephyr</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-50">Waterfall theme</span>
|
||||
<select
|
||||
class="form-select form-select-sm w-50"
|
||||
id="wftheme_selector"
|
||||
@change="saveSettings"
|
||||
v-model="settings.wftheme"
|
||||
disabled
|
||||
v-model="settings.local.wf_theme"
|
||||
>
|
||||
<option value="2">Default</option>
|
||||
<option value="0">Turbo</option>
|
||||
|
@ -70,49 +30,17 @@ function saveSettings() {
|
|||
<option value="6">Binary</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable fancy GUI</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="GraphicsSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.high_graphics"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
disabled
|
||||
/>
|
||||
<label class="form-check-label" for="GraphicsSwitch"
|
||||
>Higher CPU Usage</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50" for="inputGroupFile02"
|
||||
>Received files folder</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control w-50"
|
||||
id="received_files_folder"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-50">Update channel</span>
|
||||
<select
|
||||
class="form-select form-select-sm w-50"
|
||||
id="update_channel_selector"
|
||||
@change="saveSettings"
|
||||
v-model="settings.update_channel"
|
||||
disabled
|
||||
v-model="settings.local.update_channel"
|
||||
>
|
||||
<option value="latest">stable</option>
|
||||
<option value="beta">beta</option>
|
||||
<option value="alpha">alpha</option>
|
||||
<option value="latest">Stable</option>
|
||||
<option value="beta">Beta</option>
|
||||
<option value="alpha">Alpha</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
|
@ -124,10 +52,7 @@ function saveSettings() {
|
|||
type="checkbox"
|
||||
id="NotificationSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_sys_notification"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
disabled
|
||||
v-model="settings.local.enable_sys_notification"
|
||||
/>
|
||||
<label class="form-check-label" for="NotificationSwitch"
|
||||
>Show system pop-ups</label
|
||||
|
@ -135,24 +60,4 @@ function saveSettings() {
|
|||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Auto-start Modem/rigctld</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="AutoStartSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.auto_start"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
disabled
|
||||
/>
|
||||
<label class="form-check-label" for="AutoStartSwitch"
|
||||
>Start on app launch</label
|
||||
>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,53 +1,144 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
function saveSettings() {
|
||||
saveSettingsToFile();
|
||||
}
|
||||
import { startModem, stopModem } from "../js/api.js";
|
||||
|
||||
import { useAudioStore } from "../store/audioStore";
|
||||
const audioStore = useAudioStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
id="startModem"
|
||||
class="btn btn-sm btn-outline-success"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Start the Modem. Please set your audio and radio settings first!"
|
||||
@click="startModem"
|
||||
v-bind:class="{ disabled: state.is_modem_running === true }"
|
||||
>
|
||||
<i class="bi bi-play-fill"></i>
|
||||
<span class="ms-2">start modem</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
id="stopModem"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-trigger="hover"
|
||||
data-bs-html="false"
|
||||
title="Stop the Modem."
|
||||
@click="stopModem"
|
||||
v-bind:class="{ disabled: state.is_modem_running === false }"
|
||||
>
|
||||
<i class="bi bi-stop-fill"></i>
|
||||
<span class="ms-2">stop modem</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">modem port</span>
|
||||
<span class="input-group-text" style="width: 180px">Modem port</span>
|
||||
<input
|
||||
type="text"
|
||||
type="number"
|
||||
class="form-control"
|
||||
placeholder="modem port"
|
||||
id="modem_port"
|
||||
maxlength="5"
|
||||
max="65534"
|
||||
min="1025"
|
||||
@change="saveSettings"
|
||||
v-model="settings.modem_port"
|
||||
v-model.number="settings.local.port"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">modem host</span>
|
||||
<span class="input-group-text" style="width: 180px">Modem host</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="modem host"
|
||||
id="modem_port"
|
||||
@change="saveSettings"
|
||||
v-model="settings.modem_host"
|
||||
v-model="settings.local.host"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Audio Input Device -->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Audio Input device</label>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.AUDIO.input_device"
|
||||
>
|
||||
<option v-for="device in audioStore.audioInputs" :value="device.id">
|
||||
{{ device.name }} [{{ device.api }}]
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Audio Output Device -->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Audio Output device</label>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.AUDIO.output_device"
|
||||
>
|
||||
<option v-for="device in audioStore.audioOutputs" :value="device.id">
|
||||
{{ device.name }} [{{ device.api }}]
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Audio rx level-->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-25">RX Audio Level</span>
|
||||
<span class="input-group-text w-25">{{
|
||||
settings.remote.AUDIO.rx_audio_level
|
||||
}}</span>
|
||||
<span class="input-group-text w-50">
|
||||
<input
|
||||
type="range"
|
||||
class="form-range"
|
||||
min="-30"
|
||||
max="20"
|
||||
step="1"
|
||||
id="audioLevelRX"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.AUDIO.rx_audio_level"
|
||||
/></span>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-25">TX Audio Level</span>
|
||||
<span class="input-group-text w-25">{{
|
||||
settings.remote.AUDIO.tx_audio_level
|
||||
}}</span>
|
||||
<span class="input-group-text w-50">
|
||||
<input
|
||||
type="range"
|
||||
class="form-range"
|
||||
min="-30"
|
||||
max="20"
|
||||
step="1"
|
||||
id="audioLevelTX"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.AUDIO.tx_audio_level"
|
||||
/></span>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">TX delay in ms</label>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="tx_delay"
|
||||
@change="saveSettings"
|
||||
v-model="settings.tx_delay"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.MODEM.tx_delay"
|
||||
>
|
||||
<option value="0">0</option>
|
||||
<option value="50">50</option>
|
||||
|
@ -74,105 +165,19 @@ function saveSettings() {
|
|||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-25">Tuning range</label>
|
||||
<label class="input-group-text">fmin</label>
|
||||
<label class="input-group-text w-50">Maximum used bandwidth</label>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="tuning_range_fmin"
|
||||
@change="saveSettings"
|
||||
v-model="settings.tuning_range_fmin"
|
||||
id="maximum_bandwidth"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.MODEM.maximum_bandwidth"
|
||||
>
|
||||
<option value="-50.0">-50.0</option>
|
||||
<option value="-100.0">-100.0</option>
|
||||
<option value="-150.0">-150.0</option>
|
||||
<option value="-200.0">-200.0</option>
|
||||
<option value="-250.0">-250.0</option>
|
||||
</select>
|
||||
<label class="input-group-text">fmax</label>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="tuning_range_fmax"
|
||||
@change="saveSettings"
|
||||
v-model="settings.tuning_range_fmax"
|
||||
>
|
||||
<option value="50.0">50.0</option>
|
||||
<option value="100.0">100.0</option>
|
||||
<option value="150.0">150.0</option>
|
||||
<option value="200.0">200.0</option>
|
||||
<option value="250.0">250.0</option>
|
||||
<option value="250">250 Hz</option>
|
||||
<option value="563">563 Hz</option>
|
||||
<option value="1700">1700 Hz</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text w-50">Beacon interval</span>
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="beaconInterval"
|
||||
style="width: 6rem"
|
||||
@change="saveSettings"
|
||||
v-model="settings.beacon_interval"
|
||||
>
|
||||
<option value="60">60 secs</option>
|
||||
<option value="90">90 secs</option>
|
||||
<option value="120">2 mins</option>
|
||||
<option selected value="300">5 mins</option>
|
||||
<option value="600">10 mins</option>
|
||||
<option value="900">15 mins</option>
|
||||
<option value="1800">30 mins</option>
|
||||
<option value="3600">60 mins</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable waterfall data</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="fftSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_fft"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
/>
|
||||
<label class="form-check-label" for="fftSwitch">Waterfall</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable scatter diagram data</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="scatterSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_scatter"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
/>
|
||||
<label class="form-check-label" for="scatterSwitch">Scatter</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Enable 250Hz bandwidth mode</label>
|
||||
<label class="input-group-text w-50">
|
||||
<div class="form-check form-switch form-check-inline">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="250HzModeSwitch"
|
||||
v-model="settings.low_bandwidth_mode"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
@change="saveSettings"
|
||||
/>
|
||||
<label class="form-check-label" for="250HzModeSwitch">250Hz</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">Respond to CQ</label>
|
||||
<label class="input-group-text w-50">
|
||||
|
@ -181,36 +186,11 @@ function saveSettings() {
|
|||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="respondCQSwitch"
|
||||
v-model="settings.respond_to_cq"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
@change="saveSettings"
|
||||
v-model="settings.remote.MODEM.respond_to_cq"
|
||||
@change="onChange"
|
||||
/>
|
||||
<label class="form-check-label" for="respondCQSwitch">QRV</label>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<label class="input-group-text w-50">RX buffer size</label>
|
||||
<label class="input-group-text w-50">
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
id="rx_buffer_size"
|
||||
@change="saveSettings"
|
||||
v-model="settings.rx_buffer_size"
|
||||
>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="4">4</option>
|
||||
<option value="8">8</option>
|
||||
<option value="16">16</option>
|
||||
<option value="32">32</option>
|
||||
<option value="64">64</option>
|
||||
<option value="128">128</option>
|
||||
<option value="256">256</option>
|
||||
<option value="512">512</option>
|
||||
<option value="1024">1024</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
|
79
gui/src/components/settings_rigcontrol.vue
Normal file
79
gui/src/components/settings_rigcontrol.vue
Normal file
|
@ -0,0 +1,79 @@
|
|||
<script setup lang="ts">
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
import settings_hamlib from "./settings_hamlib.vue";
|
||||
import settings_tci from "./settings_tci.vue";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Rig Control</span>
|
||||
|
||||
<select
|
||||
class="form-select form-select-sm"
|
||||
aria-label=".form-select-sm"
|
||||
id="rigcontrol_radiocontrol"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.RADIO.control"
|
||||
>
|
||||
<option selected value="disabled">
|
||||
Disabled / VOX (no rig control - use with VOX)
|
||||
</option>
|
||||
<option selected value="rigctld">Rigctld (external Hamlib)</option>
|
||||
<option selected value="rigctld_bundle">Rigctld (internal Hamlib)</option>
|
||||
<option selected value="tci">TCI</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<hr class="m-2" />
|
||||
|
||||
<nav>
|
||||
<div class="nav nav-tabs" id="nav-tab" role="tablist">
|
||||
<button
|
||||
class="nav-link active"
|
||||
id="nav-home-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#nav-hamlib"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="nav-home"
|
||||
aria-selected="true"
|
||||
>
|
||||
Hamlib
|
||||
</button>
|
||||
<button
|
||||
class="nav-link"
|
||||
id="nav-profile-tab"
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target="#nav-tci"
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-controls="nav-profile"
|
||||
aria-selected="false"
|
||||
>
|
||||
TCI
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="tab-content" id="nav-tabContent">
|
||||
<div
|
||||
class="tab-pane fade show active"
|
||||
id="nav-hamlib"
|
||||
role="tabpanel"
|
||||
aria-labelledby="nav-hamlib-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_hamlib />
|
||||
</div>
|
||||
<div
|
||||
class="tab-pane fade"
|
||||
id="nav-tci"
|
||||
role="tabpanel"
|
||||
aria-labelledby="nav-tci-tab"
|
||||
tabindex="0"
|
||||
>
|
||||
<settings_tci />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="m-2" />
|
||||
</template>
|
85
gui/src/components/settings_station.vue
Normal file
85
gui/src/components/settings_station.vue
Normal file
|
@ -0,0 +1,85 @@
|
|||
<script setup lang="ts">
|
||||
import {
|
||||
settingsStore as settings,
|
||||
onChange,
|
||||
getRemote,
|
||||
} from "../store/settingsStore.js";
|
||||
import {
|
||||
validateCallsignWithSSID,
|
||||
validateCallsignWithoutSSID,
|
||||
} from "../js/freedata";
|
||||
function validateCall() {
|
||||
//ensure callsign is uppercase:
|
||||
let call = settings.remote.STATION.mycall;
|
||||
settings.remote.STATION.mycall = call.toUpperCase();
|
||||
|
||||
if (validateCallsignWithoutSSID(settings.remote.STATION.mycall))
|
||||
//Send new callsign to modem if valid
|
||||
onChange();
|
||||
//Reload settings from modem as invalid callsign was passed in
|
||||
else getRemote();
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<!-- station callsign -->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px"
|
||||
>Your station callsign</span
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
style="text-transform: uppercase"
|
||||
placeholder="Enter your callsign and save it"
|
||||
id="myCall"
|
||||
aria-label="Station Callsign"
|
||||
aria-describedby="basic-addon1"
|
||||
v-model="settings.remote.STATION.mycall"
|
||||
@change="validateCall"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- station ssid -->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Call SSID</span>
|
||||
<select
|
||||
class="form-select form-select-sm w-50"
|
||||
id="myCallSSID"
|
||||
@change="onChange"
|
||||
v-model.number="settings.remote.STATION.myssid"
|
||||
>
|
||||
<option selected value="0">0</option>
|
||||
<option value="1">1</option>
|
||||
<option value="2">2</option>
|
||||
<option value="3">3</option>
|
||||
<option value="4">4</option>
|
||||
<option value="5">5</option>
|
||||
<option value="6">6</option>
|
||||
<option value="7">7</option>
|
||||
<option value="8">8</option>
|
||||
<option value="9">9</option>
|
||||
<option value="10">10</option>
|
||||
<option value="11">11</option>
|
||||
<option value="12">12</option>
|
||||
<option value="13">13</option>
|
||||
<option value="14">14</option>
|
||||
<option value="15">15</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- station grid locator -->
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">Grid Locator</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="Your grid locator"
|
||||
id="myGrid"
|
||||
maxlength="6"
|
||||
aria-label="Station Grid Locator"
|
||||
aria-describedby="basic-addon1"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.STATION.mygrid"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
33
gui/src/components/settings_tci.vue
Normal file
33
gui/src/components/settings_tci.vue
Normal file
|
@ -0,0 +1,33 @@
|
|||
<script setup lang="ts">
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<hr class="m-2" />
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">TCI IP Address</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="TCI IP"
|
||||
id="rigcontrol_tci_ip"
|
||||
aria-label="Device IP"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.TCI.tci_ip"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="input-group input-group-sm mb-1">
|
||||
<span class="input-group-text" style="width: 180px">TCI port</span>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="TCI port"
|
||||
id="rigcontrol_tci_port"
|
||||
aria-label="Device Port"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.TCI.tci_port"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
|
@ -1,16 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import { saveSettingsToFile } from "../js/settingsHandler";
|
||||
import { setConfig } from "../js/api";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
function saveSettings() {
|
||||
saveSettingsToFile();
|
||||
}
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -22,10 +17,8 @@ function saveSettings() {
|
|||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="ExplorerSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.enable_explorer"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.STATION.enable_explorer"
|
||||
/>
|
||||
<label class="form-check-label" for="ExplorerSwitch">Publish</label>
|
||||
</div>
|
||||
|
@ -39,10 +32,8 @@ function saveSettings() {
|
|||
class="form-check-input"
|
||||
type="checkbox"
|
||||
id="ExplorerStatsSwitch"
|
||||
@change="saveSettings"
|
||||
v-model="settings.explorer_stats"
|
||||
true-value="True"
|
||||
false-value="False"
|
||||
@change="onChange"
|
||||
v-model="settings.remote.STATION.enable_stats"
|
||||
/>
|
||||
<label class="form-check-label" for="ExplorerStatsSwitch"
|
||||
>Publish stats</label
|
||||
|
|
192
gui/src/js/api.js
Normal file
192
gui/src/js/api.js
Normal file
|
@ -0,0 +1,192 @@
|
|||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
import {
|
||||
validateCallsignWithSSID,
|
||||
validateCallsignWithoutSSID,
|
||||
} from "./freedata";
|
||||
|
||||
import { processFreedataMessages } from "./messagesHandler";
|
||||
|
||||
function buildURL(params, endpoint) {
|
||||
const url = "http://" + params.host + ":" + params.port + endpoint;
|
||||
return url;
|
||||
}
|
||||
|
||||
async function apiGet(endpoint) {
|
||||
try {
|
||||
const response = await fetch(buildURL(settings.local, endpoint));
|
||||
if (!response.ok) {
|
||||
throw new Error(`REST response not ok: ${response.statusText}`);
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
//console.error("Error getting from REST:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiPost(endpoint, payload = {}) {
|
||||
try {
|
||||
const response = await fetch(buildURL(settings.local, endpoint), {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`REST response not ok: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error posting to REST:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function apiDelete(endpoint, payload = {}) {
|
||||
try {
|
||||
const response = await fetch(buildURL(settings.local, endpoint), {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`REST response not ok: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error deleting from REST:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getVersion() {
|
||||
let data = await apiGet("/version").then((res) => {
|
||||
return res;
|
||||
});
|
||||
|
||||
if (typeof data !== "undefined" && typeof data.version !== "undefined") {
|
||||
return data.version;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export async function getConfig() {
|
||||
return await apiGet("/config");
|
||||
}
|
||||
|
||||
export async function setConfig(config) {
|
||||
return await apiPost("/config", config);
|
||||
}
|
||||
|
||||
export async function getAudioDevices() {
|
||||
return await apiGet("/devices/audio");
|
||||
}
|
||||
|
||||
export async function getSerialDevices() {
|
||||
return await apiGet("/devices/serial");
|
||||
}
|
||||
|
||||
export async function setModemBeacon(enabled = false) {
|
||||
return await apiPost("/modem/beacon", { enabled: enabled });
|
||||
}
|
||||
|
||||
export async function sendModemCQ() {
|
||||
return await apiPost("/modem/cqcqcq");
|
||||
}
|
||||
|
||||
export async function sendModemPing(dxcall) {
|
||||
if (
|
||||
validateCallsignWithSSID(dxcall) === false &&
|
||||
validateCallsignWithoutSSID(dxcall) === true
|
||||
) {
|
||||
dxcall = String(dxcall).toUpperCase().trim();
|
||||
dxcall = dxcall + "-0";
|
||||
}
|
||||
dxcall = String(dxcall).toUpperCase().trim();
|
||||
if (validateCallsignWithSSID(dxcall))
|
||||
return await apiPost("/modem/ping_ping", { dxcall: dxcall });
|
||||
}
|
||||
|
||||
export async function sendModemARQRaw(mycall, dxcall, data, uuid) {
|
||||
return await apiPost("/modem/send_arq_raw", {
|
||||
mycallsign: mycall,
|
||||
dxcall: dxcall,
|
||||
data: data,
|
||||
uuid: uuid,
|
||||
});
|
||||
}
|
||||
|
||||
export async function stopTransmission() {
|
||||
return await apiPost("/modem/stop_transmission");
|
||||
}
|
||||
|
||||
export async function sendModemTestFrame() {
|
||||
return await apiPost("/modem/send_test_frame");
|
||||
}
|
||||
|
||||
export async function startModem() {
|
||||
return await apiPost("/modem/start");
|
||||
}
|
||||
|
||||
export async function stopModem() {
|
||||
return await apiPost("/modem/stop");
|
||||
}
|
||||
|
||||
export async function getModemState() {
|
||||
return await apiGet("/modem/state");
|
||||
}
|
||||
|
||||
export async function setRadioParametersFrequency(frequency) {
|
||||
return await apiPost("/radio", {
|
||||
radio_frequency: frequency,
|
||||
});
|
||||
}
|
||||
export async function setRadioParametersMode(mode) {
|
||||
return await apiPost("/radio", {
|
||||
radio_mode: mode,
|
||||
});
|
||||
}
|
||||
export async function setRadioParametersRFLevel(rf_level) {
|
||||
return await apiPost("/radio", {
|
||||
radio_rf_level: rf_level,
|
||||
});
|
||||
}
|
||||
export async function getRadioStatus() {
|
||||
return await apiGet("/radio");
|
||||
}
|
||||
|
||||
export async function getFreedataMessages() {
|
||||
let res = await apiGet("/freedata/messages");
|
||||
processFreedataMessages(res);
|
||||
}
|
||||
|
||||
export async function getFreedataAttachmentBySha512(data_sha512) {
|
||||
let res = await apiGet(`/freedata/messages/attachment/${data_sha512}`);
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function sendFreedataMessage(destination, body, attachments) {
|
||||
return await apiPost("/freedata/messages", {
|
||||
destination: destination,
|
||||
body: body,
|
||||
attachments: attachments,
|
||||
});
|
||||
}
|
||||
|
||||
export async function retransmitFreedataMessage(id) {
|
||||
return await apiPost(`/freedata/messages/${id}`);
|
||||
}
|
||||
|
||||
export async function deleteFreedataMessage(id) {
|
||||
return await apiDelete(`/freedata/messages/${id}`);
|
||||
}
|
||||
|
||||
export async function getBeaconDataByCallsign(callsign) {
|
||||
return await apiGet(`/freedata/beacons/${callsign}`);
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,281 +0,0 @@
|
|||
//var net = require("net");
|
||||
var net = require("node:net");
|
||||
|
||||
// ----------------- init pinia stores -------------
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
import { useAudioStore } from "../store/audioStore.js";
|
||||
const audioStore = useAudioStore(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const state = useStateStore(pinia);
|
||||
|
||||
var daemon = new net.Socket();
|
||||
var socketchunk = []; // Current message, per connection.
|
||||
|
||||
// global to keep track of daemon connection error emissions
|
||||
var daemonShowConnectStateError = 1;
|
||||
|
||||
setTimeout(connectDAEMON, 500);
|
||||
|
||||
function connectDAEMON() {
|
||||
if (daemonShowConnectStateError == 1) {
|
||||
console.log("connecting to daemon");
|
||||
}
|
||||
|
||||
//clear message buffer after reconnecting or initial connection
|
||||
socketchunk = [];
|
||||
|
||||
daemon.connect(settings.daemon_port, settings.daemon_host);
|
||||
|
||||
//client.setTimeout(5000);
|
||||
}
|
||||
|
||||
daemon.on("connect", function () {
|
||||
console.log("daemon connection established");
|
||||
daemonShowConnectStateError = 1;
|
||||
});
|
||||
|
||||
daemon.on("error", function (err) {
|
||||
if (daemonShowConnectStateError == 1) {
|
||||
console.log("daemon connection error");
|
||||
console.log("Make sure the daemon is started.");
|
||||
console.log('Run "python daemon.py" in the modem directory.');
|
||||
console.log(err);
|
||||
daemonShowConnectStateError = 0;
|
||||
}
|
||||
setTimeout(connectDAEMON, 500);
|
||||
daemon.destroy();
|
||||
});
|
||||
|
||||
/*
|
||||
client.on('close', function(data) {
|
||||
console.log(' Modem connection closed');
|
||||
setTimeout(connectModem, 2000)
|
||||
let Data = {
|
||||
daemon_connection: daemon.readyState,
|
||||
};
|
||||
ipcRenderer.send('request-update-daemon-connection', Data);
|
||||
});
|
||||
*/
|
||||
|
||||
daemon.on("end", function (data) {
|
||||
console.log("daemon connection ended");
|
||||
console.log(data);
|
||||
daemon.destroy();
|
||||
setTimeout(connectDAEMON, 500);
|
||||
});
|
||||
|
||||
//exports.writeDaemonCommand = function(command){
|
||||
//writeDaemonCommand = function (command) {
|
||||
function writeDaemonCommand(command) {
|
||||
// we use the writingCommand function to update our TCPIP state because we are calling this function a lot
|
||||
// if socket opened, we are able to run commands
|
||||
if (daemon.readyState == "open") {
|
||||
//uiMain.setDAEMONconnection('open')
|
||||
daemon.write(command + "\n");
|
||||
}
|
||||
|
||||
if (daemon.readyState == "closed") {
|
||||
//uiMain.setDAEMONconnection('closed')
|
||||
}
|
||||
|
||||
if (daemon.readyState == "opening") {
|
||||
//uiMain.setDAEMONconnection('opening')
|
||||
}
|
||||
}
|
||||
|
||||
// "https://stackoverflow.com/questions/9070700/nodejs-net-createserver-large-amount-of-data-coming-in"
|
||||
|
||||
daemon.on("data", function (socketdata) {
|
||||
/*
|
||||
inspired by:
|
||||
stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in
|
||||
*/
|
||||
|
||||
socketdata = socketchunk.join("\n") + socketdata.toString("utf8"); //append incoming data to socketchunk
|
||||
//socketdata = socketdata.toString("utf8"); // convert data to string
|
||||
|
||||
//socketchunk += socketdata; // append data to buffer so we can stick long data together
|
||||
|
||||
// check if we received begin and end of json data
|
||||
if (socketdata.startsWith('{"') && socketdata.endsWith('"}\n')) {
|
||||
var data = "";
|
||||
|
||||
// split data into chunks if we received multiple commands
|
||||
socketchunk = socketdata.split("\n");
|
||||
data = JSON.parse(socketchunk[0]);
|
||||
|
||||
// search for empty entries in socketchunk and remove them
|
||||
for (var i = 0; i < socketchunk.length; i++) {
|
||||
if (socketchunk[i] === "") {
|
||||
socketchunk.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
//iterate through socketchunks array to execute multiple commands in row
|
||||
for (i = 0; i < socketchunk.length; i++) {
|
||||
//check if data is not empty
|
||||
if (socketchunk[i].length > 0) {
|
||||
//try to parse JSON
|
||||
try {
|
||||
data = JSON.parse(socketchunk[i]);
|
||||
} catch (e) {
|
||||
console.log(e); // "SyntaxError
|
||||
//daemonLog.debug(socketchunk[i]);
|
||||
socketchunk = [];
|
||||
}
|
||||
}
|
||||
|
||||
console.log(data);
|
||||
if (data["command"] == "daemon_state") {
|
||||
// update audio devices by putting them to audio store
|
||||
audioStore.inputDevices = data["input_devices"];
|
||||
audioStore.outputDevices = data["output_devices"];
|
||||
settings.serial_devices = data["serial_devices"];
|
||||
state.python_version = data["python_version"];
|
||||
state.modem_version = data["version"];
|
||||
state.modem_running_state = data["daemon_state"][0]["status"];
|
||||
state.rigctld_started = data["rigctld_state"][0]["status"];
|
||||
//state.rigctld_process = data["daemon_state"][0]["rigctld_process"];
|
||||
}
|
||||
|
||||
if (data["command"] == "test_hamlib") {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
//finally delete message buffer
|
||||
socketchunk = [];
|
||||
}
|
||||
});
|
||||
|
||||
// START Modem
|
||||
// ` `== multi line string
|
||||
export function startModem() {
|
||||
var json_command = JSON.stringify({
|
||||
type: "set",
|
||||
command: "start_modem",
|
||||
parameter: [
|
||||
{
|
||||
mycall: settings.mycall,
|
||||
mygrid: settings.mygrid,
|
||||
rx_audio: audioStore.startupInputDevice,
|
||||
tx_audio: audioStore.startupOutputDevice,
|
||||
radiocontrol: settings.radiocontrol,
|
||||
devicename: settings.hamlib_deviceid,
|
||||
deviceport: settings.hamlib_deviceport,
|
||||
pttprotocol: settings.hamlib_pttprotocol,
|
||||
pttport: settings.hamlib_ptt_port,
|
||||
serialspeed: settings.hamlib_serialspeed,
|
||||
data_bits: settings.hamlib_data_bits,
|
||||
stop_bits: settings.hamlib_stop_bits,
|
||||
handshake: settings.hamlib_handshake,
|
||||
rigctld_port: settings.hamlib_rigctld_port,
|
||||
rigctld_ip: settings.hamlib_rigctld_ip,
|
||||
enable_scatter: settings.enable_scatter,
|
||||
enable_fft: settings.enable_fft,
|
||||
enable_fsk: settings.enable_fsk,
|
||||
low_bandwidth_mode: settings.low_bandwidth_mode,
|
||||
tuning_range_fmin: settings.tuning_range_fmin,
|
||||
tuning_range_fmax: settings.tuning_range_fmax,
|
||||
//tx_audio_level: settings.tx_audio_level,
|
||||
respond_to_cq: settings.respond_to_cq,
|
||||
rx_buffer_size: settings.rx_buffer_size,
|
||||
enable_explorer: settings.enable_explorer,
|
||||
enable_stats: settings.explorer_stats,
|
||||
enable_auto_tune: settings.auto_tune,
|
||||
tx_delay: settings.tx_delay,
|
||||
tci_ip: settings.tci_ip,
|
||||
tci_port: settings.tci_port,
|
||||
enable_mesh: settings.enable_mesh_features,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
console.log(json_command);
|
||||
writeDaemonCommand(json_command);
|
||||
}
|
||||
|
||||
// STOP Modem
|
||||
//exports.stopModem = function () {
|
||||
export function stopModem() {
|
||||
var command =
|
||||
'{"type" : "set", "command": "stop_modem" , "parameter": "---" }';
|
||||
writeDaemonCommand(command);
|
||||
}
|
||||
|
||||
// TEST HAMLIB
|
||||
function testHamlib(
|
||||
//exports.testHamlib = function (
|
||||
radiocontrol,
|
||||
devicename,
|
||||
deviceport,
|
||||
serialspeed,
|
||||
pttprotocol,
|
||||
pttport,
|
||||
data_bits,
|
||||
stop_bits,
|
||||
handshake,
|
||||
rigctld_ip,
|
||||
rigctld_port,
|
||||
) {
|
||||
var json_command = JSON.stringify({
|
||||
type: "get",
|
||||
command: "test_hamlib",
|
||||
parameter: [
|
||||
{
|
||||
radiocontrol: radiocontrol,
|
||||
devicename: devicename,
|
||||
deviceport: deviceport,
|
||||
pttprotocol: pttprotocol,
|
||||
pttport: pttport,
|
||||
serialspeed: serialspeed,
|
||||
data_bits: data_bits,
|
||||
stop_bits: stop_bits,
|
||||
handshake: handshake,
|
||||
rigctld_port: rigctld_port,
|
||||
rigctld_ip: rigctld_ip,
|
||||
},
|
||||
],
|
||||
});
|
||||
console.log(json_command);
|
||||
writeDaemonCommand(json_command);
|
||||
}
|
||||
|
||||
export function startRigctld() {
|
||||
var json_command = JSON.stringify({
|
||||
type: "set",
|
||||
command: "start_rigctld",
|
||||
parameter: [
|
||||
{
|
||||
hamlib_deviceid: settings.hamlib_deviceid,
|
||||
hamlib_deviceport: settings.hamlib_deviceport,
|
||||
hamlib_stop_bits: settings.hamlib_stop_bits,
|
||||
hamlib_data_bits: settings.hamlib_data_bits,
|
||||
hamlib_handshake: settings.hamlib_handshake,
|
||||
hamlib_serialspeed: settings.hamlib_serialspeed,
|
||||
hamlib_dtrstate: settings.hamlib_dtrstate,
|
||||
hamlib_pttprotocol: settings.hamlib_pttprotocol,
|
||||
hamlib_ptt_port: settings.hamlib_ptt_port,
|
||||
hamlib_dcd: settings.hamlib_dcd,
|
||||
hamlbib_serialspeed_ptt: settings.hamlib_serialspeed,
|
||||
hamlib_rigctld_port: settings.hamlib_rigctld_port,
|
||||
hamlib_rigctld_ip: settings.hamlib_rigctld_ip,
|
||||
hamlib_rigctld_path: settings.hamlib_rigctld_path,
|
||||
hamlib_rigctld_server_port: settings.hamlib_rigctld_server_port,
|
||||
hamlib_rigctld_custom_args: settings.hamlib_rigctld_custom_args,
|
||||
},
|
||||
],
|
||||
});
|
||||
console.log(json_command);
|
||||
writeDaemonCommand(json_command);
|
||||
}
|
||||
export function stopRigctld() {
|
||||
let command = '{"type" : "set", "command": "stop_rigctld"}';
|
||||
writeDaemonCommand(command);
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,186 +0,0 @@
|
|||
const path = require("path");
|
||||
const { ipcRenderer } = require("electron");
|
||||
|
||||
// https://stackoverflow.com/a/26227660
|
||||
var appDataFolder =
|
||||
process.env.APPDATA ||
|
||||
(process.platform == "darwin"
|
||||
? process.env.HOME + "/Library/Application Support"
|
||||
: process.env.HOME + "/.config");
|
||||
var configFolder = path.join(appDataFolder, "FreeDATA");
|
||||
var configPath = path.join(configFolder, "config.json");
|
||||
const config = require(configPath);
|
||||
|
||||
// WINDOW LISTENER
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
document
|
||||
.getElementById("enable_filter_info")
|
||||
.addEventListener("click", () => {
|
||||
if (document.getElementById("enable_filter_info").checked) {
|
||||
display_class("table-info", true);
|
||||
} else {
|
||||
display_class("table-info", false);
|
||||
}
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById("enable_filter_debug")
|
||||
.addEventListener("click", () => {
|
||||
if (document.getElementById("enable_filter_debug").checked) {
|
||||
display_class("table-debug", true);
|
||||
} else {
|
||||
display_class("table-debug", false);
|
||||
}
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById("enable_filter_warning")
|
||||
.addEventListener("click", () => {
|
||||
if (document.getElementById("enable_filter_warning").checked) {
|
||||
display_class("table-warning", true);
|
||||
} else {
|
||||
display_class("table-warning", false);
|
||||
}
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById("enable_filter_error")
|
||||
.addEventListener("click", () => {
|
||||
if (document.getElementById("enable_filter_error").checked) {
|
||||
display_class("table-danger", true);
|
||||
} else {
|
||||
display_class("table-danger", false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function display_class(class_name, state) {
|
||||
var collection = document.getElementsByClassName(class_name);
|
||||
console.log(collection);
|
||||
for (let i = 0; i < collection.length; i++) {
|
||||
if (state == true) {
|
||||
collection[i].style.display = "table-row";
|
||||
} else {
|
||||
collection[i].style.display = "None";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipcRenderer.on("action-update-log", (event, arg) => {
|
||||
var entry = arg.entry;
|
||||
|
||||
// remove ANSI characters from string, caused by color logging
|
||||
// https://stackoverflow.com/a/29497680
|
||||
entry = entry.replace(
|
||||
/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
||||
"",
|
||||
);
|
||||
|
||||
var tbl = document.getElementById("log");
|
||||
var row = document.createElement("tr");
|
||||
|
||||
var timestamp = document.createElement("td");
|
||||
var timestampText = document.createElement("span");
|
||||
|
||||
//datetime = new Date();
|
||||
//timestampText.innerText = datetime.toISOString();
|
||||
timestampText.innerText = entry.slice(0, 19);
|
||||
timestamp.appendChild(timestampText);
|
||||
|
||||
var type = document.createElement("td");
|
||||
var typeText = document.createElement("span");
|
||||
// typeText.innerText = entry.slice(10, 30).match(/[\[](.*)[^\]]/g);
|
||||
console.log(entry.match(/\[[^\]]+\]/g));
|
||||
|
||||
try {
|
||||
typeText.innerText = entry.match(/\[[^\]]+\]/g)[0];
|
||||
} catch (e) {
|
||||
typeText.innerText = "-";
|
||||
}
|
||||
|
||||
// let res = str.match(/[\[](.*)[^\]]/g);
|
||||
|
||||
type.appendChild(typeText);
|
||||
|
||||
var area = document.createElement("td");
|
||||
var areaText = document.createElement("span");
|
||||
//areaText.innerText = entry.slice(10, 50).match(/[\] \[](.*)[^\]]/g);
|
||||
//areaText.innerText = entry.match(/\[[^\]]+\]/g)[1];
|
||||
|
||||
try {
|
||||
areaText.innerText = entry.match(/\[[^\]]+\]/g)[1];
|
||||
} catch (e) {
|
||||
areaText.innerText = "-";
|
||||
}
|
||||
area.appendChild(areaText);
|
||||
|
||||
var logEntry = document.createElement("td");
|
||||
var logEntryText = document.createElement("span");
|
||||
try {
|
||||
logEntryText.innerText = entry.split("]")[2];
|
||||
} catch (e) {
|
||||
logEntryText.innerText = "-";
|
||||
}
|
||||
logEntry.appendChild(logEntryText);
|
||||
|
||||
row.appendChild(timestamp);
|
||||
row.appendChild(type);
|
||||
row.appendChild(area);
|
||||
row.appendChild(logEntry);
|
||||
|
||||
//row.classList.add("table-blablubb");
|
||||
/*
|
||||
if (logEntryText.innerText.includes('ALSA lib pcm')) {
|
||||
row.classList.add("table-secondary");
|
||||
}
|
||||
*/
|
||||
if (typeText.innerText.includes("info")) {
|
||||
row.classList.add("table-info");
|
||||
}
|
||||
if (typeText.innerText.includes("debug")) {
|
||||
row.classList.add("table-secondary");
|
||||
}
|
||||
|
||||
if (typeText.innerText.includes("warning")) {
|
||||
row.classList.add("table-warning");
|
||||
}
|
||||
|
||||
if (typeText.innerText.includes("error")) {
|
||||
row.classList.add("table-danger");
|
||||
}
|
||||
|
||||
if (document.getElementById("enable_filter_info").checked) {
|
||||
row.style.display = "table-row";
|
||||
display_class("table-info", true);
|
||||
} else {
|
||||
row.style.display = "None";
|
||||
display_class("table-info", false);
|
||||
}
|
||||
if (document.getElementById("enable_filter_debug").checked) {
|
||||
row.style.display = "table-row";
|
||||
display_class("table-secondary", true);
|
||||
} else {
|
||||
row.style.display = "None";
|
||||
display_class("table-secondary", false);
|
||||
}
|
||||
if (document.getElementById("enable_filter_warning").checked) {
|
||||
row.style.display = "table-row";
|
||||
display_class("table-warning", true);
|
||||
} else {
|
||||
row.style.display = "None";
|
||||
display_class("table-warning", false);
|
||||
}
|
||||
if (document.getElementById("enable_filter_error").checked) {
|
||||
row.style.display = "table-row";
|
||||
display_class("table-danger", true);
|
||||
} else {
|
||||
row.style.display = "None";
|
||||
display_class("table-danger", false);
|
||||
}
|
||||
|
||||
tbl.appendChild(row);
|
||||
|
||||
// scroll to bottom of page
|
||||
// https://stackoverflow.com/a/11715670
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
});
|
File diff suppressed because it is too large
Load diff
|
@ -1,231 +0,0 @@
|
|||
const path = require("path");
|
||||
const { ipcRenderer } = require("electron");
|
||||
|
||||
// https://stackoverflow.com/a/26227660
|
||||
var appDataFolder =
|
||||
process.env.APPDATA ||
|
||||
(process.platform == "darwin"
|
||||
? process.env.HOME + "/Library/Application Support"
|
||||
: process.env.HOME + "/.config");
|
||||
var configFolder = path.join(appDataFolder, "FreeDATA");
|
||||
var configPath = path.join(configFolder, "config.json");
|
||||
const config = require(configPath);
|
||||
|
||||
var callsignPath = path.join(configFolder, "callsigns.json");
|
||||
const callsigns = require(callsignPath);
|
||||
|
||||
// WINDOW LISTENER
|
||||
window.addEventListener("DOMContentLoaded", () => {
|
||||
// startPing button clicked
|
||||
document
|
||||
.getElementById("transmit_mesh_ping")
|
||||
.addEventListener("click", () => {
|
||||
var dxcallsign = document
|
||||
.getElementById("dxCallMesh")
|
||||
.value.toUpperCase();
|
||||
if (dxcallsign == "" || dxcallsign == null || dxcallsign == undefined)
|
||||
return;
|
||||
//pauseButton(document.getElementById("transmit_mesh_ping"), 2000);
|
||||
ipcRenderer.send("run-tnc-command", {
|
||||
command: "mesh_ping",
|
||||
dxcallsign: dxcallsign,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
ipcRenderer.on("action-update-mesh-table", (event, arg) => {
|
||||
var routes = arg.routing_table;
|
||||
|
||||
if (typeof routes == "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
var tbl = document.getElementById("mesh-table");
|
||||
if (tbl !== null) {
|
||||
tbl.innerHTML = "";
|
||||
}
|
||||
|
||||
for (i = 0; i < routes.length; i++) {
|
||||
var row = document.createElement("tr");
|
||||
var datetime = new Date(routes[i]["timestamp"] * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hourCycle: "h23",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
},
|
||||
);
|
||||
var timestamp = document.createElement("td");
|
||||
var timestampText = document.createElement("span");
|
||||
timestampText.innerText = datetime;
|
||||
timestamp.appendChild(timestampText);
|
||||
|
||||
var dxcall = document.createElement("td");
|
||||
var dxcallText = document.createElement("span");
|
||||
dxcallText.innerText = routes[i]["dxcall"];
|
||||
|
||||
// check for callsign in callsign list, else use checksum
|
||||
for (let call in callsigns) {
|
||||
if (callsigns[call] == routes[i]["dxcall"]) {
|
||||
dxcallText.innerText += " (" + call + ")";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
dxcall.appendChild(dxcallText);
|
||||
|
||||
var router = document.createElement("td");
|
||||
var routerText = document.createElement("span");
|
||||
routerText.innerText = routes[i]["router"];
|
||||
|
||||
// check for callsign in callsign list, else use checksum
|
||||
for (let call in callsigns) {
|
||||
if (callsigns[call] == routes[i]["router"]) {
|
||||
routerText.innerHTML += `<span class="badge ms-2 bg-secondary">${call}</span>`;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
router.appendChild(routerText);
|
||||
|
||||
var hops = document.createElement("td");
|
||||
var hopsText = document.createElement("span");
|
||||
hopsText.innerText = routes[i]["hops"];
|
||||
hops.appendChild(hopsText);
|
||||
|
||||
var score = document.createElement("td");
|
||||
var scoreText = document.createElement("span");
|
||||
scoreText.innerText = routes[i]["score"];
|
||||
score.appendChild(scoreText);
|
||||
|
||||
var snr = document.createElement("td");
|
||||
var snrText = document.createElement("span");
|
||||
snrText.innerText = routes[i]["snr"];
|
||||
snr.appendChild(snrText);
|
||||
|
||||
row.appendChild(timestamp);
|
||||
row.appendChild(dxcall);
|
||||
row.appendChild(router);
|
||||
row.appendChild(hops);
|
||||
row.appendChild(score);
|
||||
row.appendChild(snr);
|
||||
|
||||
tbl.appendChild(row);
|
||||
}
|
||||
/*-------------------------------------------*/
|
||||
var routes = arg.mesh_signalling_table;
|
||||
|
||||
//console.log(routes);
|
||||
if (typeof routes == "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
var tbl = document.getElementById("mesh-signalling-table");
|
||||
if (tbl !== null) {
|
||||
tbl.innerHTML = "";
|
||||
}
|
||||
|
||||
for (i = 0; i < routes.length; i++) {
|
||||
var row = document.createElement("tr");
|
||||
var datetime = new Date(routes[i]["timestamp"] * 1000).toLocaleString(
|
||||
navigator.language,
|
||||
{
|
||||
hourCycle: "h23",
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
second: "2-digit",
|
||||
},
|
||||
);
|
||||
var timestamp = document.createElement("td");
|
||||
var timestampText = document.createElement("span");
|
||||
timestampText.innerText = datetime;
|
||||
timestamp.appendChild(timestampText);
|
||||
|
||||
var destination = document.createElement("td");
|
||||
var destinationText = document.createElement("span");
|
||||
destinationText.innerText = routes[i]["destination"];
|
||||
// check for callsign in callsign list, else use checksum
|
||||
for (let call in callsigns) {
|
||||
if (callsigns[call] == routes[i]["destination"]) {
|
||||
destinationText.innerHTML += `<span class="badge ms-2 bg-secondary">${call}</span>`;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
destination.appendChild(destinationText);
|
||||
|
||||
var origin = document.createElement("td");
|
||||
var originText = document.createElement("span");
|
||||
originText.innerText = routes[i]["origin"];
|
||||
// check for callsign in callsign list, else use checksum
|
||||
for (let call in callsigns) {
|
||||
if (callsigns[call] == routes[i]["origin"]) {
|
||||
originText.innerHTML += `<span class="badge ms-2 bg-secondary">${call}</span>`;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
origin.appendChild(originText);
|
||||
|
||||
var frametype = document.createElement("td");
|
||||
var frametypeText = document.createElement("span");
|
||||
frametypeText.innerText = routes[i]["frametype"];
|
||||
frametype.appendChild(frametypeText);
|
||||
|
||||
var payload = document.createElement("td");
|
||||
var payloadText = document.createElement("span");
|
||||
payloadText.innerText = routes[i]["payload"];
|
||||
payload.appendChild(payloadText);
|
||||
|
||||
var attempt = document.createElement("td");
|
||||
var attemptText = document.createElement("span");
|
||||
attemptText.innerText = routes[i]["attempt"];
|
||||
attempt.appendChild(attemptText);
|
||||
|
||||
var status = document.createElement("td");
|
||||
var statusText = document.createElement("span");
|
||||
//statusText.innerText = routes[i]["status"];
|
||||
switch (routes[i]["status"]) {
|
||||
case "acknowledged":
|
||||
var status_icon = '<i class="bi bi-check-circle-fill"></i>';
|
||||
var status_color = "bg-success";
|
||||
break;
|
||||
case "acknowledging":
|
||||
var status_icon = '<i class="bi bi-check-circle"></i>';
|
||||
var status_color = "bg-warning";
|
||||
break;
|
||||
case "forwarding":
|
||||
var status_icon = '<i class="bi bi-arrow-left-right"></i>';
|
||||
var status_color = "bg-secondary";
|
||||
break;
|
||||
case "awaiting_ack":
|
||||
var status_icon = '<i class="bi bi-clock-history"></i>';
|
||||
var status_color = "bg-info";
|
||||
break;
|
||||
default:
|
||||
var status_icon = '<i class="bi bi-question-circle-fill"></i>';
|
||||
var status_color = "bg-primary";
|
||||
break;
|
||||
}
|
||||
|
||||
statusText.innerHTML = `
|
||||
<span class="badge ${status_color}">${status_icon}</span>
|
||||
<span class="badge ${status_color}">${routes[i]["status"]}</span>
|
||||
`;
|
||||
status.appendChild(statusText);
|
||||
|
||||
row.appendChild(timestamp);
|
||||
row.appendChild(destination);
|
||||
row.appendChild(origin);
|
||||
row.appendChild(frametype);
|
||||
row.appendChild(payload);
|
||||
row.appendChild(attempt);
|
||||
row.appendChild(status);
|
||||
|
||||
tbl.appendChild(row);
|
||||
}
|
||||
});
|
349
gui/src/js/eventHandler.js
Normal file
349
gui/src/js/eventHandler.js
Normal file
|
@ -0,0 +1,349 @@
|
|||
/*
|
||||
import {
|
||||
newMessageReceived,
|
||||
newBeaconReceived,
|
||||
updateTransmissionStatus,
|
||||
setStateSuccess,
|
||||
setStateFailed,
|
||||
} from "./chatHandler";
|
||||
*/
|
||||
import { displayToast } from "./popupHandler";
|
||||
import { getFreedataMessages, getModemState, getAudioDevices } from "./api";
|
||||
import { processFreedataMessages } from "./messagesHandler.ts";
|
||||
import { processRadioStatus } from "./radioHandler.ts";
|
||||
|
||||
import { useAudioStore } from "../store/audioStore";
|
||||
const audioStore = useAudioStore();
|
||||
import { useSerialStore } from "../store/serialStore";
|
||||
const serialStore = useSerialStore();
|
||||
|
||||
// ----------------- init pinia stores -------------
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const stateStore = useStateStore(pinia);
|
||||
|
||||
import {
|
||||
settingsStore as settings,
|
||||
getRemote,
|
||||
} from "../store/settingsStore.js";
|
||||
|
||||
export function loadAllData() {
|
||||
getModemState();
|
||||
getRemote();
|
||||
getOverallHealth();
|
||||
audioStore.loadAudioDevices();
|
||||
serialStore.loadSerialDevices();
|
||||
getFreedataMessages();
|
||||
processFreedataMessages();
|
||||
processRadioStatus();
|
||||
}
|
||||
|
||||
export function connectionFailed(endpoint, event) {
|
||||
stateStore.modem_connection = "disconnected";
|
||||
}
|
||||
export function stateDispatcher(data) {
|
||||
data = JSON.parse(data);
|
||||
//Leave commented when not needed, otherwise can lead to heap overflows due to the amount of data logged
|
||||
//console.debug(data);
|
||||
if (data["type"] == "state-change" || data["type"] == "state") {
|
||||
stateStore.modem_connection = "connected";
|
||||
|
||||
stateStore.busy_state = data["is_modem_busy"];
|
||||
|
||||
stateStore.channel_busy = data["channel_busy"];
|
||||
stateStore.is_codec2_traffic = data["is_codec2_traffic"];
|
||||
stateStore.is_modem_running = data["is_modem_running"];
|
||||
stateStore.dbfs_level = Math.round(data["audio_dbfs"]);
|
||||
stateStore.dbfs_level_percent = Math.round(
|
||||
Math.pow(10, data["audio_dbfs"] / 20) * 100,
|
||||
);
|
||||
|
||||
stateStore.s_meter_strength_raw = Math.round(data["s_meter_strength"]);
|
||||
stateStore.s_meter_strength_percent = Math.round(
|
||||
Math.pow(10, data["s_meter_strength"] / 20) * 100,
|
||||
);
|
||||
|
||||
stateStore.channel_busy_slot = data["channel_busy_slot"];
|
||||
stateStore.beacon_state = data["is_beacon_running"];
|
||||
stateStore.radio_status = data["radio_status"];
|
||||
stateStore.frequency = data["radio_frequency"];
|
||||
stateStore.mode = data["radio_mode"];
|
||||
//Reverse entries so most recent is first
|
||||
stateStore.activities = Object.entries(data["activities"]).reverse();
|
||||
build_HSL();
|
||||
}
|
||||
}
|
||||
|
||||
export function eventDispatcher(data) {
|
||||
data = JSON.parse(data);
|
||||
console.debug(data);
|
||||
|
||||
if (data["scatter"] !== undefined) {
|
||||
stateStore.scatter = JSON.parse(data["scatter"]);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data["message-db"]) {
|
||||
case "changed":
|
||||
console.log("fetching new messages...");
|
||||
var messages = getFreedataMessages();
|
||||
processFreedataMessages(messages);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data["ptt"]) {
|
||||
case true:
|
||||
case false:
|
||||
// get ptt state as a first test
|
||||
//console.warn("PTT state true")
|
||||
stateStore.ptt_state = data.ptt;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data["modem"]) {
|
||||
case "started":
|
||||
displayToast("success", "bi-arrow-left-right", "Modem started", 5000);
|
||||
loadAllData();
|
||||
return;
|
||||
|
||||
case "stopped":
|
||||
displayToast("warning", "bi-arrow-left-right", "Modem stopped", 5000);
|
||||
return;
|
||||
|
||||
case "restarted":
|
||||
displayToast("secondary", "bi-bootstrap-reboot", "Modem restarted", 5000);
|
||||
loadAllData();
|
||||
return;
|
||||
|
||||
case "failed":
|
||||
displayToast(
|
||||
"danger",
|
||||
"bi-bootstrap-reboot",
|
||||
"Modem startup failed | bad config?",
|
||||
5000,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var message = "";
|
||||
|
||||
switch (data["type"]) {
|
||||
case "hello-client":
|
||||
message = "Connected to server";
|
||||
displayToast("success", "bi-ethernet", message, 5000);
|
||||
stateStore.modem_connection = "connected";
|
||||
|
||||
loadAllData();
|
||||
|
||||
return;
|
||||
|
||||
switch (data["received"]) {
|
||||
case "PING":
|
||||
message = `Ping request from: ${data.dxcallsign}, SNR: ${data.snr}`;
|
||||
displayToast("success", "bi-check-circle", message, 5000);
|
||||
return;
|
||||
|
||||
case "PING_ACK":
|
||||
message = `Ping acknowledged from: ${data.dxcallsign}, SNR: ${data.snr}`;
|
||||
displayToast("success", "bi-check-circle", message, 5000);
|
||||
return;
|
||||
}
|
||||
|
||||
case "arq":
|
||||
if (data["arq-transfer-outbound"]) {
|
||||
switch (data["arq-transfer-outbound"].state) {
|
||||
case "NEW":
|
||||
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-outbound"].session_id}, DXCall: ${data["arq-transfer-outbound"].dxcall}, Total Bytes: ${data["arq-transfer-outbound"].total_bytes}, State: ${data["arq-transfer-outbound"].state}`;
|
||||
displayToast("success", "bi-check-circle", message, 5000);
|
||||
stateStore.dxcallsign = data["arq-transfer-outbound"].dxcall;
|
||||
stateStore.arq_transmission_percent = 0;
|
||||
stateStore.arq_total_bytes = 0;
|
||||
return;
|
||||
case "OPEN_SENT":
|
||||
console.info("state OPEN_SENT needs to be implemented");
|
||||
return;
|
||||
|
||||
case "INFO_SENT":
|
||||
console.info("state INFO_SENT needs to be implemented");
|
||||
return;
|
||||
|
||||
case "BURST_SENT":
|
||||
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-outbound"].session_id}, DXCall: ${data["arq-transfer-outbound"].dxcall}, Received Bytes: ${data["arq-transfer-outbound"].received_bytes}/${data["arq-transfer-outbound"].total_bytes}, State: ${data["arq-transfer-outbound"].state}`;
|
||||
displayToast("info", "bi-info-circle", message, 5000);
|
||||
stateStore.arq_transmission_percent =
|
||||
(data["arq-transfer-outbound"].received_bytes /
|
||||
data["arq-transfer-outbound"].total_bytes) *
|
||||
100;
|
||||
stateStore.arq_total_bytes =
|
||||
data["arq-transfer-outbound"].received_bytes;
|
||||
stateStore.arq_speed_list_timestamp =
|
||||
data["arq-transfer-outbound"].statistics.time_histogram;
|
||||
stateStore.arq_speed_list_bpm =
|
||||
data["arq-transfer-outbound"].statistics.bpm_histogram;
|
||||
stateStore.arq_speed_list_snr =
|
||||
data["arq-transfer-outbound"].statistics.snr_histogram;
|
||||
return;
|
||||
|
||||
case "ABORTING":
|
||||
console.info("state ABORTING needs to be implemented");
|
||||
return;
|
||||
|
||||
case "ABORTED":
|
||||
message = `Type: ${data.type}, Session ID: ${
|
||||
data["arq-transfer-outbound"].session_id
|
||||
}, DXCall: ${data["arq-transfer-outbound"].dxcall}, Total Bytes: ${
|
||||
data["arq-transfer-outbound"].total_bytes
|
||||
}, Success: ${
|
||||
data["arq-transfer-outbound"].success ? "Yes" : "No"
|
||||
}, State: ${data["arq-transfer-outbound"].state}, Data: ${
|
||||
data["arq-transfer-outbound"].data ? "Available" : "Not Available"
|
||||
}`;
|
||||
displayToast("warning", "bi-exclamation-triangle", message, 5000);
|
||||
return;
|
||||
|
||||
case "FAILED":
|
||||
message = `Type: ${data.type}, Session ID: ${
|
||||
data["arq-transfer-outbound"].session_id
|
||||
}, DXCall: ${data["arq-transfer-outbound"].dxcall}, Total Bytes: ${
|
||||
data["arq-transfer-outbound"].total_bytes
|
||||
}, Success: ${
|
||||
data["arq-transfer-outbound"].success ? "Yes" : "No"
|
||||
}, State: ${data["arq-transfer-outbound"].state}, Data: ${
|
||||
data["arq-transfer-outbound"].data ? "Available" : "Not Available"
|
||||
}`;
|
||||
displayToast("danger", "bi-x-octagon", message, 5000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (data["arq-transfer-inbound"]) {
|
||||
switch (data["arq-transfer-inbound"].state) {
|
||||
case "NEW":
|
||||
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-inbound"].session_id}, DXCall: ${data["arq-transfer-inbound"].dxcall}, State: ${data["arq-transfer-inbound"].state}`;
|
||||
displayToast("info", "bi-info-circle", message, 5000);
|
||||
stateStore.dxcallsign = data["arq-transfer-inbound"].dxcall;
|
||||
stateStore.arq_transmission_percent = 0;
|
||||
stateStore.arq_total_bytes = 0;
|
||||
stateStore.arq_speed_list_timestamp =
|
||||
data["arq-transfer-inbound"].statistics.time_histogram;
|
||||
stateStore.arq_speed_list_bpm =
|
||||
data["arq-transfer-inbound"].statistics.bpm_histogram;
|
||||
stateStore.arq_speed_list_snr =
|
||||
data["arq-transfer-inbound"].statistics.snr_histogram;
|
||||
|
||||
return;
|
||||
|
||||
case "OPEN_ACK_SENT":
|
||||
message = `Session ID: ${data["arq-transfer-inbound"].session_id}, DXCall: ${data["arq-transfer-inbound"].dxcall}, Total Bytes: ${data["arq-transfer-inbound"].total_bytes}, State: ${data["arq-transfer-inbound"].state}`;
|
||||
displayToast("info", "bi-arrow-left-right", message, 5000);
|
||||
stateStore.arq_transmission_percent =
|
||||
(data["arq-transfer-inbound"].received_bytes /
|
||||
data["arq-transfer-inbound"].total_bytes) *
|
||||
100;
|
||||
stateStore.arq_total_bytes =
|
||||
data["arq-transfer-inbound"].received_bytes;
|
||||
return;
|
||||
|
||||
case "INFO_ACK_SENT":
|
||||
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-inbound"].session_id}, DXCall: ${data["arq-transfer-inbound"].dxcall}, Received Bytes: ${data["arq-transfer-inbound"].received_bytes}/${data["arq-transfer-inbound"].total_bytes}, State: ${data["arq-transfer-inbound"].state}`;
|
||||
displayToast("info", "bi-info-circle", message, 5000);
|
||||
stateStore.arq_transmission_percent =
|
||||
(data["arq-transfer-inbound"].received_bytes /
|
||||
data["arq-transfer-inbound"].total_bytes) *
|
||||
100;
|
||||
stateStore.arq_total_bytes =
|
||||
data["arq-transfer-inbound"].received_bytes;
|
||||
return;
|
||||
|
||||
case "BURST_REPLY_SENT":
|
||||
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-inbound"].session_id}, DXCall: ${data["arq-transfer-inbound"].dxcall}, Received Bytes: ${data["arq-transfer-inbound"].received_bytes}/${data["arq-transfer-inbound"].total_bytes}, State: ${data["arq-transfer-inbound"].state}`;
|
||||
displayToast("info", "bi-info-circle", message, 5000);
|
||||
stateStore.arq_transmission_percent =
|
||||
(data["arq-transfer-inbound"].received_bytes /
|
||||
data["arq-transfer-inbound"].total_bytes) *
|
||||
100;
|
||||
stateStore.arq_total_bytes =
|
||||
data["arq-transfer-inbound"].received_bytes;
|
||||
return;
|
||||
|
||||
case "ENDED":
|
||||
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-inbound"].session_id}, DXCall: ${data["arq-transfer-inbound"].dxcall}, Received Bytes: ${data["arq-transfer-inbound"].received_bytes}/${data["arq-transfer-inbound"].total_bytes}, State: ${data["arq-transfer-inbound"].state}`;
|
||||
displayToast("info", "bi-info-circle", message, 5000);
|
||||
// Forward data to chat module
|
||||
newMessageReceived(
|
||||
data["arq-transfer-inbound"].data,
|
||||
data["arq-transfer-inbound"],
|
||||
);
|
||||
stateStore.arq_transmission_percent =
|
||||
(data["arq-transfer-inbound"].received_bytes /
|
||||
data["arq-transfer-inbound"].total_bytes) *
|
||||
100;
|
||||
stateStore.arq_total_bytes =
|
||||
data["arq-transfer-inbound"].received_bytes;
|
||||
return;
|
||||
|
||||
case "ABORTED":
|
||||
console.info("state ABORTED needs to be implemented");
|
||||
return;
|
||||
|
||||
case "FAILED":
|
||||
message = `Type: ${data.type}, Session ID: ${data["arq-transfer-outbound"].session_id}, DXCall: ${data["arq-transfer-outbound"].dxcall}, Received Bytes: ${data["arq-transfer-outbound"].received_bytes}/${data["arq-transfer-outbound"].total_bytes}, State: ${data["arq-transfer-outbound"].state}`;
|
||||
displayToast("info", "bi-info-circle", message, 5000);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function build_HSL() {
|
||||
//Use data from activities to build HSL list
|
||||
for (let i = 0; i < stateStore.activities.length; i++) {
|
||||
if (
|
||||
stateStore.activities[i][1].direction != "received" ||
|
||||
stateStore.activities[i][1].origin == undefined
|
||||
) {
|
||||
//Ignore stations without origin and not received type
|
||||
//console.warn("HSL: Ignoring " + stateStore.activities[i][0]);
|
||||
continue;
|
||||
}
|
||||
let found = false;
|
||||
for (let ii = 0; ii < stateStore.heard_stations.length; ii++) {
|
||||
if (
|
||||
stateStore.heard_stations[ii].origin ==
|
||||
stateStore.activities[i][1].origin
|
||||
) {
|
||||
//Station already in HSL, check if newer than one in HSL
|
||||
found = true;
|
||||
if (
|
||||
stateStore.heard_stations[ii].timestamp <
|
||||
stateStore.activities[i][1].timestamp
|
||||
) {
|
||||
//Update existing entry in HSL
|
||||
stateStore.heard_stations[ii] = stateStore.activities[i][1];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found == false) {
|
||||
//Station not in HSL, let us add it
|
||||
stateStore.heard_stations.push(stateStore.activities[i][1]);
|
||||
}
|
||||
}
|
||||
stateStore.heard_stations.sort((a, b) => b.timestamp - a.timestamp); // b - a for reverse sort
|
||||
}
|
||||
|
||||
export function getOverallHealth() {
|
||||
//Return a number indicating health for icon bg color; lower the number the healthier
|
||||
let health = 0;
|
||||
if (stateStore.modem_connection !== "connected") {
|
||||
health += 5;
|
||||
stateStore.is_modem_running = false;
|
||||
stateStore.radio_status = false;
|
||||
}
|
||||
if (!stateStore.is_modem_running) health += 3;
|
||||
if (stateStore.radio_status === false) health += 2;
|
||||
if (process.env.FDUpdateAvail === "1") health += 1;
|
||||
return health;
|
||||
}
|
56
gui/src/js/event_sock.js
Normal file
56
gui/src/js/event_sock.js
Normal file
|
@ -0,0 +1,56 @@
|
|||
import {
|
||||
eventDispatcher,
|
||||
stateDispatcher,
|
||||
connectionFailed,
|
||||
} from "../js/eventHandler.js";
|
||||
import { addDataToWaterfall } from "../js/waterfallHandler.js";
|
||||
|
||||
// ----------------- init pinia stores -------------
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
|
||||
function connect(endpoint, dispatcher) {
|
||||
let socket = new WebSocket(
|
||||
"ws://" + settings.local.host + ":" + settings.local.port + "/" + endpoint,
|
||||
);
|
||||
|
||||
// handle opening
|
||||
socket.addEventListener("open", function (event) {
|
||||
//console.log("Connected to the WebSocket server: " + endpoint);
|
||||
});
|
||||
|
||||
// handle data
|
||||
socket.addEventListener("message", function (event) {
|
||||
dispatcher(event.data);
|
||||
return;
|
||||
});
|
||||
|
||||
// handle errors
|
||||
socket.addEventListener("error", function (event) {
|
||||
//console.error("WebSocket error:", event);
|
||||
connectionFailed(endpoint, event);
|
||||
});
|
||||
|
||||
// handle closing and reconnect
|
||||
socket.addEventListener("close", function (event) {
|
||||
//console.log("WebSocket connection closed:", event.code);
|
||||
|
||||
// Reconnect handler
|
||||
if (!event.wasClean) {
|
||||
setTimeout(() => {
|
||||
//console.log("Reconnecting to websocket");
|
||||
connect(endpoint, dispatcher);
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initial connection attempts to endpoints
|
||||
export function initConnections() {
|
||||
connect("states", stateDispatcher);
|
||||
connect("events", eventDispatcher);
|
||||
connect("fft", addDataToWaterfall);
|
||||
}
|
|
@ -1,3 +1,6 @@
|
|||
const os = require("os");
|
||||
const path = require("path");
|
||||
|
||||
/**
|
||||
* Binary to ASCII replacement
|
||||
* @param {string} data in normal/usual utf-8 format
|
||||
|
@ -25,3 +28,103 @@ export function atob(data) {
|
|||
//exports.atob = function (data) {
|
||||
return window.btoa(Buffer.from(data, "base64").toString("utf8"));
|
||||
}
|
||||
//https://medium.com/@asadise/sorting-a-json-array-according-one-property-in-javascript-18b1d22cd9e9
|
||||
/**
|
||||
* Sort a json collection by a property ascending
|
||||
* @param {string} property property to sort on
|
||||
* @returns sorted json collection
|
||||
*/
|
||||
export function sortByProperty(property) {
|
||||
return function (a, b) {
|
||||
if (a[property] > b[property]) return 1;
|
||||
else if (a[property] < b[property]) return -1;
|
||||
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
//https://medium.com/@asadise/sorting-a-json-array-according-one-property-in-javascript-18b1d22cd9e9
|
||||
/**
|
||||
* Sort a json collection by a property descending
|
||||
* @param {string} property property to sort on
|
||||
* @returns sorted json collection
|
||||
*/
|
||||
export function sortByPropertyDesc(property) {
|
||||
return function (a, b) {
|
||||
if (a[property] < b[property]) return 1;
|
||||
else if (a[property] > b[property]) return -1;
|
||||
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a call sign with ssid
|
||||
* @param {string} callsign callsign to check
|
||||
* @returns true or false if callsign appears to be valid with an SSID
|
||||
*/
|
||||
export function validateCallsignWithSSID(callsign: string) {
|
||||
var patt = new RegExp("^[A-Za-z0-9]{1,7}-[0-9]{1,3}$");
|
||||
callsign = callsign;
|
||||
if (
|
||||
callsign === undefined ||
|
||||
callsign === "" ||
|
||||
patt.test(callsign) === false
|
||||
) {
|
||||
console.error(
|
||||
"Call sign given is not in correct format or missing; callsign passed is: " +
|
||||
callsign,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Validate/check if a call sign has an SSID
|
||||
* @param {string} callsign callsign to check
|
||||
* @returns true or false if callsign appears to be valid without an SSID
|
||||
*/
|
||||
export function validateCallsignWithoutSSID(callsign: string) {
|
||||
var patt = new RegExp("^[A-Za-z0-9]{1,7}$");
|
||||
|
||||
if (
|
||||
callsign === undefined ||
|
||||
callsign === "" ||
|
||||
patt.test(callsign) === false
|
||||
) {
|
||||
console.error(
|
||||
"Call sign given is not in correct format or missing; callsign passed is: " +
|
||||
callsign,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getAppDataPath() {
|
||||
const platform = os.platform();
|
||||
let appDataPath;
|
||||
|
||||
// Check if running in GitHub Actions
|
||||
const isGitHubActions = process.env.GITHUB_ACTIONS === "true";
|
||||
if (isGitHubActions) {
|
||||
return "/home/runner/work/FreeDATA/FreeDATA/gui/config";
|
||||
}
|
||||
|
||||
switch (platform) {
|
||||
case "darwin": // macOS
|
||||
appDataPath = path.join(os.homedir(), "Library", "Application Support");
|
||||
break;
|
||||
case "win32": // Windows
|
||||
appDataPath =
|
||||
process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
||||
break;
|
||||
case "linux": // Linux
|
||||
appDataPath = path.join(os.homedir(), ".config");
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unsupported platform");
|
||||
}
|
||||
|
||||
return appDataPath;
|
||||
}
|
||||
|
|
110
gui/src/js/messagesHandler.ts
Normal file
110
gui/src/js/messagesHandler.ts
Normal file
|
@ -0,0 +1,110 @@
|
|||
// pinia store setup
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useChatStore } from "../store/chatStore.js";
|
||||
const chatStore = useChatStore(pinia);
|
||||
|
||||
import {
|
||||
sendFreedataMessage,
|
||||
deleteFreedataMessage,
|
||||
retransmitFreedataMessage,
|
||||
getFreedataAttachmentBySha512,
|
||||
} from "./api";
|
||||
|
||||
interface Message {
|
||||
id: string;
|
||||
timestamp: string;
|
||||
origin: string;
|
||||
destination: string;
|
||||
direction: string;
|
||||
body: string;
|
||||
attachments: any[];
|
||||
status: any;
|
||||
statistics: any;
|
||||
}
|
||||
|
||||
export async function processFreedataMessages(data) {
|
||||
if (
|
||||
typeof data !== "undefined" &&
|
||||
typeof data.messages !== "undefined" &&
|
||||
Array.isArray(data.messages)
|
||||
) {
|
||||
chatStore.callsign_list = createCallsignListFromAPI(data);
|
||||
chatStore.sorted_chat_list = createSortedMessagesList(data);
|
||||
}
|
||||
}
|
||||
|
||||
function createCallsignListFromAPI(data: {
|
||||
total_messages: number;
|
||||
messages: Message[];
|
||||
}): { [key: string]: { timestamp: string; body: string } } {
|
||||
const callsignList: { [key: string]: { timestamp: string; body: string } } =
|
||||
{};
|
||||
|
||||
data.messages.forEach((message) => {
|
||||
let callsign =
|
||||
message.direction === "receive" ? message.origin : message.destination;
|
||||
|
||||
if (
|
||||
!callsignList[callsign] ||
|
||||
callsignList[callsign].timestamp < message.timestamp
|
||||
) {
|
||||
callsignList[callsign] = {
|
||||
timestamp: message.timestamp,
|
||||
body: message.body,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return callsignList;
|
||||
}
|
||||
|
||||
function createSortedMessagesList(data: {
|
||||
total_messages: number;
|
||||
messages: Message[];
|
||||
}): { [key: string]: Message[] } {
|
||||
const callsignMessages: { [key: string]: Message[] } = {};
|
||||
|
||||
data.messages.forEach((message) => {
|
||||
let callsign =
|
||||
message.direction === "receive" ? message.origin : message.destination;
|
||||
|
||||
if (!callsignMessages[callsign]) {
|
||||
callsignMessages[callsign] = [];
|
||||
}
|
||||
|
||||
callsignMessages[callsign].push(message);
|
||||
});
|
||||
|
||||
return callsignMessages;
|
||||
}
|
||||
|
||||
export function newMessage(dxcall, body, attachments) {
|
||||
sendFreedataMessage(dxcall, body, attachments);
|
||||
chatStore.triggerScrollToBottom();
|
||||
}
|
||||
|
||||
/* ------ TEMPORARY DUMMY FUNCTIONS --- */
|
||||
export function repeatMessageTransmission(id) {
|
||||
retransmitFreedataMessage(id);
|
||||
}
|
||||
|
||||
export function deleteCallsignFromDB(callsign) {
|
||||
for (var message of chatStore.sorted_chat_list[callsign]) {
|
||||
deleteFreedataMessage(message["id"]);
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteMessageFromDB(id) {
|
||||
deleteFreedataMessage(id);
|
||||
}
|
||||
|
||||
export function requestMessageInfo(id) {
|
||||
return;
|
||||
}
|
||||
|
||||
export async function getMessageAttachment(data_sha512) {
|
||||
return await getFreedataAttachmentBySha512(data_sha512);
|
||||
}
|
20
gui/src/js/radioHandler.ts
Normal file
20
gui/src/js/radioHandler.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
// pinia store setup
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
|
||||
import { useStateStore } from "../store/stateStore";
|
||||
const stateStore = useStateStore(pinia);
|
||||
|
||||
import {
|
||||
getRadioStatus,
|
||||
} from "./api";
|
||||
|
||||
export async function processRadioStatus(){
|
||||
let result = await getRadioStatus()
|
||||
stateStore.mode = result.radio_mode
|
||||
stateStore.frequency = result.radio_frequency
|
||||
stateStore.rf_level = result.radio_rf_level
|
||||
}
|
|
@ -1,13 +1,16 @@
|
|||
import path from "node:path";
|
||||
import fs from "fs";
|
||||
|
||||
import { setColormap } from "./waterfallHandler";
|
||||
// pinia store setup
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
import { settingsStore as settings, onChange } from "../store/settingsStore.js";
|
||||
|
||||
import { useStateStore } from "../store/stateStore";
|
||||
const stateStore = useStateStore(pinia);
|
||||
|
||||
// ---------------------------------
|
||||
|
||||
console.log(process.env);
|
||||
|
@ -47,67 +50,18 @@ if (!fs.existsSync(configFolder)) {
|
|||
}
|
||||
|
||||
// create config file if not exists with defaults
|
||||
const configDefaultSettings =
|
||||
'{\
|
||||
"modem_host": "127.0.0.1",\
|
||||
"modem_port": 3000,\
|
||||
"daemon_host": "127.0.0.1",\
|
||||
"daemon_port": 3001,\
|
||||
"rx_audio" : "",\
|
||||
"tx_audio" : "",\
|
||||
"mycall": "AA0AA-0",\
|
||||
"myssid": "0",\
|
||||
"mygrid": "JN40aa",\
|
||||
"radiocontrol" : "disabled",\
|
||||
"hamlib_deviceid": 6,\
|
||||
"hamlib_deviceport": "ignore",\
|
||||
"hamlib_stop_bits": "ignore",\
|
||||
"hamlib_data_bits": "ignore",\
|
||||
"hamlib_handshake": "ignore",\
|
||||
"hamlib_serialspeed": "ignore",\
|
||||
"hamlib_dtrstate": "ignore",\
|
||||
"hamlib_pttprotocol": "ignore",\
|
||||
"hamlib_ptt_port": "ignore",\
|
||||
"hamlib_dcd": "ignore",\
|
||||
"hamlbib_serialspeed_ptt": 9600,\
|
||||
"hamlib_rigctld_port" : 4532,\
|
||||
"hamlib_rigctld_ip" : "127.0.0.1",\
|
||||
"hamlib_rigctld_path" : "",\
|
||||
"hamlib_rigctld_server_port" : 4532,\
|
||||
"hamlib_rigctld_custom_args": "",\
|
||||
"tci_port" : 50001,\
|
||||
"tci_ip" : "127.0.0.1",\
|
||||
"spectrum": "waterfall",\
|
||||
"enable_scatter" : "False",\
|
||||
"enable_fft" : "False",\
|
||||
"enable_fsk" : "False",\
|
||||
"low_bandwidth_mode" : "False",\
|
||||
"theme" : "default",\
|
||||
"screen_height" : 430,\
|
||||
"screen_width" : 1050,\
|
||||
"update_channel" : "latest",\
|
||||
"beacon_interval" : 300,\
|
||||
"received_files_folder" : "None",\
|
||||
"tuning_range_fmin" : "-50.0",\
|
||||
"tuning_range_fmax" : "50.0",\
|
||||
"respond_to_cq" : "True",\
|
||||
"rx_buffer_size" : 16, \
|
||||
"enable_explorer" : "False", \
|
||||
"wftheme": 2, \
|
||||
"high_graphics" : "True",\
|
||||
"explorer_stats" : "False", \
|
||||
"auto_tune" : "False", \
|
||||
"enable_is_writing" : "True", \
|
||||
"shared_folder_path" : ".", \
|
||||
"enable_request_profile" : "True", \
|
||||
"enable_request_shared_folder" : "False", \
|
||||
"max_retry_attempts" : 5, \
|
||||
"enable_auto_retry" : "False", \
|
||||
"tx_delay" : 0, \
|
||||
"auto_start": 0, \
|
||||
"enable_sys_notification": 1, \
|
||||
"enable_mesh_features": "False" \
|
||||
}';
|
||||
const configDefaultSettings = `{
|
||||
"modem_host": "127.0.0.1",
|
||||
"modem_port": 5000,
|
||||
"spectrum": "waterfall",
|
||||
"theme": "default",
|
||||
"screen_height": 430,
|
||||
"screen_width": 1050,
|
||||
"update_channel": "latest",
|
||||
"wftheme": 2,
|
||||
"enable_sys_notification": 1
|
||||
}`;
|
||||
var parsedConfig = JSON.parse(configDefaultSettings);
|
||||
|
||||
if (!fs.existsSync(configPath)) {
|
||||
fs.writeFileSync(configPath, configDefaultSettings);
|
||||
|
@ -122,7 +76,6 @@ export function loadSettings() {
|
|||
// if parameter not exists, add it to running config to prevent errors
|
||||
console.log("CONFIG VALIDATION ----------------------------- ");
|
||||
|
||||
var parsedConfig = JSON.parse(configDefaultSettings);
|
||||
for (var key in parsedConfig) {
|
||||
if (config.hasOwnProperty(key)) {
|
||||
console.log("FOUND SETTTING [" + key + "]: " + config[key]);
|
||||
|
@ -132,9 +85,12 @@ export function loadSettings() {
|
|||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
}
|
||||
try {
|
||||
if (key == "wftheme") {
|
||||
setColormap();
|
||||
}
|
||||
if (key == "mycall") {
|
||||
settings.mycall = config[key].split("-")[0];
|
||||
settings.myssid = config[key].split("-")[1];
|
||||
settings.remote.STATION.mycall = config[key].split("-")[0];
|
||||
settings.remote.STATION.myssid = config[key].split("-")[1];
|
||||
} else {
|
||||
settings[key] = config[key];
|
||||
}
|
||||
|
@ -144,9 +100,46 @@ export function loadSettings() {
|
|||
}
|
||||
}
|
||||
|
||||
export function saveSettingsToFile() {
|
||||
console.log("save settings to file...");
|
||||
let config = settings.getJSON();
|
||||
console.log(config);
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
//No longer used...
|
||||
//export function saveSettingsToFile() {
|
||||
// console.log("save settings to file...");
|
||||
// let config = settings.getJSON();
|
||||
// console.log(config);
|
||||
// fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
//}
|
||||
|
||||
export function processModemConfig(data) {
|
||||
// update our settings from get request
|
||||
// TODO Can we make this more dynamic? Maybe using a settings object?
|
||||
// For now its a hardcoded structure until we found a better way
|
||||
|
||||
console.log(data);
|
||||
for (const category in data) {
|
||||
if (data.hasOwnProperty(category)) {
|
||||
for (const setting in data[category]) {
|
||||
if (data[category].hasOwnProperty(setting)) {
|
||||
// Create a variable name combining the category and setting name
|
||||
const variableName = setting;
|
||||
// Assign the value to the variable
|
||||
if (variableName == "mycall") {
|
||||
let mycall = data[category][setting];
|
||||
if (mycall.includes("-")) {
|
||||
const splittedCallsign = mycall.split("-");
|
||||
settings.remote.STATION.mycall = splittedCallsign[0]; // The part before the hyphen
|
||||
settings.remote.STATION.myssid = parseInt(
|
||||
splittedCallsign[1],
|
||||
10,
|
||||
); // The part after the hyphen, converted to a number
|
||||
} else {
|
||||
settings.remote.STATION.mycall = mycall; // Use the original mycall if no SSID is present
|
||||
settings.remote.STATION.myssid = 0; // Default SSID if not provided
|
||||
}
|
||||
} else {
|
||||
settings[variableName] = data[category][setting];
|
||||
}
|
||||
console.log(variableName + ": " + settings[variableName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,870 +0,0 @@
|
|||
var net = require("net");
|
||||
import { atob_FD, btoa_FD } from "./freedata";
|
||||
import { addDataToWaterfall } from "../js/waterfallHandler.js";
|
||||
|
||||
import {
|
||||
newMessageReceived,
|
||||
newBeaconReceived,
|
||||
updateTransmissionStatus,
|
||||
setStateSuccess,
|
||||
setStateFailed,
|
||||
} from "./chatHandler";
|
||||
import { displayToast } from "./popupHandler";
|
||||
|
||||
// ----------------- init pinia stores -------------
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
import { useStateStore } from "../store/stateStore.js";
|
||||
const stateStore = useStateStore(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
|
||||
var client = new net.Socket();
|
||||
var socketchunk = ""; // Current message, per connection.
|
||||
|
||||
// split character
|
||||
//const split_char = "\0;\1;";
|
||||
const split_char = "0;1;";
|
||||
|
||||
// global to keep track of Modem connection error emissions
|
||||
var modemShowConnectStateError = 1;
|
||||
|
||||
// network connection Timeout
|
||||
setTimeout(connectModem, 2000);
|
||||
|
||||
function connectModem() {
|
||||
//exports.connectModem = function(){
|
||||
//console.log('connecting to Modem...')
|
||||
|
||||
//clear message buffer after reconnecting or initial connection
|
||||
socketchunk = "";
|
||||
|
||||
client.connect(settings.modem_port, settings.modem_host);
|
||||
}
|
||||
|
||||
client.on("connect", function () {
|
||||
console.log("Modem connection established");
|
||||
|
||||
stateStore.busy_state = "-";
|
||||
stateStore.arq_state = "-";
|
||||
stateStore.frequency = "-";
|
||||
stateStore.mode = "-";
|
||||
stateStore.bandwidth = "-";
|
||||
stateStore.dbfs_level = 0;
|
||||
stateStore.updateTncState(client.readyState);
|
||||
|
||||
modemShowConnectStateError = 1;
|
||||
});
|
||||
|
||||
client.on("error", function (err) {
|
||||
if (modemShowConnectStateError == 1) {
|
||||
console.log("Modem connection error");
|
||||
console.log(err);
|
||||
modemShowConnectStateError = 0;
|
||||
}
|
||||
setTimeout(connectModem, 500);
|
||||
client.destroy();
|
||||
stateStore.busy_state = "-";
|
||||
stateStore.arq_state = "-";
|
||||
stateStore.frequency = "-";
|
||||
stateStore.mode = "-";
|
||||
stateStore.bandwidth = "-";
|
||||
stateStore.dbfs_level = 0;
|
||||
stateStore.updateTncState(client.readyState);
|
||||
});
|
||||
|
||||
/*
|
||||
client.on('close', function(data) {
|
||||
console.log(' Modem connection closed');
|
||||
setTimeout(connectModem, 2000)
|
||||
});
|
||||
*/
|
||||
|
||||
client.on("end", function (data) {
|
||||
console.log("Modem connection ended");
|
||||
console.log(data);
|
||||
stateStore.busy_state = "-";
|
||||
stateStore.arq_state = "-";
|
||||
stateStore.frequency = "-";
|
||||
stateStore.mode = "-";
|
||||
stateStore.bandwidth = "-";
|
||||
stateStore.dbfs_level = 0;
|
||||
stateStore.updateTncState(client.readyState);
|
||||
client.destroy();
|
||||
|
||||
setTimeout(connectModem, 500);
|
||||
});
|
||||
|
||||
function writeTncCommand(command) {
|
||||
console.log(command);
|
||||
// we use the writingCommand function to update our TCPIP state because we are calling this function a lot
|
||||
// if socket opened, we are able to run commands
|
||||
|
||||
if (client.readyState == "open") {
|
||||
client.write(command + "\n");
|
||||
}
|
||||
|
||||
if (client.readyState == "closed") {
|
||||
console.log("Modem SOCKET CONNECTION CLOSED!");
|
||||
}
|
||||
|
||||
if (client.readyState == "opening") {
|
||||
console.log("connecting to Modem...");
|
||||
}
|
||||
}
|
||||
|
||||
client.on("data", function (socketdata) {
|
||||
stateStore.updateTncState(client.readyState);
|
||||
|
||||
/*
|
||||
inspired by:
|
||||
stackoverflow.com questions 9070700 nodejs-net-createserver-large-amount-of-data-coming-in
|
||||
*/
|
||||
|
||||
socketdata = socketdata.toString("utf8"); // convert data to string
|
||||
socketchunk += socketdata; // append data to buffer so we can stick long data together
|
||||
|
||||
// check if we received begin and end of json data
|
||||
if (socketchunk.startsWith('{"') && socketchunk.endsWith('"}\n')) {
|
||||
var data = "";
|
||||
|
||||
// split data into chunks if we received multiple commands
|
||||
socketchunk = socketchunk.split("\n");
|
||||
//don't think this is needed anymore
|
||||
//data = JSON.parse(socketchunk[0])
|
||||
|
||||
// search for empty entries in socketchunk and remove them
|
||||
for (let i = 0; i < socketchunk.length; i++) {
|
||||
if (socketchunk[i] === "") {
|
||||
socketchunk.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
//iterate through socketchunks array to execute multiple commands in row
|
||||
for (let i = 0; i < socketchunk.length; i++) {
|
||||
//check if data is not empty
|
||||
if (socketchunk[i].length > 0) {
|
||||
//try to parse JSON
|
||||
try {
|
||||
data = JSON.parse(socketchunk[i]);
|
||||
} catch (e) {
|
||||
console.log("Throwing away data!!!!\n" + e); // "SyntaxError
|
||||
//console.log(e); // "SyntaxError
|
||||
console.log(socketchunk[i]);
|
||||
socketchunk = "";
|
||||
//If we're here, I don't think we want to process any data that may be in data variable
|
||||
continue;
|
||||
}
|
||||
}
|
||||
//console.log(data)
|
||||
if (data["command"] == "modem_state") {
|
||||
//console.log(data)
|
||||
|
||||
stateStore.rx_buffer_length = data["rx_buffer_length"];
|
||||
stateStore.frequency = data["frequency"];
|
||||
stateStore.busy_state = data["modem_state"];
|
||||
stateStore.arq_state = data["arq_state"];
|
||||
stateStore.mode = data["mode"];
|
||||
stateStore.bandwidth = data["bandwidth"];
|
||||
stateStore.dbfs_level = data["audio_dbfs"];
|
||||
stateStore.ptt_state = data["ptt_state"];
|
||||
stateStore.speed_level = data["speed_level"];
|
||||
stateStore.fft = JSON.parse(data["fft"]);
|
||||
stateStore.channel_busy = data["channel_busy"];
|
||||
stateStore.channel_busy_slot = data["channel_busy_slot"];
|
||||
|
||||
addDataToWaterfall(JSON.parse(data["fft"]));
|
||||
|
||||
if (data["scatter"].length > 0) {
|
||||
stateStore.scatter = data["scatter"];
|
||||
}
|
||||
// s meter strength
|
||||
stateStore.s_meter_strength_raw = data["strength"];
|
||||
if (stateStore.s_meter_strength_raw == "") {
|
||||
stateStore.s_meter_strength_raw = "Unsupported";
|
||||
stateStore.s_meter_strength_percent = 0;
|
||||
} else {
|
||||
// https://www.moellerstudios.org/converting-amplitude-representations/
|
||||
stateStore.s_meter_strength_percent = Math.round(
|
||||
Math.pow(10, stateStore.s_meter_strength_raw / 20) * 100,
|
||||
);
|
||||
}
|
||||
|
||||
stateStore.dbfs_level_percent = Math.round(
|
||||
Math.pow(10, stateStore.dbfs_level / 20) * 100,
|
||||
);
|
||||
stateStore.dbfs_level = Math.round(stateStore.dbfs_level);
|
||||
|
||||
stateStore.arq_total_bytes = data["total_bytes"];
|
||||
stateStore.heard_stations = data["stations"];
|
||||
stateStore.dxcallsign = data["dxcallsign"];
|
||||
|
||||
stateStore.beacon_state = data["beacon_state"];
|
||||
stateStore.audio_recording = data["audio_recording"];
|
||||
|
||||
stateStore.hamlib_status = data["hamlib_status"];
|
||||
stateStore.alc = data["alc"];
|
||||
stateStore.rf_level = data["rf_level"];
|
||||
|
||||
stateStore.is_codec2_traffic = data["is_codec2_traffic"];
|
||||
|
||||
stateStore.arq_session_state = data["arq_session"];
|
||||
stateStore.arq_state = data["arq_state"];
|
||||
stateStore.arq_transmission_percent = data["arq_transmission_percent"];
|
||||
stateStore.arq_seconds_until_finish = data["arq_seconds_until_finish"];
|
||||
stateStore.arq_seconds_until_timeout =
|
||||
data["arq_seconds_until_timeout"];
|
||||
stateStore.arq_seconds_until_timeout_percent =
|
||||
(stateStore.arq_seconds_until_timeout / 180) * 100;
|
||||
|
||||
if (data["speed_list"].length > 0) {
|
||||
prepareStatsDataForStore(data["speed_list"]);
|
||||
}
|
||||
|
||||
// TODO: Remove ported objects
|
||||
/*
|
||||
let Data = {
|
||||
mycallsign: data["mycallsign"],
|
||||
mygrid: data["mygrid"],
|
||||
//channel_state: data['CHANNEL_STATE'],
|
||||
|
||||
info: data["info"],
|
||||
rx_msg_buffer_length: data["rx_msg_buffer_length"],
|
||||
tx_n_max_retries: data["tx_n_max_retries"],
|
||||
arq_tx_n_frames_per_burst: data["arq_tx_n_frames_per_burst"],
|
||||
arq_tx_n_bursts: data["arq_tx_n_bursts"],
|
||||
arq_tx_n_current_arq_frame: data["arq_tx_n_current_arq_frame"],
|
||||
arq_tx_n_total_arq_frames: data["arq_tx_n_total_arq_frames"],
|
||||
arq_rx_frame_n_bursts: data["arq_rx_frame_n_bursts"],
|
||||
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_bytes_per_minute: data["arq_bytes_per_minute"],
|
||||
arq_compression_factor: data["arq_compression_factor"],
|
||||
routing_table: data["routing_table"],
|
||||
mesh_signalling_table: data["mesh_signalling_table"],
|
||||
listen: data["listen"],
|
||||
//speed_table: [{"bpm" : 5200, "snr": -3, "timestamp":1673555399},{"bpm" : 2315, "snr": 12, "timestamp":1673555500}],
|
||||
};
|
||||
*/
|
||||
//continue to next for loop iteration, nothing else needs to be done here
|
||||
continue;
|
||||
}
|
||||
|
||||
// ----------- catch modem messages START -----------
|
||||
//init message variable
|
||||
var message = "";
|
||||
if (data["freedata"] == "modem-message") {
|
||||
// break early if we received a dummy callsign
|
||||
// thats a kind of hotfix, as long as the modem isnt handling this better
|
||||
if (
|
||||
data["dxcallsign"] == "AA0AA-0" ||
|
||||
data["dxcallsign"] == "ZZ9YY-0"
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
console.log(data);
|
||||
|
||||
switch (data["fec"]) {
|
||||
case "is_writing":
|
||||
// RX'd FECiswriting
|
||||
break;
|
||||
|
||||
case "broadcast":
|
||||
// RX'd FEC BROADCAST
|
||||
var encoded_data = atob_FD(data["data"]);
|
||||
var splitted_data = encoded_data.split(split_char);
|
||||
var messageArray = [];
|
||||
if (splitted_data[0] == "m") {
|
||||
messageArray.push(data);
|
||||
console.log(data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["cq"]) {
|
||||
case "transmitting":
|
||||
// CQ TRANSMITTING
|
||||
displayToast(
|
||||
"success",
|
||||
"bi-arrow-left-right",
|
||||
"Transmitting CQ",
|
||||
5000,
|
||||
);
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// CQ RECEIVED
|
||||
message = "CQ from " + data["dxcallsign"];
|
||||
displayToast("success", "bi-person-arms-up", message, 5000);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["qrv"]) {
|
||||
case "transmitting":
|
||||
// QRV TRANSMITTING
|
||||
displayToast(
|
||||
"info",
|
||||
"bi-person-raised-hand",
|
||||
"Transmitting QRV ",
|
||||
5000,
|
||||
);
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// QRV RECEIVED
|
||||
message = "QRV from " + data["dxcallsign"] + " | " + data["dxgrid"];
|
||||
displayToast("success", "bi-person-raised-hand", message, 5000);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["beacon"]) {
|
||||
case "transmitting":
|
||||
// BEACON TRANSMITTING
|
||||
displayToast(
|
||||
"success",
|
||||
"bi-broadcast-pin",
|
||||
"Transmitting beacon",
|
||||
5000,
|
||||
);
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// BEACON RECEIVED
|
||||
newBeaconReceived(data);
|
||||
|
||||
message =
|
||||
"Beacon from " + data["dxcallsign"] + " | " + data["dxgrid"];
|
||||
displayToast("info", "bi-broadcast", message, 5000);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (data["ping"]) {
|
||||
case "transmitting":
|
||||
// PING TRANSMITTING
|
||||
message = "Sending ping to " + data["dxcallsign"];
|
||||
displayToast("success", "bi-arrow-right", message, 5000);
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// PING RECEIVED
|
||||
message =
|
||||
"Ping request from " +
|
||||
data["dxcallsign"] +
|
||||
" | " +
|
||||
data["dxgrid"];
|
||||
displayToast("success", "bi-arrow-right-short", message, 5000);
|
||||
break;
|
||||
|
||||
case "acknowledge":
|
||||
// PING ACKNOWLEDGE
|
||||
message =
|
||||
"Received ping-ack from " +
|
||||
data["dxcallsign"] +
|
||||
" | " +
|
||||
data["dxgrid"];
|
||||
displayToast("success", "bi-arrow-left-right", message, 5000);
|
||||
break;
|
||||
}
|
||||
|
||||
// ARQ SESSION && freedata == modem-message
|
||||
if (data["arq"] == "session") {
|
||||
switch (data["status"]) {
|
||||
case "connecting":
|
||||
// ARQ Open
|
||||
break;
|
||||
|
||||
case "connected":
|
||||
// ARQ Opening
|
||||
break;
|
||||
|
||||
case "waiting":
|
||||
// ARQ Opening
|
||||
break;
|
||||
|
||||
case "close":
|
||||
// ARQ Closing
|
||||
break;
|
||||
|
||||
case "failed":
|
||||
// ARQ Failed
|
||||
break;
|
||||
}
|
||||
}
|
||||
// ARQ TRANSMISSION && freedata == modem-message
|
||||
if (data["arq"] == "transmission") {
|
||||
switch (data["status"]) {
|
||||
case "opened":
|
||||
// ARQ Open
|
||||
message = "ARQ session opened: " + data["dxcallsign"];
|
||||
displayToast("success", "bi-arrow-left-right", message, 5000);
|
||||
break;
|
||||
|
||||
case "opening":
|
||||
// ARQ Opening IRS/ISS
|
||||
if (data["irs"] == "False") {
|
||||
message = "ARQ session opening: " + data["dxcallsign"];
|
||||
displayToast("info", "bi-arrow-left-right", message, 5000);
|
||||
break;
|
||||
} else {
|
||||
message = "ARQ sesson request from: " + data["dxcallsign"];
|
||||
displayToast("success", "bi-arrow-left-right", message, 5000);
|
||||
break;
|
||||
}
|
||||
|
||||
case "waiting":
|
||||
// ARQ waiting
|
||||
message = "Channel busy | ARQ protocol is waiting";
|
||||
displayToast("warning", "bi-hourglass-split", message, 5000);
|
||||
break;
|
||||
|
||||
case "receiving":
|
||||
// ARQ RX
|
||||
break;
|
||||
|
||||
case "failed":
|
||||
// ARQ TX Failed
|
||||
if (data["reason"] == "protocol version missmatch") {
|
||||
message = "Protocol version mismatch!";
|
||||
displayToast("danger", "bi-chevron-bar-expand", message, 5000);
|
||||
setStateFailed();
|
||||
|
||||
break;
|
||||
} else {
|
||||
message = "Transmission failed";
|
||||
displayToast("danger", "bi-x-octagon", message, 5000);
|
||||
updateTransmissionStatus(data);
|
||||
setStateFailed();
|
||||
break;
|
||||
}
|
||||
switch (data["irs"]) {
|
||||
case "True":
|
||||
updateTransmissionStatus(data);
|
||||
|
||||
break;
|
||||
default:
|
||||
updateTransmissionStatus(data);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case "received":
|
||||
// ARQ data received
|
||||
|
||||
console.log(data);
|
||||
// we need to encode here to do a deep check for checking if file or message
|
||||
//var encoded_data = atob(data['data'])
|
||||
var encoded_data = atob_FD(data["data"]);
|
||||
var splitted_data = encoded_data.split(split_char);
|
||||
|
||||
// new message received
|
||||
if (splitted_data[0] == "m") {
|
||||
console.log(splitted_data);
|
||||
newMessageReceived(splitted_data, data);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case "transmitting":
|
||||
// ARQ transmitting
|
||||
updateTransmissionStatus(data);
|
||||
break;
|
||||
|
||||
case "transmitted":
|
||||
// ARQ transmitted
|
||||
message = "Data transmitted";
|
||||
displayToast("success", "bi-check-sqaure", message, 5000);
|
||||
updateTransmissionStatus(data);
|
||||
setStateSuccess();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//finally delete message buffer
|
||||
socketchunk = "";
|
||||
}
|
||||
});
|
||||
|
||||
// Send Ping
|
||||
//exports.sendPing = function (dxcallsign) {
|
||||
export function sendPing(dxcallsign) {
|
||||
var command =
|
||||
'{"type" : "ping", "command" : "ping", "dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// Send Mesh Ping
|
||||
//exports.sendMeshPing = function (dxcallsign) {
|
||||
function sendMeshPing(dxcallsign) {
|
||||
var command =
|
||||
'{"type" : "mesh", "command" : "ping", "dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// Send CQ
|
||||
//exports.sendCQ = function () {
|
||||
export function sendCQ() {
|
||||
var command = '{"type" : "broadcast", "command" : "cqcqcq"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// Set AUDIO Level
|
||||
export function setTxAudioLevel(value) {
|
||||
var command =
|
||||
'{"type" : "set", "command" : "tx_audio_level", "value" : "' + value + '"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// Send Message
|
||||
export function sendMessage(obj) {
|
||||
let dxcallsign = obj.dxcallsign;
|
||||
let checksum = obj.checksum;
|
||||
let uuid = obj.uuid;
|
||||
let command = obj.command;
|
||||
|
||||
let filename = Object.keys(obj._attachments)[0];
|
||||
//let filetype = filename.split(".")[1]
|
||||
let filetype = obj._attachments[filename].content_type;
|
||||
let file = obj._attachments[filename].data;
|
||||
|
||||
//console.log(obj._attachments)
|
||||
//console.log(filename)
|
||||
//console.log(filetype)
|
||||
//console.log(file)
|
||||
|
||||
let data_with_attachment =
|
||||
obj.timestamp +
|
||||
split_char +
|
||||
obj.msg +
|
||||
split_char +
|
||||
filename +
|
||||
split_char +
|
||||
filetype +
|
||||
split_char +
|
||||
file;
|
||||
|
||||
let data = btoa_FD(
|
||||
"m" +
|
||||
split_char +
|
||||
command +
|
||||
split_char +
|
||||
checksum +
|
||||
split_char +
|
||||
uuid +
|
||||
split_char +
|
||||
data_with_attachment,
|
||||
);
|
||||
|
||||
// TODO: REMOVE mode and frames from Modem!
|
||||
var mode = 255;
|
||||
var frames = 5;
|
||||
|
||||
command =
|
||||
'{"type" : "arq", "command" : "send_raw", "uuid" : "' +
|
||||
uuid +
|
||||
'", "parameter" : [{"dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'", "mode" : "' +
|
||||
mode +
|
||||
'", "n_frames" : "' +
|
||||
frames +
|
||||
'", "data" : "' +
|
||||
data +
|
||||
'", "attempts": "10"}]}';
|
||||
console.log(command);
|
||||
writeTncCommand(command);
|
||||
}
|
||||
/*
|
||||
// Send Request message
|
||||
//It would be then „m + split + request + split + request-type“
|
||||
function sendRequest(dxcallsign, mode, frames, data, command) {
|
||||
data = btoa_FD("m" + split_char + command + split_char + data);
|
||||
command =
|
||||
'{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'", "mode" : "' +
|
||||
mode +
|
||||
'", "n_frames" : "' +
|
||||
frames +
|
||||
'", "data" : "' +
|
||||
data +
|
||||
'", "attempts": "10"}]}';
|
||||
console.log(command);
|
||||
console.log("--------------REQ--------------------");
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// Send Response message
|
||||
//It would be then „m + split + request + split + request-type“
|
||||
function sendResponse(dxcallsign, mode, frames, data, command) {
|
||||
data = btoa_FD("m" + split_char + command + split_char + data);
|
||||
command =
|
||||
'{"type" : "arq", "command" : "send_raw", "parameter" : [{"dxcallsign" : "' +
|
||||
dxcallsign +
|
||||
'", "mode" : "' +
|
||||
mode +
|
||||
'", "n_frames" : "' +
|
||||
frames +
|
||||
'", "data" : "' +
|
||||
data +
|
||||
'", "attempts": "10"}]}';
|
||||
console.log(command);
|
||||
console.log("--------------RES--------------------");
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
//Send station info request
|
||||
//exports.sendRequestInfo = function (dxcallsign) {
|
||||
function sendRequestInfo(dxcallsign) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendRequest(dxcallsign, 255, 1, "0", "req");
|
||||
}
|
||||
|
||||
//Send shared folder file list request
|
||||
//exports.sendRequestSharedFolderList = function (dxcallsign) {
|
||||
function sendRequestSharedFolderList(dxcallsign) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendRequest(dxcallsign, 255, 1, "1", "req");
|
||||
}
|
||||
|
||||
//Send shared file request
|
||||
//exports.sendRequestSharedFile = function (dxcallsign, file) {
|
||||
function sendRequestSharedFile(dxcallsign, file) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendRequest(dxcallsign, 255, 1, "2" + file, "req");
|
||||
}
|
||||
|
||||
//Send station info response
|
||||
//exports.sendResponseInfo = function (dxcallsign, userinfo) {
|
||||
function sendResponseInfo(dxcallsign, userinfo) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendResponse(dxcallsign, 255, 1, userinfo, "res-0");
|
||||
}
|
||||
|
||||
//Send shared folder response
|
||||
//exports.sendResponseSharedFolderList = function (dxcallsign, sharedFolderList) {
|
||||
function sendResponseSharedFolderList(dxcallsign, sharedFolderList) {
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendResponse(dxcallsign, 255, 1, sharedFolderList, "res-1");
|
||||
}
|
||||
|
||||
//Send shared file response
|
||||
//exports.sendResponseSharedFile = function (
|
||||
function sendResponseSharedFile(dxcallsign, sharedFile, sharedFileData) {
|
||||
console.log(
|
||||
"In sendResponseSharedFile",
|
||||
dxcallsign,
|
||||
sharedFile,
|
||||
sharedFileData,
|
||||
);
|
||||
//Command 0 = user/station information
|
||||
//Command 1 = shared folder list
|
||||
//Command 2 = shared file transfer
|
||||
sendResponse(dxcallsign, 255, 1, sharedFile + "/" + sharedFileData, "res-2");
|
||||
}
|
||||
*/
|
||||
//STOP TRANSMISSION
|
||||
export function stopTransmission() {
|
||||
var command = '{"type" : "arq", "command": "stop_transmission"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// Get RX BUffer
|
||||
export function getRxBuffer() {
|
||||
var command = '{"type" : "get", "command" : "rx_buffer"}';
|
||||
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// START BEACON
|
||||
export function startBeacon(interval) {
|
||||
var command =
|
||||
'{"type" : "broadcast", "command" : "start_beacon", "parameter": "' +
|
||||
interval +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// STOP BEACON
|
||||
export function stopBeacon() {
|
||||
var command = '{"type" : "broadcast", "command" : "stop_beacon"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// OPEN ARQ SESSION
|
||||
export function connectARQ(dxcallsign) {
|
||||
var command =
|
||||
'{"type" : "arq", "command" : "connect", "dxcallsign": "' +
|
||||
dxcallsign +
|
||||
'", "attempts": "10"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// CLOSE ARQ SESSION
|
||||
export function disconnectARQ() {
|
||||
var command = '{"type" : "arq", "command" : "disconnect"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// SEND TEST FRAME
|
||||
export function sendTestFrame() {
|
||||
var command = '{"type" : "set", "command" : "send_test_frame"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// SEND FEC
|
||||
export function sendFEC(mode, payload) {
|
||||
var command =
|
||||
'{"type" : "fec", "command" : "transmit", "mode" : "' +
|
||||
mode +
|
||||
'", "payload" : "' +
|
||||
payload +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// SEND FEC IS WRITING
|
||||
export function sendFecIsWriting(mycallsign) {
|
||||
var command =
|
||||
'{"type" : "fec", "command" : "transmit_is_writing", "mycallsign" : "' +
|
||||
mycallsign +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// SEND FEC TO BROADCASTCHANNEL
|
||||
//export function sendBroadcastChannel(channel, data_out, uuid) {
|
||||
export function sendBroadcastChannel(obj) {
|
||||
//let checksum = obj.checksum;
|
||||
let command = obj.command;
|
||||
let uuid = obj.uuid;
|
||||
let channel = obj.dxcallsign;
|
||||
let data_out = obj.msg;
|
||||
|
||||
let data = btoa_FD(
|
||||
"m" +
|
||||
split_char +
|
||||
channel +
|
||||
//split_char +
|
||||
//checksum +
|
||||
split_char +
|
||||
uuid +
|
||||
split_char +
|
||||
data_out,
|
||||
);
|
||||
console.log(data.length);
|
||||
let payload = data;
|
||||
command =
|
||||
'{"type" : "fec", "command" : "transmit", "mode": "datac4", "wakeup": "True", "payload" : "' +
|
||||
payload +
|
||||
'"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// RECORD AUDIO
|
||||
export function record_audio() {
|
||||
var command = '{"type" : "set", "command" : "record_audio"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// SET FREQUENCY
|
||||
export function set_frequency(frequency) {
|
||||
var command =
|
||||
'{"type" : "set", "command" : "frequency", "frequency": ' + frequency + "}";
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// SET MODE
|
||||
export function set_mode(mode) {
|
||||
var command = '{"type" : "set", "command" : "mode", "mode": "' + mode + '"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// SET rf_level
|
||||
export function set_rf_level(rf_level) {
|
||||
var command =
|
||||
'{"type" : "set", "command" : "rf_level", "rf_level": "' + rf_level + '"}';
|
||||
writeTncCommand(command);
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/50579690
|
||||
// crc32 calculation
|
||||
//console.log(crc32('abc'));
|
||||
//console.log(crc32('abc').toString(16).toUpperCase()); // hex
|
||||
/*
|
||||
function crc32(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;
|
||||
};
|
||||
*/
|
||||
|
||||
// TODO Maybe moving this to another module
|
||||
function prepareStatsDataForStore(data) {
|
||||
// dummy data
|
||||
//state.arq_speed_list = [{"snr":0.0,"bpm":104,"timestamp":1696189769},{"snr":0.0,"bpm":80,"timestamp":1696189778},{"snr":0.0,"bpm":70,"timestamp":1696189783},{"snr":0.0,"bpm":58,"timestamp":1696189792},{"snr":0.0,"bpm":52,"timestamp":1696189797},{"snr":"NaN","bpm":42,"timestamp":1696189811},{"snr":0.0,"bpm":22,"timestamp":1696189875},{"snr":0.0,"bpm":21,"timestamp":1696189881},{"snr":0.0,"bpm":17,"timestamp":1696189913},{"snr":0.0,"bpm":15,"timestamp":1696189932},{"snr":0.0,"bpm":15,"timestamp":1696189937},{"snr":0.0,"bpm":14,"timestamp":1696189946},{"snr":-6.1,"bpm":14,"timestamp":1696189954},{"snr":-6.1,"bpm":14,"timestamp":1696189955},{"snr":-5.5,"bpm":28,"timestamp":1696189963},{"snr":-5.5,"bpm":27,"timestamp":1696189963}]
|
||||
|
||||
var speed_listSize = 0;
|
||||
if (typeof data == "undefined") {
|
||||
speed_listSize = 0;
|
||||
} else {
|
||||
speed_listSize = data.length;
|
||||
}
|
||||
|
||||
var speed_list_bpm = [];
|
||||
|
||||
for (let i = 0; i < speed_listSize; i++) {
|
||||
speed_list_bpm.push(data[i].bpm);
|
||||
}
|
||||
|
||||
var speed_list_timestamp = [];
|
||||
|
||||
for (let i = 0; i < speed_listSize; i++) {
|
||||
let timestamp = data[i].timestamp * 1000;
|
||||
let h = new Date(timestamp).getHours();
|
||||
let m = new Date(timestamp).getMinutes();
|
||||
let s = new Date(timestamp).getSeconds();
|
||||
let time = h + ":" + m + ":" + s;
|
||||
speed_list_timestamp.push(time);
|
||||
}
|
||||
|
||||
var speed_list_snr = [];
|
||||
for (let i = 0; i < speed_listSize; i++) {
|
||||
let snr = NaN;
|
||||
if (data[i].snr !== 0) {
|
||||
snr = data[i].snr;
|
||||
} else {
|
||||
snr = NaN;
|
||||
}
|
||||
speed_list_snr.push(snr);
|
||||
}
|
||||
|
||||
stateStore.arq_speed_list_bpm = speed_list_bpm;
|
||||
stateStore.arq_speed_list_timestamp = speed_list_timestamp;
|
||||
stateStore.arq_speed_list_snr = speed_list_snr;
|
||||
}
|
|
@ -4,26 +4,40 @@ import { setActivePinia } from "pinia";
|
|||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
import { settingsStore as settings } from "../store/settingsStore.js";
|
||||
|
||||
var spectrum = new Object();
|
||||
|
||||
export function initWaterfall() {
|
||||
spectrum = new Spectrum("waterfall", {
|
||||
var spectrums = [];
|
||||
export function initWaterfall(id) {
|
||||
spectrum = new Spectrum(id, {
|
||||
spectrumPercent: 0,
|
||||
wf_rows: 192, //Assuming 1 row = 1 pixe1, 192 is the height of the spectrum container
|
||||
wf_rows: 1024, //Assuming 1 row = 1 pixe1, 192 is the height of the spectrum container
|
||||
wf_size: 1024,
|
||||
});
|
||||
|
||||
console.log(settings.wftheme);
|
||||
spectrum.setColorMap(settings.wftheme);
|
||||
spectrum.setColorMap(settings.local.wf_theme);
|
||||
spectrums.push(spectrum);
|
||||
return spectrum;
|
||||
}
|
||||
|
||||
export function addDataToWaterfall(data) {
|
||||
//console.log(spectrum)
|
||||
try {
|
||||
spectrum.addData(data);
|
||||
} catch (e) {
|
||||
//console.log(e);
|
||||
}
|
||||
data = JSON.parse(data);
|
||||
if (data.constructor !== Array) return;
|
||||
spectrums.forEach((element) => {
|
||||
//console.log(element);
|
||||
element.addData(data);
|
||||
});
|
||||
//window.dispatchEvent(new CustomEvent("wf-data-avail", {bubbles:true, detail: data }));
|
||||
}
|
||||
/**
|
||||
* Setwaterfall colormap array by index
|
||||
* @param {number} index colormap index to use
|
||||
*/
|
||||
export function setColormap() {
|
||||
let index = settings.local.wf_theme;
|
||||
if (isNaN(index)) index = 0;
|
||||
console.log("Setting waterfall colormap to " + index);
|
||||
spectrums.forEach((element) => {
|
||||
//console.log(element);
|
||||
element.setColorMap(index);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
import { createApp } from "vue";
|
||||
import { createPinia } from "pinia";
|
||||
import { loadSettings } from "./js/settingsHandler";
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
// Import all of Bootstrap's JS
|
||||
//import * as bootstrap from 'bootstrap'
|
||||
|
||||
import "bootstrap/dist/js/bootstrap.bundle.min.js";
|
||||
import "bootstrap/dist/css/bootstrap.css";
|
||||
import "bootstrap-icons/font/bootstrap-icons.css";
|
||||
import { Chart, Filler } from "chart.js";
|
||||
// Register the Filler plugin globally
|
||||
Chart.register(Filler);
|
||||
|
||||
// Import our custom CSS
|
||||
//import './scss/styles.scss'
|
||||
|
@ -19,11 +13,27 @@ const app = createApp(App);
|
|||
//.mount('#app').$nextTick(() => postMessage({ payload: 'removeLoading' }, '*'))
|
||||
const pinia = createPinia();
|
||||
app.mount("#app");
|
||||
|
||||
app.use(pinia);
|
||||
loadSettings();
|
||||
|
||||
//import './js/settingsHandler.js'
|
||||
import "./js/daemon";
|
||||
import "./js/sock.js";
|
||||
//import './js/settingsHandler.js'
|
||||
// Import all of Bootstrap's JS
|
||||
//import * as bootstrap from 'bootstrap'
|
||||
|
||||
import * as bootstrap from "bootstrap";
|
||||
import "bootstrap/dist/css/bootstrap.css";
|
||||
import "bootstrap-icons/font/bootstrap-icons.css";
|
||||
const tooltipTriggerList = document.querySelectorAll(
|
||||
'[data-bs-toggle="tooltip"]',
|
||||
);
|
||||
// @ts-expect-error
|
||||
const tooltipList = [...tooltipTriggerList].map(
|
||||
(tooltipTriggerEl) => new bootstrap.Tooltip(tooltipTriggerEl),
|
||||
);
|
||||
|
||||
import { getRemote } from "./store/settingsStore";
|
||||
import { initConnections } from "./js/event_sock.js";
|
||||
import { getModemState } from "./js/api";
|
||||
|
||||
getRemote().then(() => {
|
||||
initConnections();
|
||||
getModemState();
|
||||
});
|
||||
|
|
|
@ -1,55 +1,39 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { getAudioDevices } from "../js/api";
|
||||
import { ref } from "vue";
|
||||
|
||||
import { setActivePinia } from "pinia";
|
||||
import pinia from "../store/index";
|
||||
setActivePinia(pinia);
|
||||
|
||||
import { useSettingsStore } from "../store/settingsStore.js";
|
||||
const settings = useSettingsStore(pinia);
|
||||
// Define skel fallback data
|
||||
const skel = [
|
||||
{
|
||||
api: "ERR",
|
||||
id: "0000",
|
||||
name: "No devices received from modem",
|
||||
native_index: 0,
|
||||
},
|
||||
];
|
||||
|
||||
export const useAudioStore = defineStore("audioStore", () => {
|
||||
var inputDevices = ref([{ id: 0, name: "no input devices" }]);
|
||||
var outputDevices = ref([{ id: 0, name: "no output devices" }]);
|
||||
const audioInputs = ref([]);
|
||||
const audioOutputs = ref([]);
|
||||
|
||||
var startupInputDevice = ref(0);
|
||||
var startupOutputDevice = ref(0);
|
||||
|
||||
function getInputDevices() {
|
||||
var html = "";
|
||||
for (var key in inputDevices.value) {
|
||||
let selected = "";
|
||||
if (inputDevices.value[key]["name"] == settings.rx_audio) {
|
||||
selected = "selected";
|
||||
} else {
|
||||
selected = "";
|
||||
}
|
||||
|
||||
html += `<option value=${inputDevices.value[key]["id"]} ${selected}>${inputDevices.value[key]["name"]}</option>`;
|
||||
const loadAudioDevices = async () => {
|
||||
try {
|
||||
const devices = await getAudioDevices();
|
||||
// Check if devices are valid and have entries, otherwise use skel
|
||||
audioInputs.value = devices && devices.in.length > 0 ? devices.in : skel;
|
||||
audioOutputs.value =
|
||||
devices && devices.out.length > 0 ? devices.out : skel;
|
||||
} catch (error) {
|
||||
console.error("Failed to load audio devices:", error);
|
||||
// Use skel as fallback in case of error
|
||||
audioInputs.value = skel;
|
||||
audioOutputs.value = skel;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
function getOutputDevices() {
|
||||
var html = "";
|
||||
for (var key in outputDevices.value) {
|
||||
let selected = "";
|
||||
if (outputDevices.value[key]["name"] == settings.tx_audio) {
|
||||
selected = "selected";
|
||||
} else {
|
||||
selected = "";
|
||||
}
|
||||
html += `<option value=${outputDevices.value[key]["id"]} ${selected}>${outputDevices.value[key]["name"]}</option>`;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
inputDevices,
|
||||
outputDevices,
|
||||
getInputDevices,
|
||||
getOutputDevices,
|
||||
startupInputDevice,
|
||||
startupOutputDevice,
|
||||
audioInputs,
|
||||
audioOutputs,
|
||||
loadAudioDevices,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -2,6 +2,21 @@ import { defineStore } from "pinia";
|
|||
import { ref } from "vue";
|
||||
|
||||
export const useChatStore = defineStore("chatStore", () => {
|
||||
var callsign_list = ref();
|
||||
var sorted_chat_list = ref();
|
||||
var newChatCallsign = ref();
|
||||
var newChatMessage = ref();
|
||||
|
||||
/* ------------------------------------------------ */
|
||||
// Scroll to bottom functions
|
||||
const scrollTrigger = ref(0);
|
||||
|
||||
function triggerScrollToBottom() {
|
||||
scrollTrigger.value++;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------ */
|
||||
|
||||
var chat_filter = ref([
|
||||
{ type: "newchat" },
|
||||
{ type: "received" },
|
||||
|
@ -42,13 +57,9 @@ export const useChatStore = defineStore("chatStore", () => {
|
|||
});
|
||||
var inputText = ref("");
|
||||
var inputFile = ref();
|
||||
var inputFileName = ref();
|
||||
var inputFileType = ref();
|
||||
var inputFileSize = ref();
|
||||
|
||||
var callsign_list = ref();
|
||||
var sorted_chat_list = ref();
|
||||
var unsorted_chat_list = ref([]);
|
||||
var inputFileName = ref("-");
|
||||
var inputFileType = ref("-");
|
||||
var inputFileSize = ref("-");
|
||||
|
||||
var sorted_beacon_list = ref({});
|
||||
var unsorted_beacon_list = ref({});
|
||||
|
@ -68,12 +79,13 @@ export const useChatStore = defineStore("chatStore", () => {
|
|||
|
||||
return {
|
||||
selectedCallsign,
|
||||
newChatCallsign,
|
||||
newChatMessage,
|
||||
selectedMessageObject,
|
||||
inputText,
|
||||
chat_filter,
|
||||
callsign_list,
|
||||
sorted_chat_list,
|
||||
unsorted_chat_list,
|
||||
inputFileName,
|
||||
inputFileSize,
|
||||
inputFileType,
|
||||
|
@ -88,5 +100,7 @@ export const useChatStore = defineStore("chatStore", () => {
|
|||
arq_speed_list_bpm,
|
||||
arq_speed_list_snr,
|
||||
arq_speed_list_timestamp,
|
||||
scrollTrigger,
|
||||
triggerScrollToBottom,
|
||||
};
|
||||
});
|
||||
|
|
38
gui/src/store/serialStore.js
Normal file
38
gui/src/store/serialStore.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { getSerialDevices } from "../js/api"; // Make sure this points to the correct file
|
||||
import { ref } from "vue";
|
||||
|
||||
// Define "skel" fallback data for serial devices
|
||||
const skelSerial = [
|
||||
{
|
||||
description: "No devices received from modem",
|
||||
port: "ignore", // Using "ignore" as a placeholder value
|
||||
},
|
||||
];
|
||||
|
||||
export const useSerialStore = defineStore("serialStore", () => {
|
||||
const serialDevices = ref([]);
|
||||
|
||||
const loadSerialDevices = async () => {
|
||||
try {
|
||||
const devices = await getSerialDevices();
|
||||
// Check if devices are valid and have entries, otherwise use skelSerial
|
||||
serialDevices.value =
|
||||
devices && devices.length > 0 ? devices : skelSerial;
|
||||
} catch (error) {
|
||||
console.error("Failed to load serial devices:", error);
|
||||
// Use skelSerial as fallback in case of error
|
||||
serialDevices.value = skelSerial;
|
||||
}
|
||||
|
||||
// Ensure the "-- ignore --" option is always available
|
||||
if (!serialDevices.value.some((device) => device.port === "ignore")) {
|
||||
serialDevices.value.push({ description: "-- ignore --", port: "ignore" });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
serialDevices,
|
||||
loadSerialDevices,
|
||||
};
|
||||
});
|
|
@ -1,228 +1,137 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import { reactive, ref, watch } from "vue";
|
||||
import { getConfig, setConfig } from "../js/api";
|
||||
import { getAppDataPath } from "../js/freedata";
|
||||
import fs from "fs";
|
||||
const path = require("path");
|
||||
const nconf = require("nconf");
|
||||
|
||||
export const useSettingsStore = defineStore("settingsStore", () => {
|
||||
// audio
|
||||
var tx_audio = ref();
|
||||
var rx_audio = ref();
|
||||
var appDataPath = getAppDataPath();
|
||||
var configFolder = path.join(appDataPath, "FreeDATA");
|
||||
let configFile = "config.json";
|
||||
|
||||
// network
|
||||
var modem_host = ref("127.0.0.1");
|
||||
var modem_port = ref(3000);
|
||||
var daemon_host = ref(modem_host.value);
|
||||
var daemon_port = ref(modem_port.value + 1);
|
||||
const isGitHubActions = process.env.GITHUB_ACTIONS === "true";
|
||||
if (isGitHubActions) {
|
||||
configFile = "example.json";
|
||||
configFolder = appDataPath;
|
||||
}
|
||||
|
||||
// app
|
||||
var screen_height = ref(430);
|
||||
var screen_width = ref(1050);
|
||||
var theme = ref("default");
|
||||
var wftheme = ref(2);
|
||||
var high_graphics = ref("False");
|
||||
var auto_start = ref(0);
|
||||
var enable_sys_notification = ref(1);
|
||||
var configPath = path.join(configFolder, configFile);
|
||||
|
||||
// chat
|
||||
var shared_folder_path = ref(".");
|
||||
var enable_request_profile = ref("True");
|
||||
var enable_request_shared_folder = ref("False");
|
||||
var max_retry_attempts = ref(5);
|
||||
var enable_auto_retry = ref("False");
|
||||
console.log("AppData Path:", appDataPath);
|
||||
console.log(configFolder);
|
||||
console.log(configPath);
|
||||
|
||||
// station
|
||||
var mycall = ref("AA0AA-5");
|
||||
var myssid = ref(0);
|
||||
var mygrid = ref("JN20aa");
|
||||
nconf.file({ file: configPath });
|
||||
|
||||
// rigctld
|
||||
var hamlib_rigctld_port = ref(4532);
|
||||
var hamlib_rigctld_ip = ref("127.0.0.1");
|
||||
var radiocontrol = ref("disabled");
|
||||
var hamlib_deviceid = ref("RIG_MODEL_DUMMY_NOVFO");
|
||||
var hamlib_deviceport = ref("ignore");
|
||||
var hamlib_stop_bits = ref("ignore");
|
||||
var hamlib_data_bits = ref("ignore");
|
||||
var hamlib_handshake = ref("ignore");
|
||||
var hamlib_serialspeed = ref("ignore");
|
||||
var hamlib_dtrstate = ref("ignore");
|
||||
var hamlib_pttprotocol = ref("ignore");
|
||||
var hamlib_ptt_port = ref("ignore");
|
||||
var hamlib_dcd = ref("ignore");
|
||||
var hamlbib_serialspeed_ptt = ref(9600);
|
||||
var hamlib_rigctld_path = ref("");
|
||||
var hamlib_rigctld_server_port = ref(4532);
|
||||
var hamlib_rigctld_custom_args = ref("");
|
||||
// +++
|
||||
//GUI DEFAULT SETTINGS........
|
||||
//Set GUI defaults here, they will be used if not found in config/config.json
|
||||
//They should be an exact mirror (variable wise) of settingsStore.local
|
||||
//Nothing else should be needed aslong as components are using v-bind
|
||||
// +++
|
||||
|
||||
// tci
|
||||
var tci_ip = ref("127.0.0.1");
|
||||
var tci_port = ref(50001);
|
||||
const defaultConfig = {
|
||||
local: {
|
||||
host: "127.0.0.1",
|
||||
port: "5000",
|
||||
spectrum: "waterfall",
|
||||
wf_theme: 2,
|
||||
update_channel: "alpha",
|
||||
enable_sys_notification: false,
|
||||
grid_layout: "[]",
|
||||
grid_preset: "[]",
|
||||
grid_enabled: true,
|
||||
},
|
||||
remote: {
|
||||
AUDIO: {
|
||||
enable_auto_tune: false,
|
||||
input_device: "",
|
||||
output_device: "",
|
||||
rx_audio_level: 0,
|
||||
tx_audio_level: 0,
|
||||
},
|
||||
MESH: {
|
||||
enable_protocol: false,
|
||||
},
|
||||
MODEM: {
|
||||
respond_to_cq: false,
|
||||
tx_delay: 0,
|
||||
enable_hamc: false,
|
||||
enable_morse_identifier: false,
|
||||
maximum_bandwidth: 3000,
|
||||
},
|
||||
RADIO: {
|
||||
control: "disabled",
|
||||
model_id: 0,
|
||||
serial_port: "",
|
||||
serial_speed: "",
|
||||
data_bits: 0,
|
||||
stop_bits: 0,
|
||||
serial_handshake: "",
|
||||
ptt_port: "",
|
||||
ptt_type: "",
|
||||
serial_dcd: "",
|
||||
serial_dtr: "",
|
||||
},
|
||||
RIGCTLD: {
|
||||
ip: "127.0.0.1",
|
||||
port: 0,
|
||||
path: "",
|
||||
command: "",
|
||||
arguments: "",
|
||||
},
|
||||
STATION: {
|
||||
enable_explorer: false,
|
||||
enable_stats: false,
|
||||
mycall: "DEFAULT",
|
||||
myssid: 0,
|
||||
mygrid: "",
|
||||
ssid_list: [],
|
||||
},
|
||||
TCI: {
|
||||
tci_ip: "127.0.0.1",
|
||||
tci_port: 0,
|
||||
},
|
||||
MESSAGES: {
|
||||
enable_auto_repeat: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
//modem
|
||||
var spectrum = ref("waterfall");
|
||||
var enable_scatter = ref("False");
|
||||
var enable_fft = ref("False");
|
||||
var enable_fsk = ref("False");
|
||||
var low_bandwidth_mode = ref("False");
|
||||
var update_channel = ref("latest");
|
||||
var beacon_interval = ref(300);
|
||||
var received_files_folder = ref("None");
|
||||
var tuning_range_fmin = ref(-50.0);
|
||||
var tuning_range_fmax = ref(50.0);
|
||||
var respond_to_cq = ref("True");
|
||||
var rx_buffer_size = ref(16);
|
||||
var enable_explorer = ref("False");
|
||||
var explorer_stats = ref("False");
|
||||
var auto_tune = ref("False");
|
||||
var enable_is_writing = ref("True");
|
||||
var tx_delay = ref(0);
|
||||
var enable_mesh_features = ref("False");
|
||||
var serial_devices = ref();
|
||||
nconf.defaults(defaultConfig);
|
||||
nconf.required(["local:host", "local:port"]);
|
||||
|
||||
function getSerialDevices() {
|
||||
if (this.hamlib_deviceport == "ignore")
|
||||
var html =
|
||||
'<option value ="ignore" selected>None - (use custom options for hamlib)</option>';
|
||||
else
|
||||
var html =
|
||||
'<option value ="ignore">None - (use custom options for hamlib)</option>';
|
||||
for (var key in serial_devices.value) {
|
||||
let selected = "";
|
||||
if (serial_devices.value[key]["port"] == this.hamlib_deviceport) {
|
||||
selected = "selected";
|
||||
} else {
|
||||
selected = "";
|
||||
}
|
||||
export const settingsStore = reactive(defaultConfig);
|
||||
|
||||
html += `<option value="${serial_devices.value[key]["port"]}" ${selected}>${serial_devices.value[key]["port"]} - ${serial_devices.value[key]["description"]}</option>`;
|
||||
//Save settings for GUI to config file
|
||||
settingsStore.local = nconf.get("local");
|
||||
saveLocalSettingsToConfig();
|
||||
|
||||
export function onChange() {
|
||||
setConfig(settingsStore.remote).then((conf) => {
|
||||
settingsStore.remote = conf;
|
||||
});
|
||||
}
|
||||
|
||||
export function getRemote() {
|
||||
return getConfig().then((conf) => {
|
||||
if (typeof conf !== "undefined") {
|
||||
settingsStore.remote = conf;
|
||||
onChange();
|
||||
} else {
|
||||
console.warn("Received undefined configuration, using default!");
|
||||
settingsStore.remote = defaultConfig.remote;
|
||||
}
|
||||
return html;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getJSON() {
|
||||
var config_export = {
|
||||
modem_host: modem_host.value,
|
||||
modem_port: modem_port.value,
|
||||
daemon_host: modem_host.value,
|
||||
daemon_port: (parseInt(modem_port.value) + 1).toString(),
|
||||
mycall: mycall.value,
|
||||
myssid: myssid.value,
|
||||
mygrid: mygrid.value,
|
||||
radiocontrol: radiocontrol.value,
|
||||
hamlib_deviceid: hamlib_deviceid.value,
|
||||
hamlib_deviceport: hamlib_deviceport.value,
|
||||
hamlib_stop_bits: hamlib_stop_bits.value,
|
||||
hamlib_data_bits: hamlib_data_bits.value,
|
||||
hamlib_handshake: hamlib_handshake.value,
|
||||
hamlib_serialspeed: hamlib_serialspeed.value,
|
||||
hamlib_dtrstate: hamlib_dtrstate.value,
|
||||
hamlib_pttprotocol: hamlib_pttprotocol.value,
|
||||
hamlib_ptt_port: hamlib_ptt_port.value,
|
||||
hamlib_dcd: hamlib_dcd.value,
|
||||
hamlbib_serialspeed_ptt: hamlib_serialspeed.value,
|
||||
hamlib_rigctld_port: hamlib_rigctld_port.value,
|
||||
hamlib_rigctld_ip: hamlib_rigctld_ip.value,
|
||||
hamlib_rigctld_path: hamlib_rigctld_path.value,
|
||||
hamlib_rigctld_server_port: hamlib_rigctld_server_port.value,
|
||||
hamlib_rigctld_custom_args: hamlib_rigctld_custom_args.value,
|
||||
tci_port: tci_port.value,
|
||||
tci_ip: tci_ip.value,
|
||||
spectrum: spectrum.value,
|
||||
enable_scatter: enable_scatter.value,
|
||||
enable_fft: enable_fft.value,
|
||||
enable_fsk: enable_fsk.value,
|
||||
low_bandwidth_mode: low_bandwidth_mode.value,
|
||||
theme: theme.value,
|
||||
screen_height: screen_height.value,
|
||||
screen_width: screen_width.value,
|
||||
update_channel: update_channel.value,
|
||||
beacon_interval: beacon_interval.value,
|
||||
received_files_folder: received_files_folder.value,
|
||||
tuning_range_fmin: tuning_range_fmin.value,
|
||||
tuning_range_fmax: tuning_range_fmax.value,
|
||||
respond_to_cq: respond_to_cq.value,
|
||||
rx_buffer_size: rx_buffer_size.value,
|
||||
enable_explorer: enable_explorer.value,
|
||||
wftheme: wftheme.value,
|
||||
high_graphics: high_graphics.value,
|
||||
explorer_stats: explorer_stats.value,
|
||||
auto_tune: auto_tune.value,
|
||||
enable_is_writing: enable_is_writing.value,
|
||||
shared_folder_path: shared_folder_path.value,
|
||||
enable_request_profile: enable_request_profile.value,
|
||||
enable_request_shared_folder: enable_request_shared_folder.value,
|
||||
max_retry_attempts: max_retry_attempts.value,
|
||||
enable_auto_retry: enable_auto_retry.value,
|
||||
tx_delay: tx_delay.value,
|
||||
auto_start: auto_start.value,
|
||||
enable_sys_notification: enable_sys_notification.value,
|
||||
enable_mesh_features: enable_mesh_features.value,
|
||||
tx_audio: tx_audio.value,
|
||||
rx_audio: rx_audio.value,
|
||||
};
|
||||
|
||||
return config_export;
|
||||
}
|
||||
|
||||
return {
|
||||
modem_host,
|
||||
modem_port,
|
||||
daemon_host,
|
||||
daemon_port,
|
||||
screen_height,
|
||||
screen_width,
|
||||
theme,
|
||||
wftheme,
|
||||
high_graphics,
|
||||
auto_start,
|
||||
enable_sys_notification,
|
||||
shared_folder_path,
|
||||
enable_request_profile,
|
||||
enable_request_shared_folder,
|
||||
max_retry_attempts,
|
||||
enable_auto_retry,
|
||||
mycall,
|
||||
myssid,
|
||||
mygrid,
|
||||
hamlib_rigctld_port,
|
||||
hamlib_rigctld_ip,
|
||||
radiocontrol,
|
||||
hamlib_deviceid,
|
||||
hamlib_deviceport,
|
||||
hamlib_stop_bits,
|
||||
hamlib_data_bits,
|
||||
hamlib_handshake,
|
||||
hamlib_serialspeed,
|
||||
hamlib_dtrstate,
|
||||
hamlib_pttprotocol,
|
||||
hamlib_ptt_port,
|
||||
hamlib_dcd,
|
||||
hamlbib_serialspeed_ptt,
|
||||
hamlib_rigctld_path,
|
||||
hamlib_rigctld_server_port,
|
||||
hamlib_rigctld_custom_args,
|
||||
tci_ip,
|
||||
tci_port,
|
||||
spectrum,
|
||||
enable_scatter,
|
||||
enable_fft,
|
||||
enable_fsk,
|
||||
low_bandwidth_mode,
|
||||
update_channel,
|
||||
beacon_interval,
|
||||
received_files_folder,
|
||||
tuning_range_fmin,
|
||||
tuning_range_fmax,
|
||||
respond_to_cq,
|
||||
rx_buffer_size,
|
||||
enable_explorer,
|
||||
explorer_stats,
|
||||
auto_tune,
|
||||
enable_is_writing,
|
||||
tx_delay,
|
||||
enable_mesh_features,
|
||||
getJSON,
|
||||
tx_audio,
|
||||
rx_audio,
|
||||
getSerialDevices,
|
||||
serial_devices,
|
||||
};
|
||||
watch(settingsStore.local, (oldValue, newValue) => {
|
||||
//This function watches for changes, and triggers a save of local settings
|
||||
saveLocalSettingsToConfig();
|
||||
});
|
||||
|
||||
export function saveLocalSettingsToConfig() {
|
||||
nconf.set("local", settingsStore.local);
|
||||
nconf.save();
|
||||
//console.log("Settings saved!");
|
||||
}
|
||||
|
|
|
@ -3,44 +3,48 @@ import { ref } from "vue";
|
|||
import * as bootstrap from "bootstrap";
|
||||
|
||||
export const useStateStore = defineStore("stateStore", () => {
|
||||
var busy_state = ref("-");
|
||||
var busy_state = ref();
|
||||
var arq_state = ref("-");
|
||||
var frequency = ref("-");
|
||||
var new_frequency = ref(0);
|
||||
var frequency = ref(0);
|
||||
var new_frequency = ref(14093000);
|
||||
var mode = ref("-");
|
||||
var rf_level = ref("10");
|
||||
var bandwidth = ref("-");
|
||||
var dbfs_level_percent = ref(0);
|
||||
var dbfs_level = ref(0);
|
||||
var radio_status = ref(false);
|
||||
|
||||
var ptt_state = ref("False");
|
||||
var ptt_state = ref(false);
|
||||
|
||||
var speed_level = ref(0);
|
||||
var fft = ref();
|
||||
var channel_busy = ref("");
|
||||
var channel_busy_slot = ref();
|
||||
var scatter = ref();
|
||||
var channel_busy = ref(false);
|
||||
var channel_busy_slot = ref([false, false, false, false, false]);
|
||||
var scatter = ref([]);
|
||||
var s_meter_strength_percent = ref(0);
|
||||
var s_meter_strength_raw = ref(0);
|
||||
|
||||
var modem_connection = ref("disconnected");
|
||||
var modemStartCount = ref(0);
|
||||
var modem_running_state = ref("--------");
|
||||
var is_modem_running = ref();
|
||||
|
||||
var arq_total_bytes = ref(0);
|
||||
var arq_transmission_percent = ref(0);
|
||||
|
||||
var heard_stations = ref("");
|
||||
var activities = ref([]);
|
||||
var heard_stations = ref([]);
|
||||
var dxcallsign = ref("");
|
||||
|
||||
var arq_session_state = ref("");
|
||||
var arq_state = ref("");
|
||||
var beacon_state = ref("False");
|
||||
var beacon_state = ref(false);
|
||||
|
||||
var audio_recording = ref("");
|
||||
var audio_recording = ref(false);
|
||||
|
||||
var hamlib_status = ref("");
|
||||
var audio_level = ref("");
|
||||
var tx_audio_level = ref("");
|
||||
var rx_audio_level = ref("");
|
||||
|
||||
var alc = ref("");
|
||||
|
||||
var is_codec2_traffic = ref("");
|
||||
|
@ -49,6 +53,7 @@ export const useStateStore = defineStore("stateStore", () => {
|
|||
var arq_speed_list_bpm = ref([]);
|
||||
var arq_speed_list_snr = ref([]);
|
||||
|
||||
/* TODO Those 3 can be removed I guess , DJ2LS*/
|
||||
var arq_seconds_until_finish = ref();
|
||||
var arq_seconds_until_timeout = ref();
|
||||
var arq_seconds_until_timeout_percent = ref();
|
||||
|
@ -61,78 +66,12 @@ export const useStateStore = defineStore("stateStore", () => {
|
|||
|
||||
var rx_buffer_length = ref();
|
||||
|
||||
function getChannelBusySlotState(slot) {
|
||||
const slot_state = channel_busy_slot.value;
|
||||
|
||||
if (typeof slot_state !== "undefined") {
|
||||
// Replace 'False' with 'false' to match JavaScript's boolean representation
|
||||
const string = slot_state
|
||||
.replace(/False/g, "false")
|
||||
.replace(/True/g, "true");
|
||||
|
||||
// Parse the string to get an array
|
||||
const arr = JSON.parse(string);
|
||||
|
||||
return arr[slot];
|
||||
} else {
|
||||
// Handle the undefined case
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function updateTncState(state) {
|
||||
modem_connection.value = state;
|
||||
|
||||
if (modem_connection.value == "open") {
|
||||
// collapse settings screen
|
||||
var collapseFirstRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseFirstRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseFirstRow.hide();
|
||||
var collapseSecondRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseSecondRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseSecondRow.hide();
|
||||
var collapseThirdRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseThirdRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseThirdRow.show();
|
||||
var collapseFourthRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseFourthRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseFourthRow.show();
|
||||
|
||||
//Set tuning for fancy graphics mode (high/low CPU)
|
||||
//set_CPU_mode();
|
||||
|
||||
//GUI will auto connect to TNC if already running, if that is the case increment start count if 0
|
||||
if (modemStartCount.value == 0) modemStartCount.value++;
|
||||
} else {
|
||||
// collapse settings screen
|
||||
var collapseFirstRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseFirstRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseFirstRow.show();
|
||||
var collapseSecondRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseSecondRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseSecondRow.show();
|
||||
var collapseThirdRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseThirdRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseThirdRow.hide();
|
||||
var collapseFourthRow = new bootstrap.Collapse(
|
||||
document.getElementById("collapseFourthRow"),
|
||||
{ toggle: false },
|
||||
);
|
||||
collapseFourthRow.hide();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,7 +89,6 @@ export const useStateStore = defineStore("stateStore", () => {
|
|||
fft,
|
||||
channel_busy,
|
||||
channel_busy_slot,
|
||||
getChannelBusySlotState,
|
||||
scatter,
|
||||
ptt_state,
|
||||
s_meter_strength_percent,
|
||||
|
@ -158,7 +96,8 @@ export const useStateStore = defineStore("stateStore", () => {
|
|||
arq_total_bytes,
|
||||
audio_recording,
|
||||
hamlib_status,
|
||||
audio_level,
|
||||
tx_audio_level,
|
||||
rx_audio_level,
|
||||
alc,
|
||||
updateTncState,
|
||||
arq_transmission_percent,
|
||||
|
@ -168,10 +107,12 @@ export const useStateStore = defineStore("stateStore", () => {
|
|||
arq_seconds_until_finish,
|
||||
arq_seconds_until_timeout,
|
||||
arq_seconds_until_timeout_percent,
|
||||
modem_running_state,
|
||||
modem_connection,
|
||||
is_modem_running,
|
||||
arq_session_state,
|
||||
is_codec2_traffic,
|
||||
rf_level,
|
||||
activities,
|
||||
heard_stations,
|
||||
beacon_state,
|
||||
rigctld_started,
|
||||
|
@ -179,5 +120,6 @@ export const useStateStore = defineStore("stateStore", () => {
|
|||
python_version,
|
||||
modem_version,
|
||||
rx_buffer_length,
|
||||
radio_status,
|
||||
};
|
||||
});
|
||||
|
|
8
gui/tests/api.test.js
Normal file
8
gui/tests/api.test.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { expect, test } from "vitest";
|
||||
import { buildURL } from "../src/js/api";
|
||||
|
||||
test("builds URLs correctly"),
|
||||
() => {
|
||||
const params = { host: "127.0.0.1", port: 123 };
|
||||
expect(buildURL(params, "/config")).toBe("http://127.0.0.1/config");
|
||||
};
|
|
@ -15,6 +15,14 @@ export default defineConfig(({ command }) => {
|
|||
const sourcemap = isServe || !!process.env.VSCODE_DEBUG;
|
||||
|
||||
return {
|
||||
optimizeDeps: {
|
||||
esbuildOptions: {
|
||||
target: "esnext",
|
||||
},
|
||||
},
|
||||
build: {
|
||||
target: "esnext",
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
electron([
|
||||
|
|
27
modem/api_validations.py
Normal file
27
modem/api_validations.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
import re
|
||||
|
||||
|
||||
def validate_remote_config(config):
|
||||
if not config:
|
||||
return
|
||||
|
||||
mygrid = config["STATION"]["mygrid"]
|
||||
if len(mygrid) != 6:
|
||||
raise ValueError(f"Gridsquare must be 6 characters!")
|
||||
|
||||
return True
|
||||
|
||||
def validate_freedata_callsign(callsign):
|
||||
#regexp = "^[a-zA-Z]+\d+\w+-\d{1,2}$"
|
||||
regexp = "^[A-Za-z0-9]{1,7}-[0-9]{1,3}$" # still broken - we need to allow all ssids form 0 - 255
|
||||
return re.compile(regexp).match(callsign) is not None
|
||||
|
||||
def validate_message_attachment(attachment):
|
||||
for field in ['name', 'type', 'data']:
|
||||
if field not in attachment:
|
||||
raise ValueError(f"Attachment missing '{field}'")
|
||||
|
||||
# check for content length, except type
|
||||
# there are some files out there, don't having a mime type
|
||||
if len(attachment[field]) < 1 and field not in ["type"]:
|
||||
raise ValueError(f"Attachment has empty '{field}'")
|
203
modem/arq_data_type_handler.py
Normal file
203
modem/arq_data_type_handler.py
Normal file
|
@ -0,0 +1,203 @@
|
|||
# File: arq_data_type_handler.py
|
||||
|
||||
import structlog
|
||||
import lzma
|
||||
import gzip
|
||||
from message_p2p import message_received, message_failed, message_transmitted
|
||||
from enum import Enum
|
||||
|
||||
class ARQ_SESSION_TYPES(Enum):
|
||||
raw = 0
|
||||
raw_lzma = 10
|
||||
raw_gzip = 11
|
||||
p2pmsg_lzma = 20
|
||||
p2p_connection = 30
|
||||
|
||||
class ARQDataTypeHandler:
|
||||
def __init__(self, event_manager, state_manager):
|
||||
self.logger = structlog.get_logger(type(self).__name__)
|
||||
self.event_manager = event_manager
|
||||
self.state_manager = state_manager
|
||||
|
||||
self.handlers = {
|
||||
ARQ_SESSION_TYPES.raw: {
|
||||
'prepare': self.prepare_raw,
|
||||
'handle': self.handle_raw,
|
||||
'failed': self.failed_raw,
|
||||
'transmitted': self.transmitted_raw,
|
||||
},
|
||||
ARQ_SESSION_TYPES.raw_lzma: {
|
||||
'prepare': self.prepare_raw_lzma,
|
||||
'handle': self.handle_raw_lzma,
|
||||
'failed': self.failed_raw_lzma,
|
||||
'transmitted': self.transmitted_raw_lzma,
|
||||
},
|
||||
ARQ_SESSION_TYPES.raw_gzip: {
|
||||
'prepare': self.prepare_raw_gzip,
|
||||
'handle': self.handle_raw_gzip,
|
||||
'failed': self.failed_raw_gzip,
|
||||
'transmitted': self.transmitted_raw_gzip,
|
||||
},
|
||||
ARQ_SESSION_TYPES.p2pmsg_lzma: {
|
||||
'prepare': self.prepare_p2pmsg_lzma,
|
||||
'handle': self.handle_p2pmsg_lzma,
|
||||
'failed' : self.failed_p2pmsg_lzma,
|
||||
'transmitted': self.transmitted_p2pmsg_lzma,
|
||||
},
|
||||
ARQ_SESSION_TYPES.p2p_connection: {
|
||||
'prepare': self.prepare_p2p_connection,
|
||||
'handle': self.handle_p2p_connection,
|
||||
'failed': self.failed_p2p_connection,
|
||||
'transmitted': self.transmitted_p2p_connection,
|
||||
},
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_session_type_from_value(value):
|
||||
for session_type in ARQ_SESSION_TYPES:
|
||||
if session_type.value == value:
|
||||
return session_type
|
||||
return None
|
||||
|
||||
def dispatch(self, type_byte: int, data: bytearray, statistics: dict):
|
||||
session_type = self.get_session_type_from_value(type_byte)
|
||||
|
||||
self.state_manager.setARQ(False)
|
||||
|
||||
if session_type and session_type in self.handlers and 'handle' in self.handlers[session_type]:
|
||||
return self.handlers[session_type]['handle'](data, statistics)
|
||||
else:
|
||||
self.log(f"Unknown handling endpoint for type: {type_byte}", isWarning=True)
|
||||
|
||||
def failed(self, type_byte: int, data: bytearray, statistics: dict):
|
||||
session_type = self.get_session_type_from_value(type_byte)
|
||||
|
||||
self.state_manager.setARQ(False)
|
||||
|
||||
if session_type in self.handlers and 'failed' in self.handlers[session_type]:
|
||||
return self.handlers[session_type]['failed'](data, statistics)
|
||||
else:
|
||||
self.log(f"Unknown handling endpoint: {session_type}", isWarning=True)
|
||||
|
||||
def prepare(self, data: bytearray, session_type=ARQ_SESSION_TYPES.raw):
|
||||
if session_type in self.handlers and 'prepare' in self.handlers[session_type]:
|
||||
return self.handlers[session_type]['prepare'](data), session_type.value
|
||||
else:
|
||||
self.log(f"Unknown preparation endpoint: {session_type}", isWarning=True)
|
||||
|
||||
def transmitted(self, type_byte: int, data: bytearray, statistics: dict):
|
||||
session_type = self.get_session_type_from_value(type_byte)
|
||||
|
||||
self.state_manager.setARQ(False)
|
||||
|
||||
if session_type in self.handlers and 'transmitted' in self.handlers[session_type]:
|
||||
return self.handlers[session_type]['transmitted'](data, statistics)
|
||||
else:
|
||||
self.log(f"Unknown handling endpoint: {session_type}", isWarning=True)
|
||||
|
||||
def log(self, message, isWarning=False):
|
||||
msg = f"[{type(self).__name__}]: {message}"
|
||||
logger = self.logger.warn if isWarning else self.logger.info
|
||||
logger(msg)
|
||||
|
||||
def prepare_raw(self, data):
|
||||
self.log(f"Preparing uncompressed data: {len(data)} Bytes")
|
||||
return data
|
||||
|
||||
def handle_raw(self, data, statistics):
|
||||
self.log(f"Handling uncompressed data: {len(data)} Bytes")
|
||||
return data
|
||||
|
||||
def failed_raw(self, data, statistics):
|
||||
return
|
||||
|
||||
def transmitted_raw(self, data, statistics):
|
||||
return data
|
||||
|
||||
def prepare_raw_lzma(self, data):
|
||||
compressed_data = lzma.compress(data)
|
||||
self.log(f"Preparing LZMA compressed data: {len(data)} Bytes >>> {len(compressed_data)} Bytes")
|
||||
return compressed_data
|
||||
|
||||
def handle_raw_lzma(self, data, statistics):
|
||||
decompressed_data = lzma.decompress(data)
|
||||
self.log(f"Handling LZMA compressed data: {len(decompressed_data)} Bytes from {len(data)} Bytes")
|
||||
return decompressed_data
|
||||
|
||||
def failed_raw_lzma(self, data, statistics):
|
||||
return
|
||||
|
||||
def transmitted_raw_lzma(self, data, statistics):
|
||||
decompressed_data = lzma.decompress(data)
|
||||
return decompressed_data
|
||||
|
||||
def prepare_raw_gzip(self, data):
|
||||
compressed_data = gzip.compress(data)
|
||||
self.log(f"Preparing GZIP compressed data: {len(data)} Bytes >>> {len(compressed_data)} Bytes")
|
||||
return compressed_data
|
||||
|
||||
def handle_raw_gzip(self, data, statistics):
|
||||
decompressed_data = gzip.decompress(data)
|
||||
self.log(f"Handling GZIP compressed data: {len(decompressed_data)} Bytes from {len(data)} Bytes")
|
||||
return decompressed_data
|
||||
|
||||
def failed_raw_gzip(self, data, statistics):
|
||||
return
|
||||
|
||||
def transmitted_raw_gzip(self, data, statistics):
|
||||
decompressed_data = gzip.decompress(data)
|
||||
return decompressed_data
|
||||
|
||||
def prepare_p2pmsg_lzma(self, data):
|
||||
compressed_data = lzma.compress(data)
|
||||
self.log(f"Preparing LZMA compressed P2PMSG data: {len(data)} Bytes >>> {len(compressed_data)} Bytes")
|
||||
return compressed_data
|
||||
|
||||
def handle_p2pmsg_lzma(self, data, statistics):
|
||||
decompressed_data = lzma.decompress(data)
|
||||
self.log(f"Handling LZMA compressed P2PMSG data: {len(decompressed_data)} Bytes from {len(data)} Bytes")
|
||||
message_received(self.event_manager, self.state_manager, decompressed_data, statistics)
|
||||
return decompressed_data
|
||||
|
||||
def failed_p2pmsg_lzma(self, data, statistics):
|
||||
decompressed_data = lzma.decompress(data)
|
||||
self.log(f"Handling failed LZMA compressed P2PMSG data: {len(decompressed_data)} Bytes from {len(data)} Bytes", isWarning=True)
|
||||
message_failed(self.event_manager, self.state_manager, decompressed_data, statistics)
|
||||
return decompressed_data
|
||||
|
||||
def transmitted_p2pmsg_lzma(self, data, statistics):
|
||||
decompressed_data = lzma.decompress(data)
|
||||
message_transmitted(self.event_manager, self.state_manager, decompressed_data, statistics)
|
||||
return decompressed_data
|
||||
|
||||
|
||||
def prepare_p2p_connection(self, data):
|
||||
compressed_data = gzip.compress(data)
|
||||
self.log(f"Preparing gzip compressed P2P_CONNECTION data: {len(data)} Bytes >>> {len(compressed_data)} Bytes")
|
||||
print(self.state_manager.p2p_connection_sessions)
|
||||
return compressed_data
|
||||
|
||||
def handle_p2p_connection(self, data, statistics):
|
||||
decompressed_data = gzip.decompress(data)
|
||||
self.log(f"Handling gzip compressed P2P_CONNECTION data: {len(decompressed_data)} Bytes from {len(data)} Bytes")
|
||||
print(self.state_manager.p2p_connection_sessions)
|
||||
print(decompressed_data)
|
||||
print(self.state_manager.p2p_connection_sessions)
|
||||
for session_id in self.state_manager.p2p_connection_sessions:
|
||||
print(session_id)
|
||||
self.state_manager.p2p_connection_sessions[session_id].received_arq(decompressed_data)
|
||||
|
||||
def failed_p2p_connection(self, data, statistics):
|
||||
decompressed_data = gzip.decompress(data)
|
||||
self.log(f"Handling failed gzip compressed P2P_CONNECTION data: {len(decompressed_data)} Bytes from {len(data)} Bytes", isWarning=True)
|
||||
print(self.state_manager.p2p_connection_sessions)
|
||||
return decompressed_data
|
||||
|
||||
def transmitted_p2p_connection(self, data, statistics):
|
||||
|
||||
decompressed_data = gzip.decompress(data)
|
||||
print(decompressed_data)
|
||||
print(self.state_manager.p2p_connection_sessions)
|
||||
for session_id in self.state_manager.p2p_connection_sessions:
|
||||
print(session_id)
|
||||
self.state_manager.p2p_connection_sessions[session_id].transmitted_arq()
|
183
modem/arq_session.py
Normal file
183
modem/arq_session.py
Normal file
|
@ -0,0 +1,183 @@
|
|||
import datetime
|
||||
import threading
|
||||
import codec2
|
||||
import data_frame_factory
|
||||
import structlog
|
||||
from event_manager import EventManager
|
||||
from modem_frametypes import FRAME_TYPE
|
||||
import time
|
||||
from arq_data_type_handler import ARQDataTypeHandler
|
||||
|
||||
|
||||
class ARQSession:
|
||||
|
||||
SPEED_LEVEL_DICT = {
|
||||
0: {
|
||||
'mode': codec2.FREEDV_MODE.datac4,
|
||||
'min_snr': -10,
|
||||
'duration_per_frame': 5.17,
|
||||
'bandwidth': 250,
|
||||
},
|
||||
1: {
|
||||
'mode': codec2.FREEDV_MODE.datac3,
|
||||
'min_snr': 0,
|
||||
'duration_per_frame': 3.19,
|
||||
'bandwidth': 563,
|
||||
},
|
||||
2: {
|
||||
'mode': codec2.FREEDV_MODE.datac1,
|
||||
'min_snr': 3,
|
||||
'duration_per_frame': 4.18,
|
||||
'bandwidth': 1700,
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, config: dict, modem, dxcall: str, state_manager):
|
||||
self.logger = structlog.get_logger(type(self).__name__)
|
||||
self.config = config
|
||||
|
||||
self.event_manager: EventManager = modem.event_manager
|
||||
#self.states = modem.states
|
||||
self.states = state_manager
|
||||
self.states.setARQ(True)
|
||||
|
||||
self.snr = []
|
||||
|
||||
self.dxcall = dxcall
|
||||
self.dx_snr = []
|
||||
|
||||
self.modem = modem
|
||||
self.speed_level = 0
|
||||
self.previous_speed_level = 0
|
||||
|
||||
self.frames_per_burst = 1
|
||||
|
||||
self.frame_factory = data_frame_factory.DataFrameFactory(self.config)
|
||||
self.event_frame_received = threading.Event()
|
||||
|
||||
self.arq_data_type_handler = ARQDataTypeHandler(self.event_manager, self.states)
|
||||
self.id = None
|
||||
self.session_started = time.time()
|
||||
self.session_ended = 0
|
||||
self.session_max_age = 500
|
||||
|
||||
# histogram lists for storing statistics
|
||||
self.snr_histogram = []
|
||||
self.bpm_histogram = []
|
||||
self.time_histogram = []
|
||||
|
||||
def log(self, message, isWarning=False):
|
||||
msg = f"[{type(self).__name__}][id={self.id}][state={self.state}]: {message}"
|
||||
logger = self.logger.warn if isWarning else self.logger.info
|
||||
logger(msg)
|
||||
|
||||
def get_mode_by_speed_level(self, speed_level):
|
||||
return self.SPEED_LEVEL_DICT[speed_level]["mode"]
|
||||
|
||||
def transmit_frame(self, frame: bytearray, mode='auto'):
|
||||
self.log("Transmitting frame")
|
||||
if mode in ['auto']:
|
||||
mode = self.get_mode_by_speed_level(self.speed_level)
|
||||
|
||||
self.modem.transmit(mode, 1, 1, frame)
|
||||
|
||||
def set_state(self, state):
|
||||
if self.state == state:
|
||||
self.log(f"{type(self).__name__} state {self.state.name} unchanged.")
|
||||
else:
|
||||
self.log(f"{type(self).__name__} state change from {self.state.name} to {state.name}")
|
||||
self.state = state
|
||||
|
||||
def get_data_payload_size(self):
|
||||
return self.frame_factory.get_available_data_payload_for_mode(
|
||||
FRAME_TYPE.ARQ_BURST_FRAME,
|
||||
self.SPEED_LEVEL_DICT[self.speed_level]["mode"]
|
||||
)
|
||||
|
||||
def set_details(self, snr, frequency_offset):
|
||||
self.snr = snr
|
||||
self.frequency_offset = frequency_offset
|
||||
|
||||
def on_frame_received(self, frame):
|
||||
self.event_frame_received.set()
|
||||
self.log(f"Received {frame['frame_type']}")
|
||||
frame_type = frame['frame_type_int']
|
||||
if self.state in self.STATE_TRANSITION and frame_type in self.STATE_TRANSITION[self.state]:
|
||||
action_name = self.STATE_TRANSITION[self.state][frame_type]
|
||||
received_data, type_byte = getattr(self, action_name)(frame)
|
||||
if isinstance(received_data, bytearray) and isinstance(type_byte, int):
|
||||
self.arq_data_type_handler.dispatch(type_byte, received_data, self.update_histograms(len(received_data), len(received_data)))
|
||||
return
|
||||
|
||||
self.log(f"Ignoring unknown transition from state {self.state.name} with frame {frame['frame_type']}")
|
||||
|
||||
def is_session_outdated(self):
|
||||
session_alivetime = time.time() - self.session_max_age
|
||||
return self.session_ended < session_alivetime and self.state.name in [
|
||||
'FAILED',
|
||||
'ENDED',
|
||||
'ABORTED',
|
||||
]
|
||||
|
||||
def calculate_session_duration(self):
|
||||
if self.session_ended == 0:
|
||||
return time.time() - self.session_started
|
||||
|
||||
return self.session_ended - self.session_started
|
||||
|
||||
def calculate_session_statistics(self, confirmed_bytes, total_bytes):
|
||||
duration = self.calculate_session_duration()
|
||||
# total_bytes = self.total_length
|
||||
# self.total_length
|
||||
duration_in_minutes = duration / 60 # Convert duration from seconds to minutes
|
||||
|
||||
# Calculate bytes per minute
|
||||
if duration_in_minutes > 0:
|
||||
bytes_per_minute = int(confirmed_bytes / duration_in_minutes)
|
||||
else:
|
||||
bytes_per_minute = 0
|
||||
|
||||
# Convert histograms lists to dictionaries
|
||||
time_histogram_dict = dict(enumerate(self.time_histogram))
|
||||
snr_histogram_dict = dict(enumerate(self.snr_histogram))
|
||||
bpm_histogram_dict = dict(enumerate(self.bpm_histogram))
|
||||
|
||||
return {
|
||||
'total_bytes': total_bytes,
|
||||
'duration': duration,
|
||||
'bytes_per_minute': bytes_per_minute,
|
||||
'time_histogram': time_histogram_dict,
|
||||
'snr_histogram': snr_histogram_dict,
|
||||
'bpm_histogram': bpm_histogram_dict,
|
||||
}
|
||||
|
||||
def update_histograms(self, confirmed_bytes, total_bytes):
|
||||
stats = self.calculate_session_statistics(confirmed_bytes, total_bytes)
|
||||
self.snr_histogram.append(self.snr)
|
||||
self.bpm_histogram.append(stats['bytes_per_minute'])
|
||||
self.time_histogram.append(datetime.datetime.now().isoformat())
|
||||
|
||||
# Limit the size of each histogram to the last 20 entries
|
||||
self.snr_histogram = self.snr_histogram[-20:]
|
||||
self.bpm_histogram = self.bpm_histogram[-20:]
|
||||
self.time_histogram = self.time_histogram[-20:]
|
||||
|
||||
return stats
|
||||
|
||||
def get_appropriate_speed_level(self, snr, maximum_bandwidth=None):
|
||||
if maximum_bandwidth is None:
|
||||
maximum_bandwidth = self.config['MODEM']['maximum_bandwidth']
|
||||
|
||||
# Adjust maximum_bandwidth based on special conditions or invalid configurations
|
||||
if maximum_bandwidth == 0:
|
||||
# Use the maximum available bandwidth from the speed level dictionary
|
||||
maximum_bandwidth = max(details['bandwidth'] for details in self.SPEED_LEVEL_DICT.values())
|
||||
|
||||
# Initialize appropriate_speed_level to the lowest level that meets the minimum criteria
|
||||
appropriate_speed_level = min(self.SPEED_LEVEL_DICT.keys())
|
||||
|
||||
for level, details in self.SPEED_LEVEL_DICT.items():
|
||||
if snr >= details['min_snr'] and details['bandwidth'] <= maximum_bandwidth and level > appropriate_speed_level:
|
||||
appropriate_speed_level = level
|
||||
|
||||
return appropriate_speed_level
|
276
modem/arq_session_irs.py
Normal file
276
modem/arq_session_irs.py
Normal file
|
@ -0,0 +1,276 @@
|
|||
import threading
|
||||
import arq_session
|
||||
import helpers
|
||||
from modem_frametypes import FRAME_TYPE
|
||||
from codec2 import FREEDV_MODE
|
||||
from enum import Enum
|
||||
import time
|
||||
|
||||
class IRS_State(Enum):
|
||||
NEW = 0
|
||||
OPEN_ACK_SENT = 1
|
||||
INFO_ACK_SENT = 2
|
||||
BURST_REPLY_SENT = 3
|
||||
ENDED = 4
|
||||
FAILED = 5
|
||||
ABORTED = 6
|
||||
|
||||
class ARQSessionIRS(arq_session.ARQSession):
|
||||
|
||||
TIMEOUT_CONNECT = 55 #14.2
|
||||
TIMEOUT_DATA = 120
|
||||
|
||||
STATE_TRANSITION = {
|
||||
IRS_State.NEW: {
|
||||
FRAME_TYPE.ARQ_SESSION_OPEN.value : 'send_open_ack',
|
||||
FRAME_TYPE.ARQ_STOP.value: 'send_stop_ack'
|
||||
},
|
||||
IRS_State.OPEN_ACK_SENT: {
|
||||
FRAME_TYPE.ARQ_SESSION_OPEN.value: 'send_open_ack',
|
||||
FRAME_TYPE.ARQ_SESSION_INFO.value: 'send_info_ack',
|
||||
FRAME_TYPE.ARQ_STOP.value: 'send_stop_ack'
|
||||
|
||||
},
|
||||
IRS_State.INFO_ACK_SENT: {
|
||||
FRAME_TYPE.ARQ_SESSION_INFO.value: 'send_info_ack',
|
||||
FRAME_TYPE.ARQ_BURST_FRAME.value: 'receive_data',
|
||||
FRAME_TYPE.ARQ_STOP.value: 'send_stop_ack'
|
||||
|
||||
},
|
||||
IRS_State.BURST_REPLY_SENT: {
|
||||
FRAME_TYPE.ARQ_BURST_FRAME.value: 'receive_data',
|
||||
FRAME_TYPE.ARQ_STOP.value: 'send_stop_ack'
|
||||
|
||||
},
|
||||
IRS_State.ENDED: {
|
||||
FRAME_TYPE.ARQ_BURST_FRAME.value: 'receive_data',
|
||||
FRAME_TYPE.ARQ_STOP.value: 'send_stop_ack'
|
||||
|
||||
},
|
||||
IRS_State.FAILED: {
|
||||
FRAME_TYPE.ARQ_BURST_FRAME.value: 'receive_data',
|
||||
FRAME_TYPE.ARQ_STOP.value: 'send_stop_ack'
|
||||
},
|
||||
IRS_State.ABORTED: {
|
||||
FRAME_TYPE.ARQ_STOP.value: 'send_stop_ack',
|
||||
FRAME_TYPE.ARQ_SESSION_OPEN.value: 'send_open_ack',
|
||||
FRAME_TYPE.ARQ_SESSION_INFO.value: 'send_info_ack',
|
||||
FRAME_TYPE.ARQ_BURST_FRAME.value: 'receive_data',
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, config: dict, modem, dxcall: str, session_id: int, state_manager):
|
||||
super().__init__(config, modem, dxcall, state_manager)
|
||||
|
||||
self.id = session_id
|
||||
self.dxcall = dxcall
|
||||
self.version = 1
|
||||
|
||||
self.state = IRS_State.NEW
|
||||
self.state_enum = IRS_State # needed for access State enum from outside
|
||||
|
||||
self.type_byte = None
|
||||
self.total_length = 0
|
||||
self.total_crc = ''
|
||||
self.received_data = None
|
||||
self.received_bytes = 0
|
||||
self.received_crc = None
|
||||
|
||||
self.maximum_bandwidth = 0
|
||||
|
||||
self.abort = False
|
||||
|
||||
def all_data_received(self):
|
||||
return self.total_length == self.received_bytes
|
||||
|
||||
def final_crc_matches(self) -> bool:
|
||||
return self.total_crc == helpers.get_crc_32(bytes(self.received_data)).hex()
|
||||
|
||||
def transmit_and_wait(self, frame, timeout, mode):
|
||||
self.event_frame_received.clear()
|
||||
self.transmit_frame(frame, mode)
|
||||
self.log(f"Waiting {timeout} seconds...")
|
||||
if not self.event_frame_received.wait(timeout):
|
||||
self.log("Timeout waiting for ISS. Session failed.")
|
||||
self.transmission_failed()
|
||||
|
||||
def launch_transmit_and_wait(self, frame, timeout, mode):
|
||||
thread_wait = threading.Thread(target = self.transmit_and_wait,
|
||||
args = [frame, timeout, mode], daemon=True)
|
||||
thread_wait.start()
|
||||
|
||||
def send_open_ack(self, open_frame):
|
||||
self.maximum_bandwidth = open_frame['maximum_bandwidth']
|
||||
# check for maximum bandwidth. If ISS bandwidth is higher than own, then use own
|
||||
if open_frame['maximum_bandwidth'] > self.config['MODEM']['maximum_bandwidth']:
|
||||
self.maximum_bandwidth = self.config['MODEM']['maximum_bandwidth']
|
||||
|
||||
|
||||
self.event_manager.send_arq_session_new(
|
||||
False, self.id, self.dxcall, 0, self.state.name)
|
||||
ack_frame = self.frame_factory.build_arq_session_open_ack(
|
||||
self.id,
|
||||
self.dxcall,
|
||||
self.version,
|
||||
self.snr, flag_abort=self.abort)
|
||||
self.launch_transmit_and_wait(ack_frame, self.TIMEOUT_CONNECT, mode=FREEDV_MODE.signalling)
|
||||
if not self.abort:
|
||||
self.set_state(IRS_State.OPEN_ACK_SENT)
|
||||
return None, None
|
||||
|
||||
def send_info_ack(self, info_frame):
|
||||
# Get session info from ISS
|
||||
self.received_data = bytearray(info_frame['total_length'])
|
||||
self.total_length = info_frame['total_length']
|
||||
self.total_crc = info_frame['total_crc']
|
||||
self.dx_snr.append(info_frame['snr'])
|
||||
self.type_byte = info_frame['type']
|
||||
|
||||
self.calibrate_speed_settings()
|
||||
|
||||
self.log(f"New transfer of {self.total_length} bytes")
|
||||
self.event_manager.send_arq_session_new(False, self.id, self.dxcall, self.total_length, self.state.name)
|
||||
|
||||
info_ack = self.frame_factory.build_arq_session_info_ack(
|
||||
self.id, self.total_crc, self.snr,
|
||||
self.speed_level, self.frames_per_burst, flag_abort=self.abort)
|
||||
self.launch_transmit_and_wait(info_ack, self.TIMEOUT_CONNECT, mode=FREEDV_MODE.signalling)
|
||||
if not self.abort:
|
||||
self.set_state(IRS_State.INFO_ACK_SENT)
|
||||
return None, None
|
||||
|
||||
def process_incoming_data(self, frame):
|
||||
if frame['offset'] != self.received_bytes:
|
||||
self.log(f"Discarding data offset {frame['offset']}")
|
||||
return False
|
||||
|
||||
remaining_data_length = self.total_length - self.received_bytes
|
||||
|
||||
# Is this the last data part?
|
||||
if remaining_data_length <= len(frame['data']):
|
||||
# we only want the remaining length, not the entire frame data
|
||||
data_part = frame['data'][:remaining_data_length]
|
||||
else:
|
||||
# we want the entire frame data
|
||||
data_part = frame['data']
|
||||
|
||||
self.received_data[frame['offset']:] = data_part
|
||||
self.received_bytes += len(data_part)
|
||||
self.log(f"Received {self.received_bytes}/{self.total_length} bytes")
|
||||
self.event_manager.send_arq_session_progress(
|
||||
False, self.id, self.dxcall, self.received_bytes, self.total_length, self.state.name, self.calculate_session_statistics(self.received_bytes, self.total_length))
|
||||
|
||||
return True
|
||||
|
||||
def receive_data(self, burst_frame):
|
||||
self.process_incoming_data(burst_frame)
|
||||
# update statistics
|
||||
self.update_histograms(self.received_bytes, self.total_length)
|
||||
|
||||
if not self.all_data_received():
|
||||
self.calibrate_speed_settings(burst_frame=burst_frame)
|
||||
ack = self.frame_factory.build_arq_burst_ack(
|
||||
self.id,
|
||||
self.received_bytes,
|
||||
self.speed_level,
|
||||
self.frames_per_burst,
|
||||
self.snr,
|
||||
flag_abort=self.abort
|
||||
)
|
||||
|
||||
self.set_state(IRS_State.BURST_REPLY_SENT)
|
||||
self.launch_transmit_and_wait(ack, self.TIMEOUT_DATA, mode=FREEDV_MODE.signalling)
|
||||
return None, None
|
||||
|
||||
if self.final_crc_matches():
|
||||
self.log("All data received successfully!")
|
||||
ack = self.frame_factory.build_arq_burst_ack(self.id,
|
||||
self.received_bytes,
|
||||
self.speed_level,
|
||||
self.frames_per_burst,
|
||||
self.snr,
|
||||
flag_final=True,
|
||||
flag_checksum=True)
|
||||
self.transmit_frame(ack, mode=FREEDV_MODE.signalling)
|
||||
self.log("ACK sent")
|
||||
self.session_ended = time.time()
|
||||
self.set_state(IRS_State.ENDED)
|
||||
self.event_manager.send_arq_session_finished(
|
||||
False, self.id, self.dxcall, True, self.state.name, data=self.received_data, statistics=self.calculate_session_statistics(self.received_bytes, self.total_length))
|
||||
|
||||
return self.received_data, self.type_byte
|
||||
else:
|
||||
|
||||
ack = self.frame_factory.build_arq_burst_ack(self.id,
|
||||
self.received_bytes,
|
||||
self.speed_level,
|
||||
self.frames_per_burst,
|
||||
self.snr,
|
||||
flag_final=True,
|
||||
flag_checksum=False)
|
||||
self.transmit_frame(ack, mode=FREEDV_MODE.signalling)
|
||||
self.log("CRC fail at the end of transmission!")
|
||||
return self.transmission_failed()
|
||||
|
||||
def calibrate_speed_settings(self, burst_frame=None):
|
||||
if burst_frame:
|
||||
received_speed_level = burst_frame['speed_level']
|
||||
else:
|
||||
received_speed_level = 0
|
||||
|
||||
latest_snr = self.snr if self.snr else -10
|
||||
appropriate_speed_level = self.get_appropriate_speed_level(latest_snr, self.maximum_bandwidth)
|
||||
modes_to_decode = {}
|
||||
|
||||
# Log the latest SNR, current, appropriate speed levels, and the previous speed level
|
||||
self.log(
|
||||
f"Latest SNR: {latest_snr}, Current Speed Level: {self.speed_level}, Appropriate Speed Level: {appropriate_speed_level}, Previous Speed Level: {self.previous_speed_level}",
|
||||
isWarning=True)
|
||||
|
||||
# Adjust the speed level by one step towards the appropriate level, if needed
|
||||
if appropriate_speed_level > self.speed_level and self.speed_level < len(self.SPEED_LEVEL_DICT) - 1:
|
||||
# we need to ensure, the received data is equal to our speed level before changing it
|
||||
if received_speed_level == self.speed_level:
|
||||
self.speed_level += 1
|
||||
elif appropriate_speed_level < self.speed_level and self.speed_level > 0:
|
||||
# we need to ensure, the received data is equal to our speed level before changing it
|
||||
if received_speed_level == self.speed_level:
|
||||
self.speed_level -= 1
|
||||
|
||||
# Always decode the current mode
|
||||
current_mode = self.get_mode_by_speed_level(self.speed_level).value
|
||||
modes_to_decode[current_mode] = True
|
||||
|
||||
# Decode the previous speed level mode
|
||||
if self.previous_speed_level != self.speed_level:
|
||||
previous_mode = self.get_mode_by_speed_level(self.previous_speed_level).value
|
||||
modes_to_decode[previous_mode] = True
|
||||
self.previous_speed_level = self.speed_level # Update the previous speed level
|
||||
|
||||
self.log(f"Modes to Decode: {list(modes_to_decode.keys())}", isWarning=True)
|
||||
# Apply the new decode mode based on the updated and previous speed levels
|
||||
self.modem.demodulator.set_decode_mode(modes_to_decode)
|
||||
|
||||
return self.speed_level
|
||||
|
||||
def abort_transmission(self):
|
||||
self.log("Aborting transmission... setting abort flag")
|
||||
self.abort = True
|
||||
|
||||
def send_stop_ack(self, stop_frame):
|
||||
stop_ack = self.frame_factory.build_arq_stop_ack(self.id)
|
||||
self.launch_transmit_and_wait(stop_ack, self.TIMEOUT_CONNECT, mode=FREEDV_MODE.signalling)
|
||||
self.set_state(IRS_State.ABORTED)
|
||||
self.states.setARQ(False)
|
||||
self.event_manager.send_arq_session_finished(
|
||||
False, self.id, self.dxcall, False, self.state.name, statistics=self.calculate_session_statistics(self.received_bytes, self.total_length))
|
||||
return None, None
|
||||
|
||||
def transmission_failed(self, irs_frame=None):
|
||||
# final function for failed transmissions
|
||||
self.session_ended = time.time()
|
||||
self.set_state(IRS_State.FAILED)
|
||||
self.log("Transmission failed!")
|
||||
self.event_manager.send_arq_session_finished(True, self.id, self.dxcall,False, self.state.name, statistics=self.calculate_session_statistics(self.received_bytes, self.total_length))
|
||||
self.states.setARQ(False)
|
||||
return None, None
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue