Porównaj commity
1264 Commity
Autor | SHA1 | Data |
---|---|---|
Roeland Jansen | 85178de1d5 | |
Roeland Jansen | c7e8e90928 | |
Phil Taylor | 9db9be5dec | |
Phil Taylor | 637e94dfd8 | |
Elliott Liggett | b68874d328 | |
AsciiWolf | e5c6e2030a | |
AsciiWolf | 9be0acdf2e | |
Roeland Jansen | 1805861274 | |
Phil Taylor | b4c079f7f4 | |
Phil Taylor | 80d6614ca7 | |
Phil Taylor | 7df55bc41f | |
Roeland Jansen | 54b00b9022 | |
Roeland Jansen | 0c072f4665 | |
M0VSE | 6fc63df321 | |
M0VSE | d5e4a67ac1 | |
Roeland Jansen | c7e53f6a4d | |
Roeland Jansen | 375acead89 | |
Phil Taylor | e274271820 | |
Phil Taylor | 34655f6348 | |
Phil Taylor | 8ad74ac7f6 | |
Phil Taylor | 5d897c127a | |
Phil Taylor | 3c90a9363b | |
Phil Taylor | c392874b3b | |
Phil Taylor | 7b101d5ad4 | |
Phil Taylor | 6a2e2024ef | |
Phil Taylor | d941c831f3 | |
Phil Taylor | f35ac25ad3 | |
Phil Taylor | 58f38c1735 | |
Phil Taylor | 59bc2077e4 | |
Phil Taylor | 8b786c1821 | |
Phil Taylor | 5ded2a0e79 | |
M0VSE | c9300f4d86 | |
Roeland Jansen | 1549819ad6 | |
M0VSE | 4874d70215 | |
Phil Taylor | d5f0e0f63c | |
Phil Taylor | 1da3696ba2 | |
Phil Taylor | 289170e22d | |
Phil Taylor | ae0271c7f9 | |
Phil Taylor | a0f3f7de07 | |
Phil Taylor | 0304ac67f6 | |
Phil Taylor | 468cd2de0d | |
Phil Taylor | 592c583d19 | |
Phil Taylor | cadf835d95 | |
Phil Taylor | 75288b5e78 | |
Phil Taylor | 6fd0d4e763 | |
Phil Taylor | 03c45663dc | |
Phil Taylor | ac58289188 | |
Phil Taylor | d7ba3d9d9a | |
Phil Taylor | d1688a99c5 | |
Phil Taylor | a74f68d9d4 | |
Phil Taylor | f663550da7 | |
M0VSE | 9d63c5cb71 | |
Phil Taylor | eb65ae0b1c | |
Phil Taylor | 898d5960d8 | |
Phil Taylor | a461babf73 | |
Phil Taylor | 792fac11d6 | |
Phil Taylor | f3a25fade4 | |
Roeland Jansen | 3fa14ebc37 | |
Roeland Jansen | 6049e1dee6 | |
Roeland Jansen | e8ca39ca18 | |
Phil Taylor | 48a9710c11 | |
Phil Taylor | 5328838c73 | |
Phil Taylor | 87a261be12 | |
Phil Taylor | b738a38cbe | |
Phil Taylor | b2af4c898e | |
Phil Taylor | cf4e872e4f | |
Phil Taylor | 37c30404d8 | |
Phil Taylor | 5b9a5baaa1 | |
Roeland Jansen | fc0bd2232d | |
Phil Taylor | 2186a74490 | |
Phil Taylor | b9288175bd | |
Phil Taylor | 7901ceba79 | |
Phil Taylor | e49eb2878e | |
Phil Taylor | c8b97e8952 | |
Phil Taylor | a6b1bce69e | |
Phil Taylor | e6644cdc11 | |
Phil Taylor | fe0fc0a5bb | |
Phil Taylor | 5fb9588c4a | |
Phil Taylor | 62f923d8ad | |
Phil Taylor | 9f97df0ea5 | |
Phil Taylor | ed1e8c3a8e | |
Phil Taylor | acec37ea17 | |
Phil Taylor | 428ea9156c | |
Phil Taylor | 18254cad10 | |
Phil Taylor | 67685dc100 | |
Phil Taylor | 71fd70f78b | |
Phil Taylor | f9ca8e7e84 | |
Phil Taylor | acaed4601e | |
Phil Taylor | 1177fa3419 | |
Phil Taylor | 97ed3f6f42 | |
Phil Taylor | e594efdbbc | |
Phil Taylor | 3ce35500b9 | |
Phil Taylor | 9eb89a9cb1 | |
Phil Taylor | fd6d132fd3 | |
Phil Taylor | 6f1d4e24b7 | |
Phil Taylor | 8b7377f096 | |
Roeland Jansen | ce9e3e9f2c | |
Phil Taylor | dd4770a85f | |
Phil Taylor | 32e662237b | |
Phil Taylor | 3582c95c58 | |
Phil Taylor | da71bb3bc3 | |
Phil Taylor | 0397b4b68a | |
Phil Taylor | feac83b997 | |
Phil Taylor | 9afc661c4c | |
Phil Taylor | 6de14df1da | |
Phil Taylor | 7e89e9f457 | |
Phil Taylor | 3cb54f9cd7 | |
Phil Taylor | 80838e4f82 | |
Phil Taylor | 54c75d5def | |
Phil Taylor | 8b59efbf33 | |
Phil Taylor | 9db0698927 | |
Phil Taylor | 251239e445 | |
Phil Taylor | a3a82cd442 | |
Phil Taylor | 3461a0e5e5 | |
Phil Taylor | 3429786e10 | |
Phil Taylor | c2e9100a40 | |
Phil Taylor | c8a29bb400 | |
Phil Taylor | fa47bfb4bc | |
Phil Taylor | 82425b445e | |
Phil Taylor | 7aa7546660 | |
Phil Taylor | 4e61a455b5 | |
Phil Taylor | cad3bb9418 | |
Phil Taylor | 1f1800f046 | |
Phil Taylor | 5ed380c03d | |
Phil Taylor | a8951813f5 | |
Roeland Jansen | 6e5e22a672 | |
Elliott Liggett | bbbfe38e0b | |
Roeland Jansen | 6a76efa368 | |
Phil Taylor | 17a6547264 | |
Phil Taylor | 1515e135e5 | |
Phil Taylor | 0ff2551026 | |
Phil Taylor | cb339edc1f | |
Phil Taylor | f4a86ac02e | |
Phil Taylor | b783f8057a | |
Phil Taylor | 1064038138 | |
Phil Taylor | dda3c8ee9d | |
Phil Taylor | 63b8a356e8 | |
Phil Taylor | 4fc37290e5 | |
Phil Taylor | 72e8bc6fa6 | |
Phil Taylor | 8a28a89760 | |
Phil Taylor | 5298949f3a | |
Phil Taylor | 1e217bc5b8 | |
Phil Taylor | fbea7f1a13 | |
Dawid Szymanski - SQ6EMM/SN6M | 32639c5394 | |
Roeland Jansen | b373685fd3 | |
Roeland Jansen | ea1fefc219 | |
Phil Taylor | 3d2c16201b | |
Roeland Jansen | 6e0ebcdd5a | |
Roeland Jansen | 2488d62f2e | |
Phil Taylor | 40f49c56e7 | |
Phil Taylor | 529d101540 | |
Phil Taylor | 14a4b25e97 | |
Phil Taylor | 78fa248963 | |
Phil Taylor | 7de515b70d | |
Phil Taylor | 76694361b5 | |
Phil Taylor | 0e28f288ff | |
Phil Taylor | b619be3e2a | |
Phil Taylor | 64c0007c78 | |
Phil Taylor | b566a760ed | |
Phil Taylor | 69afce7712 | |
Phil Taylor | 5654fa524e | |
Phil Taylor | 3d053c3c96 | |
Phil Taylor | 872c8aee30 | |
Phil Taylor | e2d6101a53 | |
Phil Taylor | b35e1af185 | |
Phil Taylor | a60cb39483 | |
Phil Taylor | 6464f28159 | |
Phil Taylor | 7b7d21d3a7 | |
Roeland Jansen | 16eb4591eb | |
Roeland Jansen | eae5b93f0e | |
Phil Taylor | 02c1dcdb8b | |
Phil Taylor | f4e772e1c7 | |
Phil Taylor | 4a3f8692b5 | |
Phil Taylor | 2ec62b6c80 | |
Phil Taylor | 01e01321d3 | |
Phil Taylor | f0c8842c05 | |
Phil Taylor | 979dffc176 | |
Phil Taylor | 2128e6fc10 | |
Phil Taylor | fd86efa4b9 | |
Phil Taylor | 27ac972f4a | |
Elliott Liggett | bb632b8e2b | |
Roeland Jansen | 11f8cb702f | |
Roeland Jansen | 82f268508e | |
Phil Taylor | a1db8d1d60 | |
Roeland Jansen | a08ada4cab | |
Phil Taylor | 5671e26591 | |
Roeland Jansen | 953399de06 | |
Phil Taylor | 903d1965ba | |
Phil Taylor | eae1fc725e | |
Phil Taylor | 857c62f99f | |
Phil Taylor | 7b2aca6cbd | |
Phil Taylor | f3da86940f | |
Roeland Jansen | c70bbb0998 | |
Phil Taylor | d7fa78fcd2 | |
Roeland | 0af2615833 | |
Phil Taylor | b928f23051 | |
Roeland Jansen | 41aa95129a | |
Roeland Jansen | 072ab1967f | |
Phil Taylor | 75b86a0e4a | |
Phil Taylor | b334241ea0 | |
Phil Taylor | a511282505 | |
Phil Taylor | 4ea80af164 | |
Phil Taylor | 80dc5d2df6 | |
Phil Taylor | 3993046771 | |
Phil Taylor | 06c3e33b4d | |
Phil Taylor | d0cd7e2eb3 | |
Phil Taylor | 5237c930e5 | |
Phil Taylor | 777d08ef24 | |
Phil Taylor | c4fcc89830 | |
Phil Taylor | 193e07284e | |
Phil Taylor | 6c44ea9e0d | |
Phil Taylor | ad50d41071 | |
Phil Taylor | 1ac8f5c2f0 | |
Phil Taylor | e20900dbcf | |
Phil Taylor | a192e6643b | |
Phil Taylor | c6bd93a2b6 | |
Phil Taylor | 4816427954 | |
Elliott Liggett | 156f45d42f | |
Elliott Liggett | bbcc0e4842 | |
Elliott Liggett | 4411c1480e | |
Phil Taylor | 5fa14d13e1 | |
Phil Taylor | 180b7b0095 | |
Phil Taylor | 071606fe5a | |
Phil Taylor | 096c223715 | |
Elliott Liggett | eaf7eb126e | |
Elliott Liggett | 38e6cb2ef4 | |
Elliott Liggett | 3c31687b15 | |
Elliott Liggett | a5f3750988 | |
Elliott Liggett | eea17e5e09 | |
Elliott Liggett | d503df03bd | |
Elliott Liggett | 17d6493338 | |
Elliott Liggett | cb7cf6c218 | |
Phil Taylor | 2696d2c8d9 | |
Phil Taylor | 63a7ec0a21 | |
Phil Taylor | 28c9d1beb2 | |
Phil Taylor | db9f6cb933 | |
Phil Taylor | 2c9c304844 | |
Roeland Jansen | b2915b9f5e | |
Roeland Jansen | 3bdee82501 | |
Roeland Jansen | 199295b6ae | |
Roeland Jansen | c89a7bde97 | |
Phil Taylor | 0b37b1f984 | |
Elliott Liggett | fc4bce8cd7 | |
Elliott Liggett | 5d5723fae6 | |
Elliott Liggett | eec2fff4ce | |
Phil Taylor | db686b1c8b | |
Phil Taylor | 5afcfb4e1d | |
Phil Taylor | cb81bef3bc | |
Phil Taylor | db5f8a0893 | |
Phil Taylor | 479671386e | |
Elliott Liggett | 3faf8d331c | |
Phil Taylor | 3ca82deb6c | |
Phil Taylor | 8d8f945cfa | |
Phil Taylor | 12f7a9cf0a | |
Phil Taylor | 9d711f3a17 | |
Elliott Liggett | 78ec6b1423 | |
Phil Taylor | 0e9a26dbc1 | |
Elliott Liggett | de9f510d76 | |
Elliott Liggett | 2d1773720d | |
Dawid SQ6EMM | e13d85489d | |
Phil Taylor | 5926c12a20 | |
Phil Taylor | c55ff52fc0 | |
Phil Taylor | 79decc552b | |
Phil Taylor | 5d5ea25fb6 | |
Phil Taylor | 7e7dceaa99 | |
Phil Taylor | 166905ee76 | |
Elliott Liggett | c91d06fea5 | |
Elliott Liggett | bc508565ee | |
Elliott Liggett | 264badb9c9 | |
Elliott Liggett | 6976120d48 | |
Elliott Liggett | 8903c7cb07 | |
Elliott Liggett | b30230fc05 | |
Elliott Liggett | 2c6884d827 | |
Elliott Liggett | 404d6ddceb | |
Elliott Liggett | f660681c53 | |
Phil Taylor | eb782da010 | |
Phil Taylor | fa41571eca | |
Phil Taylor | 5d0a95cd00 | |
Phil Taylor | 5dc9107f00 | |
Phil Taylor | 5128ea0e69 | |
Roeland Jansen | 5db5f40ed4 | |
Roeland Jansen | fe36ff1ac7 | |
Phil Taylor | 49501273d0 | |
Phil Taylor | ea46d6c625 | |
Roeland Jansen | 13f06e1dd7 | |
Phil Taylor | ded6bd1ade | |
Phil Taylor | 4b40e20d98 | |
Roeland Jansen | 6df7099cb3 | |
Phil Taylor | 5483732766 | |
Phil Taylor | 8b8668a6de | |
Roeland Jansen | bc0f9f01c7 | |
Elliott Liggett | be0aee6183 | |
Roeland Jansen | 85e38d940f | |
Elliott Liggett | 4fea16c356 | |
Elliott Liggett | ae64677c42 | |
Phil Taylor | a1d3f93dad | |
Roeland Jansen | 7f97625e38 | |
Elliott Liggett | d53c9b028e | |
Elliott Liggett | 0a7f6b0b65 | |
Roeland Jansen | 2df82d4f92 | |
Phil Taylor | 152f71c6cf | |
Phil Taylor | 2ada127124 | |
Phil Taylor | 646ee94341 | |
Elliott Liggett | 68ed3dc56b | |
Elliott Liggett | 4137fce70c | |
Phil Taylor | 7ebfcdefbd | |
Phil Taylor | b89ae0459e | |
Phil Taylor | 525f984f01 | |
Phil Taylor | 9c3ca568ed | |
Phil Taylor | 4e89c91f0d | |
Phil Taylor | f93dd83268 | |
Phil Taylor | 9349679e5b | |
Elliott Liggett | cba4ad345b | |
Elliott Liggett | 3297f66208 | |
Elliott Liggett | 294fac72c6 | |
Elliott Liggett | 27dd547e2a | |
Elliott Liggett | a205e003c9 | |
Elliott Liggett | 4a0f5a7f59 | |
Phil Taylor | e7091cb2c2 | |
Elliott Liggett | deade9a38e | |
Elliott Liggett | c5c01cf81d | |
Elliott Liggett | 7a4f708e68 | |
Elliott Liggett | c26409b2a8 | |
Elliott Liggett | ff50718563 | |
Elliott Liggett | 2f098be1a9 | |
Elliott Liggett | f772801b02 | |
Elliott Liggett | fd1dc860c1 | |
Elliott Liggett | 5664842f47 | |
Elliott Liggett | b3258c57a5 | |
Elliott Liggett | 23c191a091 | |
Phil Taylor | 93d20cc1ba | |
Phil Taylor | ee4f00e153 | |
Elliott Liggett | 1e79d5d32d | |
Roeland Jansen | 18281798d6 | |
Phil Taylor | c98d7094f9 | |
Phil Taylor | 037f05297d | |
Elliott Liggett | 71326cd385 | |
Elliott Liggett | b99249692f | |
Elliott Liggett | e9e042fad8 | |
Phil Taylor | f224068ec1 | |
Phil Taylor | e94c3e53c5 | |
Elliott Liggett | 0c27bf7208 | |
Elliott Liggett | 05295acc6f | |
Elliott Liggett | fc753ffd39 | |
Elliott Liggett | f820d289b2 | |
Elliott Liggett | 9c0aea0102 | |
Phil Taylor | a0f7d5d86f | |
Phil Taylor | a6ea32d996 | |
Phil Taylor | 450ccefb60 | |
Phil Taylor | ca84555c15 | |
Phil Taylor | 8fca384135 | |
Phil Taylor | 794a8b71c8 | |
Phil Taylor | 3baf3eae65 | |
Phil Taylor | ffd7abb376 | |
Phil Taylor | 120899e9f8 | |
Roeland Jansen | b468c514df | |
Roeland Jansen | c221fb4a04 | |
Phil Taylor | c3ba36ebfd | |
Phil Taylor | d1ec010e68 | |
Phil Taylor | e2f4646207 | |
Phil Taylor | 19818dc3b3 | |
Phil Taylor | 58f86f029b | |
Phil Taylor | b285b81d72 | |
Phil Taylor | 8f98696bec | |
Phil Taylor | 4f4cd5146c | |
Phil Taylor | cac454cbf6 | |
Phil Taylor | 3e46b5a42d | |
Phil Taylor | 41e5e4b1c3 | |
Phil Taylor | d48bdfa6e2 | |
Phil Taylor | 4ee8741157 | |
Phil Taylor | efb953a8c9 | |
Roeland Jansen | 596607e95a | |
Phil Taylor | 5c210f0699 | |
Phil Taylor | 996c813251 | |
Phil Taylor | 8f5016a2f4 | |
Roeland Jansen | f86a721a05 | |
Roeland Jansen | 7052dbf6ef | |
Phil Taylor | 5d2089c3e1 | |
Phil Taylor | 460641972a | |
Phil Taylor | b2d62dab3c | |
Phil Taylor | c0a70ae0b7 | |
Phil Taylor | b45bf9ce41 | |
Phil Taylor | 0d9a01cd1f | |
Phil Taylor | 93f18fae9e | |
Roeland Jansen | 29897e5795 | |
Roeland Jansen | 1deaec3720 | |
Phil Taylor | b7f83a7807 | |
Phil Taylor | 8a4b9f1934 | |
Phil Taylor | 03ebafae5b | |
Phil Taylor | 00a5c78e98 | |
Phil Taylor | d9ff07486d | |
Phil Taylor | 4a8de3c0c9 | |
Phil Taylor | 3e149ee742 | |
Phil Taylor | 373b670482 | |
Phil Taylor | 7bb5e0f9c5 | |
Phil Taylor | 322cc54ad6 | |
Phil Taylor | 4e62f365cd | |
Roeland Jansen | 24ac791ad4 | |
Roeland Jansen | 5dabcb34df | |
Phil Taylor | 39dacf54f2 | |
Phil Taylor | e6a5152734 | |
roeland jansen | b3f76fce67 | |
Phil Taylor | 436428b3a9 | |
Phil Taylor | a327b2690e | |
Phil Taylor | 7348fc2ed0 | |
Phil Taylor | ff6dd19e53 | |
Phil Taylor | db0112c8a3 | |
Phil Taylor | 6199c648c1 | |
Phil Taylor | 32528a02b8 | |
Phil Taylor | 91ce6040e0 | |
Dawid SQ6EMM | 8ead34b253 | |
Phil Taylor | 891fd68a52 | |
Phil Taylor | f4c455396a | |
Phil Taylor | a89af116e9 | |
Phil Taylor | aabe690bae | |
Phil Taylor | 14fc07905e | |
Phil Taylor | 0aca239b2a | |
Phil Taylor | 2bf14520d8 | |
Phil Taylor | 0b51d5bb05 | |
Phil Taylor | 72e8b2ec2f | |
Phil Taylor | c3fa41490a | |
Phil Taylor | 978a444759 | |
Dawid Szymański | 3aa4375bdf | |
Phil Taylor | 7dfdc5e3e2 | |
Phil Taylor | f232ff2396 | |
Phil Taylor | d9e839b6f2 | |
Roeland Jansen | 0d2d9ba23e | |
Roeland Jansen | 28d3c55e2f | |
Elliott Liggett | 093e6e799c | |
Roeland Jansen | ac3894f897 | |
Phil Taylor | e8142a11ba | |
Phil Taylor | 3bf095020c | |
Phil Taylor | e56294406a | |
Roeland Jansen | 8d3406a8ae | |
Elliott Liggett | f6b1428267 | |
Elliott Liggett | 82dfb99822 | |
Elliott Liggett | 8cfb3f70e5 | |
Phil Taylor | 0b39b91665 | |
Phil Taylor | 65ba69822a | |
Phil Taylor | f91dea47b5 | |
Roeland Jansen | 9367d605f4 | |
Roeland Jansen | 508f870062 | |
Roeland Jansen | 06e4567b12 | |
Phil Taylor | 4c76cf6320 | |
Phil Taylor | 0a1a7b2336 | |
Elliott Liggett | 8a1347f17c | |
Elliott Liggett | 4f6da00206 | |
Elliott Liggett | 39caf41905 | |
Elliott Liggett | b25c8a93f3 | |
Phil Taylor | 81f9e9bfcf | |
Phil Taylor | 7138ef69ea | |
Phil Taylor | 1fe19166f3 | |
Elliott Liggett | afcf087a70 | |
Elliott Liggett | e1e9167622 | |
Elliott Liggett | 9a63a6c4c4 | |
Elliott Liggett | bb5ae03235 | |
Elliott Liggett | 45244c7c64 | |
Elliott Liggett | a5ac42fe57 | |
Elliott Liggett | 9da6f77078 | |
Roeland Jansen | b57e4ebd76 | |
Phil Taylor | a1252eec1e | |
Phil Taylor | 29060eb43e | |
Phil Taylor | 209c5a9b0b | |
Phil Taylor | b46dfb8f3e | |
Phil Taylor | d47b9be7e1 | |
Phil Taylor | f7c532007e | |
Phil Taylor | 429aba7d68 | |
Phil Taylor | e49e00c3ad | |
Phil Taylor | bf340e7790 | |
Phil Taylor | 3a07830d47 | |
Phil Taylor | 5ffbfc0f1d | |
Roeland Jansen | 103dc9c86a | |
Phil Taylor | 034ec90662 | |
Phil Taylor | ce9ab6d7ff | |
Phil Taylor | e2338edde6 | |
Phil Taylor | 16dd595f52 | |
Phil Taylor | eb363db09c | |
Phil Taylor | b1b6f26b7d | |
Phil Taylor | 4452e0127b | |
Phil Taylor | b0ebed916c | |
Phil Taylor | 1c8f08a077 | |
Phil Taylor | 91b4ab9f94 | |
Phil Taylor | 75aab75b46 | |
Phil Taylor | d753315c55 | |
Phil Taylor | 6a0997017e | |
Phil Taylor | 6255542ee1 | |
Phil Taylor | a2c348f646 | |
Phil Taylor | ba07c26a2b | |
Phil Taylor | 2dad92b9f6 | |
Phil Taylor | 45c7429c40 | |
Phil Taylor | ecd93b3841 | |
Phil Taylor | 09cd0ba541 | |
Phil Taylor | eff37c586c | |
Phil Taylor | 710566715b | |
Phil Taylor | 358375be8a | |
Phil Taylor | 677eb3c944 | |
Roeland Jansen | 6609aa58d9 | |
Phil Taylor | 3f84e093be | |
Roeland Jansen | ce12135c4a | |
Phil Taylor | 066af2d9b8 | |
Phil Taylor | 3dd1d7bb9a | |
Phil Taylor | cb0ace1a51 | |
Phil Taylor | 5d0f6e82c5 | |
Roeland Jansen | faf5caefa3 | |
Phil Taylor | e15496e0b6 | |
Roeland Jansen | f8cef53a7d | |
Phil Taylor | f4e25d2273 | |
Phil Taylor | 3ec54f1ebb | |
Phil Taylor | 6862009e94 | |
Phil Taylor | b7bcd58578 | |
Phil Taylor | 22abc65849 | |
Phil Taylor | cc6e4d042a | |
Phil Taylor | f7e2631ee3 | |
Phil Taylor | 62d9ec2442 | |
Phil Taylor | fa9cab1374 | |
Phil Taylor | 880a9c3da0 | |
Phil Taylor | b110ce7fbb | |
Phil Taylor | 4d158f5078 | |
Phil Taylor | f31aadd991 | |
Phil Taylor | b011165d5b | |
Phil Taylor | 9fdc09c5be | |
Phil Taylor | 855438e843 | |
Phil Taylor | 711b86e91b | |
Phil Taylor | 9db695cc63 | |
Phil Taylor | 2d58ea2afb | |
Phil Taylor | 4db4510501 | |
Phil Taylor | 3b1efbd69e | |
Phil Taylor | e6713dc516 | |
Phil Taylor | 7d141608bf | |
Roeland Jansen | d5aeec61ba | |
Phil Taylor | f48e65d147 | |
Phil Taylor | e7e7821f2b | |
Phil Taylor | dbc13a0533 | |
Phil Taylor | cef827f7f8 | |
Phil Taylor | f7bde0fe64 | |
Phil Taylor | bbc2613942 | |
Phil Taylor | 0bb57a859d | |
Phil Taylor | 1193841103 | |
Phil Taylor | b3756391c1 | |
Phil Taylor | 242d39ce39 | |
Phil Taylor | 51878e5a3d | |
Phil Taylor | b7af0d4edd | |
Phil Taylor | 518e1700ae | |
Phil Taylor | 87ebd0cdcc | |
Phil Taylor | 08b6224471 | |
Phil Taylor | ac0771f034 | |
Elliott Liggett | 65b4a9cc17 | |
Phil Taylor | 0c530303c7 | |
Phil Taylor | 9d9a817f97 | |
Phil Taylor | d8e244f33a | |
Roeland Jansen | 70101487a3 | |
Roeland Jansen | e93086f6ac | |
Roeland Jansen | fe459fa736 | |
Phil Taylor | 6e7d006b0e | |
Roeland Jansen | 32d214b24f | |
Roeland Jansen | dfcbc3b2f6 | |
Phil Taylor | f838e22498 | |
Roeland Jansen | 70fbd989f1 | |
M0VSE | d9190a5060 | |
Phil Taylor | b6d9c26ebb | |
Phil Taylor | 5216e78390 | |
Phil Taylor | c62d8832f1 | |
Phil Taylor | 7fc3a7bf79 | |
Phil Taylor | 26510c4166 | |
Phil Taylor | 3ee237e736 | |
Roeland Jansen | 1aff6b9cb6 | |
Elliott Liggett | 79531712b0 | |
Elliott Liggett | 9c49395fa0 | |
Elliott Liggett | 11c1d2f5df | |
Roeland Jansen | 71537c21e1 | |
Roeland Jansen | 12ea418f1e | |
Phil Taylor | 3dbfc94419 | |
Phil Taylor | 2f35e009e9 | |
Phil Taylor | eda1f9fcd3 | |
Phil Taylor | ae6f1f2ca3 | |
Roeland Jansen | 9ecdd02246 | |
Elliott Liggett | cd3320ec9a | |
Roeland Jansen | 6b44b81a2a | |
Roeland Jansen | 520b1d051a | |
Phil Taylor | 31969d0647 | |
Phil Taylor | 722e5b3c35 | |
Phil Taylor | 5ccc2fc375 | |
Elliott Liggett | 881823a839 | |
Phil Taylor | e6506cd55f | |
Elliott Liggett | bb2593be6f | |
Phil Taylor | c87dc5ef73 | |
Phil Taylor | 546330e829 | |
Phil Taylor | 4bd80cb9e5 | |
Phil Taylor | 079f03886f | |
Phil Taylor | a04f7f57d3 | |
Roeland Jansen | 8638086f3f | |
Roeland Jansen | c09c548eb0 | |
Roeland Jansen | 6dd7cc0247 | |
Elliott Liggett | a7ba906569 | |
Phil Taylor | 0c22f81690 | |
Phil Taylor | 4dcc7c4753 | |
Phil Taylor | 77f48f5f0c | |
Phil Taylor | da0644f1ba | |
Phil Taylor | fca463f346 | |
Elliott Liggett | 0735c35cec | |
Roeland Jansen | b13de1d9ee | |
Roeland Jansen | 81607a8069 | |
Elliott Liggett | cf50e9d18a | |
Elliott Liggett | aba2928d42 | |
Phil Taylor | 7e2682170f | |
Elliott Liggett | 221086ed4f | |
Elliott Liggett | b54f724ce9 | |
Elliott Liggett | 6ed1a8e80e | |
Elliott Liggett | de46d62346 | |
Elliott Liggett | d90104a79e | |
Elliott Liggett | b849df9f7f | |
Elliott Liggett | 940f5fb961 | |
Elliott Liggett | d4ae4181ed | |
Elliott Liggett | 4e3af7cccd | |
Elliott Liggett | c5f626065a | |
Elliott Liggett | d25c9ca245 | |
Elliott Liggett | 2225953d03 | |
Elliott Liggett | 95994ed737 | |
Elliott Liggett | 42e3ee444d | |
Elliott Liggett | 73b9c88e5e | |
Elliott Liggett | 1b6cff85f4 | |
Elliott Liggett | 8b688548c2 | |
Elliott Liggett | 3290b64a0b | |
Roeland Jansen | c97f0f7826 | |
Elliott Liggett | 2198e3ca19 | |
Elliott Liggett | a1d2f57d8b | |
Elliott Liggett | d658e21220 | |
Roeland Jansen | 7145de132f | |
Roeland Jansen | b91c033ef6 | |
Roeland Jansen | 76389c0fbd | |
Elliott Liggett | 39cf34af23 | |
Elliott Liggett | 4f4abfc41a | |
Elliott Liggett | 0f226fdca5 | |
Elliott Liggett | 9623226b0c | |
Elliott Liggett | fbf1c4bce9 | |
Elliott Liggett | 4ea7ec8090 | |
Elliott Liggett | d90eb6f11f | |
Roeland Jansen | be95896205 | |
Roeland Jansen | 25d3d40548 | |
Elliott Liggett | 2afcef5ef0 | |
Elliott Liggett | f7e787e097 | |
Elliott Liggett | 9fb74ed6e1 | |
Elliott Liggett | d3493988af | |
Elliott Liggett | c989dba9f9 | |
Elliott Liggett | 31f62fde67 | |
Elliott Liggett | 1471567808 | |
Elliott Liggett | b8ba9a16dc | |
Roeland Jansen | 173a311c7a | |
Elliott Liggett | 7e80eb0ed7 | |
Elliott Liggett | d49b6cd082 | |
Elliott Liggett | 8012a58bd9 | |
Elliott Liggett | 2a456b2adb | |
Elliott Liggett | fa0752518d | |
Elliott Liggett | 9afc44ce42 | |
Elliott Liggett | da1cd8b14f | |
Elliott Liggett | 6f7f1f84ec | |
Phil Taylor | d1ffbe84f5 | |
Elliott Liggett | 59d2d735e6 | |
Elliott Liggett | 7e4d3f76d5 | |
Elliott Liggett | 92904f8ae5 | |
Elliott Liggett | 952585261c | |
Elliott Liggett | d11ae30b1a | |
Elliott Liggett | 1388c756c4 | |
Elliott Liggett | 16237a47f5 | |
Elliott Liggett | a84fb6d091 | |
Elliott Liggett | 93b1af6eb6 | |
Elliott Liggett | ecb9b8fbb4 | |
Elliott Liggett | 2a509a6177 | |
Elliott Liggett | ac8a98049e | |
Elliott Liggett | b77715e0d8 | |
Phil Taylor | 4574e2b7b2 | |
Elliott Liggett | 4b0ce8537e | |
Elliott Liggett | 1423e7c300 | |
Roeland Jansen | 105675a128 | |
Roeland Jansen | bd097dc3a5 | |
Elliott Liggett | ab65f8e38d | |
Elliott Liggett | 088d34af8b | |
Elliott Liggett | d8dc9daa15 | |
Elliott Liggett | a947ba6b18 | |
Elliott Liggett | 0e15e8ec1e | |
Elliott Liggett | 1d14d9520e | |
Elliott Liggett | ebb2c59a45 | |
Elliott Liggett | a7587963b0 | |
Elliott Liggett | c59050e632 | |
Elliott Liggett | 7093ac104a | |
Elliott Liggett | f1f58a10cf | |
Elliott Liggett | 7639880861 | |
Elliott Liggett | b2204b3519 | |
Elliott Liggett | 5215984de8 | |
Elliott Liggett | 123084c779 | |
Elliott Liggett | 03a279087e | |
Elliott Liggett | 6ebb3b7680 | |
Elliott Liggett | 32d55ef490 | |
Elliott Liggett | 53bed16b45 | |
Elliott Liggett | 02e6733cdd | |
Elliott Liggett | c753089ac7 | |
Phil Taylor | 9a77baaead | |
Phil Taylor | ac2c104209 | |
Phil Taylor | bcc16b8d29 | |
Phil Taylor | a9e9c80708 | |
roeland jansen | 5dcb0819a8 | |
Roeland Jansen | d6263223af | |
Roeland Jansen | 485d012b42 | |
Roeland Jansen | b4f379d52d | |
Phil Taylor | 06017148dd | |
Phil Taylor | 5cb81b412f | |
Phil Taylor | 227ea8fc34 | |
Phil Taylor | c807c40bd2 | |
Phil Taylor | f2e9a90000 | |
Phil Taylor | cd03ac40d5 | |
Phil Taylor | 62c48ac2c2 | |
Roeland Jansen | 49773706d7 | |
Phil Taylor | 05f7283930 | |
Phil Taylor | 079a50980d | |
Phil Taylor | 5ef50c9706 | |
Phil Taylor | 8f5ba2efb1 | |
Phil Taylor | b3f611543c | |
Phil Taylor | bcde69c92b | |
Phil Taylor | 2e1d88c194 | |
Phil Taylor | d468e01d20 | |
Phil Taylor | 1305f06eab | |
Phil Taylor | b049704dd2 | |
Phil Taylor | 6078a31a3c | |
Phil Taylor | f003a8a1b8 | |
Phil Taylor | 630d2eaba3 | |
Phil Taylor | e248882e31 | |
Phil Taylor | 466571a758 | |
Phil Taylor | b95cf79318 | |
Phil Taylor | 50eb498026 | |
Phil Taylor | 28a3209c21 | |
Phil Taylor | 35d54468cd | |
Phil Taylor | 30073d07d7 | |
Phil Taylor | c1461947eb | |
Phil Taylor | 30f9574dd6 | |
Phil Taylor | 48562aa23b | |
Phil Taylor | 8fd8fa390e | |
Phil Taylor | 22692a6897 | |
Phil Taylor | 647b51449b | |
Phil Taylor | f0cec0279c | |
Phil Taylor | c84cc4330d | |
Phil Taylor | 129348c12f | |
Phil Taylor | 19c16daa06 | |
Phil Taylor | b78f613ef2 | |
Roeland Jansen | 2b9d163613 | |
Phil Taylor | 6a5f9ce988 | |
Roeland Jansen | dccafb9ffb | |
M0VSE | 1aa45dc849 | |
Roeland Jansen | a7671b403d | |
Phil Taylor | 0b74407448 | |
Phil Taylor | 4f4527bc6b | |
Roeland Jansen | 9285809819 | |
Phil Taylor | e75c4873f0 | |
Phil Taylor | 6043e857e1 | |
Phil Taylor | 11c0973897 | |
Phil Taylor | b28dab4dc2 | |
Phil Taylor | a855c931b6 | |
Phil Taylor | c90611e444 | |
Phil Taylor | 717b71ad4c | |
Phil Taylor | 2e433ed71f | |
Phil Taylor | e6ff2ae152 | |
Phil Taylor | 5eb294dc6d | |
Phil Taylor | 728f01abf4 | |
Phil Taylor | cd1f0f0ba9 | |
Phil Taylor | 33211908bc | |
Phil Taylor | 95ec59d46d | |
Phil Taylor | 6b7387cba2 | |
Phil Taylor | 92b43e8fa2 | |
Phil Taylor | 578b993f70 | |
Phil Taylor | 44f6ec2740 | |
Roeland Jansen | eded80efe8 | |
Phil Taylor | 63c5e0257d | |
Phil Taylor | 04ad04757d | |
Phil Taylor | 2fa83028d3 | |
Phil Taylor | 772375fb9d | |
Phil Taylor | 498f5020a5 | |
Phil Taylor | 00f15a059e | |
Phil Taylor | 30ca8aa44a | |
Phil Taylor | fba9c6f207 | |
Roeland Jansen | 5694963450 | |
Phil Taylor | 82faf2c463 | |
Phil Taylor | 8d924b6fda | |
Phil Taylor | bddf283e7a | |
Phil Taylor | ec169ca3d4 | |
Phil Taylor | fda7d21239 | |
Phil Taylor | 3e0c008144 | |
Phil Taylor | 7098564121 | |
Phil Taylor | 75f1f2d6ea | |
Phil Taylor | 135640df44 | |
Phil Taylor | ed3ba16a8d | |
Phil Taylor | ac677db7ac | |
Phil Taylor | 066c1b58ac | |
Phil Taylor | 7cb31cc8af | |
Phil Taylor | b8838e2468 | |
Roeland Jansen | 9ef7fbab0a | |
Phil Taylor | 181c2eeb07 | |
Phil Taylor | 3deb7ab6ec | |
Phil Taylor | 376fe72b49 | |
Phil Taylor | 8e38a8efb2 | |
Roeland Jansen | 3308bbeb54 | |
Phil Taylor | 72bef7f783 | |
Roeland Jansen | ad71e437a2 | |
Phil Taylor | 71f88cdfed | |
Phil Taylor | bbbb36ebae | |
Phil Taylor | 3068b0f5cc | |
Phil Taylor | 05c3433bcd | |
Phil Taylor | 1e51e36c9d | |
Phil Taylor | 0d529b4a1b | |
Phil Taylor | de6db645de | |
Phil Taylor | 550407e0a9 | |
Phil Taylor | 9118d8f4dc | |
Phil Taylor | 78f16b7cff | |
Roeland Jansen | 4125737819 | |
Phil Taylor | 5bd29096e5 | |
Phil Taylor | 8b958d572c | |
Roeland Jansen | 3bc0b4840a | |
Roeland Jansen | 159d757d53 | |
Phil Taylor | bf5f819927 | |
Roeland Jansen | 7956684f7d | |
Phil Taylor | 7924d2b87f | |
Phil Taylor | f662d1f7cd | |
Roeland Jansen | b3068c269d | |
Phil Taylor | 069c5368d8 | |
Phil Taylor | 7b5f29f4fe | |
Phil Taylor | 4b19ed522f | |
Phil Taylor | 24841c5da9 | |
Phil Taylor | cac93198a6 | |
Phil Taylor | 85ba0fb569 | |
Phil Taylor | 3aefa68939 | |
Phil Taylor | bdca404457 | |
Phil Taylor | f42fbc2e54 | |
Roeland Jansen | df2fb20d78 | |
Roeland Jansen | 6826586ef6 | |
Phil Taylor | 105d1782ed | |
Phil Taylor | 2b04b84eb1 | |
Roeland Jansen | f3bb4655ff | |
Phil Taylor | 8d75d01e2c | |
Phil Taylor | 95cace832b | |
Phil Taylor | 8217b12bc2 | |
Phil Taylor | a929b2b8a8 | |
Phil Taylor | 76e52c42db | |
Phil Taylor | fb99a57272 | |
Phil Taylor | 43f423f1ad | |
Phil Taylor | 92288aa768 | |
Phil Taylor | 09ea7a2c26 | |
Phil Taylor | 7bf2f54255 | |
Phil Taylor | fb0d662b40 | |
Phil Taylor | 94bb78af50 | |
Phil Taylor | ab36165d9d | |
Phil Taylor | 8f3a8d38d4 | |
Phil Taylor | 1ba9713408 | |
Phil Taylor | 26ea3bc3dc | |
Phil Taylor | 60824c2055 | |
Phil Taylor | 7130f71335 | |
Phil Taylor | bb6c615b4c | |
Phil Taylor | 2525641e76 | |
Phil Taylor | 5b50127d0b | |
Phil Taylor | d0df7004ac | |
Phil Taylor | ff17a74df8 | |
Phil Taylor | a9c21871b2 | |
Phil Taylor | 382f0951e5 | |
Phil Taylor | b510b70f04 | |
Phil Taylor | 2ec20ce18b | |
Phil Taylor | eb85bb1802 | |
Phil Taylor | 7f003c588e | |
Phil Taylor | 41c5637c30 | |
Phil Taylor | 8fc9c48d5c | |
Phil Taylor | 49d1cc085a | |
Phil Taylor | 3ef1950dc2 | |
Phil Taylor | 120bd7d302 | |
Phil Taylor | 45b01cafb3 | |
Phil Taylor | 96037cd055 | |
Phil Taylor | aa002ed0b5 | |
Phil Taylor | 8ee8d23fb9 | |
Phil Taylor | fad416c5f3 | |
Roeland Jansen | b2d2ee7001 | |
Phil Taylor | 6716a57d5b | |
Roeland Jansen | e0f50f579f | |
Phil Taylor | 86502a5c3a | |
Roeland Jansen | 5685f6aa67 | |
Phil Taylor | d86f8958b2 | |
Roeland Jansen | 4ba435845b | |
Roeland Jansen | c1f9358d38 | |
Roeland Jansen | 2ff36e26b7 | |
Phil Taylor | 4fe9b2cba7 | |
Phil Taylor | a3977395c1 | |
Phil Taylor | e834396387 | |
Roeland Jansen | b67b43d0cb | |
Phil Taylor | 55fdf52436 | |
Phil Taylor | 7542cc3f56 | |
Phil Taylor | b7dee4f5f4 | |
Phil Taylor | 2b588ffce0 | |
Phil Taylor | af2be44919 | |
Phil Taylor | 1e94a97964 | |
Phil Taylor | 718d54ee3b | |
Phil Taylor | 9d63c36e51 | |
Phil Taylor | c0a1000c9a | |
Phil Taylor | 74de3087c8 | |
Phil Taylor | 3296e16195 | |
Phil Taylor | 3815ac67e7 | |
Phil Taylor | ecbac9b48c | |
Phil Taylor | 5ba0a382a9 | |
Phil Taylor | 4ca21b84cb | |
Phil Taylor | 109bdb2812 | |
Phil Taylor | 0a711e5c1f | |
Phil Taylor | e068bc5745 | |
Phil Taylor | 50c8b4e545 | |
Phil Taylor | a108f1ca99 | |
Phil Taylor | 87c0850c5b | |
Phil Taylor | 9e269d774a | |
Phil Taylor | 03b324be1e | |
Phil Taylor | e87719bfd1 | |
Phil Taylor | 1f25ca67e8 | |
Phil Taylor | 2ee20d9415 | |
Phil Taylor | 0aaf5de976 | |
Phil Taylor | 87b1a9f84d | |
Phil Taylor | 23b37f2754 | |
Phil Taylor | 2ca376a3c1 | |
Phil Taylor | 4f5e4addb6 | |
Phil Taylor | 86237afd8b | |
Phil Taylor | c027413ee2 | |
Phil Taylor | 2a889488c4 | |
Phil Taylor | e4d5efb3d5 | |
Phil Taylor | d543f1474d | |
Phil Taylor | a21a44df86 | |
Phil Taylor | 53cae9173d | |
Phil Taylor | 0f7a5566fc | |
Phil Taylor | f00051ecd4 | |
Phil Taylor | cfc22dcb30 | |
Phil Taylor | 833371530a | |
Phil Taylor | 47772a4925 | |
Phil Taylor | 38fdec3da6 | |
Phil Taylor | 45ac1fbe1c | |
Phil Taylor | ac70ba5f83 | |
Phil Taylor | 2cc875ff7c | |
Phil Taylor | 64b4ef2019 | |
Phil Taylor | cf7a947beb | |
M0VSE | 5c2d6e57b2 | |
Phil Taylor | 83c494ecc1 | |
Phil Taylor | 389f661c79 | |
Phil Taylor | 8ec62fec8d | |
Phil Taylor | acf4c1bf63 | |
roeland jansen | e5b975038c | |
Daniele Forsi | 32f438aa01 | |
roeland jansen | 6bd1395c1a | |
Phil Taylor | c5cf0fdf57 | |
Phil Taylor | bfd9ddea52 | |
Russ Woodman - K5TUX | 72663310c5 | |
Phil Taylor | ece5933d55 | |
Philip Kubat | 38274cc3c8 | |
Phil Taylor | 468cfa1303 | |
Phil Taylor | feb5d13d13 | |
Phil Taylor | 824a91b125 | |
Phil Taylor | 21a8bbc1a6 | |
Phil Taylor | 156d03706a | |
Phil Taylor | 46cd6c1e53 | |
Phil Taylor | 9ae8bc660c | |
Phil Taylor | dd9be7c99c | |
Phil Taylor | 30d5858663 | |
Phil Taylor | c0fabb3999 | |
Phil Taylor | 6427be05b8 | |
Phil Taylor | be7dbbb395 | |
Phil Taylor | 9d2801a505 | |
Phil Taylor | 3568c80fcc | |
Phil Taylor | fe7bdf1345 | |
Phil Taylor | d29c1ddba7 | |
Phil Taylor | 3d1e04c556 | |
Phil Taylor | 77b7462f74 | |
Phil Taylor | bce66393fb | |
Phil Taylor | 2f4fe061b3 | |
Phil Taylor | 4a1be30c40 | |
Phil Taylor | 5ae3549ba5 | |
Phil Taylor | 87a36426cf | |
Phil Taylor | 264ad231c0 | |
Phil Taylor | ff47fbd82f | |
Phil Taylor | 1c8bc62fcc | |
Phil Taylor | 45e074783a | |
Phil Taylor | 3ff6e7180a | |
Phil Taylor | 88d2124f35 | |
Phil Taylor | 39540612c7 | |
Phil Taylor | 96de9c55fa | |
Phil Taylor | 41e90bb309 | |
Phil Taylor | fcc7c9a5dc | |
Elliott Liggett | 440429be9f | |
Phil Taylor | e4cc2962b3 | |
Phil Taylor | 4e086ac220 | |
Phil Taylor | 9f059c9e73 | |
Phil Taylor | bcadb2176b | |
Phil Taylor | 55baec6100 | |
Phil Taylor | 4ebfe457c7 | |
Phil Taylor | 387b26187d | |
Phil Taylor | 28812be8be | |
Phil Taylor | b87e0de05d | |
Phil Taylor | b691398f6a | |
Phil Taylor | 914477d1dc | |
Phil Taylor | ada263efed | |
Phil Taylor | a75c6e0fdf | |
Roeland Jansen | 01ea44df0b | |
Phil Taylor | 3abb4bc584 | |
Phil Taylor | b8f6feacbf | |
Phil Taylor | 03799b2cda | |
Phil Taylor | dcb4712740 | |
Phil Taylor | 7d5ec8a065 | |
Phil Taylor | b4884e773a | |
Phil Taylor | 918238835d | |
Phil Taylor | 2eace96be9 | |
Phil Taylor | ed7d88eb1e | |
Phil Taylor | d9a611e118 | |
Phil Taylor | 4a200006eb | |
Phil Taylor | 02f222355e | |
Phil Taylor | 5a9342d9a9 | |
Phil Taylor | 94a89dea33 | |
Phil Taylor | 317cbd640a | |
Phil Taylor | e00b598fd9 | |
Phil Taylor | ce0544ac6a | |
Phil Taylor | 4696fe6824 | |
Phil Taylor | 99ca97370b | |
Phil Taylor | 1291990d54 | |
Phil Taylor | 4fa83b79f0 | |
Phil Taylor | 184af7a582 | |
Phil Taylor | d4868ae14c | |
Phil Taylor | e00fa26229 | |
Phil Taylor | 5a92c071d1 | |
Roeland Jansen | f8351ad610 | |
Roeland Jansen | 9cbcb3aa1c | |
Roeland Jansen | b85c268f2c | |
Roeland Jansen | ae5684238c | |
Roeland Jansen | 0a42db97c2 | |
Roeland Jansen | 942d7beb99 | |
Roeland Jansen | 1667df89ba | |
Roeland Jansen | 91d90f2ae6 | |
Roeland Jansen | 837cc5fa8b | |
Roeland Jansen | eaa7a8edad | |
Roeland Jansen | 809f67ad19 | |
Roeland Jansen | 4f34e54b02 | |
Roeland Jansen | 9dd9293036 | |
Roeland Jansen | d1d0194600 | |
Roeland Jansen | 54d08c5b8a | |
Roeland Jansen | 41ec08ecba | |
Roeland Jansen | a231cb086f | |
Roeland Jansen | d742c7564a | |
Roeland Jansen | 9e87d35069 | |
Roeland Jansen | 91f4269de8 | |
Roeland Jansen | f36f4db497 | |
Phil Taylor | 89ac99b3f9 | |
Phil Taylor | 82b655cc09 | |
Phil Taylor | db702123f9 | |
Phil Taylor | 6e44480d6b | |
Phil Taylor | 3036108d6f | |
Phil Taylor | 7dd325c3ae | |
Phil Taylor | 5a6ff84892 | |
Phil Taylor | 6c29eb12a2 | |
Phil Taylor | 9284a49469 | |
Phil Taylor | 43fb31e8d0 | |
Phil Taylor | 5e2c77b872 | |
Phil Taylor | 3035e5c712 | |
Phil Taylor | 8bf107fadc | |
Phil Taylor | 9b8221b32e | |
Phil Taylor | f77defd9d7 | |
Phil Taylor | a91450c90b | |
Phil Taylor | 1611058f77 | |
Phil Taylor | 18646ab0cf | |
Phil Taylor | fce3a3d6be | |
Phil Taylor | 8ec82e7406 | |
Phil Taylor | 360ceccdc6 | |
Phil Taylor | 93e2c5529e | |
Phil Taylor | 6f719d8961 | |
Phil Taylor | ff6d0a6a65 | |
Phil Taylor | 88ebf74e14 | |
Phil Taylor | 4203b62327 | |
Phil Taylor | 60a1d40549 | |
Phil Taylor | 2a0b79449d | |
Phil Taylor | 7d95f2d863 | |
Phil Taylor | daf94543ac | |
Phil Taylor | cf1caf0a0f | |
Phil Taylor | 2c82561ff5 | |
Phil Taylor | 3f06ab6061 | |
Elliott Liggett | 8dd42ba392 | |
Elliott Liggett | d1165980a9 | |
Elliott Liggett | c8b4992bab | |
Phil Taylor | e1cdcad65b | |
Elliott Liggett | 605a87dec9 | |
Elliott Liggett | 8e95919aa9 | |
Elliott Liggett | 7977de42d9 | |
Elliott Liggett | c860fa77a1 | |
Elliott Liggett | 37a2c1caf3 | |
Elliott Liggett | 9a781c671e | |
Elliott Liggett | 7564239a68 | |
Elliott Liggett | 314d78ad05 | |
Elliott Liggett | 6b30cb53bc | |
Elliott Liggett | 26f15cc9db | |
Elliott Liggett | 700ac53b28 | |
Elliott Liggett | 888824f9a4 | |
Elliott Liggett | 0c807f54c2 | |
Elliott Liggett | 4be3398dfa | |
Roeland Jansen | 1dc5cc0428 | |
Roeland Jansen | da9f60e0bc | |
Phil Taylor | 60118169e1 | |
Phil Taylor | 247817077c | |
Roeland Jansen | e729c34ec5 | |
Roeland Jansen | 55cda83ec2 | |
Phil Taylor | 81c9563f16 | |
Phil Taylor | e104262bff | |
Roeland Jansen | 43119e101c | |
Roeland Jansen | 0d9dfed85e | |
Phil Taylor | 0d9c7b5e30 | |
Phil Taylor | d71fb9ef4a | |
Phil Taylor | b1ef680dbf | |
Phil Taylor | f67bed5832 | |
Phil Taylor | 47b439b741 | |
Roeland Jansen | 104b395545 | |
Roeland Jansen | 66734b3be0 | |
Phil Taylor | 9ca9379f88 | |
Phil Taylor | 10fc10ef82 | |
Phil Taylor | 8ddf6880ee | |
Phil Taylor | c5d2ecb793 | |
Elliott Liggett | ac72eed7c8 | |
Phil Taylor | 9ba216f1d9 | |
Phil Taylor | ee6889d163 | |
Roeland Jansen | 9ff394e0cd | |
Roeland Jansen | a4a9297747 | |
Phil Taylor | b0ffc94ff9 | |
Roeland Jansen | 1f4878d453 | |
Phil Taylor | d86eb07832 | |
Roeland Jansen | 8908f5a34c | |
Phil Taylor | 39f07414ee | |
Elliott Liggett | 13af5c0b1b | |
Elliott Liggett | 1a03214f59 | |
Roeland Jansen | cbaac5fd26 | |
Roeland Jansen | 62087bfd5b | |
Phil Taylor | 62771f6b59 | |
Phil Taylor | 130d105535 | |
Phil Taylor | 4743cd126b | |
Phil Taylor | 9f0673ae71 | |
Phil Taylor | 395469daf3 | |
Phil Taylor | cc2f2b2576 | |
Phil Taylor | 42675ae770 | |
Phil Taylor | b5591e0867 | |
Phil Taylor | 04526db635 | |
Phil Taylor | 0cc2945d28 | |
Roeland Jansen | 1503ecd9f7 | |
Phil Taylor | c99439e759 | |
Phil Taylor | 81c83357f2 | |
Roeland Jansen | a2cfc8c064 | |
Phil Taylor | bb53558c92 | |
Roeland Jansen | d99b14cc41 | |
Phil Taylor | 827614e080 | |
Phil Taylor | f04284db0e | |
Phil Taylor | ca552a0d80 | |
Phil Taylor | 6247611d85 | |
Phil Taylor | 6fbd9cec70 | |
Roeland Jansen | 847e60791f | |
Phil Taylor | 1c0f33c721 | |
Phil Taylor | 93869b1bea | |
Phil Taylor | 2b3c016546 | |
Elliott Liggett | 1d03764684 | |
Elliott Liggett | 0315033f89 | |
Elliott Liggett | 88430db9ac | |
Elliott Liggett | 863dd6ba95 | |
Elliott Liggett | fe3d645711 | |
Roeland Jansen | bd5a233563 | |
Roeland Jansen | 35cb1ed053 | |
Roeland Jansen | d58a078f70 | |
Roeland Jansen | facdd7dfbd | |
Phil Taylor | 35bc1afb4b | |
Phil Taylor | 589df71805 | |
Phil Taylor | 0f48959a75 | |
Elliott Liggett | 77509aead6 | |
Elliott Liggett | cd7ea97242 | |
Elliott Liggett | 76c5488983 | |
Elliott Liggett | a9fb81d8f6 | |
Elliott Liggett | cecaee397d | |
Elliott Liggett | 522557c6e8 | |
Elliott Liggett | c3bdcf2287 | |
Elliott Liggett | 12d202cbcd | |
Phil Taylor | 2f480e4b82 | |
Phil Taylor | 6f4cf6bbbe | |
Phil Taylor | 0c7bc17382 | |
Phil Taylor | 43efadbeaa | |
Phil Taylor | 5b0a3620bd | |
Phil Taylor | ffdcada18c | |
Phil Taylor | 4e5f315f08 | |
Roeland Jansen | 37205a11da | |
Roeland Jansen | 53d47c16c0 | |
Phil Taylor | bab77ebaba | |
Phil Taylor | 2c1272de66 | |
Roeland Jansen | 511f2b5e98 | |
Roeland Jansen | 52a4016379 | |
Phil Taylor | 620d7890cd | |
Phil Taylor | 24a88794e6 | |
Roeland Jansen | c4a9465fb3 | |
Phil Taylor | b7edb2cf96 | |
Roeland Jansen | 66912e13b2 | |
Phil Taylor | 6d0ad471c7 | |
Phil Taylor | c712e87373 | |
Phil Taylor | 4cb16b105f | |
Phil Taylor | 2c5f37d06c | |
Phil Taylor | 18eb9119be | |
Roeland Jansen | 21bc02cc25 | |
Phil Taylor | 8b06e9d048 | |
Roeland Jansen | f7524c7fba | |
Roeland Jansen | b37713124b | |
Roeland Jansen | 9413d20906 | |
Roeland Jansen | f1221c6f1b | |
Roeland Jansen | c46eb65974 | |
Phil Taylor | bf16dfe19a | |
Phil Taylor | 57e68571af | |
Phil Taylor | 67c80c2510 | |
Phil Taylor | 0647caa640 | |
Phil Taylor | 52070ddbc4 | |
Phil Taylor | b7407fc108 | |
Phil Taylor | cffb2bf93a | |
Phil Taylor | 1b3edbfec1 | |
Phil Taylor | af440fbf55 | |
Phil Taylor | 886b597d56 | |
Phil Taylor | 5e6bab9d57 | |
Phil Taylor | edef09c8bf | |
Phil Taylor | 4bbd06a988 | |
Phil Taylor | a2d7ae0e56 | |
Phil Taylor | 65b4538968 | |
Phil Taylor | 87dc468ae9 | |
Elliott Liggett | f44dd4bda2 | |
Elliott Liggett | 390f9500dc | |
Phil Taylor | 445a4157b7 | |
Phil Taylor | 20f3e70d2a | |
Phil Taylor | 27e8577efe | |
Phil Taylor | eb81196238 | |
Phil Taylor | be979108e8 | |
Phil Taylor | 725914d045 | |
Phil Taylor | 389ea9c831 | |
Phil Taylor | 1f1e489cf3 | |
Phil Taylor | 8fb894f076 | |
Phil Taylor | 133fb1cf32 | |
Phil Taylor | 24133a4f70 | |
Phil Taylor | 7e40f7faf0 | |
Phil Taylor | b5676b7773 | |
Phil Taylor | a0c6243528 | |
Phil Taylor | 62eef7f0f4 | |
Phil Taylor | f9bc4e3122 | |
Phil Taylor | 28ba131135 | |
Phil Taylor | 4704b0dc4d | |
Phil Taylor | 7603201886 | |
Phil Taylor | 871c7106b6 | |
Phil Taylor | ec12e31163 | |
Phil Taylor | 1cbf77ade0 | |
Phil Taylor | d877efd234 | |
Phil Taylor | 0366d5ccb4 | |
Phil Taylor | 3f23b0fdb4 | |
Phil Taylor | 942866ee65 | |
Phil Taylor | 4483a252b6 | |
Phil Taylor | 14f78c1b36 | |
Phil Taylor | ccebdb6840 | |
Phil Taylor | 8b975218d5 | |
Phil Taylor | e271c29c41 | |
Phil Taylor | 70df0ecfd9 | |
Phil Taylor | ef3beecc64 | |
M0VSE | 4159e535fc | |
Phil Taylor | fbf28be5db | |
Phil Taylor | 05b8fe5f7c | |
Phil Taylor | 7b9a911079 | |
Phil Taylor | 6f6bf3387f | |
Phil Taylor | 8a16b467dc | |
Phil Taylor | 84a4e7c871 | |
Phil Taylor | 8037dac05e | |
Phil Taylor | acbbd40c37 |
|
@ -3,4 +3,10 @@
|
|||
.qmake.stash
|
||||
debug
|
||||
release
|
||||
ui_*
|
||||
wfview-debug
|
||||
wfserver-debug
|
||||
wfview-release
|
||||
wfserver-release
|
||||
ui_*
|
||||
portaudio_x86.dll
|
||||
qcustomplot2.dll
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
|
||||
|
||||
|
||||
|
||||
## this is a merged list of CI-V addresses, type of interface(s), rates and all.
|
||||
|
||||
It has been merged from many sources but the most important ones are these:
|
||||
|
||||
1) https://www.docksideradio.com/Icom%20Radio%20Hex%20Addresses.htm
|
||||
2) http://www.plicht.de/ci-v/civ-bus-adressing/
|
||||
3) https://www.icomjapan.com/support/manual/
|
||||
|
||||
|
||||
|
||||
first of all a list that appears not to be a known CI-V address in use:
|
||||
|
||||
| model | addr |
|
||||
| ----- | ---- |
|
||||
|? | 0x06 |
|
||||
|? | 0x36 |
|
||||
|? | 0x54 |
|
||||
|? | 0x7E |
|
||||
|? | 0x82 |
|
||||
|? | 0x84 |
|
||||
|? | 0x8A |
|
||||
|? | 0x90 |
|
||||
|? | 0x92 |
|
||||
|? | 0x9E |
|
||||
|? | 0xA0 |
|
||||
|? | 0xA8 |
|
||||
|? | 0xAA |
|
||||
|
||||
|
||||
|
||||
and here the list of known CI-V addresses
|
||||
|
||||
|
||||
|
||||
| model | addr | year | interface | rate |
|
||||
| ------------ | ---- | ---- | -------------------------------------- | ---- |
|
||||
| IC-1271 | 0x24 | 1987 | TTL | auto |
|
||||
| IC-1275 | 0x18 | 1989 | TTL | auto |
|
||||
| IC-271 | 0x20 | 1983 | TTL | auto |
|
||||
| IC-275 | 0x10 | 1987 | TTL | auto |
|
||||
| IC-375 | 0x12 | 198? | TTL | auto |
|
||||
| IC-471 | 0x22 | 1984 | TTL | auto |
|
||||
| IC-475 | 0x14 | 1987 | TTL | auto |
|
||||
| IC-575 | 0x16 | 1988 | TTL | auto |
|
||||
| IC-7000 | 0x70 | 2005 | TTL | auto |
|
||||
| IC-703 | 0x68 | 2003 | TTL | auto |
|
||||
| IC-705 | 0xA4 | 2020 | USB, Bluetooth, Wifi | auto |
|
||||
| IC-706 | 0x48 | 1995 | TTL | auto |
|
||||
| IC-706MkII | 0x4E | 1997 | TTL | auto |
|
||||
| IC-706MkII-G | 0x58 | 1998 | TTL | auto |
|
||||
| IC-707 | 0x3E | 1993 | TTL | auto |
|
||||
| IC-7100 | 0x88 | 2012 | TTL | auto |
|
||||
| IC-718 | 0x5E | 2001 | TTL | auto |
|
||||
| IC-7200 | 0x76 | 2007 | TTL | auto |
|
||||
| IC-725 | 0x28 | 1988 | TTL | 1200 |
|
||||
| IC-726 | 0x30 | 1989 | TTL | auto |
|
||||
| IC-728 | 0x38 | 1992 | TTL | 1200 |
|
||||
| IC-729 | 0x3A | 1992 | TTL | 9600 |
|
||||
| IC-7300 | 0x94 | 2016 | TTL, USB | auto |
|
||||
| IC-735 | 0x04 | 1985 | TTL | 1200 |
|
||||
| IC-736 | 0x40 | 1994 | TTL | 9600 |
|
||||
| IC-737 | 0x3C | 1993 | TTL | 9600 |
|
||||
| IC-738 | 0x44 | 1994 | TTL | 9600 |
|
||||
| IC-7410 | 0x80 | 2011 | TTL, USB | auto |
|
||||
| IC-746 | 0x56 | 1998 | TTL | auto |
|
||||
| IC-746Pro | 0x66 | 2001 | TTL | auto |
|
||||
| IC-751A | 0x1C | 1985 | TTL | 1200 |
|
||||
| IC-756 | 0x50 | 1997 | TTL | auto |
|
||||
| IC-756Pro | 0x5C | 2000 | TTL | auto |
|
||||
| IC-756ProII | 0x64 | 2001 | TTL | auto |
|
||||
| IC-756ProIII | 0x6E | 2004 | TTL | auto |
|
||||
| IC-7600 | 0x7A | 2009 | TTL, USB | auto |
|
||||
| IC-7610 | 0x98 | 2017 | TTL, USB, Ethernet | auto |
|
||||
| IC-761 | 0x1E | 1987 | TTL | 1200 |
|
||||
| IC-765 | 0x2C | 1989 | TTL | 1200 |
|
||||
| IC-7700 | 0x74 | 2007 | TTL, RS-232C 9 pin, Ethernet | auto |
|
||||
| IC-775 | 0x46 | 1995 | TTL | auto |
|
||||
| IC-7800 | 0x6A | 2004 | TTL, RS-232C 9 pin, Ethernet | auto |
|
||||
| IC-78 | 0x62 | 2000 | TTL | auto |
|
||||
| IC-781 | 0x26 | 1987 | TTL | 1200 |
|
||||
| IC-785x | 0x8E | 2015 | TTL, USB, Ethernet | auto |
|
||||
| IC-820 | 0x42 | 1994 | TTL | auto |
|
||||
| IC-821 | 0x4C | 1996 | TTL | auto |
|
||||
| IC-905 | 0xAC | 2023 | USB, Ethernet | auto |
|
||||
| IC-9100 | 0x7C | 2010 | TTL, USB | auto |
|
||||
| IC-910 | 0x60 | 2001 | TTL | auto |
|
||||
| IC-9700 | 0xA2 | 2019 | TTL, USB, Ethernet | auto |
|
||||
| IC-970 | 0x2E | 1990 | TTL | auto |
|
||||
| IC-R10 | 0x52 | 1996 | TTL | auto |
|
||||
| IC-R20 | 0x6C | 2004 | TTL | auto |
|
||||
| IC-R30 | 0x9C | 2018 | TTL, Bluetooth | auto |
|
||||
| IC-R7000 | 0x08 | 1986 | TTL | auto |
|
||||
| IC-R7100 | 0x34 | 1991 | TTL | auto |
|
||||
| IC-R71 | 0x1A | 1984 | TTL | auto |
|
||||
| IC-R72 | 0x32 | 1992 | TTL | auto |
|
||||
| IC-R75 | 0x5A | 1999 | TTL, RS-232C 9 pin | auto |
|
||||
| IC-R8500 | 0x4A | 1996 | TTL, RS-232C 25 pin | auto |
|
||||
| IC-R8600 | 0x96 | 2017 | TTL, USB, Ethernet | auto |
|
||||
| IC-R9000 | 0x2A | 1989 | TTL | auto |
|
||||
| IC-R9500 | 0x72 | 2007 | TTL, RS-232C 9 pin, Ethernet | auto |
|
||||
| IC-RX7 | 0x78 | 2007 | TTL | auto |
|
||||
| ID-4100 | 0x9A | 2017 | TTL, Bluetooth | auto |
|
||||
| ID-5100 | 0x8C | 2014 | TTL, Bluetooth | auto |
|
||||
| ID-51 | 0x86 | 2012 | TTL | auto |
|
||||
| ID-52 | 0xA6 | 2021 | TTL, Bluetooth | auto |
|
|
@ -6,3 +6,12 @@ the following people currently contribute to this Project:
|
|||
- Jim PA8E
|
||||
- Phil M0VSE
|
||||
- Roeland PA3MET
|
||||
|
||||
|
||||
Also contributions by:
|
||||
|
||||
Daniele Forsi <dforsi@gmail.com>
|
||||
Russ Woodman - K5TUX <k5tux@k5tux.us>
|
||||
(and some others I will add too)
|
||||
|
||||
|
||||
|
|
43
INSTALL.md
|
@ -14,6 +14,11 @@ sudo apt-get install libqt5multimedia5-plugins
|
|||
sudo apt-get install qtmultimedia5-dev
|
||||
sudo apt-get install git
|
||||
sudo apt-get install libopus-dev
|
||||
sudo apt-get install libeigen3-dev
|
||||
sudo apt-get install portaudio19-dev
|
||||
sudo apt-get install librtaudio-dev
|
||||
sudo apt-get install libhidapi-dev libqt5gamepad5-dev
|
||||
sudo apt-get install libudev-dev
|
||||
~~~
|
||||
Now you need to install qcustomplot. There are two versions that are commonly found in linux distros: 1.3 and 2.0. Either will work fine. If you are not sure which version your linux install comes with, simply run both commands. One will work and the other will fail, and that's fine!
|
||||
|
||||
|
@ -70,12 +75,12 @@ sudo usermod -aG dialout $USER
|
|||
### opensuse/sles/tumbleweed install
|
||||
---
|
||||
|
||||
install wfview on suse 15.x sles 15.x or tumbleweed; this was done on a clean install/updated OS.
|
||||
install wfview on suse 15.3 & up, sles 15.x or tumbleweed; this was done on a clean install/updated OS.
|
||||
|
||||
we need to add packages to be able to build the stuff.
|
||||
|
||||
- sudo zypper in --type pattern devel_basis
|
||||
- sudo zypper in libQt5Widgets-devel libqt5-qtbase-common-devel libqt5-qtserialport-devel libQt5SerialPort5 qcustomplot-devel libqcustomplot2 libQt5PrintSupport-devel libqt5-qtmultimedia-devel lv2-devel libopus-devel
|
||||
- sudo zypper in libQt5Widgets-devel libqt5-qtbase-common-devel libqt5-qtserialport-devel libQt5SerialPort5 qcustomplot-devel libqcustomplot2 libQt5PrintSupport-devel libqt5-qtmultimedia-devel lv2-devel libopus-devel eigen3-devel libQt5Xml-devel portaudio-devel rtaudio-devel libqt5-qtgamepad-devel libQt5Gamepad5
|
||||
|
||||
optional (mainly for development specifics): get and install qt5:
|
||||
|
||||
|
@ -94,9 +99,9 @@ in this case, use your homedir:
|
|||
- mkdir -p ~/src/build && cd src
|
||||
- git clone https://gitlab.com/eliggett/wfview.git
|
||||
- cd build
|
||||
- qmake-qt5 ../wfview/wfview.pro
|
||||
- qmake-qt5 ../wfview/wfview.pro (wfserver.pro if you build the server version)
|
||||
- make -j
|
||||
- sudo ./install.sh
|
||||
- sudo make install
|
||||
|
||||
wfview is now installed in /usr/local/bin
|
||||
|
||||
|
@ -124,4 +129,34 @@ When done, create a build area, clone the repo, build and install:
|
|||
|
||||
wfview is now installed in /usr/local/bin
|
||||
|
||||
|
||||
Fedora 36:
|
||||
|
||||
2023-04-11/Knud OZ1DGN
|
||||
|
||||
|
||||
Precondition:
|
||||
|
||||
F36 KDE plasma desktop
|
||||
wfview 1.61 prebuild binary
|
||||
|
||||
Install following packages:
|
||||
sudo dnf install qcustomplot-qt5 qt5-qtmultimedia qt5-qtserialport rtaudio portaudio hidapi qt5-qtgamepad
|
||||
|
||||
sudo ln -s /usr/lib64/libqcustomplot-qt5.so.2 /usr/lib64/libqcustomplot.so.2
|
||||
|
||||
Move /usr/share/wfview to /usr/local/share:
|
||||
|
||||
sudo mv /usr/share/wfview/stylesheets/* /usr/local/share/wfview
|
||||
|
||||
|
||||
|
||||
# How to configure your RC-28 knob under Linux
|
||||
|
||||
To use RC-28 knob you need to add udev rules, please execute as root:
|
||||
|
||||
~~~
|
||||
echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="0c26", ATTRS{idProduct}=="001e", MODE="0666"' >> /etc/udev/rules.d/99-ham-wfview.rules
|
||||
udevadm control --reload-rules && udevadm trigger
|
||||
~~~
|
||||
---
|
||||
|
|
|
@ -22,8 +22,8 @@ Debian 11 (Debian 10 is outdated)
|
|||
Fedora 33
|
||||
Fedora 34
|
||||
mint 20.1 (and up?)
|
||||
openSUSE 15.2
|
||||
openSUSE 15.3 (see notes at the end)
|
||||
openSUSE 15.2 (outdated/deprecated)
|
||||
openSUSE 15.3/15.4)
|
||||
openSUSE Tumbleweed(s)
|
||||
SLES 15.x
|
||||
Ubuntu 20.04.2
|
||||
|
@ -86,16 +86,8 @@ sudo ln -s /lib/x86_64-linux-gnu/libqcustomplot.so.2.0.1 /lib/x86_64-linux-gnu/l
|
|||
~~~
|
||||
|
||||
|
||||
### openSUSE/Tumbleweed/SLES based on 15.2:
|
||||
### openSUSE/Tumbleweed/SLES based on 15.3/15.4:
|
||||
~~~
|
||||
sudo zypper in libqcustomplot2 libQt5SerialPort5
|
||||
wfview
|
||||
~~~
|
||||
|
||||
### openSUSE/Tumbleweed/SLES based on 15.3:
|
||||
~~~
|
||||
|
||||
SEE THE NOTES AT THE END. You need wfview153 here
|
||||
|
||||
sudo zypper in libqcustomplot2 libQt5SerialPort5
|
||||
wfview
|
||||
|
@ -119,14 +111,8 @@ sudo ln -s /lib/x86_64-linux-gnu/libqcustomplot.so.2.0.1 /lib/x86_64-linux-gnu/l
|
|||
### notes:
|
||||
~~~
|
||||
|
||||
Some newer versions of mint. ubuntu, openSUSE have different kernels and such which cause wfview to segfault.
|
||||
openSUSE 15.2 is deprecated; old binary has been removed
|
||||
|
||||
For these cases we created two binaries: one for current systems ("wfview") and one for the new systems ("wfview153")
|
||||
|
||||
So if you encounter a SEGFAULT at start:
|
||||
|
||||
go in to the dist directory, rename wfview to wfvie152; rename wfview153 to wfview and re-execute the install.sh
|
||||
script
|
||||
|
||||
~~~
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ Other models to be tested/added (including the IC-705)..
|
|||
|
||||
website - [WFVIEW](https://wfview.org/) wfview.org
|
||||
|
||||
wfview supports viewing the spectrum display waterfall and most normal radio controls. Using wfview, the radio can be operated using the mouse, or just the keyboard (great for those with visual impairments), or even a touch screen display. The gorgous waterfall spectrum can be displayed on a monitor of any size, and can even projected onto a wall for a presentation. Even a VNC session can make use of wfview for interesting remote rig posibilities. wfview runs on humble hardware, ranging from the $35 Raspberry Pi, to laptops, to desktops. wfview is designed to run on GNU Linux, but can probably be adapted to run on other operating systems. In fact we do have working example in windows as well.
|
||||
wfview supports viewing the spectrum display waterfall and most normal radio controls. Using wfview, the radio can be operated using the mouse, or just the keyboard (great for those with visual impairments), or even a touch screen display. The gorgous waterfall spectrum can be displayed on a monitor of any size, and can even projected onto a wall for a presentation. Even a VNC session can make use of wfview for interesting remote rig possibilities. wfview runs on humble hardware, ranging from the $35 Raspberry Pi, to laptops, to desktops. wfview is designed to run on GNU Linux, but can probably be adapted to run on other operating systems. In fact we do have working example in windows as well.
|
||||
|
||||
|
||||
|
||||
|
@ -29,16 +29,16 @@ wfview is copyright 2017-2020 Elliott H. Liggett. All rights reserved. wfview so
|
|||
2. Double-click anywhere on the bandscope or waterfall to tune the radio.
|
||||
3. Entry of frequency is permitted under the "Frequency" tab. Buttons are provided for touch-screen control
|
||||
4. Bandscope parameters (span and mode) are adjustable.
|
||||
5. Full [keyboard](https://gitlab.com/eliggett/wfview/-/wikis/Keystrokes) and mouse control. Operate in whichever way you like. Most radio functions can be operated from a numberic keypad! This also enables those with visual impairments to use the IC-7300.
|
||||
5. Full [keyboard](https://gitlab.com/eliggett/wfview/-/wikis/Keystrokes) and mouse control. Operate in whichever way you like. Most radio functions can be operated from a numeric keypad! This also enables those with visual impairments to use the IC-7300.
|
||||
6. 100 user memories stored in plain text on the computer
|
||||
7. Stylable GUI using CSS
|
||||
8. pseudo-terminal device, which allows for secondary program to control the radio while wfview is running
|
||||
9. works for radios that support the ethernet interface with compareable waterfall speeds as on the radio itself.
|
||||
9. works for radios that support the ethernet interface with comparable waterfall speeds as on the radio itself.
|
||||
|
||||
### Build Requirements:
|
||||
1. gcc / g++ / make
|
||||
2. qmake
|
||||
3. qt5 (proably the package named "qt5-default")
|
||||
3. qt5 (probably the package named "qt5-default")
|
||||
4. libqt5serialport5-dev
|
||||
5. libqcustomplot-dev
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
|
||||
this file contains known limits and things that maybe needs fixing..
|
||||
|
||||
|
||||
7851
|
||||
|
||||
repeater controls:
|
||||
|
||||
|
||||
- quick split does not work correct?
|
||||
- split does not work correct?
|
||||
- M <--> S "change" works
|
||||
- M => S "M = S" works
|
||||
- autotrack "track" works but is slow?
|
||||
|
|
@ -42,7 +42,7 @@ on/off switch for the scope/waterfall
|
|||
wf theme
|
||||
|
||||
Currently fixed selections how the scope and waterfall look like
|
||||
with colors. At som epoint we may add the ability to accept the
|
||||
with colors. At some point we may add the ability to accept the
|
||||
RGB values like the rig does.
|
||||
|
||||
|
||||
|
|
19
WHATSNEW
|
@ -1,10 +1,15 @@
|
|||
|
||||
The following highlights are in this 1.x-release:
|
||||
The following highlights are in this 1.6x-release since v1.60:
|
||||
|
||||
many changes/mods/updates/enhancements to rigctld
|
||||
rigctld box added in the UI
|
||||
build process changed: you can add the install prefix (derSuessmann)
|
||||
added "do not ask again" for switching off rig and exiting wfview
|
||||
added opus as audio transport
|
||||
dual meter support
|
||||
+1.60 released
|
||||
+1.61 many small fixes for several platforms
|
||||
+1.62 cw side tone added
|
||||
performance fix for 9700
|
||||
+1.63 started adding IC-905 (obviously untested)
|
||||
started multi-usb
|
||||
|
||||
Notes:
|
||||
|
||||
- We know about high CPU usage on RPi.
|
||||
- try the audiostack you like the most/what works for you
|
||||
|
||||
|
|
22
aboutbox.cpp
|
@ -12,22 +12,30 @@ aboutbox::aboutbox(QWidget *parent) :
|
|||
ui->logoBtn->setIcon(QIcon(":resources/wfview.png"));
|
||||
ui->logoBtn->setStyleSheet("Text-align:left");
|
||||
|
||||
ui->topText->setText("wfview version 1.2a");
|
||||
ui->topText->setText("wfview version " + QString(WFVIEW_VERSION));
|
||||
|
||||
QString head = QString("<html><head></head><body>");
|
||||
QString copyright = QString("Copyright 2017-2021 Elliott H. Liggett, W6EL. All rights reserved. wfview source code is <a href=\"https://gitlab.com/eliggett/wfview/-/blob/master/LICENSE\">licensed</a> under the GNU GPLv3.");
|
||||
QString nacode = QString("<br/><br/>Networking, audio, rigctl server, and much more written by Phil Taylor, M0VSE");
|
||||
QString doctest = QString("<br/><br/>Testing, documentation, bug fixes, and development mentorship from<br/>Roeland Jansen, PA3MET, and Jim Nijkamp, PA8E.");
|
||||
QString copyright = QString("Copyright 2017-2023 Elliott H. Liggett, W6EL and Phil E. Taylor, M0VSE. All rights reserved.<br/>wfview source code is <a href=\"https://gitlab.com/eliggett/wfview/-/blob/master/LICENSE\">licensed</a> under the GNU GPLv3.");
|
||||
QString scm = QString("<br/><br/>Source code and issues managed by Roeland Jansen, PA3MET");
|
||||
QString doctest = QString("<br/><br/>Testing and development mentorship from Jim Nijkamp, PA8E.");
|
||||
|
||||
QString dedication = QString("<br/><br/>This version of wfview is dedicated to the ones we lost.");
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
QString ssCredit = QString("<br/><br/>Stylesheet <a href=\"https://github.com/ColinDuquesnoy/QDarkStyleSheet/tree/master/qdarkstyle\" style=\"color: cyan;\">qdarkstyle</a> used under MIT license, stored in /usr/share/wfview/stylesheets/.");
|
||||
#else
|
||||
QString ssCredit = QString("<br/><br/>Stylesheet <a href=\"https://github.com/ColinDuquesnoy/QDarkStyleSheet/tree/master/qdarkstyle\" style=\"color: cyan;\">qdarkstyle</a> used under MIT license.");
|
||||
#endif
|
||||
|
||||
QString website = QString("<br/><br/>Please visit <a href=\"https://wfview.org/\" style=\"color: cyan;\">https://wfview.org/</a> for the latest information.");
|
||||
|
||||
QString donate = QString("<br/><br/>Join us on <a href=\"https://www.patreon.com/wfview\">Patreon</a> for a behind-the-scenes look at wfview development, nightly builds, and to support the software you love.");
|
||||
|
||||
QString docs = QString("<br/><br/>Be sure to check the <a href=\"https://wfview.org/wfview-user-manual/\" style=\"color: cyan;\">User Manual</a> and <a href=\"https://forum.wfview.org/\" style=\"color: cyan;\">the Forum</a> if you have any questions.");
|
||||
QString support = QString("<br/><br/>For support, please visit <a href=\"https://forum.wfview.org/\">the official wfview support forum</a>.");
|
||||
QString gitcodelink = QString("<a href=\"https://gitlab.com/eliggett/wfview/-/tree/%1\" style=\"color: cyan;\">").arg(GITSHORT);
|
||||
|
||||
QString contact = QString("<br/>email W6EL: kilocharlie8@gmail.com");
|
||||
QString contact = QString("<br/>email W6EL: kilocharlie8 at gmail.com");
|
||||
|
||||
QString buildInfo = QString("<br/><br/>Build " + gitcodelink + QString(GITSHORT) + "</a> on " + QString(__DATE__) + " at " + __TIME__ + " by " + UNAME + "@" + HOST);
|
||||
QString end = QString("</body></html>");
|
||||
|
@ -77,8 +85,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.");
|
|||
|
||||
// String it all together:
|
||||
|
||||
QString aboutText = head + copyright + "\n" + nacode + "\n" + doctest + wfviewcommunityack;
|
||||
aboutText.append(website + "\n"+ docs + support + contact +"\n");
|
||||
QString aboutText = head + copyright + "\n" + "\n" + scm + "\n" + doctest + dedication + wfviewcommunityack;
|
||||
aboutText.append(website + "\n" + donate + "\n"+ docs + support + contact +"\n");
|
||||
aboutText.append("\n" + ssCredit + "\n" + rsCredit + "\n");
|
||||
|
||||
#if defined(RTAUDIO)
|
||||
|
|
|
@ -0,0 +1,360 @@
|
|||
#include "audioconverter.h"
|
||||
#include "logcategories.h"
|
||||
#include "ulaw.h"
|
||||
|
||||
audioConverter::audioConverter(QObject* parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool audioConverter::init(QAudioFormat inFormat, codecType inCodec, QAudioFormat outFormat, codecType outCodec, quint8 opusComplexity, quint8 resampleQuality)
|
||||
{
|
||||
|
||||
this->inFormat = inFormat;
|
||||
this->inCodec = inCodec;
|
||||
this->outFormat = outFormat;
|
||||
this->outCodec = outCodec;
|
||||
this->opusComplexity = opusComplexity;
|
||||
this->resampleQuality = resampleQuality;
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
qInfo(logAudioConverter) << "Starting audioConverter() Input:" << inFormat.channelCount() << "Channels of" << inCodec << inFormat.sampleRate() << inFormat.sampleType() << inFormat.sampleSize() <<
|
||||
"Output:" << outFormat.channelCount() << "Channels of" << outCodec << outFormat.sampleRate() << outFormat.sampleType() << outFormat.sampleSize();
|
||||
|
||||
if (inFormat.byteOrder() != outFormat.byteOrder()) {
|
||||
qInfo(logAudioConverter) << "Byteorder mismatch in:" << inFormat.byteOrder() << "out:" << outFormat.byteOrder();
|
||||
}
|
||||
#else
|
||||
qInfo(logAudioConverter) << "Starting audioConverter() Input:" << inFormat.channelCount() << "Channels of" << inCodec << inFormat.sampleRate() << inFormat.sampleFormat() <<
|
||||
"Output:" << outFormat.channelCount() << "Channels of" << outCodec << outFormat.sampleRate() << outFormat.sampleFormat();
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
if (inCodec == OPUS)
|
||||
{
|
||||
// Create instance of opus decoder
|
||||
int opus_err = 0;
|
||||
opusDecoder = opus_decoder_create(inFormat.sampleRate(), inFormat.channelCount(), &opus_err);
|
||||
qInfo(logAudioConverter()) << "Creating opus decoder: " << opus_strerror(opus_err);
|
||||
}
|
||||
|
||||
if (outCodec == OPUS)
|
||||
{
|
||||
// Create instance of opus encoder
|
||||
int opus_err = 0;
|
||||
opusEncoder = opus_encoder_create(outFormat.sampleRate(), outFormat.channelCount(), OPUS_APPLICATION_AUDIO, &opus_err);
|
||||
//opus_encoder_ctl(opusEncoder, OPUS_SET_LSB_DEPTH(16));
|
||||
//opus_encoder_ctl(opusEncoder, OPUS_SET_INBAND_FEC(1));
|
||||
//opus_encoder_ctl(opusEncoder, OPUS_SET_DTX(1));
|
||||
//opus_encoder_ctl(opusEncoder, OPUS_SET_PACKET_LOSS_PERC(5));
|
||||
opus_encoder_ctl(opusEncoder, OPUS_SET_COMPLEXITY(opusComplexity)); // Reduce complexity to maybe lower CPU?
|
||||
qInfo(logAudioConverter()) << "Creating opus encoder: " << opus_strerror(opus_err);
|
||||
}
|
||||
|
||||
if (inFormat.sampleRate() != outFormat.sampleRate())
|
||||
{
|
||||
int resampleError = 0;
|
||||
unsigned int ratioNum;
|
||||
unsigned int ratioDen;
|
||||
// Sample rate conversion required.
|
||||
resampler = wf_resampler_init(outFormat.channelCount(), inFormat.sampleRate(), outFormat.sampleRate(), resampleQuality, &resampleError);
|
||||
wf_resampler_get_ratio(resampler, &ratioNum, &ratioDen);
|
||||
resampleRatio = static_cast<double>(ratioDen) / ratioNum;
|
||||
qInfo(logAudioConverter()) << "wf_resampler_init() returned: " << resampleError << " resampleRatio: " << resampleRatio;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
audioConverter::~audioConverter()
|
||||
{
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
qInfo(logAudioConverter) << "Closing audioConverter() Input:" << inFormat.channelCount() << "Channels of" << inCodec << inFormat.sampleRate() << inFormat.sampleType() << inFormat.sampleSize() <<
|
||||
"Output:" << outFormat.channelCount() << "Channels of" << outCodec << outFormat.sampleRate() << outFormat.sampleType() << outFormat.sampleSize();
|
||||
#else
|
||||
qInfo(logAudioConverter) << "Closing audioConverter() Input:" << inFormat.channelCount() << "Channels of" << inCodec << inFormat.sampleRate() << inFormat.sampleFormat() <<
|
||||
"Output:" << outFormat.channelCount() << "Channels of" << outCodec << outFormat.sampleRate() << outFormat.sampleFormat();
|
||||
#endif
|
||||
|
||||
if (opusEncoder != Q_NULLPTR) {
|
||||
qInfo(logAudioConverter()) << "Destroying opus encoder";
|
||||
opus_encoder_destroy(opusEncoder);
|
||||
}
|
||||
|
||||
if (opusDecoder != Q_NULLPTR) {
|
||||
qInfo(logAudioConverter()) << "Destroying opus decoder";
|
||||
opus_decoder_destroy(opusDecoder);
|
||||
}
|
||||
|
||||
if (resampler != Q_NULLPTR) {
|
||||
speex_resampler_destroy(resampler);
|
||||
qDebug(logAudioConverter()) << "Resampler closed";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool audioConverter::convert(audioPacket audio)
|
||||
{
|
||||
|
||||
// If inFormat and outFormat are identical, just emit the data back (removed as it doesn't then process amplitude)
|
||||
if (audio.data.size() > 0)
|
||||
{
|
||||
|
||||
if (inCodec == OPUS)
|
||||
{
|
||||
unsigned char* in = (unsigned char*)audio.data.data();
|
||||
|
||||
//Decode the frame.
|
||||
int nSamples = opus_packet_get_nb_samples(in, audio.data.size(), inFormat.sampleRate());
|
||||
if (nSamples == -1) {
|
||||
// No opus data yet?
|
||||
return false;
|
||||
}
|
||||
QByteArray outPacket(nSamples * sizeof(float) * inFormat.channelCount(), (char)0xff); // Preset the output buffer size.
|
||||
float* out = (float*)outPacket.data();
|
||||
|
||||
int ret = opus_decode_float(opusDecoder, in, audio.data.size(), out, nSamples, 0);
|
||||
if (ret != nSamples)
|
||||
{
|
||||
qDebug(logAudio()) << "opus_decode_float: returned:" << ret << "samples, expected:" << nSamples;
|
||||
}
|
||||
audio.data.clear();
|
||||
audio.data = outPacket; // Replace incoming data with converted.
|
||||
}
|
||||
else if (inCodec == PCMU)
|
||||
{
|
||||
// Current packet is "technically" 8bit so need to create a new buffer that is 16bit
|
||||
QByteArray outPacket((int)audio.data.length() * 2, (char)0xff);
|
||||
qint16* out = (qint16*)outPacket.data();
|
||||
for (int f = 0; f < audio.data.length(); f++)
|
||||
{
|
||||
*out++ = ulaw_decode[(quint8)audio.data[f]];
|
||||
}
|
||||
audio.data.clear();
|
||||
audio.data = outPacket; // Replace incoming data with converted.
|
||||
// Make sure that sample size/type is set correctly
|
||||
}
|
||||
|
||||
Eigen::VectorXf samplesF;
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
if (inFormat.sampleType() == QAudioFormat::SignedInt && inFormat.sampleSize() == 32)
|
||||
#else
|
||||
if (inFormat.sampleFormat() == QAudioFormat::Int32)
|
||||
#endif
|
||||
{
|
||||
Eigen::Ref<VectorXint32> samplesI = Eigen::Map<VectorXint32>(reinterpret_cast<qint32*>(audio.data.data()), audio.data.size() / int(sizeof(qint32)));
|
||||
samplesF = samplesI.cast<float>() / float(std::numeric_limits<qint32>::max());
|
||||
}
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
else if (inFormat.sampleType() == QAudioFormat::SignedInt && inFormat.sampleSize() == 16)
|
||||
#else
|
||||
else if (inFormat.sampleFormat() == QAudioFormat::Int16)
|
||||
#endif
|
||||
{
|
||||
Eigen::Ref<VectorXint16> samplesI = Eigen::Map<VectorXint16>(reinterpret_cast<qint16*>(audio.data.data()), audio.data.size() / int(sizeof(qint16)));
|
||||
samplesF = samplesI.cast<float>() / float(std::numeric_limits<qint16>::max());
|
||||
}
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
else if (inFormat.sampleType() == QAudioFormat::UnSignedInt && inFormat.sampleSize() == 8)
|
||||
#else
|
||||
else if (inFormat.sampleFormat() == QAudioFormat::UInt8)
|
||||
#endif
|
||||
{
|
||||
Eigen::Ref<VectorXuint8> samplesI = Eigen::Map<VectorXuint8>(reinterpret_cast<quint8*>(audio.data.data()), audio.data.size() / int(sizeof(quint8)));
|
||||
samplesF = samplesI.cast<float>() / float(std::numeric_limits<quint8>::max());;
|
||||
}
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
else if (inFormat.sampleType() == QAudioFormat::Float)
|
||||
#else
|
||||
else if (inFormat.sampleFormat() == QAudioFormat::Float)
|
||||
#endif
|
||||
{
|
||||
|
||||
samplesF = Eigen::Map<Eigen::VectorXf>(reinterpret_cast<float*>(audio.data.data()), audio.data.size() / int(sizeof(float)));
|
||||
}
|
||||
else
|
||||
{
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
qInfo(logAudio()) << "Unsupported Input Sample Type:" << inFormat.sampleType() << "Size:" << inFormat.sampleSize();
|
||||
#else
|
||||
qInfo(logAudio()) << "Unsupported Input Sample Format:" << inFormat.sampleFormat();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (samplesF.size() > 0)
|
||||
|
||||
{
|
||||
audio.amplitudePeak = samplesF.array().abs().maxCoeff();
|
||||
//audio.amplitudeRMS = samplesF.array().abs().mean(); // zero for tx audio
|
||||
//audio.amplitudeRMS = samplesF.norm() / sqrt(samplesF.size()); // too high values. Zero for tx audio.
|
||||
//audio.amplitudeRMS = samplesF.squaredNorm(); // tx not zero. Values higher than peak sometimes
|
||||
//audio.amplitudeRMS = samplesF.norm(); // too small values. also too small on TX
|
||||
//audio.amplitudeRMS = samplesF.blueNorm(); // scale same as norm, too small.
|
||||
|
||||
|
||||
// Set the volume
|
||||
samplesF *= audio.volume;
|
||||
|
||||
/*
|
||||
samplesF is now an Eigen Vector of the current samples in float format
|
||||
The next step is to convert to the correct number of channels in outFormat.channelCount()
|
||||
*/
|
||||
|
||||
|
||||
if (inFormat.channelCount() == 2 && outFormat.channelCount() == 1) {
|
||||
// If we need to drop one of the audio channels, do it now
|
||||
Eigen::VectorXf samplesTemp(samplesF.size() / 2);
|
||||
samplesTemp = Eigen::Map<Eigen::VectorXf, 0, Eigen::InnerStride<2> >(samplesF.data(), samplesF.size() / 2);
|
||||
samplesF = samplesTemp;
|
||||
}
|
||||
else if (inFormat.channelCount() == 1 && outFormat.channelCount() == 2) {
|
||||
// Convert mono to stereo if required
|
||||
Eigen::VectorXf samplesTemp(samplesF.size() * 2);
|
||||
Eigen::Map<Eigen::VectorXf, 0, Eigen::InnerStride<2> >(samplesTemp.data(), samplesF.size()) = samplesF;
|
||||
Eigen::Map<Eigen::VectorXf, 0, Eigen::InnerStride<2> >(samplesTemp.data() + 1, samplesF.size()) = samplesF;
|
||||
samplesF = samplesTemp;
|
||||
}
|
||||
|
||||
/*
|
||||
Next step is to resample (if needed)
|
||||
*/
|
||||
|
||||
if (resampler != Q_NULLPTR && resampleRatio != 1.0)
|
||||
{
|
||||
quint32 outFrames = ((samplesF.size() / outFormat.channelCount()) * resampleRatio);
|
||||
quint32 inFrames = (samplesF.size() / outFormat.channelCount());
|
||||
QByteArray outPacket(outFrames * outFormat.channelCount() * sizeof(float), (char)0xff); // Preset the output buffer size.
|
||||
const float* in = (float*)samplesF.data();
|
||||
float* out = (float*)outPacket.data();
|
||||
|
||||
int err = 0;
|
||||
if (outFormat.channelCount() == 1) {
|
||||
err = wf_resampler_process_float(resampler, 0, in, &inFrames, out, &outFrames);
|
||||
}
|
||||
else {
|
||||
err = wf_resampler_process_interleaved_float(resampler, in, &inFrames, out, &outFrames);
|
||||
}
|
||||
|
||||
if (err) {
|
||||
qInfo(logAudioConverter()) << "Resampler error " << err << " inFrames:" << inFrames << " outFrames:" << outFrames;
|
||||
}
|
||||
samplesF = Eigen::Map<Eigen::VectorXf>(reinterpret_cast<float*>(outPacket.data()), outPacket.size() / int(sizeof(float)));
|
||||
}
|
||||
|
||||
/*
|
||||
If output is Opus so encode it now, don't do any more conversion on the output of Opus.
|
||||
*/
|
||||
|
||||
if (outCodec == OPUS)
|
||||
{
|
||||
float* in = (float*)samplesF.data();
|
||||
QByteArray outPacket(1275, (char)0xff); // Preset the output buffer size to MAXIMUM possible Opus frame size
|
||||
unsigned char* out = (unsigned char*)outPacket.data();
|
||||
|
||||
int nbBytes = opus_encode_float(opusEncoder, in, (samplesF.size() / outFormat.channelCount()), out, outPacket.length());
|
||||
if (nbBytes < 0)
|
||||
{
|
||||
qInfo(logAudioConverter()) << "Opus encode failed:" << opus_strerror(nbBytes) << "Num Samples:" << samplesF.size();
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
outPacket.resize(nbBytes);
|
||||
audio.data.clear();
|
||||
audio.data = outPacket; // Copy output packet back to input buffer.
|
||||
//samplesF = Eigen::Map<Eigen::VectorXf>(reinterpret_cast<float*>(outPacket.data()), outPacket.size() / int(sizeof(float)));
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
|
||||
/*
|
||||
Now convert back into the output format required
|
||||
*/
|
||||
audio.data.clear();
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
if (outFormat.sampleType() == QAudioFormat::UnSignedInt && outFormat.sampleSize() == 8)
|
||||
#else
|
||||
if (outFormat.sampleFormat() == QAudioFormat::UInt8)
|
||||
#endif
|
||||
{
|
||||
Eigen::VectorXf samplesITemp = samplesF * float(std::numeric_limits<qint8>::max());
|
||||
samplesITemp.array() += 127;
|
||||
VectorXuint8 samplesI = samplesITemp.cast<quint8>();
|
||||
audio.data = QByteArray(reinterpret_cast<char*>(samplesI.data()), int(samplesI.size()) * int(sizeof(quint8)));
|
||||
}
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
else if (outFormat.sampleType() == QAudioFormat::SignedInt && outFormat.sampleSize() == 16)
|
||||
#else
|
||||
else if (outFormat.sampleFormat() == QAudioFormat::Int16)
|
||||
#endif
|
||||
{
|
||||
Eigen::VectorXf samplesITemp = samplesF * float(std::numeric_limits<qint16>::max());
|
||||
VectorXint16 samplesI = samplesITemp.cast<qint16>();
|
||||
audio.data = QByteArray(reinterpret_cast<char*>(samplesI.data()), int(samplesI.size()) * int(sizeof(qint16)));
|
||||
}
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
else if (outFormat.sampleType() == QAudioFormat::SignedInt && outFormat.sampleSize() == 32)
|
||||
#else
|
||||
else if (outFormat.sampleFormat() == QAudioFormat::Int32)
|
||||
#endif
|
||||
{
|
||||
Eigen::VectorXf samplesITemp = samplesF * float(std::numeric_limits<qint32>::max());
|
||||
VectorXint32 samplesI = samplesITemp.cast<qint32>();
|
||||
audio.data = QByteArray(reinterpret_cast<char*>(samplesI.data()), int(samplesI.size()) * int(sizeof(qint32)));
|
||||
}
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
else if (outFormat.sampleType() == QAudioFormat::Float)
|
||||
#else
|
||||
else if (outFormat.sampleFormat() == QAudioFormat::Float)
|
||||
#endif
|
||||
{
|
||||
audio.data = QByteArray(reinterpret_cast<char*>(samplesF.data()), int(samplesF.size()) * int(sizeof(float)));
|
||||
}
|
||||
else {
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
qInfo(logAudio()) << "Unsupported Output Sample Type:" << outFormat.sampleType() << "Size:" << outFormat.sampleSize();
|
||||
#else
|
||||
qInfo(logAudio()) << "Unsupported Output Sample Type:" << outFormat.sampleFormat();
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
As we currently don't have a float based uLaw encoder, this must be done
|
||||
after all other conversion has taken place.
|
||||
*/
|
||||
if (outCodec == PCMU)
|
||||
{
|
||||
QByteArray outPacket((int)audio.data.length() / 2, (char)0xff);
|
||||
qint16* in = (qint16*)audio.data.data();
|
||||
for (int f = 0; f < outPacket.length(); f++)
|
||||
{
|
||||
qint16 sample = *in++;
|
||||
int sign = (sample >> 8) & 0x80;
|
||||
if (sign)
|
||||
sample = (short)-sample;
|
||||
if (sample > cClip)
|
||||
sample = cClip;
|
||||
sample = (short)(sample + cBias);
|
||||
int exponent = (int)MuLawCompressTable[(sample >> 7) & 0xFF];
|
||||
int mantissa = (sample >> (exponent + 3)) & 0x0F;
|
||||
int compressedByte = ~(sign | (exponent << 4) | mantissa);
|
||||
outPacket[f] = (quint8)compressedByte;
|
||||
}
|
||||
audio.data.clear();
|
||||
audio.data = outPacket; // Copy output packet back to input buffer.
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug(logAudioConverter) << "Detected empty packet";
|
||||
}
|
||||
}
|
||||
|
||||
emit converted(audio);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -0,0 +1,172 @@
|
|||
#ifndef AUDIOCONVERTER_H
|
||||
#define AUDIOCONVERTER_H
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QTime>
|
||||
#include <QMap>
|
||||
#include <QDebug>
|
||||
#include <QAudioFormat>
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
#include <QAudioDeviceInfo>
|
||||
#include <QAudioInput>
|
||||
#include <QAudioOutput>
|
||||
#else
|
||||
#include <QMediaDevices>
|
||||
#include <QAudioDevice>
|
||||
#include <QAudioSource>
|
||||
#include <QAudioSink>
|
||||
#endif
|
||||
|
||||
/* Opus and Eigen */
|
||||
#ifdef Q_OS_WIN
|
||||
#include "opus.h"
|
||||
#include <Eigen/Eigen>
|
||||
#else
|
||||
#include "opus/opus.h"
|
||||
#include <eigen3/Eigen/Eigen>
|
||||
#endif
|
||||
|
||||
#include "wfviewtypes.h"
|
||||
|
||||
#include "resampler/speex_resampler.h"
|
||||
|
||||
#include "packettypes.h"
|
||||
|
||||
struct audioPacket {
|
||||
quint32 seq;
|
||||
QTime time;
|
||||
quint16 sent;
|
||||
QByteArray data;
|
||||
quint8 guid[GUIDLEN];
|
||||
float amplitudePeak;
|
||||
float amplitudeRMS;
|
||||
qreal volume = 1.0;
|
||||
};
|
||||
|
||||
struct audioSetup {
|
||||
audioType type;
|
||||
QString name;
|
||||
quint16 latency;
|
||||
quint8 codec;
|
||||
bool ulaw = false;
|
||||
bool isinput;
|
||||
quint32 sampleRate;
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
QAudioDeviceInfo port;
|
||||
#else
|
||||
QAudioDevice port;
|
||||
#endif
|
||||
int portInt;
|
||||
quint8 resampleQuality;
|
||||
unsigned char localAFgain;
|
||||
quint16 blockSize = 20; // Each 'block' of audio is 20ms long by default.
|
||||
quint8 guid[GUIDLEN];
|
||||
};
|
||||
|
||||
class audioConverter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit audioConverter(QObject* parent = nullptr);;
|
||||
~audioConverter();
|
||||
|
||||
public slots:
|
||||
bool init(QAudioFormat inFormat, codecType inCodec, QAudioFormat outFormat, codecType outCodec, quint8 opusComplexity, quint8 resampleQuality);
|
||||
bool convert(audioPacket audio);
|
||||
|
||||
signals:
|
||||
void converted(audioPacket audio);
|
||||
|
||||
protected:
|
||||
QAudioFormat inFormat;
|
||||
QAudioFormat outFormat;
|
||||
OpusEncoder* opusEncoder = Q_NULLPTR;
|
||||
OpusDecoder* opusDecoder = Q_NULLPTR;
|
||||
SpeexResamplerState* resampler = Q_NULLPTR;
|
||||
quint8 opusComplexity;
|
||||
quint8 resampleQuality = 4;
|
||||
double resampleRatio=1.0; // Default resample ratio is 1:1
|
||||
quint32 lastAudioSequence;
|
||||
codecType inCodec;
|
||||
codecType outCodec;
|
||||
};
|
||||
|
||||
|
||||
// Various audio handling functions declared inline
|
||||
|
||||
typedef Eigen::Matrix<quint8, Eigen::Dynamic, 1> VectorXuint8;
|
||||
typedef Eigen::Matrix<qint8, Eigen::Dynamic, 1> VectorXint8;
|
||||
typedef Eigen::Matrix<qint16, Eigen::Dynamic, 1> VectorXint16;
|
||||
typedef Eigen::Matrix<qint32, Eigen::Dynamic, 1> VectorXint32;
|
||||
|
||||
static inline QAudioFormat toQAudioFormat(quint8 codec, quint32 sampleRate)
|
||||
{
|
||||
QAudioFormat format;
|
||||
|
||||
/*
|
||||
0x01 uLaw 1ch 8bit
|
||||
0x02 PCM 1ch 8bit
|
||||
0x04 PCM 1ch 16bit
|
||||
0x08 PCM 2ch 8bit
|
||||
0x10 PCM 2ch 16bit
|
||||
0x20 uLaw 2ch 8bit
|
||||
0x40 Opus 1ch
|
||||
0x80 Opus 2ch
|
||||
*/
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
format.setByteOrder(QAudioFormat::LittleEndian);
|
||||
format.setCodec("audio/pcm");
|
||||
#endif
|
||||
|
||||
format.setSampleRate(sampleRate);
|
||||
|
||||
if (codec == 0x01 || codec == 0x20) {
|
||||
/* Set sample to be what is expected by the encoder and the output of the decoder */
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
format.setSampleSize(16);
|
||||
format.setSampleType(QAudioFormat::SignedInt);
|
||||
format.setCodec("audio/PCMU");
|
||||
#else
|
||||
format.setSampleFormat(QAudioFormat::Int16);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (codec == 0x02 || codec == 0x08) {
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
format.setSampleSize(8);
|
||||
format.setSampleType(QAudioFormat::UnSignedInt);
|
||||
#else
|
||||
format.setSampleFormat(QAudioFormat::UInt8);
|
||||
#endif
|
||||
}
|
||||
if (codec == 0x08 || codec == 0x10 || codec == 0x20 || codec == 0x80) {
|
||||
format.setChannelCount(2);
|
||||
} else {
|
||||
format.setChannelCount(1);
|
||||
}
|
||||
|
||||
if (codec == 0x04 || codec == 0x10) {
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
format.setSampleSize(16);
|
||||
format.setSampleType(QAudioFormat::SignedInt);
|
||||
#else
|
||||
format.setSampleFormat(QAudioFormat::Int16);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (codec == 0x40 || codec == 0x80) {
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
format.setSampleSize(32);
|
||||
format.setSampleType(QAudioFormat::Float);
|
||||
format.setCodec("audio/opus");
|
||||
#else
|
||||
format.setSampleFormat(QAudioFormat::Float);
|
||||
#endif
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,453 @@
|
|||
/*
|
||||
wfview class to enumerate audio devices and assist with matching saved devices
|
||||
|
||||
Written by Phil Taylor M0VSE Nov 2022.
|
||||
|
||||
*/
|
||||
|
||||
#include "audiodevices.h"
|
||||
#include "logcategories.h"
|
||||
|
||||
audioDevices::audioDevices(audioType type, QFontMetrics fm, QObject* parent) :
|
||||
QObject(parent),
|
||||
system(type),
|
||||
fm(fm)
|
||||
{
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
|
||||
connect(&mediaDevices, &QMediaDevices::audioInputsChanged, this, &audioDevices::enumerate);
|
||||
connect(&mediaDevices, &QMediaDevices::audioOutputsChanged, this, &audioDevices::enumerate);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
||||
void audioDevices::enumerate()
|
||||
{
|
||||
numInputDevices = 0;
|
||||
numOutputDevices = 0;
|
||||
numCharsIn = 0;
|
||||
numCharsOut = 0;
|
||||
inputs.clear();
|
||||
outputs.clear();
|
||||
|
||||
switch (system)
|
||||
{
|
||||
case qtAudio:
|
||||
{
|
||||
Pa_Terminate();
|
||||
|
||||
qInfo(logAudio()) << "Audio device(s) found (*=default)";
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
foreach(const QAudioDeviceInfo & deviceInfo, QAudioDeviceInfo::availableDevices(QAudio::AudioInput))
|
||||
#else
|
||||
const auto audioInputs = mediaDevices.audioInputs();
|
||||
for (const QAudioDevice& deviceInfo : audioInputs)
|
||||
#endif
|
||||
{
|
||||
bool isDefault = false;
|
||||
if (numInputDevices == 0) {
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
defaultInputDeviceName = QString(deviceInfo.deviceName());
|
||||
#else
|
||||
defaultInputDeviceName = QString(deviceInfo.description());
|
||||
#endif
|
||||
}
|
||||
#if (defined(Q_OS_WIN) && (QT_VERSION < QT_VERSION_CHECK(6,0,0)))
|
||||
if (deviceInfo.realm() == audioApi || audioApi == "") {
|
||||
#endif
|
||||
/* Append Input Device Here */
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
if (deviceInfo.deviceName() == defaultInputDeviceName) {
|
||||
#else
|
||||
if (deviceInfo.description() == defaultInputDeviceName) {
|
||||
#endif
|
||||
isDefault = true;
|
||||
}
|
||||
|
||||
#if ((QT_VERSION >= QT_VERSION_CHECK(5,15,0)) && (QT_VERSION < QT_VERSION_CHECK(6,0,0)))
|
||||
inputs.append(new audioDevice(deviceInfo.deviceName(), deviceInfo, deviceInfo.realm(), isDefault));
|
||||
qInfo(logAudio()) << (deviceInfo.deviceName() == defaultInputDeviceName ? "*" : " ") <<
|
||||
"(" << numInputDevices <<" " << deviceInfo.realm() << ") Input Device : " <<
|
||||
deviceInfo.deviceName();
|
||||
#elif (QT_VERSION < QT_VERSION_CHECK(5,15,0))
|
||||
inputs.append(new audioDevice(deviceInfo.deviceName(), deviceInfo, "", isDefault));
|
||||
qInfo(logAudio()) << (deviceInfo.deviceName() == defaultInputDeviceName ? "*" : " ") <<
|
||||
"(" << numInputDevices << ") Input Device : " << deviceInfo.deviceName();
|
||||
#else
|
||||
inputs.append(new audioDevice(deviceInfo.description(), deviceInfo, "", isDefault));
|
||||
qInfo(logAudio()) << (deviceInfo.description() == defaultInputDeviceName ? "*" : " ") <<
|
||||
"(" << numInputDevices << ") Input Device : " << deviceInfo.description();
|
||||
#endif
|
||||
|
||||
#ifndef BUILD_WFSERVER
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
if (fm.boundingRect(deviceInfo.deviceName()).width() > numCharsIn)
|
||||
numCharsIn = fm.boundingRect(deviceInfo.deviceName()).width();
|
||||
#else
|
||||
if (fm.boundingRect(deviceInfo.description()).width() > numCharsIn)
|
||||
numCharsIn = fm.boundingRect(deviceInfo.description()).width();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (defined(Q_OS_WIN) && (QT_VERSION < QT_VERSION_CHECK(6,0,0)))
|
||||
}
|
||||
#endif
|
||||
numInputDevices++;
|
||||
}
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
foreach(const QAudioDeviceInfo & deviceInfo, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput))
|
||||
#else
|
||||
const auto audioOutputs = mediaDevices.audioOutputs();
|
||||
for (const QAudioDevice& deviceInfo : audioOutputs)
|
||||
#endif
|
||||
{
|
||||
bool isDefault = false;
|
||||
if (numOutputDevices == 0)
|
||||
{
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
defaultOutputDeviceName = QString(deviceInfo.deviceName());
|
||||
#else
|
||||
defaultOutputDeviceName = QString(deviceInfo.description());
|
||||
#endif
|
||||
}
|
||||
|
||||
#if (defined(Q_OS_WIN) && (QT_VERSION < QT_VERSION_CHECK(6,0,0)))
|
||||
if (deviceInfo.realm() == "wasapi") {
|
||||
#endif
|
||||
/* Append Output Device Here */
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
if (deviceInfo.deviceName() == defaultOutputDeviceName) {
|
||||
#else
|
||||
if (deviceInfo.description() == defaultOutputDeviceName) {
|
||||
#endif
|
||||
isDefault = true;
|
||||
}
|
||||
|
||||
#if ((QT_VERSION >= QT_VERSION_CHECK(5,15,0)) && (QT_VERSION < QT_VERSION_CHECK(6,0,0)))
|
||||
outputs.append(new audioDevice(deviceInfo.deviceName(), deviceInfo, deviceInfo.realm() , isDefault));
|
||||
qInfo(logAudio()) << (deviceInfo.deviceName() == defaultOutputDeviceName ? "*" : " ") <<
|
||||
"(" << numOutputDevices << " " << deviceInfo.realm() << ") Output Device : " <<
|
||||
deviceInfo.deviceName();
|
||||
#elif (QT_VERSION < QT_VERSION_CHECK(5,15,0))
|
||||
outputs.append(new audioDevice(deviceInfo.deviceName(), deviceInfo, "", isDefault));
|
||||
qInfo(logAudio()) << (deviceInfo.deviceName() == defaultOutputDeviceName ? "*" : " ") <<
|
||||
"(" << numOutputDevices << ") Output Device : " << deviceInfo.deviceName();
|
||||
#else
|
||||
outputs.append(new audioDevice(deviceInfo.description(), deviceInfo, "", isDefault));
|
||||
qInfo(logAudio()) << (deviceInfo.description() == defaultOutputDeviceName ? "*" : " ") <<
|
||||
"(" << numOutputDevices << ") Output Device : " << deviceInfo.description();
|
||||
#endif
|
||||
|
||||
#ifndef BUILD_WFSERVER
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
if (fm.boundingRect(deviceInfo.deviceName()).width() > numCharsOut)
|
||||
numCharsOut = fm.boundingRect(deviceInfo.deviceName()).width();
|
||||
#else
|
||||
if (fm.boundingRect(deviceInfo.description()).width() > numCharsOut)
|
||||
numCharsOut = fm.boundingRect(deviceInfo.description()).width();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if (defined(Q_OS_WIN) && (QT_VERSION < QT_VERSION_CHECK(6,0,0)))
|
||||
}
|
||||
#endif
|
||||
numOutputDevices++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case portAudio:
|
||||
{
|
||||
PaError err;
|
||||
|
||||
err = Pa_Initialize();
|
||||
|
||||
if (err != paNoError)
|
||||
{
|
||||
qInfo(logAudio()) << "ERROR: Cannot initialize Portaudio";
|
||||
return;
|
||||
}
|
||||
|
||||
qInfo(logAudio()) << "PortAudio version: " << Pa_GetVersionInfo()->versionText;
|
||||
|
||||
int numDevices = Pa_GetDeviceCount();
|
||||
qInfo(logAudio()) << "Pa_CountDevices returned" << numDevices << "audio device(s) (*=default)";
|
||||
|
||||
|
||||
const PaDeviceInfo* info;
|
||||
for (int i = 0; i < numDevices; i++)
|
||||
{
|
||||
info = Pa_GetDeviceInfo(i);
|
||||
if (info->maxInputChannels > 0) {
|
||||
bool isDefault = false;
|
||||
numInputDevices++;
|
||||
qInfo(logAudio()) << (i == Pa_GetDefaultInputDevice() ? "*" : " ") << "(" << i << ") Input Device : " << QString(info->name);
|
||||
if (i == Pa_GetDefaultInputDevice()) {
|
||||
defaultInputDeviceName = info->name;
|
||||
isDefault = true;
|
||||
}
|
||||
inputs.append(new audioDevice(QString(info->name), i,isDefault));
|
||||
#ifndef BUILD_WFSERVER
|
||||
if (fm.boundingRect(QString(info->name)).width() > numCharsIn)
|
||||
numCharsIn = fm.boundingRect(QString(info->name)).width();
|
||||
#endif
|
||||
|
||||
}
|
||||
if (info->maxOutputChannels > 0) {
|
||||
bool isDefault = false;
|
||||
numOutputDevices++;
|
||||
qInfo(logAudio()) << (i == Pa_GetDefaultOutputDevice() ? "*" : " ") << "(" << i << ") Output Device : " << QString(info->name);
|
||||
if (i == Pa_GetDefaultOutputDevice()) {
|
||||
defaultOutputDeviceName = info->name;
|
||||
isDefault = true;
|
||||
}
|
||||
outputs.append(new audioDevice(QString(info->name), i,isDefault));
|
||||
#ifndef BUILD_WFSERVER
|
||||
if (fm.boundingRect(QString(info->name)).width() > numCharsOut)
|
||||
numCharsOut = fm.boundingRect(QString(info->name)).width();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case rtAudio:
|
||||
{
|
||||
Pa_Terminate();
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
RtAudio* audio = new RtAudio(RtAudio::Api::LINUX_ALSA);
|
||||
#elif defined(Q_OS_WIN)
|
||||
RtAudio* audio = new RtAudio(RtAudio::Api::WINDOWS_WASAPI);
|
||||
#elif defined(Q_OS_MACX)
|
||||
RtAudio* audio = new RtAudio(RtAudio::Api::MACOSX_CORE);
|
||||
#endif
|
||||
|
||||
|
||||
// Enumerate audio devices, need to do before settings are loaded.
|
||||
std::map<int, std::string> apiMap;
|
||||
apiMap[RtAudio::MACOSX_CORE] = "OS-X Core Audio";
|
||||
apiMap[RtAudio::WINDOWS_ASIO] = "Windows ASIO";
|
||||
apiMap[RtAudio::WINDOWS_DS] = "Windows DirectSound";
|
||||
apiMap[RtAudio::WINDOWS_WASAPI] = "Windows WASAPI";
|
||||
apiMap[RtAudio::UNIX_JACK] = "Jack Client";
|
||||
apiMap[RtAudio::LINUX_ALSA] = "Linux ALSA";
|
||||
apiMap[RtAudio::LINUX_PULSE] = "Linux PulseAudio";
|
||||
apiMap[RtAudio::LINUX_OSS] = "Linux OSS";
|
||||
apiMap[RtAudio::RTAUDIO_DUMMY] = "RtAudio Dummy";
|
||||
|
||||
std::vector< RtAudio::Api > apis;
|
||||
RtAudio::getCompiledApi(apis);
|
||||
|
||||
qInfo(logAudio()) << "RtAudio Version " << QString::fromStdString(RtAudio::getVersion());
|
||||
|
||||
qInfo(logAudio()) << "Compiled APIs:";
|
||||
for (unsigned int i = 0; i < apis.size(); i++) {
|
||||
qInfo(logAudio()) << " " << QString::fromStdString(apiMap[apis[i]]);
|
||||
}
|
||||
|
||||
RtAudio::DeviceInfo info;
|
||||
|
||||
qInfo(logAudio()) << "Current API: " << QString::fromStdString(apiMap[audio->getCurrentApi()]);
|
||||
|
||||
unsigned int devicecount = audio->getDeviceCount();
|
||||
|
||||
#if (RTAUDIO_VERSION_MAJOR > 5)
|
||||
std::vector<unsigned int> devices = audio->getDeviceIds();
|
||||
#endif
|
||||
qInfo(logAudio()) << "Found:" << devicecount << " audio device(s) (*=default)";
|
||||
|
||||
for (unsigned int i = 1; i < devicecount; i++) {
|
||||
|
||||
#if (RTAUDIO_VERSION_MAJOR > 5)
|
||||
info = audio->getDeviceInfo(devices[i]);
|
||||
#else
|
||||
info = audio->getDeviceInfo(i);
|
||||
#endif
|
||||
if (info.inputChannels > 0) {
|
||||
bool isDefault = false;
|
||||
qInfo(logAudio()) << (info.isDefaultInput ? "*" : " ") << "(" << i << ") Input Device : " << QString::fromStdString(info.name);
|
||||
numInputDevices++;
|
||||
|
||||
if (info.isDefaultInput) {
|
||||
defaultInputDeviceName = QString::fromStdString(info.name);
|
||||
isDefault = true;
|
||||
}
|
||||
|
||||
#if (RTAUDIO_VERSION_MAJOR > 5)
|
||||
inputs.append(new audioDevice(QString::fromStdString(info.name), devices[i], isDefault));
|
||||
#else
|
||||
inputs.append(new audioDevice(QString::fromStdString(info.name), i, isDefault));
|
||||
#endif
|
||||
|
||||
#ifndef BUILD_WFSERVER
|
||||
if (fm.boundingRect(QString::fromStdString(info.name)).width() > numCharsIn)
|
||||
numCharsIn = fm.boundingRect(QString::fromStdString(info.name)).width();
|
||||
#endif
|
||||
|
||||
}
|
||||
if (info.outputChannels > 0) {
|
||||
bool isDefault = false;
|
||||
qInfo(logAudio()) << (info.isDefaultOutput ? "*" : " ") << "(" << i << ") Output Device : " << QString::fromStdString(info.name);
|
||||
numOutputDevices++;
|
||||
|
||||
if (info.isDefaultOutput) {
|
||||
defaultOutputDeviceName = QString::fromStdString(info.name);
|
||||
isDefault = true;
|
||||
}
|
||||
|
||||
#if (RTAUDIO_VERSION_MAJOR > 5)
|
||||
outputs.append(new audioDevice(QString::fromStdString(info.name), devices[i], isDefault));
|
||||
#else
|
||||
outputs.append(new audioDevice(QString::fromStdString(info.name), i, isDefault));
|
||||
#endif
|
||||
|
||||
#ifndef BUILD_WFSERVER
|
||||
if (fm.boundingRect(QString::fromStdString(info.name)).width() > numCharsOut)
|
||||
numCharsOut = fm.boundingRect(QString::fromStdString(info.name)).width();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
delete audio;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
emit updated();
|
||||
|
||||
}
|
||||
|
||||
audioDevices::~audioDevices()
|
||||
{
|
||||
outputs.clear();
|
||||
inputs.clear();
|
||||
}
|
||||
|
||||
QStringList audioDevices::getInputs()
|
||||
{
|
||||
QStringList list;
|
||||
|
||||
for (int f = 0; f < inputs.size(); f++) {
|
||||
list.append(inputs[f]->name);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
QStringList audioDevices::getOutputs()
|
||||
{
|
||||
QStringList list;
|
||||
|
||||
for (int f = 0; f < outputs.size(); f++) {
|
||||
list.append(outputs[f]->name);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
int audioDevices::findInput(QString type, QString name)
|
||||
{
|
||||
int ret = -1;
|
||||
int def = -1;
|
||||
int usb = -1;
|
||||
QString msg;
|
||||
QTextStream s(&msg);
|
||||
|
||||
for (int f = 0; f < inputs.size(); f++)
|
||||
{
|
||||
//qInfo(logAudio()) << "Found device" << inputs[f].name;
|
||||
if (inputs[f]->name.startsWith(name)) {
|
||||
s << type << " Audio input device " << name << " found! ";
|
||||
ret = f;
|
||||
}
|
||||
if (inputs[f]->isDefault == true)
|
||||
{
|
||||
def = f;
|
||||
}
|
||||
if (inputs[f]->name.toUpper().contains("USB")) {
|
||||
// This is a USB device...
|
||||
usb = f;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == -1)
|
||||
{
|
||||
s << type << " Audio input device " << name << " Not found: ";
|
||||
|
||||
if (inputs.size() == 1) {
|
||||
s << "Selecting first device " << inputs[0]->name;
|
||||
ret = 0;
|
||||
}
|
||||
else if (usb > -1 && type != "Client")
|
||||
{
|
||||
s << " Selecting found USB device " << inputs[usb]->name;
|
||||
ret = usb;
|
||||
}
|
||||
else if (def > -1)
|
||||
{
|
||||
s << " Selecting default device " << inputs[def]->name;
|
||||
ret = def;
|
||||
}
|
||||
else {
|
||||
s << " and no default device found, aborting!";
|
||||
}
|
||||
}
|
||||
|
||||
qInfo(logAudio()) << msg;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int audioDevices::findOutput(QString type, QString name)
|
||||
{
|
||||
int ret = -1;
|
||||
int def = -1;
|
||||
int usb = -1;
|
||||
QString msg;
|
||||
QTextStream s(&msg);
|
||||
|
||||
for (int f = 0; f < outputs.size(); f++)
|
||||
{
|
||||
//qInfo(logAudio()) << "Found device" << outputs[f].name;
|
||||
if (outputs[f]->name.startsWith(name)) {
|
||||
ret = f;
|
||||
s << type << " Audio output device " << name << " found! ";
|
||||
}
|
||||
if (outputs[f]->isDefault == true)
|
||||
{
|
||||
def = f;
|
||||
}
|
||||
if (outputs[f]->name.toUpper().contains("USB")) {
|
||||
// This is a USB device...
|
||||
usb = f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (ret == -1)
|
||||
{
|
||||
s << type << " Audio output device " << name << " Not found: ";
|
||||
|
||||
if (outputs.size() == 1) {
|
||||
s << " Selecting first device " << outputs[0]->name;
|
||||
ret = 0;
|
||||
}
|
||||
else if (usb > -1 && type != "Client")
|
||||
{
|
||||
s << " Selecting found USB device " << outputs[usb]->name;
|
||||
ret = usb;
|
||||
}
|
||||
else if (def > -1)
|
||||
{
|
||||
s << " Selecting default device " << outputs[def]->name;
|
||||
ret = def;
|
||||
}
|
||||
else {
|
||||
s << " and no default device found, aborting!";
|
||||
}
|
||||
}
|
||||
qInfo(logAudio()) << msg;
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
#ifndef AUDIODEVICES_H
|
||||
#define AUDIODEVICES_H
|
||||
#include <QObject>
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
#include <QAudioDeviceInfo>
|
||||
#else
|
||||
#include <QMediaDevices>
|
||||
#include <QAudioDevice>
|
||||
#include <QString>
|
||||
#endif
|
||||
|
||||
#include <QFontMetrics>
|
||||
|
||||
#include <portaudio.h>
|
||||
#ifndef Q_OS_LINUX
|
||||
#include "RtAudio.h"
|
||||
#else
|
||||
#include "rtaudio/RtAudio.h"
|
||||
#endif
|
||||
|
||||
#include "wfviewtypes.h"
|
||||
|
||||
|
||||
struct audioDevice {
|
||||
audioDevice(QString name, int deviceInt, bool isDefault) : name(name), deviceInt(deviceInt), isDefault(isDefault) {};
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
audioDevice(QString name, const QAudioDeviceInfo deviceInfo, QString realm, bool isDefault) : name(name), deviceInfo(deviceInfo), realm(realm), isDefault(isDefault) {};
|
||||
#else
|
||||
audioDevice(QString name, QAudioDevice deviceInfo, QString realm, bool isDefault) : name(name), deviceInfo(deviceInfo), realm(realm), isDefault(isDefault) {};
|
||||
#endif
|
||||
|
||||
QString name;
|
||||
int deviceInt;
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
const QAudioDeviceInfo deviceInfo;
|
||||
#else
|
||||
QAudioDevice deviceInfo;
|
||||
#endif
|
||||
|
||||
QString realm;
|
||||
bool isDefault;
|
||||
};
|
||||
|
||||
class audioDevices : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit audioDevices(audioType type, QFontMetrics fm, QObject* parent = nullptr);
|
||||
~audioDevices();
|
||||
void setAudioType(audioType type) { system = type; };
|
||||
audioType getAudioType() { return system; };
|
||||
int getNumCharsIn() { return numCharsIn; };
|
||||
int getNumCharsOut() { return numCharsOut; };
|
||||
|
||||
QString getInputName(int num) { return inputs[num]->name; };
|
||||
QString getOutputName(int num) { return outputs[num]->name; };
|
||||
|
||||
int getInputDeviceInt(int num) { return inputs[num]->deviceInt; };
|
||||
int getOutputDeviceInt(int num) { return outputs[num]->deviceInt; };
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
const QAudioDeviceInfo getInputDeviceInfo(int num) { return inputs[num]->deviceInfo; };
|
||||
const QAudioDeviceInfo getOutputDeviceInfo(int num) { return outputs[num]->deviceInfo; };
|
||||
#else
|
||||
const QAudioDevice getInputDeviceInfo(int num) { return inputs[num]->deviceInfo; };
|
||||
const QAudioDevice getOutputDeviceInfo(int num) { return outputs[num]->deviceInfo; };
|
||||
#endif
|
||||
|
||||
void enumerate();
|
||||
|
||||
QStringList getInputs();
|
||||
QStringList getOutputs();
|
||||
|
||||
int findInput(QString type, QString name);
|
||||
int findOutput(QString type, QString name);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
|
||||
QMediaDevices mediaDevices;
|
||||
#endif
|
||||
|
||||
public slots:
|
||||
|
||||
signals:
|
||||
void updated();
|
||||
protected:
|
||||
private:
|
||||
|
||||
audioType system;
|
||||
QFontMetrics fm;
|
||||
QString defaultInputDeviceName;
|
||||
QString defaultOutputDeviceName;
|
||||
int numInputDevices;
|
||||
int numOutputDevices;
|
||||
QList<audioDevice*> inputs;
|
||||
QList<audioDevice*> outputs;
|
||||
int numCharsIn = 0;
|
||||
int numCharsOut = 0;
|
||||
QString audioApi = "wasapi";
|
||||
|
||||
};
|
||||
|
||||
#endif
|
1025
audiohandler.cpp
207
audiohandler.h
|
@ -1,193 +1,146 @@
|
|||
#ifndef AUDIOHANDLER_H
|
||||
#define AUDIOHANDLER_H
|
||||
|
||||
/* QT Headers */
|
||||
#include <QObject>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QMutex>
|
||||
#include <QtEndian>
|
||||
#include <QtMath>
|
||||
|
||||
#if defined(RTAUDIO)
|
||||
#include "rtaudio/RtAudio.h"
|
||||
#elif defined (PORTAUDIO)
|
||||
#include "portaudio.h"
|
||||
#error "PORTAUDIO is not currently supported"
|
||||
#else
|
||||
#include <QAudioOutput>
|
||||
#include <QAudioFormat>
|
||||
#include <QAudioDeviceInfo>
|
||||
#include <QAudioInput>
|
||||
#include <QIODevice>
|
||||
#endif
|
||||
|
||||
typedef signed short MY_TYPE;
|
||||
#define FORMAT RTAUDIO_SINT16
|
||||
#define SCALE 32767.0
|
||||
|
||||
#define LOG100 4.60517018599
|
||||
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QTime>
|
||||
#include <QMap>
|
||||
#include "resampler/speex_resampler.h"
|
||||
#include "ring/ring.h"
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "opus.h"
|
||||
/* QT Audio Headers */
|
||||
#include <QAudioOutput>
|
||||
#include <QAudioFormat>
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
#include <QAudioDeviceInfo>
|
||||
#include <QAudioInput>
|
||||
#include <QAudioOutput>
|
||||
#else
|
||||
#include "opus/opus.h"
|
||||
#include <QMediaDevices>
|
||||
#include <QAudioDevice>
|
||||
#include <QAudioSource>
|
||||
#include <QAudioSink>
|
||||
#endif
|
||||
|
||||
#include <QIODevice>
|
||||
|
||||
|
||||
/* wfview Packet types */
|
||||
#include "packettypes.h"
|
||||
|
||||
/* Logarithmic taper for volume control */
|
||||
#include "audiotaper.h"
|
||||
|
||||
|
||||
#include <QDebug>
|
||||
/* Audio converter class*/
|
||||
#include "audioconverter.h"
|
||||
|
||||
//#define BUFFER_SIZE (32*1024)
|
||||
|
||||
#define INTERNAL_SAMPLE_RATE 48000
|
||||
|
||||
#define MULAW_BIAS 33
|
||||
#define MULAW_MAX 0x1fff
|
||||
|
||||
struct audioPacket {
|
||||
quint32 seq;
|
||||
QTime time;
|
||||
quint16 sent;
|
||||
QByteArray data;
|
||||
};
|
||||
|
||||
struct audioSetup {
|
||||
QString name;
|
||||
quint8 bits;
|
||||
quint8 radioChan;
|
||||
quint16 samplerate;
|
||||
quint16 latency;
|
||||
quint8 codec;
|
||||
bool ulaw;
|
||||
bool isinput;
|
||||
#if defined(RTAUDIO)
|
||||
int port;
|
||||
#elif defined(PORTAUDIO)
|
||||
#else
|
||||
QAudioDeviceInfo port;
|
||||
#endif
|
||||
quint8 resampleQuality;
|
||||
unsigned char localAFgain;
|
||||
};
|
||||
|
||||
// For QtMultimedia, use a native QIODevice
|
||||
#if !defined(PORTAUDIO) && !defined(RTAUDIO)
|
||||
class audioHandler : public QIODevice
|
||||
#else
|
||||
//class audioHandler : public QIODevice
|
||||
class audioHandler : public QObject
|
||||
#endif
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
audioHandler(QObject* parent = 0);
|
||||
~audioHandler();
|
||||
audioHandler(QObject* parent = nullptr);
|
||||
virtual ~audioHandler();
|
||||
|
||||
int getLatency();
|
||||
virtual int getLatency();
|
||||
|
||||
#if !defined (RTAUDIO) && !defined(PORTAUDIO)
|
||||
bool setDevice(QAudioDeviceInfo deviceInfo);
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
|
||||
void start();
|
||||
void flush();
|
||||
void stop();
|
||||
qint64 bytesAvailable() const;
|
||||
bool isSequential() const;
|
||||
#endif
|
||||
|
||||
void getNextAudioChunk(QByteArray &data);
|
||||
virtual quint16 getAmplitude();
|
||||
|
||||
public slots:
|
||||
bool init(audioSetup setup);
|
||||
void changeLatency(const quint16 newSize);
|
||||
void setVolume(unsigned char volume);
|
||||
void incomingAudio(const audioPacket data);
|
||||
virtual bool init(audioSetup setup);
|
||||
virtual void changeLatency(const quint16 newSize);
|
||||
virtual void setVolume(unsigned char volume);
|
||||
virtual void incomingAudio(const audioPacket data);
|
||||
virtual void convertedInput(audioPacket audio);
|
||||
virtual void convertedOutput(audioPacket audio);
|
||||
virtual void getNextAudioChunk();
|
||||
|
||||
private slots:
|
||||
#if !defined (RTAUDIO) && !defined(PORTAUDIO)
|
||||
void notified();
|
||||
void stateChanged(QAudio::State state);
|
||||
#endif
|
||||
virtual void stateChanged(QAudio::State state);
|
||||
virtual void clearUnderrun();
|
||||
|
||||
|
||||
signals:
|
||||
void audioMessage(QString message);
|
||||
void sendLatency(quint16 newSize);
|
||||
void haveAudioData(const QByteArray& data);
|
||||
void haveAudioData(const audioPacket& data);
|
||||
void haveLevels(quint16 amplitudePeak, quint16 amplitudeRMS,quint16 latency,quint16 current,bool under,bool over);
|
||||
void setupConverter(QAudioFormat in, codecType codecIn, QAudioFormat out, codecType codecOut, quint8 opus, quint8 resamp);
|
||||
void sendToConverter(audioPacket audio);
|
||||
|
||||
|
||||
private:
|
||||
|
||||
#if defined(RTAUDIO)
|
||||
int readData(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status);
|
||||
//qint64 readData(char* data, qint64 nBytes);
|
||||
//qint64 writeData(const char* data, qint64 nBytes);
|
||||
|
||||
static int staticRead(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void* userData) {
|
||||
return static_cast<audioHandler*>(userData)->readData(outputBuffer, inputBuffer, nFrames, streamTime, status);
|
||||
}
|
||||
|
||||
int writeData(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status);
|
||||
|
||||
static int staticWrite(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void* userData) {
|
||||
return static_cast<audioHandler*>(userData)->writeData(outputBuffer, inputBuffer, nFrames, streamTime, status);
|
||||
}
|
||||
#elif defined(PORTAUDIO)
|
||||
|
||||
#else
|
||||
qint64 readData(char* data, qint64 nBytes);
|
||||
qint64 writeData(const char* data, qint64 nBytes);
|
||||
#endif
|
||||
|
||||
void reinit();
|
||||
bool isUnderrun = false;
|
||||
bool isOverrun = false;
|
||||
bool isInitialized=false;
|
||||
bool isReady = false;
|
||||
bool audioBuffered = false;
|
||||
|
||||
#if defined(RTAUDIO)
|
||||
RtAudio* audio = Q_NULLPTR;
|
||||
int audioDevice = 0;
|
||||
RtAudio::StreamParameters aParams;
|
||||
RtAudio::StreamOptions options;
|
||||
RtAudio::DeviceInfo info;
|
||||
#elif defined(PORTAUDIO)
|
||||
#else
|
||||
QAudioOutput* audioOutput=Q_NULLPTR;
|
||||
QAudioInput* audioInput=Q_NULLPTR;
|
||||
QAudioFormat format;
|
||||
QIODevice* audioDevice=Q_NULLPTR;
|
||||
|
||||
QAudioFormat radioFormat;
|
||||
QAudioFormat nativeFormat;
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
QAudioOutput* audioOutput = Q_NULLPTR;
|
||||
QAudioInput* audioInput = Q_NULLPTR;
|
||||
QAudioDeviceInfo deviceInfo;
|
||||
#else
|
||||
QAudioSink* audioOutput = Q_NULLPTR;
|
||||
QAudioSource* audioInput = Q_NULLPTR;
|
||||
QAudioDevice deviceInfo;
|
||||
#endif
|
||||
SpeexResamplerState* resampler = Q_NULLPTR;
|
||||
|
||||
audioConverter* converter=Q_NULLPTR;
|
||||
QThread* converterThread = Q_NULLPTR;
|
||||
QTime lastReceived;
|
||||
//r8b::CFixedBuffer<double>* resampBufs;
|
||||
//r8b::CPtrKeeper<r8b::CDSPResampler24*>* resamps;
|
||||
|
||||
quint16 audioLatency;
|
||||
unsigned int chunkSize;
|
||||
bool chunkAvailable;
|
||||
|
||||
quint32 lastSeq;
|
||||
quint32 lastSentSeq=0;
|
||||
qint64 elapsedMs = 0;
|
||||
|
||||
quint16 nativeSampleRate=0;
|
||||
quint8 radioSampleBits;
|
||||
quint8 radioChannels;
|
||||
int delayedPackets=0;
|
||||
|
||||
QMap<quint32, audioPacket>audioBuffer;
|
||||
|
||||
unsigned int ratioNum;
|
||||
unsigned int ratioDen;
|
||||
|
||||
wilt::Ring<audioPacket> *ringBuf=Q_NULLPTR;
|
||||
double resampleRatio;
|
||||
|
||||
volatile bool ready = false;
|
||||
audioPacket tempBuf;
|
||||
quint16 currentLatency;
|
||||
qreal volume=1.0;
|
||||
int devChannels;
|
||||
float amplitude=0.0;
|
||||
qreal volume = 1.0;
|
||||
|
||||
audioSetup setup;
|
||||
OpusEncoder* encoder=Q_NULLPTR;
|
||||
OpusDecoder* decoder=Q_NULLPTR;
|
||||
|
||||
OpusEncoder* encoder = Q_NULLPTR;
|
||||
OpusDecoder* decoder = Q_NULLPTR;
|
||||
QTimer* underTimer;
|
||||
codecType codec;
|
||||
};
|
||||
|
||||
|
||||
#endif // AUDIOHANDLER_H
|
||||
|
|
|
@ -173,7 +173,7 @@
|
|||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Load the calibration data fromthe indicated slot in the preference file. </p></body></html></string>
|
||||
<string><html><head/><body><p>Load the calibration data from the indicated slot in the preference file. </p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Load</string>
|
||||
|
|
|
@ -0,0 +1,314 @@
|
|||
#include "cluster.h"
|
||||
#include "logcategories.h"
|
||||
|
||||
|
||||
dxClusterClient::dxClusterClient(QObject* parent):
|
||||
QObject(parent)
|
||||
{
|
||||
qInfo(logCluster()) << "starting dxClusterClient()";
|
||||
}
|
||||
|
||||
dxClusterClient::~dxClusterClient()
|
||||
{
|
||||
qInfo(logCluster()) << "closing dxClusterClient()";
|
||||
enableUdp(false);
|
||||
enableTcp(false);
|
||||
#ifdef USESQL
|
||||
database db;
|
||||
db.close();
|
||||
#else
|
||||
QMap<QString, spotData*>::iterator spot = allSpots.begin();
|
||||
while (spot != allSpots.end())
|
||||
{
|
||||
delete spot.value(); // Stop memory leak?
|
||||
spot = allSpots.erase(spot);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void dxClusterClient::enableUdp(bool enable)
|
||||
{
|
||||
udpEnable = enable;
|
||||
if (enable)
|
||||
{
|
||||
if (udpSocket == Q_NULLPTR)
|
||||
{
|
||||
udpSocket = new QUdpSocket(this);
|
||||
bool result = udpSocket->bind(QHostAddress::AnyIPv4, udpPort);
|
||||
qInfo(logCluster()) << "Starting udpSocket() on:" << udpPort << "Result:" << result;
|
||||
|
||||
connect(udpSocket, SIGNAL(readyRead()), this, SLOT(udpDataReceived()), Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (udpSocket != Q_NULLPTR)
|
||||
{
|
||||
qInfo(logCluster()) << "Stopping udpSocket() on:" << udpPort;
|
||||
|
||||
udpSocket->disconnect();
|
||||
delete udpSocket;
|
||||
udpSocket = Q_NULLPTR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dxClusterClient::enableTcp(bool enable)
|
||||
{
|
||||
tcpEnable = enable;
|
||||
if (enable)
|
||||
{
|
||||
tcpRegex = QRegularExpression("^DX de ([a-z-|A-Z|0-9|#|/]+):\\s+([0-9|.]+)\\s+([a-z|A-Z|0-9|/]+)+\\s+(.*)\\s+(\\d{4}Z)");
|
||||
|
||||
if (tcpSocket == Q_NULLPTR)
|
||||
{
|
||||
tcpSocket = new QTcpSocket(this);
|
||||
tcpSocket->connectToHost(tcpServerName, tcpPort);
|
||||
qInfo(logCluster()) << "Starting tcpSocket() on:" << tcpPort;
|
||||
|
||||
connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(tcpDataReceived()), Qt::QueuedConnection);
|
||||
connect(tcpSocket, SIGNAL(disconnected()), this, SLOT(tcpDisconnected()));
|
||||
|
||||
tcpCleanupTimer = new QTimer(this);
|
||||
tcpCleanupTimer->setInterval(1000 * 10); // Run once a minute
|
||||
connect(tcpCleanupTimer, SIGNAL(timeout()), this, SLOT(tcpCleanup()));
|
||||
tcpCleanupTimer->start();
|
||||
authenticated = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (tcpSocket != Q_NULLPTR)
|
||||
{
|
||||
sendTcpData(QString("bye\n"));
|
||||
qInfo(logCluster()) << "Disconnecting tcpSocket() on:" << tcpPort;
|
||||
if (tcpCleanupTimer != Q_NULLPTR)
|
||||
{
|
||||
tcpCleanupTimer->stop();
|
||||
delete tcpCleanupTimer;
|
||||
tcpCleanupTimer = Q_NULLPTR;
|
||||
}
|
||||
tcpSocket->disconnect();
|
||||
delete tcpSocket;
|
||||
tcpSocket = Q_NULLPTR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dxClusterClient::udpDataReceived()
|
||||
{
|
||||
QHostAddress sender;
|
||||
quint16 port;
|
||||
QByteArray datagram;
|
||||
datagram.resize(udpSocket->pendingDatagramSize());
|
||||
udpSocket->readDatagram(datagram.data(), datagram.size(), &sender, &port);
|
||||
|
||||
if (udpSpotReader.setContent(datagram))
|
||||
{
|
||||
QDomElement spot = udpSpotReader.firstChildElement("spot");
|
||||
if (spot.nodeName() == "spot")
|
||||
{
|
||||
// This is a spot?
|
||||
QString action = spot.firstChildElement("action").text();
|
||||
if (action == "add") {
|
||||
spotData* data = new spotData();
|
||||
data->dxcall = spot.firstChildElement("dxcall").text();
|
||||
data->frequency = spot.firstChildElement("frequency").text().toDouble() / 1000.0;
|
||||
data->spottercall = spot.firstChildElement("spottercall").text();
|
||||
data->timestamp = QDateTime::fromString(spot.firstChildElement("timestamp").text(),"yyyy-MM-dd hh:mm:ss");
|
||||
data->mode = spot.firstChildElement("mode").text();
|
||||
data->comment = spot.firstChildElement("comment").text();
|
||||
|
||||
#ifdef USESQL
|
||||
database db = database();
|
||||
db.query(QString("DELETE from spots where dxcall='%1'").arg(data->dxcall));
|
||||
QString query = QString("INSERT INTO spots(type,spottercall,frequency,dxcall,mode,comment,timestamp) VALUES('%1','%2',%3,'%4','%5','%6','%7')\n")
|
||||
.arg("UDP").arg(data->spottercall).arg(data->frequency).arg(data->dxcall).arg(data->mode).arg(data->comment).arg(data->timestamp.toString("yyyy-MM-dd hh:mm:ss"));
|
||||
db.query(query);
|
||||
#else
|
||||
bool found = false;
|
||||
QMap<QString, spotData*>::iterator spot = allSpots.find(data->dxcall);
|
||||
while (spot != allSpots.end() && spot.key() == data->dxcall && spot.value()->frequency == data->frequency) {
|
||||
found = true;
|
||||
++spot;
|
||||
}
|
||||
if (found == false) {
|
||||
allSpots.insert(data->dxcall, data);
|
||||
}
|
||||
#endif
|
||||
emit sendOutput(QString("<spot><action>add</action><dxcall>%1</dxcall><spottercall>%2</spottercall><frequency>%3</frequency><comment>%4</comment></spot>\n")
|
||||
.arg(data->dxcall).arg(data->spottercall).arg(data->frequency).arg(data->comment));
|
||||
|
||||
}
|
||||
else if (action == "delete")
|
||||
{
|
||||
QString dxcall = spot.firstChildElement("dxcall").text();
|
||||
double frequency = spot.firstChildElement("frequency").text().toDouble() / 1000.0;
|
||||
|
||||
#ifdef USESQL
|
||||
database db = database();
|
||||
QString query=QString("DELETE from spots where dxcall='%1' AND frequency=%2").arg(dxcall).arg(frequency);
|
||||
db.query(query);
|
||||
qInfo(logCluster()) << query;
|
||||
#else
|
||||
QMap<QString, spotData*>::iterator spot = allSpots.find(dxcall);
|
||||
while (spot != allSpots.end() && spot.key() == dxcall && spot.value()->frequency == frequency)
|
||||
{
|
||||
delete spot.value(); // Stop memory leak?
|
||||
spot = allSpots.erase(spot);
|
||||
}
|
||||
#endif
|
||||
emit sendOutput(QString("<spot><action>delete</action><dxcall>%1</dxcall<frequency>%3</frequency></spot>\n")
|
||||
.arg(dxcall).arg(frequency));
|
||||
}
|
||||
updateSpots();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void dxClusterClient::tcpDataReceived()
|
||||
{
|
||||
QString data = QString(tcpSocket->readAll());
|
||||
|
||||
emit sendOutput(data);
|
||||
if (!authenticated) {
|
||||
if (data.contains("login:") || data.contains("call:") || data.contains("callsign:")) {
|
||||
sendTcpData(QString("%1\n").arg(tcpUserName));
|
||||
return;
|
||||
}
|
||||
if (data.contains("password:")) {
|
||||
sendTcpData(QString("%1\n").arg(tcpPassword));
|
||||
return;
|
||||
}
|
||||
if (data.contains("Hello")) {
|
||||
authenticated = true;
|
||||
enableSkimmerSpots(skimmerSpots);
|
||||
}
|
||||
}
|
||||
else {
|
||||
QRegularExpressionMatchIterator i = tcpRegex.globalMatch(data);
|
||||
while (i.hasNext()) {
|
||||
QRegularExpressionMatch match = i.next();
|
||||
if (match.hasMatch()) {
|
||||
spotData* data = new spotData();
|
||||
data->spottercall = match.captured(1);
|
||||
data->frequency = match.captured(2).toDouble() / 1000.0;
|
||||
data->dxcall = match.captured(3);
|
||||
data->comment = match.captured(4).trimmed();
|
||||
data->timestamp = QDateTime::currentDateTimeUtc();
|
||||
|
||||
#ifdef USESQL
|
||||
database db = database();
|
||||
db.query(QString("DELETE from spots where dxcall='%1'").arg(data->dxcall));
|
||||
QString query = QString("INSERT INTO spots(type,spottercall,frequency,dxcall,comment,timestamp) VALUES('%1','%2',%3,'%4','%5','%6')\n")
|
||||
.arg("TCP").arg(data->spottercall).arg(data->frequency).arg(data->dxcall).arg(data->comment).arg(data->timestamp.toString("yyyy-MM-dd hh:mm:ss"));
|
||||
db.query(query);
|
||||
#else
|
||||
bool found = false;
|
||||
QMap<QString, spotData*>::iterator spot = allSpots.find(data->dxcall);
|
||||
while (spot != allSpots.end() && spot.key() == data->dxcall && spot.value()->frequency == data->frequency) {
|
||||
found = true;
|
||||
++spot;
|
||||
}
|
||||
if (found == false) {
|
||||
allSpots.insert(data->dxcall, data);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
updateSpots();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void dxClusterClient::sendTcpData(QString data)
|
||||
{
|
||||
qInfo(logCluster()) << "Sending:" << data;
|
||||
if (tcpSocket != Q_NULLPTR && tcpSocket->isValid() && tcpSocket->isOpen())
|
||||
{
|
||||
tcpSocket->write(data.toLatin1());
|
||||
}
|
||||
else
|
||||
{
|
||||
qInfo(logCluster()) << "socket not open!";
|
||||
}
|
||||
}
|
||||
|
||||
void dxClusterClient::tcpCleanup()
|
||||
{
|
||||
#ifdef USESQL
|
||||
database db = database();
|
||||
db.query(QString("DELETE FROM spots where timestamp < datetime('now', '-%1 minutes')").arg(tcpTimeout));
|
||||
#else
|
||||
QMap<QString, spotData*>::iterator spot = allSpots.begin();;
|
||||
while (spot != allSpots.end()) {
|
||||
if (spot.value()->timestamp.addSecs(tcpTimeout * 60) < QDateTime::currentDateTimeUtc())
|
||||
{
|
||||
delete spot.value(); // Stop memory leak?
|
||||
spot = allSpots.erase(spot);
|
||||
}
|
||||
else
|
||||
{
|
||||
++spot;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void dxClusterClient::tcpDisconnected() {
|
||||
qWarning(logCluster()) << "TCP Cluster server disconnected...";
|
||||
// Need to start a timer and attempt reconnect.
|
||||
}
|
||||
|
||||
void dxClusterClient::freqRange(double low, double high)
|
||||
{
|
||||
lowFreq = low;
|
||||
highFreq = high;
|
||||
//qInfo(logCluster) << "New range" << low << "-" << high;
|
||||
updateSpots();
|
||||
}
|
||||
|
||||
void dxClusterClient::updateSpots()
|
||||
{
|
||||
QList<spotData> spots;
|
||||
#ifdef USESQL
|
||||
// Set the required frequency range.
|
||||
QString queryText = QString("SELECT * FROM spots WHERE frequency > %1 AND frequency < %2").arg(lowFreq).arg(highFreq);
|
||||
//QString queryText = QString("SELECT * FROM spots");
|
||||
database db;
|
||||
auto query = db.query(queryText);
|
||||
|
||||
while (query.next()) {
|
||||
// Step through all current spots within range
|
||||
spotData s = spotData();
|
||||
s.dxcall = query.value(query.record().indexOf("dxcall")).toString();
|
||||
s.frequency = query.value(query.record().indexOf("frequency")).toDouble();
|
||||
spots.append(s);
|
||||
}
|
||||
#else
|
||||
QMap<QString, spotData*>::iterator spot = allSpots.begin();;
|
||||
while (spot != allSpots.end()) {
|
||||
if (spot.value()->frequency > lowFreq && spot.value()->frequency < highFreq)
|
||||
{
|
||||
spots.append(**spot);
|
||||
}
|
||||
++spot;
|
||||
}
|
||||
|
||||
#endif
|
||||
emit sendSpots(spots);
|
||||
}
|
||||
|
||||
void dxClusterClient::enableSkimmerSpots(bool enable)
|
||||
{
|
||||
skimmerSpots = enable;
|
||||
if (authenticated) {
|
||||
if (skimmerSpots) {
|
||||
sendTcpData(QString("Set Dx Filter Skimmer\n"));
|
||||
}
|
||||
else
|
||||
{
|
||||
sendTcpData(QString("Set Dx Filter Not Skimmer\n"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
#ifndef CLUSTER_H
|
||||
#define CLUSTER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QUdpSocket>
|
||||
#include <QTcpSocket>
|
||||
#include <QDomDocument>
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
#include <QDateTime>
|
||||
#include <QRegularExpression>
|
||||
#include <QTimer>
|
||||
|
||||
#ifdef USESQL
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlQuery>
|
||||
#endif
|
||||
|
||||
#include <qcustomplot.h>
|
||||
|
||||
#ifdef USESQL
|
||||
#include "database.h"
|
||||
#endif
|
||||
|
||||
struct spotData {
|
||||
QString dxcall;
|
||||
double frequency;
|
||||
QString spottercall;
|
||||
QDateTime timestamp;
|
||||
QString mode;
|
||||
QString comment;
|
||||
QCPItemText* text = Q_NULLPTR;
|
||||
bool current = false;
|
||||
};
|
||||
|
||||
struct clusterSettings {
|
||||
QString server;
|
||||
int port=7300;
|
||||
QString userName;
|
||||
QString password;
|
||||
int timeout=30;
|
||||
bool isdefault;
|
||||
};
|
||||
|
||||
class dxClusterClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit dxClusterClient(QObject* parent = nullptr);
|
||||
virtual ~dxClusterClient();
|
||||
|
||||
signals:
|
||||
void addSpot(spotData* spot);
|
||||
void deleteSpot(QString dxcall);
|
||||
void deleteOldSpots(int minutes);
|
||||
void sendOutput(QString text);
|
||||
void sendSpots(QList<spotData> spots);
|
||||
|
||||
public slots:
|
||||
void udpDataReceived();
|
||||
void tcpDataReceived();
|
||||
void tcpDisconnected();
|
||||
void enableUdp(bool enable);
|
||||
void enableTcp(bool enable);
|
||||
void setUdpPort(int p) { udpPort = p; }
|
||||
void setTcpServerName(QString s) { tcpServerName = s; }
|
||||
void setTcpPort(int p) { tcpPort = p; }
|
||||
void setTcpUserName(QString s) { tcpUserName = s; }
|
||||
void setTcpPassword(QString s) { tcpPassword = s; }
|
||||
void setTcpTimeout(int p) { tcpTimeout = p; }
|
||||
void tcpCleanup();
|
||||
void freqRange(double low, double high);
|
||||
void enableSkimmerSpots(bool enable);
|
||||
|
||||
private:
|
||||
void sendTcpData(QString data);
|
||||
bool databaseOpen();
|
||||
void updateSpots();
|
||||
|
||||
bool udpEnable;
|
||||
bool tcpEnable;
|
||||
QUdpSocket* udpSocket=Q_NULLPTR;
|
||||
QTcpSocket* tcpSocket=Q_NULLPTR;
|
||||
int udpPort;
|
||||
QString tcpServerName;
|
||||
int tcpPort;
|
||||
QString tcpUserName;
|
||||
QString tcpPassword;
|
||||
int tcpTimeout;
|
||||
QDomDocument udpSpotReader;
|
||||
QRegularExpression tcpRegex;
|
||||
QMutex mutex;
|
||||
bool authenticated=false;
|
||||
QTimer* tcpCleanupTimer=Q_NULLPTR;
|
||||
#ifdef USESQL
|
||||
QSqlDatabase db;
|
||||
#endif
|
||||
double lowFreq;
|
||||
double highFreq;
|
||||
QMap<QString,spotData*> allSpots;
|
||||
bool skimmerSpots = false;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef COLORPREFS_H
|
||||
#define COLORPREFS_H
|
||||
|
||||
#include <QColor>
|
||||
#include <QString>
|
||||
|
||||
struct colorPrefsType{
|
||||
int presetNum = -1;
|
||||
QString *presetName = Q_NULLPTR;
|
||||
|
||||
// Spectrum line plot:
|
||||
QColor gridColor;
|
||||
QColor axisColor;
|
||||
QColor textColor;
|
||||
QColor spectrumLine;
|
||||
QColor spectrumFill;
|
||||
QColor underlayLine;
|
||||
QColor underlayFill;
|
||||
QColor plotBackground;
|
||||
QColor tuningLine;
|
||||
QColor passband;
|
||||
QColor pbt;
|
||||
|
||||
// Waterfall:
|
||||
QColor wfBackground;
|
||||
QColor wfGrid;
|
||||
QColor wfAxis;
|
||||
QColor wfText;
|
||||
|
||||
// Meters:
|
||||
QColor meterLevel;
|
||||
QColor meterAverage;
|
||||
QColor meterPeakLevel;
|
||||
QColor meterPeakScale;
|
||||
QColor meterLowerLine;
|
||||
QColor meterLowText;
|
||||
|
||||
// Assorted
|
||||
QColor clusterSpots;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // COLORPREFS_H
|
219
commhandler.cpp
|
@ -3,41 +3,34 @@
|
|||
|
||||
#include <QDebug>
|
||||
|
||||
// Copytight 2017-2020 Elliott H. Liggett
|
||||
// Copyright 2017-2020 Elliott H. Liggett
|
||||
|
||||
commHandler::commHandler()
|
||||
commHandler::commHandler(QObject* parent) : QObject(parent)
|
||||
{
|
||||
//constructor
|
||||
// grab baud rate and other comm port details
|
||||
// if they need to be changed later, please
|
||||
// destroy this and create a new one.
|
||||
port = new QSerialPort();
|
||||
|
||||
|
||||
// TODO: The following should become arguments and/or functions
|
||||
// Add signal/slot everywhere for comm port setup.
|
||||
// Consider how to "re-setup" and how to save the state for next time.
|
||||
baudrate = 115200;
|
||||
stopbits = 1;
|
||||
portName = "/dev/ttyUSB0";
|
||||
this->PTTviaRTS = false;
|
||||
|
||||
setupComm(); // basic parameters
|
||||
openPort();
|
||||
//qInfo(logSerial()) << "Serial buffer size: " << port->readBufferSize();
|
||||
//port->setReadBufferSize(1024); // manually. 256 never saw any return from the radio. why...
|
||||
//qInfo(logSerial()) << "Serial buffer size: " << port->readBufferSize();
|
||||
|
||||
connect(port, SIGNAL(readyRead()), this, SLOT(receiveDataIn()));
|
||||
init();
|
||||
}
|
||||
|
||||
commHandler::commHandler(QString portName, quint32 baudRate)
|
||||
commHandler::commHandler(QString portName, quint32 baudRate, quint8 wfFormat, QObject* parent) : QObject(parent)
|
||||
{
|
||||
//constructor
|
||||
// grab baud rate and other comm port details
|
||||
// if they need to be changed later, please
|
||||
// destroy this and create a new one.
|
||||
|
||||
port = new QSerialPort();
|
||||
|
||||
if (wfFormat == 1) { // Single waterfall packet
|
||||
combineWf = true;
|
||||
qDebug(logSerial()) << "*********** Combine Waterfall Mode Enabled!";
|
||||
}
|
||||
|
||||
// TODO: The following should become arguments and/or functions
|
||||
// Add signal/slot everywhere for comm port setup.
|
||||
|
@ -45,7 +38,20 @@ commHandler::commHandler(QString portName, quint32 baudRate)
|
|||
baudrate = baudRate;
|
||||
stopbits = 1;
|
||||
this->portName = portName;
|
||||
this->PTTviaRTS = false;
|
||||
init();
|
||||
|
||||
}
|
||||
|
||||
void commHandler::init()
|
||||
{
|
||||
if (port != Q_NULLPTR) {
|
||||
delete port;
|
||||
port = Q_NULLPTR;
|
||||
isConnected = false;
|
||||
}
|
||||
|
||||
port = new QSerialPort();
|
||||
setupComm(); // basic parameters
|
||||
openPort();
|
||||
// qInfo(logSerial()) << "Serial buffer size: " << port->readBufferSize();
|
||||
|
@ -53,12 +59,21 @@ commHandler::commHandler(QString portName, quint32 baudRate)
|
|||
//qInfo(logSerial()) << "Serial buffer size: " << port->readBufferSize();
|
||||
|
||||
connect(port, SIGNAL(readyRead()), this, SLOT(receiveDataIn()));
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
connect(port, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(handleError(QSerialPort::SerialPortError)));
|
||||
#else
|
||||
connect(port, SIGNAL(errorOccurred(QSerialPort::SerialPortError)), this, SLOT(handleError(QSerialPort::SerialPortError)));
|
||||
#endif
|
||||
lastDataReceived = QTime::currentTime();
|
||||
}
|
||||
|
||||
|
||||
commHandler::~commHandler()
|
||||
{
|
||||
this->closePort();
|
||||
qInfo(logSerial()) << "Closing serial port: " << port->portName();
|
||||
if (isConnected) {
|
||||
this->closePort();
|
||||
}
|
||||
delete port;
|
||||
}
|
||||
|
||||
void commHandler::setupComm()
|
||||
|
@ -76,12 +91,65 @@ void commHandler::receiveDataFromUserToRig(const QByteArray &data)
|
|||
|
||||
void commHandler::sendDataOut(const QByteArray &writeData)
|
||||
{
|
||||
// Recycle port to attempt reconnection.
|
||||
if (lastDataReceived.msecsTo(QTime::currentTime()) > 2000) {
|
||||
qDebug(logSerial()) << "Serial port error? Attempting reconnect...";
|
||||
lastDataReceived = QTime::currentTime();
|
||||
QTimer::singleShot(500, this, SLOT(init()));
|
||||
return;
|
||||
}
|
||||
|
||||
mutex.lock();
|
||||
|
||||
qint64 bytesWritten;
|
||||
bytesWritten = port->write(writeData);
|
||||
|
||||
previousSent = writeData;
|
||||
|
||||
if(PTTviaRTS)
|
||||
{
|
||||
// Size: 1 2 3 4 5 6 7 8
|
||||
//index: 0 1 2 3 4 5 6 7
|
||||
//Query: FE FE TO FROM 0x1C 0x00 0xFD
|
||||
//PTT On: FE FE TO FROM 0x1C 0x00 0x01 0xFD
|
||||
//PTT Off: FE FE TO FROM 0x1C 0x00 0x00 0xFD
|
||||
|
||||
if(writeData.endsWith(QByteArrayLiteral("\x1C\x00\xFD")))
|
||||
{
|
||||
// Query
|
||||
//qDebug(logSerial()) << "Looks like PTT Query";
|
||||
bool pttOn = this->rtsStatus();
|
||||
QByteArray pttreturncmd = QByteArray("\xFE\xFE");
|
||||
pttreturncmd.append(writeData.at(3));
|
||||
pttreturncmd.append(writeData.at(2));
|
||||
pttreturncmd.append(QByteArray("\x1C\x00", 2));
|
||||
pttreturncmd.append((char)pttOn);
|
||||
pttreturncmd.append("\xFD");
|
||||
//qDebug(logSerial()) << "Sending fake PTT query result: " << (bool)pttOn;
|
||||
printHex(pttreturncmd, false, true);
|
||||
emit haveDataFromPort(pttreturncmd);
|
||||
|
||||
|
||||
mutex.unlock();
|
||||
return;
|
||||
} else if(writeData.endsWith(QByteArrayLiteral("\x1C\x00\x01\xFD")))
|
||||
{
|
||||
// PTT ON
|
||||
//qDebug(logSerial()) << "Looks like PTT ON";
|
||||
setRTS(true);
|
||||
mutex.unlock();
|
||||
return;
|
||||
} else if(writeData.endsWith(QByteArrayLiteral("\x1C\x00\x00\xFD")))
|
||||
{
|
||||
// PTT OFF
|
||||
//qDebug(logSerial()) << "Looks like PTT OFF";
|
||||
setRTS(false);
|
||||
mutex.unlock();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bytesWritten = port->write(writeData);
|
||||
|
||||
if(bytesWritten != (qint64)writeData.size())
|
||||
{
|
||||
qDebug(logSerial()) << "bytesWritten: " << bytesWritten << " length of byte array: " << writeData.length()\
|
||||
|
@ -100,9 +168,19 @@ void commHandler::receiveDataIn()
|
|||
// because we know what constitutes a valid "frame" of data.
|
||||
|
||||
// new code:
|
||||
lastDataReceived = QTime::currentTime();
|
||||
port->startTransaction();
|
||||
inPortData = port->readAll();
|
||||
|
||||
if (inPortData.startsWith("\xFC\xFC\xFC\xFC\xFC"))
|
||||
{
|
||||
// Colission detected by remote end, re-send previous command.
|
||||
qInfo(logSerial()) << "Collision detected by remote, resending previous command";
|
||||
port->commitTransaction();
|
||||
sendDataOut(previousSent);
|
||||
return;
|
||||
}
|
||||
|
||||
if(inPortData.size() == 1)
|
||||
{
|
||||
// Generally for baud <= 9600
|
||||
|
@ -118,8 +196,7 @@ void commHandler::receiveDataIn()
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
if(inPortData.startsWith("\xFE\xFE"))
|
||||
if (inPortData.startsWith("\xFE\xFE"))
|
||||
{
|
||||
if(inPortData.contains("\xFC"))
|
||||
{
|
||||
|
@ -132,17 +209,73 @@ void commHandler::receiveDataIn()
|
|||
{
|
||||
// good!
|
||||
port->commitTransaction();
|
||||
|
||||
//payloadIn = data.right(data.length() - 4);
|
||||
|
||||
// Do we need to combine waterfall into single packet?
|
||||
int combined = 0;
|
||||
if (combineWf) {
|
||||
int pos = inPortData.indexOf(QByteArrayLiteral("\x27\x00\x00"));
|
||||
int fdPos = inPortData.mid(pos).indexOf(QByteArrayLiteral("\xfd"));
|
||||
//printHex(inPortData, false, true);
|
||||
while (pos > -1 && fdPos > -1) {
|
||||
combined++;
|
||||
spectrumDivisionNumber = inPortData[pos + 3] & 0x0f;
|
||||
spectrumDivisionNumber += ((inPortData[pos + 3] & 0xf0) >> 4) * 10;
|
||||
|
||||
if (spectrumDivisionNumber == 1) {
|
||||
// This is the first waveform data.
|
||||
spectrumDivisionMax = 0;
|
||||
spectrumDivisionMax = inPortData[pos + 4] & 0x0f;
|
||||
spectrumDivisionMax += ((inPortData[pos + 4] & 0xf0) >> 4) * 10;
|
||||
spectrumData.clear();
|
||||
spectrumData = inPortData.mid(pos - 4, fdPos+4); // Don't include terminating FD
|
||||
spectrumData[8] = spectrumData[7]; // Make max = current;
|
||||
//qDebug() << "New Spectrum seq:" << spectrumDivisionNumber << "pos = " << pos << "len" << fdPos;
|
||||
|
||||
}
|
||||
else if (spectrumDivisionNumber > lastSpectrum && spectrumDivisionNumber <= spectrumDivisionMax) {
|
||||
spectrumData.insert(spectrumData.length(), inPortData.mid(pos + 5, fdPos-5));
|
||||
//qInfo() << "Added spectrum seq:" << spectrumDivisionNumber << "len" << fdPos-5<< "Spec" << spectrumData.length();
|
||||
//printHex(inPortData.mid((pos+4),fdPos - (pos+5)), false, true);
|
||||
}
|
||||
else {
|
||||
qDebug() << "Invalid Spectrum Division received" << spectrumDivisionNumber << "last Spectrum" << lastSpectrum;
|
||||
}
|
||||
|
||||
lastSpectrum = spectrumDivisionNumber;
|
||||
|
||||
if (spectrumDivisionNumber == spectrumDivisionMax) {
|
||||
//qDebug() << "Got Spectrum! length=" << spectrumData.length();
|
||||
spectrumData.append("\xfd"); // Need to add FD on the end.
|
||||
//printHex(spectrumData, false, true);
|
||||
emit haveDataFromPort(spectrumData);
|
||||
lastSpectrum = 0;
|
||||
}
|
||||
inPortData = inPortData.remove(pos-4, fdPos+5);
|
||||
pos = inPortData.indexOf(QByteArrayLiteral("\x27\x00\x00"));
|
||||
fdPos = inPortData.mid(pos).indexOf(QByteArrayLiteral("\xfd"));
|
||||
}
|
||||
// If we still have data left, let the main function deal with it, any spectrum data has been removed
|
||||
if (inPortData.length() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// qDebug() << "Got extra data!";
|
||||
//printHex(inPortData, false, true);
|
||||
}
|
||||
//
|
||||
emit haveDataFromPort(inPortData);
|
||||
|
||||
if(rolledBack)
|
||||
{
|
||||
// qInfo(logSerial()) << "Rolled back and was successfull. Length: " << inPortData.length();
|
||||
// qInfo(logSerial()) << "Rolled back and was successful. Length: " << inPortData.length();
|
||||
//printHex(inPortData, false, true);
|
||||
rolledBack = false;
|
||||
}
|
||||
} else {
|
||||
// did not receive the entire thing so roll back:
|
||||
// qInfo(logSerial()) << "Rolling back transaction. End not detected. Lenth: " << inPortData.length();
|
||||
// qInfo(logSerial()) << "Rolling back transaction. End not detected. Length: " << inPortData.length();
|
||||
//printHex(inPortData, false, true);
|
||||
port->rollbackTransaction();
|
||||
rolledBack = true;
|
||||
|
@ -162,10 +295,28 @@ void commHandler::receiveDataIn()
|
|||
}
|
||||
}
|
||||
|
||||
void commHandler::setRTS(bool rtsOn)
|
||||
{
|
||||
bool success = port->setRequestToSend(rtsOn);
|
||||
if(!success)
|
||||
{
|
||||
qInfo(logSerial()) << "Error, could not set RTS on port " << portName;
|
||||
}
|
||||
}
|
||||
|
||||
bool commHandler::rtsStatus()
|
||||
{
|
||||
return port->isRequestToSend();
|
||||
}
|
||||
|
||||
void commHandler::setUseRTSforPTT(bool PTTviaRTS)
|
||||
{
|
||||
this->PTTviaRTS = PTTviaRTS;
|
||||
}
|
||||
|
||||
void commHandler::openPort()
|
||||
{
|
||||
bool success;
|
||||
// port->open();
|
||||
success = port->open(QIODevice::ReadWrite);
|
||||
if(success)
|
||||
{
|
||||
|
@ -178,7 +329,7 @@ void commHandler::openPort()
|
|||
qInfo(logSerial()) << "Could not open serial port " << portName << " , please restart.";
|
||||
isConnected = false;
|
||||
serialError = true;
|
||||
emit haveSerialPortError(portName, "Could not open port. Please restart.");
|
||||
emit havePortError(errorType(true, portName, "Could not open port. Please restart."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -188,7 +339,7 @@ void commHandler::closePort()
|
|||
if(port)
|
||||
{
|
||||
port->close();
|
||||
delete port;
|
||||
//delete port;
|
||||
}
|
||||
isConnected = false;
|
||||
}
|
||||
|
@ -234,3 +385,17 @@ void commHandler::printHex(const QByteArray &pdata, bool printVert, bool printHo
|
|||
}
|
||||
|
||||
|
||||
void commHandler::handleError(QSerialPort::SerialPortError err)
|
||||
{
|
||||
switch (err) {
|
||||
case QSerialPort::NoError:
|
||||
break;
|
||||
default:
|
||||
if (lastDataReceived.msecsTo(QTime::currentTime()) > 2000) {
|
||||
qDebug(logSerial()) << "Serial port" << port->portName() << "Error, attempting disconnect/reconnect";
|
||||
lastDataReceived = QTime::currentTime();
|
||||
QTimer::singleShot(500, this, SLOT(init()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
#include <QMutex>
|
||||
#include <QDataStream>
|
||||
#include <QtSerialPort/QSerialPort>
|
||||
#include <QTime>
|
||||
#include <QTimer>
|
||||
|
||||
#include "wfviewtypes.h"
|
||||
|
||||
// This class abstracts the comm port in a useful way and connects to
|
||||
// the command creator and command parser.
|
||||
|
@ -15,12 +19,19 @@ class commHandler : public QObject
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
commHandler();
|
||||
commHandler(QString portName, quint32 baudRate);
|
||||
commHandler(QObject* parent = nullptr);
|
||||
commHandler(QString portName, quint32 baudRate, quint8 wfFormat,QObject* parent = nullptr);
|
||||
bool serialError;
|
||||
bool rtsStatus();
|
||||
|
||||
~commHandler();
|
||||
|
||||
public slots:
|
||||
void setUseRTSforPTT(bool useRTS);
|
||||
void setRTS(bool rtsOn);
|
||||
void handleError(QSerialPort::SerialPortError error);
|
||||
void init();
|
||||
|
||||
private slots:
|
||||
void receiveDataIn(); // from physical port
|
||||
void receiveDataFromUserToRig(const QByteArray &data);
|
||||
|
@ -30,7 +41,7 @@ signals:
|
|||
void haveTextMessage(QString message); // status, debug only
|
||||
void sendDataOutToPort(const QByteArray &writeData); // not used
|
||||
void haveDataFromPort(QByteArray data); // emit this when we have data, connect to rigcommander
|
||||
void haveSerialPortError(const QString port, const QString error);
|
||||
void havePortError(errorType err);
|
||||
void haveStatusUpdate(const QString text);
|
||||
|
||||
private:
|
||||
|
@ -38,6 +49,7 @@ private:
|
|||
void openPort();
|
||||
void closePort();
|
||||
|
||||
|
||||
void sendDataOut(const QByteArray &writeData); // out to radio
|
||||
void debugMe();
|
||||
void hexPrint();
|
||||
|
@ -45,6 +57,7 @@ private:
|
|||
//QDataStream stream;
|
||||
QByteArray outPortData;
|
||||
QByteArray inPortData;
|
||||
QByteArray previousSent;
|
||||
|
||||
//QDataStream outStream;
|
||||
//QDataStream inStream;
|
||||
|
@ -52,7 +65,7 @@ private:
|
|||
unsigned char buffer[256];
|
||||
|
||||
QString portName;
|
||||
QSerialPort *port;
|
||||
QSerialPort *port=Q_NULLPTR;
|
||||
qint32 baudrate;
|
||||
unsigned char stopbits;
|
||||
bool rolledBack;
|
||||
|
@ -63,10 +76,20 @@ private:
|
|||
bool havePt;
|
||||
QString ptDevSlave;
|
||||
|
||||
bool PTTviaRTS = false;
|
||||
|
||||
bool isConnected; // port opened
|
||||
mutable QMutex mutex;
|
||||
void printHex(const QByteArray &pdata, bool printVert, bool printHoriz);
|
||||
|
||||
bool combineWf = false;
|
||||
QByteArray spectrumData;
|
||||
quint8 spectrumDivisionNumber;
|
||||
quint8 spectrumDivisionMax;
|
||||
quint8 spectrumCenterOrFixed;
|
||||
quint8 spectrumInformation;
|
||||
quint8 spectrumOutOfRange;
|
||||
quint8 lastSpectrum = 0;
|
||||
QTime lastDataReceived;
|
||||
};
|
||||
|
||||
#endif // COMMHANDLER_H
|
||||
|
|
|
@ -0,0 +1,996 @@
|
|||
#include "controllersetup.h"
|
||||
#include "ui_controllersetup.h"
|
||||
#include "logcategories.h"
|
||||
|
||||
controllerSetup::controllerSetup(QWidget* parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::controllerSetup)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
ui->tabWidget->clear();
|
||||
ui->tabWidget->hide();
|
||||
noControllersText = new QLabel("No USB controller found");
|
||||
noControllersText->setStyleSheet("QLabel { color : gray; }");
|
||||
ui->hboxLayout->addWidget(noControllersText);
|
||||
this->resize(this->sizeHint());
|
||||
}
|
||||
|
||||
controllerSetup::~controllerSetup()
|
||||
{
|
||||
qInfo(logUsbControl()) << "Deleting controllerSetup() window";
|
||||
delete noControllersText;
|
||||
delete updateDialog;
|
||||
delete ui; // Will delete all content in all tabs
|
||||
tabs.clear();
|
||||
// Step through ALL buttons and knobs setting their text to NULL (just in case)
|
||||
for (auto b = buttons->begin(); b != buttons->end(); b++)
|
||||
{
|
||||
b->text=Q_NULLPTR;
|
||||
b->bgRect=Q_NULLPTR;
|
||||
}
|
||||
|
||||
for (auto k= knobs->begin(); k != knobs->end(); k++)
|
||||
{
|
||||
k->text=Q_NULLPTR;
|
||||
}
|
||||
}
|
||||
|
||||
void controllerSetup::hideEvent(QHideEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
qDebug(logUsbControl()) << "Controller window hideEvent()";
|
||||
updateDialog->hide();
|
||||
}
|
||||
|
||||
void controllerSetup::on_tabWidget_currentChanged(int index)
|
||||
{
|
||||
// We may get the indexChanged event before the tabWidget has been initialized
|
||||
if (ui->tabWidget->widget(index) != Q_NULLPTR) {
|
||||
QString path = ui->tabWidget->widget(index)->objectName();
|
||||
auto tab = tabs.find(path);
|
||||
if (tab != tabs.end())
|
||||
{
|
||||
this->resize(this->sizeHint());
|
||||
}
|
||||
}
|
||||
|
||||
if (updateDialog != Q_NULLPTR)
|
||||
{
|
||||
updateDialog->hide();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void controllerSetup::init(usbDevMap* dev, QVector<BUTTON>* but, QVector<KNOB>* kb, QVector<COMMAND>* cmd, QMutex* mut)
|
||||
{
|
||||
// Store pointers to all current settings
|
||||
devices = dev;
|
||||
buttons = but;
|
||||
knobs = kb;
|
||||
commands = cmd;
|
||||
mutex = mut;
|
||||
|
||||
updateDialog = new QDialog(this);
|
||||
// Not sure if I like it Frameless or not?
|
||||
updateDialog->setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog);
|
||||
|
||||
QGridLayout* udLayout = new QGridLayout(updateDialog);
|
||||
|
||||
onLabel = new QLabel("On");
|
||||
udLayout->addWidget(onLabel,0,0);
|
||||
onEvent = new QComboBox();
|
||||
udLayout->addWidget(onEvent,0,1);
|
||||
onLabel->setBuddy(onEvent);
|
||||
|
||||
offLabel = new QLabel("Off");
|
||||
udLayout->addWidget(offLabel,1,0);
|
||||
offEvent = new QComboBox();
|
||||
udLayout->addWidget(offEvent,1,1);
|
||||
offLabel->setBuddy(offEvent);
|
||||
|
||||
knobLabel = new QLabel("Knob");
|
||||
udLayout->addWidget(knobLabel,2,0);
|
||||
knobEvent = new QComboBox();
|
||||
udLayout->addWidget(knobEvent,2,1);
|
||||
knobLabel->setBuddy(knobEvent);
|
||||
|
||||
ledNumber = new QSpinBox();
|
||||
ledNumber->setPrefix("LED: ");
|
||||
ledNumber->setMinimum(0);
|
||||
udLayout->addWidget(ledNumber,3,1);
|
||||
|
||||
buttonLatch = new QCheckBox();
|
||||
buttonLatch->setText("Toggle");
|
||||
udLayout->addWidget(buttonLatch,4,0);
|
||||
|
||||
QHBoxLayout* colorLayout = new QHBoxLayout();
|
||||
udLayout->addLayout(colorLayout,4,1);
|
||||
|
||||
buttonOnColor = new QPushButton("Color");
|
||||
colorLayout->addWidget(buttonOnColor);
|
||||
|
||||
buttonOffColor = new QPushButton("Pressed");
|
||||
colorLayout->addWidget(buttonOffColor);
|
||||
|
||||
buttonIcon = new QPushButton("Icon");
|
||||
udLayout->addWidget(buttonIcon,5,0);
|
||||
iconLabel = new QLabel("<None>");
|
||||
udLayout->addWidget(iconLabel,5,1);
|
||||
udLayout->setAlignment(iconLabel,Qt::AlignCenter);
|
||||
udLayout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
|
||||
updateDialog->hide();
|
||||
|
||||
onEvent->clear();
|
||||
offEvent->clear();
|
||||
knobEvent->clear();
|
||||
|
||||
for (COMMAND& c : *commands) {
|
||||
if (c.cmdType == commandButton || c.cmdType == commandAny) {
|
||||
if (c.command == cmdSeparator) {
|
||||
onEvent->insertSeparator(onEvent->count());
|
||||
offEvent->insertSeparator(offEvent->count());
|
||||
|
||||
} else {
|
||||
onEvent->addItem(c.text, c.index);
|
||||
offEvent->addItem(c.text, c.index);
|
||||
}
|
||||
}
|
||||
if (c.cmdType == commandKnob || c.cmdType == commandAny) {
|
||||
if (c.command == cmdSeparator) {
|
||||
knobEvent->insertSeparator(knobEvent->count());
|
||||
} else {
|
||||
knobEvent->addItem(c.text, c.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connect(offEvent, SIGNAL(currentIndexChanged(int)), this, SLOT(offEventIndexChanged(int)));
|
||||
connect(onEvent, SIGNAL(currentIndexChanged(int)), this, SLOT(onEventIndexChanged(int)));
|
||||
connect(knobEvent, SIGNAL(currentIndexChanged(int)), this, SLOT(knobEventIndexChanged(int)));
|
||||
connect(buttonOnColor, SIGNAL(clicked()), this, SLOT(buttonOnColorClicked()));
|
||||
connect(buttonOffColor, SIGNAL(clicked()), this, SLOT(buttonOffColorClicked()));
|
||||
connect(buttonIcon, SIGNAL(clicked()), this, SLOT(buttonIconClicked()));
|
||||
connect(buttonLatch, SIGNAL(stateChanged(int)), this, SLOT(latchStateChanged(int)));
|
||||
connect(ledNumber, SIGNAL(valueChanged(int)), this, SLOT(ledNumberChanged(int)));
|
||||
}
|
||||
|
||||
void controllerSetup::showMenu(controllerScene* scene, QPoint p)
|
||||
{
|
||||
Q_UNUSED (scene) // We might want it in the future?
|
||||
|
||||
// Receive mouse event from the scene
|
||||
qDebug() << "Looking for knob or button at Point x=" << p.x() << " y=" << p.y();
|
||||
|
||||
QPoint gp = this->mapToGlobal(p);
|
||||
|
||||
|
||||
// Did the user click on a button?
|
||||
auto but = std::find_if(buttons->begin(), buttons->end(), [p, this](BUTTON& b)
|
||||
{ return (b.parent != Q_NULLPTR && b.pos.contains(p) && b.page == b.parent->currentPage && ui->tabWidget->currentWidget()->objectName() == b.path ); });
|
||||
|
||||
if (but != buttons->end())
|
||||
{
|
||||
currentButton = &(*but);
|
||||
currentKnob = Q_NULLPTR;
|
||||
qDebug() << "Button" << currentButton->num << "On Event" << currentButton->onCommand->text << "Off Event" << currentButton->offCommand->text;
|
||||
|
||||
updateDialog->setWindowTitle(QString("Update button %0").arg(but->num));
|
||||
|
||||
onEvent->blockSignals(true);
|
||||
onEvent->setCurrentIndex(onEvent->findData(currentButton->onCommand->index));
|
||||
onEvent->show();
|
||||
onLabel->show();
|
||||
onEvent->blockSignals(false);
|
||||
|
||||
offEvent->blockSignals(true);
|
||||
offEvent->setCurrentIndex(offEvent->findData(currentButton->offCommand->index));
|
||||
offEvent->show();
|
||||
offLabel->show();
|
||||
offEvent->blockSignals(false);
|
||||
knobEvent->hide();
|
||||
knobLabel->hide();
|
||||
|
||||
buttonLatch->blockSignals(true);
|
||||
buttonLatch->setChecked(currentButton->toggle);
|
||||
buttonLatch->blockSignals(false);
|
||||
buttonLatch->show();
|
||||
|
||||
ledNumber->blockSignals(true);
|
||||
ledNumber->setMaximum(currentButton->parent->type.leds);
|
||||
ledNumber->setValue(currentButton->led);
|
||||
ledNumber->blockSignals(false);
|
||||
|
||||
switch (currentButton->parent->type.model)
|
||||
{
|
||||
case RC28:
|
||||
case eCoderPlus:
|
||||
ledNumber->show();
|
||||
buttonOnColor->hide();
|
||||
buttonOffColor->hide();
|
||||
buttonIcon->hide();
|
||||
iconLabel->hide();
|
||||
break;
|
||||
case StreamDeckMini:
|
||||
case StreamDeckMiniV2:
|
||||
case StreamDeckOriginal:
|
||||
case StreamDeckOriginalV2:
|
||||
case StreamDeckOriginalMK2:
|
||||
case StreamDeckXL:
|
||||
case StreamDeckXLV2:
|
||||
case StreamDeckPlus:
|
||||
buttonOnColor->setStyleSheet(QString("background-color: %1").arg(currentButton->backgroundOn.name(QColor::HexArgb)));
|
||||
buttonOffColor->setStyleSheet(QString("background-color: %1").arg(currentButton->backgroundOff.name(QColor::HexArgb)));
|
||||
buttonOnColor->show();
|
||||
buttonOffColor->show();
|
||||
buttonIcon->show();
|
||||
iconLabel->setText(currentButton->iconName);
|
||||
iconLabel->show();
|
||||
break;
|
||||
default:
|
||||
buttonOnColor->hide();
|
||||
buttonOffColor->hide();
|
||||
buttonIcon->hide();
|
||||
iconLabel->hide();
|
||||
ledNumber->hide();
|
||||
break;
|
||||
}
|
||||
|
||||
updateDialog->show();
|
||||
updateDialog->move(gp);
|
||||
updateDialog->adjustSize();
|
||||
updateDialog->raise();
|
||||
} else {
|
||||
// It wasn't a button so was it a knob?
|
||||
auto kb = std::find_if(knobs->begin(), knobs->end(), [p, this](KNOB& k)
|
||||
{ return (k.parent != Q_NULLPTR && k.pos.contains(p) && k.page == k.parent->currentPage && ui->tabWidget->currentWidget()->objectName() == k.path ); });
|
||||
|
||||
if (kb != knobs->end())
|
||||
{
|
||||
currentKnob = &(*kb);
|
||||
currentButton = Q_NULLPTR;
|
||||
qDebug() << "Knob" << currentKnob->num << "Event" << currentKnob->command->text;
|
||||
|
||||
updateDialog->setWindowTitle(QString("Update knob %0").arg(kb->num));
|
||||
|
||||
knobEvent->blockSignals(true);
|
||||
knobEvent->setCurrentIndex(knobEvent->findData(currentKnob->command->index));
|
||||
knobEvent->show();
|
||||
knobLabel->show();
|
||||
knobEvent->blockSignals(false);
|
||||
onEvent->hide();
|
||||
offEvent->hide();
|
||||
onLabel->hide();
|
||||
offLabel->hide();
|
||||
buttonLatch->hide();
|
||||
buttonOnColor->hide();
|
||||
buttonOffColor->hide();
|
||||
buttonIcon->hide();
|
||||
iconLabel->hide();
|
||||
ledNumber->hide();
|
||||
|
||||
updateDialog->show();
|
||||
updateDialog->move(gp);
|
||||
updateDialog->adjustSize();
|
||||
updateDialog->raise();
|
||||
}
|
||||
else
|
||||
{
|
||||
// It wasn't either so hide the updateDialog();
|
||||
updateDialog->hide();
|
||||
currentButton = Q_NULLPTR;
|
||||
currentKnob = Q_NULLPTR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void controllerSetup::onEventIndexChanged(int index) {
|
||||
Q_UNUSED(index);
|
||||
// If command is changed, delete current command and deep copy the new command
|
||||
if (currentButton != Q_NULLPTR && onEvent->currentData().toInt() < commands->size()) {
|
||||
QMutexLocker locker(mutex);
|
||||
currentButton->onCommand = &commands->at(onEvent->currentData().toInt());
|
||||
currentButton->text->setPlainText(currentButton->onCommand->text);
|
||||
currentButton->text->setPos(currentButton->pos.center().x() - currentButton->text->boundingRect().width() / 2,
|
||||
(currentButton->pos.center().y() - currentButton->text->boundingRect().height() / 2));
|
||||
// Signal that any button programming on the device should be completed.
|
||||
if (currentButton->icon == Q_NULLPTR) {
|
||||
emit sendRequest(currentButton->parent,usbFeatureType::featureButton,currentButton->num,currentButton->onCommand->text,Q_NULLPTR,¤tButton->backgroundOn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void controllerSetup::offEventIndexChanged(int index) {
|
||||
Q_UNUSED(index);
|
||||
// If command is changed, delete current command and deep copy the new command
|
||||
if (currentButton != Q_NULLPTR && offEvent->currentData().toInt() < commands->size()) {
|
||||
QMutexLocker locker(mutex);
|
||||
currentButton->offCommand = &commands->at(offEvent->currentData().toInt());
|
||||
}
|
||||
}
|
||||
|
||||
void controllerSetup::ledNumberChanged(int index) {
|
||||
if (currentButton != Q_NULLPTR) {
|
||||
QMutexLocker locker(mutex);
|
||||
currentButton->led = index;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void controllerSetup::knobEventIndexChanged(int index) {
|
||||
Q_UNUSED(index);
|
||||
// If command is changed, delete current command and deep copy the new command
|
||||
if (currentKnob != Q_NULLPTR && knobEvent->currentData().toInt() < commands->size()) {
|
||||
QMutexLocker locker(mutex);
|
||||
currentKnob->command = &commands->at(knobEvent->currentData().toInt());
|
||||
currentKnob->text->setPlainText(currentKnob->command->text);
|
||||
currentKnob->text->setPos(currentKnob->pos.center().x() - currentKnob->text->boundingRect().width() / 2,
|
||||
(currentKnob->pos.center().y() - currentKnob->text->boundingRect().height() / 2));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void controllerSetup::buttonOnColorClicked()
|
||||
{
|
||||
QColorDialog::ColorDialogOptions options;
|
||||
options.setFlag(QColorDialog::ShowAlphaChannel, false);
|
||||
options.setFlag(QColorDialog::DontUseNativeDialog, false);
|
||||
QColor selColor = QColorDialog::getColor(currentButton->backgroundOn, this, "Select Color to use for unpressed button", options);
|
||||
|
||||
if(selColor.isValid() && currentButton != Q_NULLPTR)
|
||||
{
|
||||
QMutexLocker locker(mutex);
|
||||
currentButton->backgroundOn = selColor;
|
||||
if (currentButton->graphics && currentButton->bgRect != Q_NULLPTR)
|
||||
{
|
||||
currentButton->bgRect->setBrush(currentButton->backgroundOn);
|
||||
}
|
||||
buttonOnColor->setStyleSheet(QString("background-color: %1").arg(currentButton->backgroundOn.name(QColor::HexArgb)));
|
||||
emit sendRequest(currentButton->parent,usbFeatureType::featureButton,currentButton->num,currentButton->onCommand->text,currentButton->icon,¤tButton->backgroundOn);
|
||||
}
|
||||
}
|
||||
|
||||
void controllerSetup::buttonOffColorClicked()
|
||||
{
|
||||
QColorDialog::ColorDialogOptions options;
|
||||
options.setFlag(QColorDialog::ShowAlphaChannel, false);
|
||||
options.setFlag(QColorDialog::DontUseNativeDialog, false);
|
||||
QColor selColor = QColorDialog::getColor(currentButton->backgroundOff, this, "Select Color to use for pressed button", options);
|
||||
|
||||
if(selColor.isValid() && currentButton != Q_NULLPTR)
|
||||
{
|
||||
QMutexLocker locker(mutex);
|
||||
currentButton->backgroundOff = selColor;
|
||||
buttonOffColor->setStyleSheet(QString("background-color: %1").arg(currentButton->backgroundOff.name(QColor::HexArgb)));
|
||||
}
|
||||
}
|
||||
|
||||
void controllerSetup::buttonIconClicked()
|
||||
{
|
||||
QString file = QFileDialog::getOpenFileName(this,"Select Icon Filename",".","Images (*png *.jpg)");
|
||||
if (!file.isEmpty()) {
|
||||
QFileInfo info = QFileInfo(file);
|
||||
currentButton->iconName = info.fileName();
|
||||
iconLabel->setText(currentButton->iconName);
|
||||
QImage image;
|
||||
image.load(file);
|
||||
if (currentButton->icon != Q_NULLPTR)
|
||||
delete currentButton->icon;
|
||||
currentButton->icon = new QImage(image.scaled(currentButton->parent->type.iconSize,currentButton->parent->type.iconSize));
|
||||
} else {
|
||||
if (currentButton->icon != Q_NULLPTR)
|
||||
{
|
||||
currentButton->iconName = "";
|
||||
delete currentButton->icon;
|
||||
currentButton->icon = Q_NULLPTR;
|
||||
}
|
||||
}
|
||||
iconLabel->setText(currentButton->iconName);
|
||||
emit sendRequest(currentButton->parent,usbFeatureType::featureButton,currentButton->num,currentButton->onCommand->text,currentButton->icon, ¤tButton->backgroundOn);
|
||||
}
|
||||
|
||||
void controllerSetup::latchStateChanged(int state)
|
||||
{
|
||||
if (currentButton != Q_NULLPTR) {
|
||||
QMutexLocker locker(mutex);
|
||||
currentButton->toggle=(int)state;
|
||||
}
|
||||
}
|
||||
|
||||
void controllerSetup::removeDevice(USBDEVICE* dev)
|
||||
{
|
||||
QMutexLocker locker(mutex);
|
||||
|
||||
/* We need to manually delete everything that has been created for this tab */
|
||||
auto tab = tabs.find(dev->path);
|
||||
if (tab == tabs.end())
|
||||
{
|
||||
qWarning(logUsbControl()) << "Cannot find tabContent for deleted tab" << dev->path;
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto b = buttons->begin();b != buttons->end(); b++)
|
||||
{
|
||||
if (b->parent == dev && b->page == dev->currentPage)
|
||||
{
|
||||
if (b->text != Q_NULLPTR) {
|
||||
tab.value()->scene->removeItem(b->text);
|
||||
delete b->text;
|
||||
b->text = Q_NULLPTR;
|
||||
}
|
||||
b->offCommand = Q_NULLPTR;
|
||||
b->onCommand = Q_NULLPTR;
|
||||
if (b->icon != Q_NULLPTR) {
|
||||
delete b->icon;
|
||||
b->icon=Q_NULLPTR;
|
||||
}
|
||||
if (b->bgRect != Q_NULLPTR) {
|
||||
tab.value()->scene->removeItem(b->bgRect);
|
||||
delete b->bgRect;
|
||||
b->bgRect = Q_NULLPTR;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
for (auto k = knobs->begin();k != knobs->end(); k++)
|
||||
{
|
||||
if (k->parent == dev && k->page == dev->currentPage)
|
||||
{
|
||||
if (k->text != Q_NULLPTR) {
|
||||
tab.value()->scene->removeItem(k->text);
|
||||
delete k->text;
|
||||
k->text = Q_NULLPTR;
|
||||
k->command = Q_NULLPTR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
qDebug(logUsbControl()) << "Removing tab content" << dev->product;
|
||||
|
||||
if (tab.value()->bgImage != Q_NULLPTR) {
|
||||
tab.value()->scene->removeItem(tab.value()->bgImage);
|
||||
delete tab.value()->bgImage;
|
||||
}
|
||||
delete tab.value()->scene;
|
||||
|
||||
// Find the tab within the tabWidget
|
||||
for (int i = 0; i < ui->tabWidget->count(); i++) {
|
||||
auto widget = ui->tabWidget->widget(i);
|
||||
if (widget->objectName() == dev->path) {
|
||||
ui->tabWidget->removeTab(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delete tab.value();
|
||||
tabs.remove(dev->path);
|
||||
|
||||
// Hide the tabWidget if no tabs exist
|
||||
if (ui->tabWidget->count() == 0)
|
||||
{
|
||||
ui->tabWidget->hide();
|
||||
noControllersText->show();
|
||||
this->adjustSize();
|
||||
}
|
||||
}
|
||||
|
||||
void controllerSetup::newDevice(USBDEVICE* dev)
|
||||
{
|
||||
QMutexLocker locker(mutex);
|
||||
|
||||
for (int i=0; i<ui->tabWidget->count();i++) {
|
||||
if (ui->tabWidget->widget(i)->objectName() == dev->path)
|
||||
{
|
||||
qInfo(logUsbControl()) <<"Tab for " << dev->product << "("<< dev->path << ") Already exists!";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto tab = tabs.find(dev->path);
|
||||
if (tab != tabs.end())
|
||||
{
|
||||
qInfo(logUsbControl()) <<"Tab content for " << dev->product << "("<< dev->path << ") Already exists!";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
qDebug(logUsbControl()) << "Adding new tab for" << dev->product;
|
||||
noControllersText->hide();
|
||||
|
||||
tabContent* c = new tabContent();
|
||||
|
||||
c->tab = new QWidget();
|
||||
c->widget = new QWidget();
|
||||
|
||||
c->tab->setObjectName(dev->path);
|
||||
ui->tabWidget->addTab(c->tab,dev->product);
|
||||
|
||||
// Create the different layouts required
|
||||
c->mainLayout = new QVBoxLayout(c->tab);
|
||||
c->layout = new QVBoxLayout();
|
||||
c->topLayout = new QHBoxLayout();
|
||||
c->sensLayout = new QHBoxLayout();
|
||||
c->grid = new QGridLayout();
|
||||
|
||||
c->mainLayout->addLayout(c->topLayout);
|
||||
c->mainLayout->addWidget(c->widget);
|
||||
c->widget->setLayout(c->layout);
|
||||
|
||||
c->disabled = new QCheckBox("Disable");
|
||||
c->topLayout->addWidget(c->disabled);
|
||||
connect(c->disabled, qOverload<bool>(&QCheckBox::clicked),
|
||||
[dev,this,c](bool checked) { this->disableClicked(dev,checked,c->widget); });
|
||||
c->disabled->setChecked(dev->disabled);
|
||||
|
||||
c->message = new QLabel();
|
||||
if (dev->connected) {
|
||||
c->message->setStyleSheet("QLabel { color : green; }");
|
||||
c->message->setText("Connected");
|
||||
} else {
|
||||
c->message->setStyleSheet("QLabel { color : red; }");
|
||||
c->message->setText("Not Connected");
|
||||
}
|
||||
c->topLayout->addWidget(c->message);
|
||||
c->message->setAlignment(Qt::AlignRight);
|
||||
|
||||
c->view = new QGraphicsView();
|
||||
c->layout->addWidget(c->view);
|
||||
|
||||
c->layout->addLayout(c->sensLayout);
|
||||
c->sensLabel = new QLabel("Sensitivity");
|
||||
c->sensLayout->addWidget(c->sensLabel);
|
||||
c->sens = new QSlider(Qt::Horizontal);
|
||||
c->sens->setMinimum(1);
|
||||
c->sens->setMaximum(21);
|
||||
c->sens->setInvertedAppearance(true);
|
||||
c->sensLayout->addWidget(c->sens);
|
||||
c->sens->setValue(dev->sensitivity);
|
||||
connect(c->sens, &QSlider::valueChanged,
|
||||
[dev,this](int val) { this->sensitivityMoved(dev,val); });
|
||||
|
||||
c->sensLayout->addStretch(0);
|
||||
|
||||
c->pageLabel = new QLabel("Page:");
|
||||
c->sensLayout->addWidget(c->pageLabel);
|
||||
c->page = new QSpinBox();
|
||||
c->page->setValue(1);
|
||||
c->page->setMinimum(1);
|
||||
c->page->setMaximum(dev->pages);
|
||||
c->page->setToolTip("Select current page to edit");
|
||||
c->sensLayout->addWidget(c->page);
|
||||
dev->pageSpin = c->page;
|
||||
|
||||
c->image = new QImage();
|
||||
switch (dev->type.model) {
|
||||
case shuttleXpress:
|
||||
c->image->load(":/resources/shuttlexpress.png");
|
||||
break;
|
||||
case shuttlePro2:
|
||||
c->image->load(":/resources/shuttlepro.png");
|
||||
break;
|
||||
case RC28:
|
||||
c->image->load(":/resources/rc28.png");
|
||||
break;
|
||||
case xBoxGamepad:
|
||||
c->image->load(":/resources/xbox.png");
|
||||
break;
|
||||
case eCoderPlus:
|
||||
c->image->load(":/resources/ecoder.png");
|
||||
break;
|
||||
case QuickKeys:
|
||||
c->image->load(":/resources/quickkeys.png");
|
||||
break;
|
||||
case StreamDeckOriginal:
|
||||
case StreamDeckOriginalV2:
|
||||
case StreamDeckOriginalMK2:
|
||||
c->image->load(":/resources/streamdeck.png");
|
||||
break;
|
||||
case StreamDeckMini:
|
||||
case StreamDeckMiniV2:
|
||||
c->image->load(":/resources/streamdeckmini.png");
|
||||
break;
|
||||
case StreamDeckXL:
|
||||
case StreamDeckXLV2:
|
||||
c->image->load(":/resources/streamdeckxl.png");
|
||||
break;
|
||||
case StreamDeckPlus:
|
||||
c->image->load(":/resources/streamdeckplus.png");
|
||||
break;
|
||||
case StreamDeckPedal:
|
||||
c->image->load(":/resources/streamdeckpedal.png");
|
||||
break;
|
||||
default:
|
||||
this->adjustSize();
|
||||
break;
|
||||
}
|
||||
|
||||
c->bgImage = new QGraphicsPixmapItem(QPixmap::fromImage(*c->image));
|
||||
c->view->setMinimumSize(c->bgImage->boundingRect().width() + 2, c->bgImage->boundingRect().height() + 2);
|
||||
ui->tabWidget->show();
|
||||
|
||||
c->scene = new controllerScene();
|
||||
c->view->setScene(c->scene);
|
||||
connect(c->scene, SIGNAL(showMenu(controllerScene*,QPoint)), this, SLOT(showMenu(controllerScene*,QPoint)));
|
||||
c->scene->addItem(c->bgImage);
|
||||
|
||||
|
||||
c->layout->addLayout(c->grid);
|
||||
|
||||
c->brightLabel = new QLabel("Brightness");
|
||||
c->grid->addWidget(c->brightLabel,0,0);
|
||||
c->brightness = new QComboBox();
|
||||
c->brightness->addItem("Off");
|
||||
c->brightness->addItem("Low");
|
||||
c->brightness->addItem("Medium");
|
||||
c->brightness->addItem("High");
|
||||
c->brightness->setCurrentIndex(dev->brightness);
|
||||
c->grid->addWidget(c->brightness,1,0);
|
||||
connect(c->brightness, qOverload<int>(&QComboBox::currentIndexChanged),
|
||||
[dev,this](int index) { this->brightnessChanged(dev,index); });
|
||||
|
||||
c->speedLabel = new QLabel("Speed");
|
||||
c->grid->addWidget(c->speedLabel,0,1);
|
||||
c->speed = new QComboBox();
|
||||
c->speed->addItem("Fastest");
|
||||
c->speed->addItem("Faster");
|
||||
c->speed->addItem("Normal");
|
||||
c->speed->addItem("Slower");
|
||||
c->speed->addItem("Slowest");
|
||||
c->speed->setCurrentIndex(dev->speed);
|
||||
c->grid->addWidget(c->speed,1,1);
|
||||
connect(c->speed, qOverload<int>(&QComboBox::currentIndexChanged),
|
||||
[dev,this](int index) { this->speedChanged(dev,index); });
|
||||
|
||||
c->orientLabel = new QLabel("Orientation");
|
||||
c->grid->addWidget(c->orientLabel,0,2);
|
||||
c->orientation = new QComboBox();
|
||||
c->orientation->addItem("Rotate 0");
|
||||
c->orientation->addItem("Rotate 90");
|
||||
c->orientation->addItem("Rotate 180");
|
||||
c->orientation->addItem("Rotate 270");
|
||||
c->orientation->setCurrentIndex(dev->orientation);
|
||||
c->grid->addWidget(c->orientation,1,2);
|
||||
connect(c->orientation, qOverload<int>(&QComboBox::currentIndexChanged),
|
||||
[dev,this](int index) { this->orientationChanged(dev,index); });
|
||||
|
||||
c->color = new QPushButton("Color");
|
||||
c->color->setStyleSheet(QString("background-color: %1").arg(dev->color.name(QColor::HexArgb)));
|
||||
c->grid->addWidget(c->color,1,3);
|
||||
connect(c->color, &QPushButton::clicked,
|
||||
[dev,c,this]() { this->colorPicker(dev,c->color,dev->color); });
|
||||
|
||||
c->timeoutLabel = new QLabel("Timeout");
|
||||
c->grid->addWidget(c->timeoutLabel,0,4);
|
||||
c->timeout = new QSpinBox();
|
||||
c->timeout->setValue(dev->timeout);
|
||||
c->grid->addWidget(c->timeout,1,4);
|
||||
connect(c->timeout, qOverload<int>(&QSpinBox::valueChanged),
|
||||
[dev,this](int index) { this->timeoutChanged(dev,index); });
|
||||
|
||||
c->pagesLabel = new QLabel("Num Pages");
|
||||
c->grid->addWidget(c->pagesLabel,0,5);
|
||||
c->pages = new QSpinBox();
|
||||
c->pages->setValue(dev->pages);
|
||||
c->pages->setMinimum(1);
|
||||
c->grid->addWidget(c->pages,1,5);
|
||||
connect(c->pages, qOverload<int>(&QSpinBox::valueChanged),
|
||||
[dev,this](int index) { this->pagesChanged(dev,index); });
|
||||
|
||||
for (int i=0;i<6;i++)
|
||||
c->grid->setColumnStretch(i,1);
|
||||
|
||||
c->helpText = new QLabel();
|
||||
c->helpText->setText("<p><b>Button configuration:</b> Right-click on each button to configure it.</p>");
|
||||
c->helpText->setAlignment(Qt::AlignCenter);
|
||||
c->layout->addWidget(c->helpText);
|
||||
|
||||
|
||||
c->view->setSceneRect(c->scene->itemsBoundingRect());
|
||||
|
||||
this->adjustSize();
|
||||
|
||||
numTabs++;
|
||||
tabs.insert(dev->path,c);
|
||||
|
||||
dev->uiCreated = true;
|
||||
|
||||
// Finally update the device with the default values
|
||||
emit sendRequest(dev,usbFeatureType::featureSensitivity,dev->sensitivity);
|
||||
emit sendRequest(dev,usbFeatureType::featureBrightness,dev->brightness);
|
||||
emit sendRequest(dev,usbFeatureType::featureOrientation,dev->orientation);
|
||||
emit sendRequest(dev,usbFeatureType::featureSpeed,dev->speed);
|
||||
emit sendRequest(dev,usbFeatureType::featureTimeout,dev->timeout);
|
||||
emit sendRequest(dev,usbFeatureType::featureColor,0,"", Q_NULLPTR, &dev->color);
|
||||
|
||||
|
||||
|
||||
// Attach pageChanged() here so we have access to all necessary vars
|
||||
connect(c->page, qOverload<int>(&QSpinBox::valueChanged),
|
||||
[dev, this](int index) { this->pageChanged(dev, index); });
|
||||
|
||||
// Hide all controls that are not relevant to this controller
|
||||
// We rely on being able to fallthrough case
|
||||
#if defined __GNUC__
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
||||
#endif
|
||||
|
||||
switch (dev->type.model) {
|
||||
case QuickKeys:
|
||||
break;
|
||||
case StreamDeckPedal:
|
||||
c->sensLabel->setVisible(false);
|
||||
c->sens->setVisible(false);
|
||||
case shuttleXpress:
|
||||
case shuttlePro2:
|
||||
case RC28:
|
||||
case xBoxGamepad:
|
||||
case eCoderPlus:
|
||||
c->brightLabel->setVisible(false);
|
||||
c->speedLabel->setVisible(false);
|
||||
c->timeoutLabel->setVisible(false);
|
||||
c->orientLabel->setVisible(false);
|
||||
c->brightness->setVisible(false);
|
||||
c->speed->setVisible(false);
|
||||
c->timeout->setVisible(false);
|
||||
c->orientation->setVisible(false);
|
||||
c->color->setVisible(false);
|
||||
break;
|
||||
case StreamDeckOriginal:
|
||||
case StreamDeckOriginalV2:
|
||||
case StreamDeckOriginalMK2:
|
||||
case StreamDeckMini:
|
||||
case StreamDeckMiniV2:
|
||||
case StreamDeckXL:
|
||||
case StreamDeckXLV2:
|
||||
c->sensLabel->setVisible(false);
|
||||
c->sens->setVisible(false); // No knobs!
|
||||
case StreamDeckPlus:
|
||||
c->speedLabel->setVisible(false);
|
||||
c->timeoutLabel->setVisible(false);
|
||||
c->orientLabel->setVisible(false);
|
||||
c->speed->setVisible(false);
|
||||
c->timeout->setVisible(false);
|
||||
c->orientation->setVisible(false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Don't allow fallthrough elsewhere in the file.
|
||||
#if defined __GNUC__
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
// pageChanged will update the buttons/knobs for the tab (using qTimer ensures mutex is unlocked first)
|
||||
QTimer::singleShot(0, this, [=]() { pageChanged(dev,1); });
|
||||
|
||||
}
|
||||
|
||||
void controllerSetup::sensitivityMoved(USBDEVICE* dev, int val)
|
||||
{
|
||||
qInfo(logUsbControl()) << "Setting sensitivity" << val <<"for device" << dev->product;
|
||||
emit sendRequest(dev,usbFeatureType::featureSensitivity,val);
|
||||
}
|
||||
|
||||
void controllerSetup::brightnessChanged(USBDEVICE* dev, int index)
|
||||
{
|
||||
emit sendRequest(dev,usbFeatureType::featureBrightness,index);
|
||||
}
|
||||
|
||||
void controllerSetup::orientationChanged(USBDEVICE* dev, int index)
|
||||
{
|
||||
emit sendRequest(dev,usbFeatureType::featureOrientation,index);
|
||||
}
|
||||
|
||||
void controllerSetup::speedChanged(USBDEVICE* dev, int index)
|
||||
{
|
||||
emit sendRequest(dev,usbFeatureType::featureSpeed,index);
|
||||
}
|
||||
|
||||
void controllerSetup::colorPicker(USBDEVICE* dev, QPushButton* btn, QColor current)
|
||||
{
|
||||
QColorDialog::ColorDialogOptions options;
|
||||
options.setFlag(QColorDialog::ShowAlphaChannel, false);
|
||||
options.setFlag(QColorDialog::DontUseNativeDialog, false);
|
||||
QColor selColor = QColorDialog::getColor(current, this, "Select Color", options);
|
||||
|
||||
if(selColor.isValid())
|
||||
{
|
||||
btn->setStyleSheet(QString("background-color: %1").arg(selColor.name(QColor::HexArgb)));
|
||||
emit sendRequest(dev,usbFeatureType::featureColor,0, "", Q_NULLPTR, &selColor);
|
||||
}
|
||||
}
|
||||
|
||||
void controllerSetup::timeoutChanged(USBDEVICE* dev, int val)
|
||||
{
|
||||
emit sendRequest(dev,usbFeatureType::featureTimeout,val);
|
||||
emit sendRequest(dev,usbFeatureType::featureOverlay,val,QString("Sleep timeout set to %0 minutes").arg(val));
|
||||
}
|
||||
|
||||
void controllerSetup::pagesChanged(USBDEVICE* dev, int val)
|
||||
{
|
||||
emit programPages(dev,val);
|
||||
dev->pageSpin->setMaximum(val); // Update pageSpin
|
||||
}
|
||||
|
||||
void controllerSetup::pageChanged(USBDEVICE* dev, int val)
|
||||
{
|
||||
|
||||
QMutexLocker locker(mutex);
|
||||
|
||||
auto tab = tabs.find(dev->path);
|
||||
if (tab == tabs.end())
|
||||
{
|
||||
qWarning(logUsbControl()) << "Cannot find tabContent while changing page" << dev->path;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (val > dev->pages)
|
||||
val=1;
|
||||
if (val < 1)
|
||||
val = dev->pages;
|
||||
|
||||
updateDialog->hide(); // Hide the dialog if the page changes.
|
||||
|
||||
dev->currentPage=val;
|
||||
|
||||
// Need to block signals so this isn't called recursively.
|
||||
dev->pageSpin->blockSignals(true);
|
||||
dev->pageSpin->setValue(val);
|
||||
dev->pageSpin->blockSignals(false);
|
||||
|
||||
// (re)set button text
|
||||
for (auto b = buttons->begin();b != buttons->end(); b++)
|
||||
{
|
||||
if (b->parent == dev)
|
||||
{
|
||||
// Make sure we delete any other pages content and then update to latest.
|
||||
if (b->text != Q_NULLPTR) {
|
||||
tab.value()->scene->removeItem(b->text);
|
||||
delete b->text;
|
||||
b->text = Q_NULLPTR;
|
||||
}
|
||||
if (b->graphics && b->bgRect != Q_NULLPTR) {
|
||||
tab.value()->scene->removeItem(b->bgRect);
|
||||
delete b->bgRect;
|
||||
b->bgRect = Q_NULLPTR;
|
||||
}
|
||||
if (b->page == dev->currentPage)
|
||||
{
|
||||
if (b->graphics)
|
||||
{
|
||||
b->bgRect = new QGraphicsRectItem(b->pos);
|
||||
b->bgRect->setBrush(b->backgroundOn);
|
||||
tab.value()->scene->addItem(b->bgRect);
|
||||
}
|
||||
b->text = new QGraphicsTextItem(b->onCommand->text);
|
||||
b->text->setDefaultTextColor(b->textColour);
|
||||
tab.value()->scene->addItem(b->text);
|
||||
b->text->setPos(b->pos.center().x() - b->text->boundingRect().width() / 2,
|
||||
(b->pos.center().y() - b->text->boundingRect().height() / 2));
|
||||
emit sendRequest(dev,usbFeatureType::featureButton,b->num,b->onCommand->text,b->icon,&b->backgroundOn);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Set knob text
|
||||
|
||||
for (auto k = knobs->begin();k != knobs->end(); k++)
|
||||
{
|
||||
if (k->parent == dev) {
|
||||
if (k->text != Q_NULLPTR) {
|
||||
tab.value()->scene->removeItem(k->text);
|
||||
delete k->text;
|
||||
k->text = Q_NULLPTR;
|
||||
}
|
||||
if (k->page == dev->currentPage)
|
||||
{
|
||||
k->text = new QGraphicsTextItem(k->command->text);
|
||||
k->text->setDefaultTextColor(k->textColour);
|
||||
tab.value()->scene->addItem(k->text);
|
||||
k->text->setPos(k->pos.center().x() - k->text->boundingRect().width() / 2,
|
||||
(k->pos.center().y() - k->text->boundingRect().height() / 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void controllerSetup::disableClicked(USBDEVICE* dev, bool clicked, QWidget* widget)
|
||||
{
|
||||
// Disable checkbox has been clicked
|
||||
emit programDisable(dev, clicked);
|
||||
|
||||
widget->setEnabled(!clicked);
|
||||
|
||||
}
|
||||
|
||||
void controllerSetup::setConnected(USBDEVICE* dev)
|
||||
{
|
||||
QMutexLocker locker(mutex);
|
||||
|
||||
|
||||
auto tab = tabs.find(dev->path);
|
||||
if (tab != tabs.end())
|
||||
{
|
||||
if (dev->connected)
|
||||
{
|
||||
tab.value()->message->setStyleSheet("QLabel { color : green; }");
|
||||
tab.value()->message->setText("Connected");
|
||||
} else {
|
||||
tab.value()->message->setStyleSheet("QLabel { color : red; }");
|
||||
tab.value()->message->setText("Not Connected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void controllerSetup::on_backupButton_clicked()
|
||||
{
|
||||
QString file = QFileDialog::getSaveFileName(this,"Select Backup Filename",".","Backup Files (*.ini)");
|
||||
if (!file.isEmpty()) {
|
||||
auto devIt = devices->find(ui->tabWidget->currentWidget()->objectName());
|
||||
if (devIt==devices->end())
|
||||
{
|
||||
qWarning(logUsbControl) << "on_restoreButton_clicked() Cannot find existing controller, aborting!";
|
||||
}
|
||||
else
|
||||
{
|
||||
emit backup(&devIt.value(), file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void controllerSetup::on_restoreButton_clicked()
|
||||
{
|
||||
QMutexLocker locker(mutex);
|
||||
|
||||
QString file = QFileDialog::getOpenFileName(this,"Select Backup Filename",".","Backup Files (*.ini)");
|
||||
if (!file.isEmpty()) {
|
||||
QString path = ui->tabWidget->currentWidget()->objectName();
|
||||
|
||||
auto devIt = devices->find(path);
|
||||
if (devIt==devices->end())
|
||||
{
|
||||
qWarning(logUsbControl) << "on_restoreButton_clicked() Cannot find existing controller, aborting!";
|
||||
return;
|
||||
}
|
||||
auto dev = &devIt.value();
|
||||
|
||||
QSettings* settings = new QSettings(file, QSettings::Format::IniFormat);
|
||||
QString version = settings->value("Version", "").toString();
|
||||
settings->beginGroup("Controller");
|
||||
QString model = settings->value("Model","").toString();
|
||||
delete settings;
|
||||
|
||||
if (model != dev->product) {
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText("Stored controller does not match");
|
||||
msgBox.setInformativeText(QString("Backup: %0 \nCurrent: %1\n\nThis will probably not work!").arg(model,dev->product));
|
||||
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
|
||||
msgBox.setDefaultButton(QMessageBox::Cancel);
|
||||
int ret= msgBox.exec();
|
||||
if (ret == QMessageBox::Cancel) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (version != QString(WFVIEW_VERSION))
|
||||
{
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText("Version mismatch");
|
||||
msgBox.setInformativeText(QString("Backup was from a different version of wfview\nBackup: %0 \nCurrent: %1\n\nPlease verify compatibility").arg(version, QString(WFVIEW_VERSION)));
|
||||
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
|
||||
msgBox.setDefaultButton(QMessageBox::Cancel);
|
||||
int ret= msgBox.exec();
|
||||
if (ret == QMessageBox::Cancel) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
emit restore(dev, file);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,223 @@
|
|||
#ifndef CONTROLLERSETUP_H
|
||||
#define CONTROLLERSETUP_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QGraphicsView>
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsTextItem>
|
||||
#include <QGraphicsPixmapItem>
|
||||
#include <QGraphicsRectItem>
|
||||
#include <QPoint>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QVector>
|
||||
#include <QRect>
|
||||
#include <QComboBox>
|
||||
#include <QLabel>
|
||||
#include <QGraphicsProxyWidget>
|
||||
#include <QAbstractItemView>
|
||||
#include <QHBoxLayout>
|
||||
#include <QGridLayout>
|
||||
#include <QPushButton>
|
||||
#include <QScopedPointer>
|
||||
#include <QCheckBox>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QLayoutItem>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QObject>
|
||||
#include <QColorDialog>
|
||||
#include <QWidget>
|
||||
#include <QSpinBox>
|
||||
#include <QCheckBox>
|
||||
|
||||
#include "usbcontroller.h"
|
||||
|
||||
|
||||
class controllerScene : public QGraphicsScene
|
||||
{
|
||||
Q_OBJECT
|
||||
QGraphicsLineItem* item = Q_NULLPTR;
|
||||
|
||||
signals:
|
||||
void showMenu(controllerScene* scene, QPoint p);
|
||||
void buttonAction(bool pressed, QPoint p);
|
||||
protected:
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent* event) {
|
||||
|
||||
if (event->button() == Qt::RightButton)
|
||||
{
|
||||
emit showMenu(this, event->scenePos().toPoint());
|
||||
}
|
||||
else if (event->button() == Qt::LeftButton)
|
||||
{
|
||||
// Simulate a button press
|
||||
emit buttonAction(true,event->scenePos().toPoint());
|
||||
}
|
||||
else
|
||||
{
|
||||
QGraphicsScene::mousePressEvent(event);
|
||||
}
|
||||
}
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) {
|
||||
|
||||
if (event->button() == Qt::LeftButton)
|
||||
{
|
||||
// Simulate a button release
|
||||
emit buttonAction(false,event->scenePos().toPoint());
|
||||
}
|
||||
else
|
||||
{
|
||||
QGraphicsScene::mouseReleaseEvent(event);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct tabContent {
|
||||
QWidget* tab;
|
||||
QVBoxLayout* mainLayout;
|
||||
QHBoxLayout* topLayout;
|
||||
QWidget* widget;
|
||||
QVBoxLayout* layout;
|
||||
QCheckBox* disabled;
|
||||
QLabel* message;
|
||||
QGraphicsView* view;
|
||||
QLabel* pageLabel;
|
||||
QSpinBox* page;
|
||||
QHBoxLayout* sensLayout;
|
||||
QLabel* sensLabel;
|
||||
QSlider* sens;
|
||||
QImage* image;
|
||||
QGraphicsItem* bgImage = Q_NULLPTR;
|
||||
controllerScene* scene = Q_NULLPTR;
|
||||
QGridLayout* grid;
|
||||
QLabel* brightLabel;
|
||||
QComboBox* brightness;
|
||||
QLabel* speedLabel;
|
||||
QComboBox* speed;
|
||||
QLabel* orientLabel;
|
||||
QComboBox* orientation;
|
||||
QLabel* colorLabel;
|
||||
QPushButton* color;
|
||||
QLabel* timeoutLabel;
|
||||
QSpinBox* timeout;
|
||||
QLabel* pagesLabel;
|
||||
QSpinBox* pages;
|
||||
QLabel* helpText;
|
||||
};
|
||||
|
||||
|
||||
|
||||
namespace Ui {
|
||||
class controllerSetup;
|
||||
}
|
||||
|
||||
class controllerSetup : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit controllerSetup(QWidget* parent = 0);
|
||||
~controllerSetup();
|
||||
|
||||
signals:
|
||||
void started();
|
||||
void sendRequest(USBDEVICE* dev, usbFeatureType request, int val=0, QString text="", QImage* img=Q_NULLPTR, QColor* color=Q_NULLPTR);
|
||||
void programDisable(USBDEVICE* dev, bool disable);
|
||||
void programPages(USBDEVICE* dev, int pages);
|
||||
void backup(USBDEVICE* dev, QString path);
|
||||
void restore(USBDEVICE *dev, QString path);
|
||||
|
||||
public slots:
|
||||
void init(usbDevMap* dev, QVector<BUTTON>* but, QVector<KNOB>* kb, QVector<COMMAND>* cmd, QMutex* mut);
|
||||
void newDevice(USBDEVICE* dev);
|
||||
void removeDevice(USBDEVICE* dev);
|
||||
void showMenu(controllerScene *scene,QPoint p);
|
||||
void onEventIndexChanged(int index);
|
||||
void offEventIndexChanged(int index);
|
||||
void knobEventIndexChanged(int index);
|
||||
void ledNumberChanged(int index);
|
||||
void sensitivityMoved(USBDEVICE* dev, int val);
|
||||
void brightnessChanged(USBDEVICE* dev, int index);
|
||||
void orientationChanged(USBDEVICE* dev, int index);
|
||||
void speedChanged(USBDEVICE* dev, int index);
|
||||
void colorPicker(USBDEVICE* dev, QPushButton* btn, QColor color);
|
||||
void buttonOnColorClicked();
|
||||
void buttonOffColorClicked();
|
||||
void buttonIconClicked();
|
||||
void latchStateChanged(int state);
|
||||
|
||||
void timeoutChanged(USBDEVICE* dev, int val);
|
||||
void pageChanged(USBDEVICE* dev, int val);
|
||||
void pagesChanged(USBDEVICE* dev, int val);
|
||||
void disableClicked(USBDEVICE* dev, bool clicked, QWidget* widget);
|
||||
void setConnected(USBDEVICE* dev);
|
||||
void hideEvent(QHideEvent *event);
|
||||
void on_tabWidget_currentChanged(int index);
|
||||
void on_backupButton_clicked();
|
||||
void on_restoreButton_clicked();
|
||||
|
||||
private:
|
||||
|
||||
usbDeviceType type = usbNone;
|
||||
Ui::controllerSetup* ui;
|
||||
QGraphicsTextItem* textItem;
|
||||
QLabel* imgLabel;
|
||||
unsigned char currentDevice = 0;
|
||||
QVector<BUTTON>* buttons;
|
||||
QVector<KNOB>* knobs;
|
||||
QVector<COMMAND>* commands;
|
||||
usbDevMap* devices;
|
||||
|
||||
BUTTON* currentButton = Q_NULLPTR;
|
||||
KNOB* currentKnob = Q_NULLPTR;
|
||||
|
||||
// Update Dialog
|
||||
QDialog * updateDialog = Q_NULLPTR;
|
||||
QComboBox* onEvent;
|
||||
QComboBox* offEvent;
|
||||
QComboBox* knobEvent;
|
||||
QLabel* onLabel;
|
||||
QLabel* offLabel;
|
||||
QLabel* knobLabel;
|
||||
QPushButton* buttonOnColor;
|
||||
QPushButton* buttonOffColor;
|
||||
QCheckBox *buttonLatch;
|
||||
QPushButton* buttonIcon;
|
||||
QLabel* iconLabel;
|
||||
QSpinBox* ledNumber;
|
||||
|
||||
QString deviceName;
|
||||
QMutex* mutex;
|
||||
QColor initialColor = Qt::white;
|
||||
|
||||
QLabel* noControllersText;
|
||||
|
||||
int numTabs=0;
|
||||
QMap<QString,tabContent*> tabs;
|
||||
|
||||
// Below are used for each tab:
|
||||
/*
|
||||
QList<QWidget *> tabs;
|
||||
QList<QVBoxLayout *> layouts;
|
||||
QList<QWidget *> widgets;
|
||||
QList<QGraphicsView *> graphicsViews;
|
||||
QList<QGraphicsScene*> scenes;
|
||||
QList<QGraphicsItem*> bgImages;
|
||||
QList<QSlider *>sensitivitys;
|
||||
|
||||
// Just used for QuickKeys device
|
||||
QList<QComboBox *>brightCombos;
|
||||
QList<QComboBox *>speedCombos;
|
||||
QList<QComboBox *>orientCombos;
|
||||
QList<QPushButton *>colorButtons;
|
||||
QList<QSpinBox *>timeoutSpins;
|
||||
*/
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
|
@ -0,0 +1,134 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>controllerSetup</class>
|
||||
<widget class="QDialog" name="controllerSetup">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>788</width>
|
||||
<height>646</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Controller setup</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="tabPosition">
|
||||
<enum>QTabWidget::North</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Tab 1</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Tab 2</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<layout class="QHBoxLayout">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="backupButton">
|
||||
<property name="text">
|
||||
<string>Backup</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="restoreButton">
|
||||
<property name="text">
|
||||
<string>Restore</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>131</width>
|
||||
<height>31</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="okButton">
|
||||
<property name="text">
|
||||
<string>OK</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancelButton">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>okButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>controllerSetup</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>278</x>
|
||||
<y>253</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>96</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>cancelButton</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>controllerSetup</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>369</x>
|
||||
<y>253</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>179</x>
|
||||
<y>282</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
|
@ -0,0 +1,493 @@
|
|||
#include "cwsender.h"
|
||||
#include "ui_cwsender.h"
|
||||
|
||||
#include "logcategories.h"
|
||||
|
||||
cwSender::cwSender(QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
ui(new Ui::cwSender)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
this->setWindowTitle("CW Sender");
|
||||
ui->textToSendEdit->setFocus();
|
||||
QFont f = QFont("Monospace");
|
||||
f.setStyleHint(QFont::TypeWriter);
|
||||
ui->textToSendEdit->setFont(f);
|
||||
ui->transcriptText->setFont(f);
|
||||
ui->textToSendEdit->setFocus();
|
||||
ui->statusbar->setToolTipDuration(3000);
|
||||
this->setToolTipDuration(3000);
|
||||
connect(ui->textToSendEdit->lineEdit(), &QLineEdit::textEdited, this, &cwSender::textChanged);
|
||||
}
|
||||
|
||||
cwSender::~cwSender()
|
||||
{
|
||||
qDebug(logCW()) << "Running CW Sender destructor.";
|
||||
|
||||
if (toneThread != Q_NULLPTR) {
|
||||
toneThread->quit();
|
||||
toneThread->wait();
|
||||
toneThread = Q_NULLPTR;
|
||||
tone = Q_NULLPTR;
|
||||
/* Finally disconnect all connections */
|
||||
for (auto conn: connections)
|
||||
{
|
||||
disconnect(conn);
|
||||
}
|
||||
connections.clear();
|
||||
}
|
||||
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void cwSender::showEvent(QShowEvent *event)
|
||||
{
|
||||
(void)event;
|
||||
emit getCWSettings();
|
||||
QMainWindow::showEvent(event);
|
||||
}
|
||||
|
||||
void cwSender::handleKeySpeed(unsigned char wpm)
|
||||
{
|
||||
if (wpm != ui->wpmSpin->value() && (wpm >= ui->wpmSpin->minimum()) && (wpm <= ui->wpmSpin->maximum()))
|
||||
{
|
||||
ui->wpmSpin->blockSignals(true);
|
||||
QMetaObject::invokeMethod(ui->wpmSpin, "setValue", Qt::QueuedConnection, Q_ARG(int, wpm));
|
||||
ui->wpmSpin->blockSignals(false);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5,10,0))
|
||||
QMetaObject::invokeMethod(tone, [=]() {
|
||||
tone->setSpeed(wpm);
|
||||
}, Qt::QueuedConnection);
|
||||
#else
|
||||
emit setKeySpeed(ratio);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void cwSender::handleDashRatio(unsigned char ratio)
|
||||
{
|
||||
double calc = double(ratio/10);
|
||||
if (calc != ui->dashSpin->value() && (calc >= ui->dashSpin->minimum()) && (ratio <= ui->dashSpin->maximum()))
|
||||
{
|
||||
ui->dashSpin->blockSignals(true);
|
||||
QMetaObject::invokeMethod(ui->dashSpin, "setValue", Qt::QueuedConnection, Q_ARG(double, calc));
|
||||
ui->dashSpin->blockSignals(false);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5,10,0))
|
||||
QMetaObject::invokeMethod(tone, [=]() {
|
||||
tone->setRatio(ratio);
|
||||
}, Qt::QueuedConnection);
|
||||
#else
|
||||
emit setDashRatio(ratio);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void cwSender::handlePitch(unsigned char pitch) {
|
||||
int cwPitch = round((((600.0 / 255.0) * pitch) + 300) / 5.0) * 5.0;
|
||||
if (cwPitch != ui->pitchSpin->value() && cwPitch >= ui->pitchSpin->minimum() && cwPitch <= ui->pitchSpin->maximum())
|
||||
{
|
||||
ui->pitchSpin->blockSignals(true);
|
||||
QMetaObject::invokeMethod(ui->pitchSpin, "setValue", Qt::QueuedConnection, Q_ARG(int, cwPitch));
|
||||
ui->pitchSpin->blockSignals(false);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5,10,0))
|
||||
QMetaObject::invokeMethod(tone, [=]() {
|
||||
tone->setFrequency(pitch);
|
||||
}, Qt::QueuedConnection);
|
||||
#else
|
||||
emit setPitch(tone);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void cwSender::handleBreakInMode(unsigned char b)
|
||||
{
|
||||
if(b < 3)
|
||||
{
|
||||
ui->breakinCombo->blockSignals(true);
|
||||
ui->breakinCombo->setCurrentIndex(b);
|
||||
ui->breakinCombo->blockSignals(false);
|
||||
}
|
||||
}
|
||||
|
||||
void cwSender::handleCurrentModeUpdate(mode_kind mode)
|
||||
{
|
||||
this->currentMode = mode;
|
||||
if( (currentMode==modeCW) || (currentMode==modeCW_R) )
|
||||
{
|
||||
} else {
|
||||
ui->statusbar->showMessage("Note: Mode needs to be set to CW or CW-R to send CW.", 3000);
|
||||
}
|
||||
}
|
||||
|
||||
void cwSender::textChanged(QString text)
|
||||
{
|
||||
if (ui->sendImmediateChk->isChecked() && text.size() && text.back() == ' ')
|
||||
{
|
||||
int toSend = text.mid(0, 30).size();
|
||||
if (toSend > 0) {
|
||||
ui->textToSendEdit->clearEditText();
|
||||
ui->transcriptText->moveCursor(QTextCursor::End);
|
||||
ui->transcriptText->insertPlainText(text.mid(0, 30).toUpper());
|
||||
ui->transcriptText->moveCursor(QTextCursor::End);
|
||||
|
||||
emit sendCW(text.mid(0, 30));
|
||||
}
|
||||
if( (currentMode != modeCW) && (currentMode != modeCW_R) )
|
||||
{
|
||||
ui->statusbar->showMessage("Note: Mode needs to be set to CW or CW-R to send CW.", 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cwSender::on_sendBtn_clicked()
|
||||
{
|
||||
if( (ui->textToSendEdit->currentText().length() > 0) &&
|
||||
(ui->textToSendEdit->currentText().length() <= 30) )
|
||||
{
|
||||
QString text = ui->textToSendEdit->currentText();
|
||||
|
||||
ui->transcriptText->moveCursor(QTextCursor::End);
|
||||
ui->transcriptText->insertPlainText(ui->textToSendEdit->currentText().toUpper()+"\n");
|
||||
ui->transcriptText->moveCursor(QTextCursor::End);
|
||||
if (!ui->sendImmediateChk->isChecked())
|
||||
{
|
||||
ui->textToSendEdit->addItem(ui->textToSendEdit->currentText());
|
||||
if (ui->textToSendEdit->count() > 5) {
|
||||
ui->textToSendEdit->removeItem(0);
|
||||
}
|
||||
ui->textToSendEdit->setCurrentIndex(-1);
|
||||
} else {
|
||||
ui->textToSendEdit->clearEditText();
|
||||
ui->textToSendEdit->clear();
|
||||
}
|
||||
|
||||
ui->textToSendEdit->setFocus();
|
||||
ui->statusbar->showMessage("Sending CW", 3000);
|
||||
|
||||
emit sendCW(text);
|
||||
}
|
||||
|
||||
if( (currentMode != modeCW) && (currentMode != modeCW_R) )
|
||||
{
|
||||
ui->statusbar->showMessage("Note: Mode needs to be set to CW or CW-R to send CW.", 3000);
|
||||
}
|
||||
}
|
||||
|
||||
void cwSender::on_stopBtn_clicked()
|
||||
{
|
||||
emit stopCW();
|
||||
ui->textToSendEdit->setFocus();
|
||||
ui->statusbar->showMessage("Stopping CW transmission.", 3000);
|
||||
}
|
||||
|
||||
//void cwSender::on_textToSendEdit_returnPressed()
|
||||
//{
|
||||
// on_sendBtn_clicked();
|
||||
//}
|
||||
|
||||
void cwSender::on_breakinCombo_activated(int brkmode)
|
||||
{
|
||||
// 0 = off, 1 = semi, 2 = full
|
||||
emit setBreakInMode((unsigned char)brkmode);
|
||||
ui->textToSendEdit->setFocus();
|
||||
}
|
||||
|
||||
void cwSender::on_wpmSpin_valueChanged(int wpm)
|
||||
{
|
||||
emit setKeySpeed((unsigned char)wpm);
|
||||
}
|
||||
|
||||
void cwSender::on_dashSpin_valueChanged(double ratio)
|
||||
{
|
||||
emit setDashRatio((unsigned char)double(ratio * 10));
|
||||
}
|
||||
|
||||
void cwSender::on_pitchSpin_valueChanged(int arg1)
|
||||
{
|
||||
// quint16 cwPitch = round((((600.0 / 255.0) * pitch) + 300) / 5.0) * 5.0;
|
||||
unsigned char pitch = 0;
|
||||
pitch = ceil((arg1 - 300) * (255.0 / 600.0));
|
||||
emit setPitch(pitch);
|
||||
}
|
||||
|
||||
void cwSender::on_macro1btn_clicked()
|
||||
{
|
||||
processMacroButton(1, ui->macro1btn);
|
||||
}
|
||||
|
||||
void cwSender::on_macro2btn_clicked()
|
||||
{
|
||||
processMacroButton(2, ui->macro2btn);
|
||||
}
|
||||
|
||||
void cwSender::on_macro3btn_clicked()
|
||||
{
|
||||
processMacroButton(3, ui->macro3btn);
|
||||
}
|
||||
|
||||
void cwSender::on_macro4btn_clicked()
|
||||
{
|
||||
processMacroButton(4, ui->macro4btn);
|
||||
}
|
||||
|
||||
void cwSender::on_macro5btn_clicked()
|
||||
{
|
||||
processMacroButton(5, ui->macro5btn);
|
||||
}
|
||||
|
||||
void cwSender::on_macro6btn_clicked()
|
||||
{
|
||||
processMacroButton(6, ui->macro6btn);
|
||||
}
|
||||
|
||||
void cwSender::on_macro7btn_clicked()
|
||||
{
|
||||
processMacroButton(7, ui->macro7btn);
|
||||
}
|
||||
|
||||
void cwSender::on_macro8btn_clicked()
|
||||
{
|
||||
processMacroButton(8, ui->macro8btn);
|
||||
}
|
||||
|
||||
void cwSender::on_macro9btn_clicked()
|
||||
{
|
||||
processMacroButton(9, ui->macro9btn);
|
||||
}
|
||||
|
||||
void cwSender::on_macro10btn_clicked()
|
||||
{
|
||||
processMacroButton(10, ui->macro10btn);
|
||||
}
|
||||
|
||||
void cwSender::on_sidetoneEnableChk_clicked(bool clicked)
|
||||
{
|
||||
ui->sidetoneLevelSlider->setEnabled(clicked);
|
||||
if (clicked && toneThread == Q_NULLPTR)
|
||||
{
|
||||
toneThread = new QThread(this);
|
||||
toneThread->setObjectName("sidetone()");
|
||||
|
||||
tone = new cwSidetone(sidetoneLevel, ui->wpmSpin->value(),ui->pitchSpin->value(),ui->dashSpin->value(),this);
|
||||
tone->moveToThread(toneThread);
|
||||
toneThread->start();
|
||||
|
||||
connections.append(connect(toneThread, &QThread::finished,
|
||||
[=]() { tone->deleteLater(); }));
|
||||
connections.append(connect(this, &cwSender::sendCW,
|
||||
[=](const QString& text) { tone->send(text); ui->sidetoneEnableChk->setEnabled(false); }));
|
||||
connections.append(connect(this, &cwSender::setKeySpeed,
|
||||
[=](const unsigned char& wpm) { tone->setSpeed(wpm); }));
|
||||
connections.append(connect(this, &cwSender::setDashRatio,
|
||||
[=](const unsigned char& ratio) { tone->setRatio(ratio); }));
|
||||
connections.append(connect(this, &cwSender::setPitch,
|
||||
[=](const unsigned char& pitch) { tone->setFrequency(pitch); }));
|
||||
connections.append(connect(this, &cwSender::setLevel,
|
||||
[=](const unsigned char& level) { tone->setLevel(level); }));
|
||||
connections.append(connect(this, &cwSender::stopCW,
|
||||
[=]() { tone->stopSending(); }));
|
||||
connections.append(connect(tone, &cwSidetone::finished,
|
||||
[=]() { ui->sidetoneEnableChk->setEnabled(true); }));
|
||||
|
||||
} else if (!clicked && toneThread != Q_NULLPTR) {
|
||||
/* disconnect all connections */
|
||||
toneThread->quit();
|
||||
toneThread->wait();
|
||||
toneThread = Q_NULLPTR;
|
||||
tone = Q_NULLPTR;
|
||||
for (auto conn: connections)
|
||||
{
|
||||
disconnect(conn);
|
||||
}
|
||||
connections.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void cwSender::on_sidetoneLevelSlider_valueChanged(int val)
|
||||
{
|
||||
sidetoneLevel = val;
|
||||
emit setLevel(val);
|
||||
}
|
||||
|
||||
|
||||
void cwSender::processMacroButton(int buttonNumber, QPushButton *btn)
|
||||
{
|
||||
if(ui->macroEditChk->isChecked())
|
||||
{
|
||||
editMacroButton(buttonNumber, btn);
|
||||
} else {
|
||||
runMacroButton(buttonNumber);
|
||||
}
|
||||
ui->textToSendEdit->setFocus();
|
||||
}
|
||||
|
||||
void cwSender::runMacroButton(int buttonNumber)
|
||||
{
|
||||
if(macroText[buttonNumber].isEmpty())
|
||||
return;
|
||||
QString outText;
|
||||
if(macroText[buttonNumber].contains("%1"))
|
||||
{
|
||||
outText = macroText[buttonNumber].arg(sequenceNumber, 3, 10, QChar('0'));
|
||||
sequenceNumber++;
|
||||
ui->sequenceSpin->blockSignals(true);
|
||||
QMetaObject::invokeMethod(ui->sequenceSpin, "setValue", Qt::QueuedConnection, Q_ARG(int, sequenceNumber));
|
||||
ui->sequenceSpin->blockSignals(false);
|
||||
} else {
|
||||
outText = macroText[buttonNumber];
|
||||
}
|
||||
|
||||
if (ui->cutNumbersChk->isChecked())
|
||||
{
|
||||
outText.replace("0", "T");
|
||||
outText.replace("9", "N");
|
||||
}
|
||||
|
||||
ui->transcriptText->moveCursor(QTextCursor::End);
|
||||
ui->transcriptText->insertPlainText(outText.toUpper()+"\n");
|
||||
ui->transcriptText->moveCursor(QTextCursor::End);
|
||||
|
||||
for (int i = 0; i < outText.size(); i = i + 30) {
|
||||
emit sendCW(outText.mid(i,30));
|
||||
}
|
||||
|
||||
ui->textToSendEdit->setFocus();
|
||||
|
||||
|
||||
if( (currentMode==modeCW) || (currentMode==modeCW_R) )
|
||||
{
|
||||
ui->statusbar->showMessage(QString("Sending CW macro %1").arg(buttonNumber));
|
||||
} else {
|
||||
ui->statusbar->showMessage("Note: Mode needs to be set to CW or CW-R to send CW.");
|
||||
}
|
||||
}
|
||||
|
||||
void cwSender::editMacroButton(int buttonNumber, QPushButton* btn)
|
||||
{
|
||||
bool ok;
|
||||
QString promptFirst = QString("Please enter the text for macro %1,\n"
|
||||
"up to 60 characters.\n").arg(buttonNumber);
|
||||
QString promptSecond = QString("You may use \"%1\" to insert a sequence number.");
|
||||
QString prompt = promptFirst+promptSecond;
|
||||
|
||||
QString newMacroText = QInputDialog::getText(this, "Macro Edit",
|
||||
prompt,
|
||||
QLineEdit::Normal, macroText[buttonNumber], &ok).toUpper();
|
||||
if(!ok)
|
||||
return;
|
||||
|
||||
if (newMacroText.length() > 60)
|
||||
{
|
||||
QMessageBox msgBox;
|
||||
msgBox.setText(QString("The text entered was too long \n"
|
||||
"(max length is 60 characters).\n"
|
||||
"Your input was %1 characters.").arg(newMacroText.length()));
|
||||
msgBox.exec();
|
||||
this->raise();
|
||||
return;
|
||||
}
|
||||
|
||||
macroText[buttonNumber] = newMacroText;
|
||||
setMacroButtonText(newMacroText, btn);
|
||||
}
|
||||
|
||||
void cwSender::setMacroButtonText(QString btnText, QPushButton *btn)
|
||||
{
|
||||
if(btn==Q_NULLPTR)
|
||||
return;
|
||||
if(btnText.isEmpty())
|
||||
return;
|
||||
|
||||
QString shortBtnName;
|
||||
if(btnText.length() <= 8)
|
||||
{
|
||||
shortBtnName = btnText;
|
||||
} else {
|
||||
shortBtnName = btnText.left(7);
|
||||
shortBtnName.append("…");
|
||||
}
|
||||
btn->setText(shortBtnName);
|
||||
}
|
||||
|
||||
void cwSender::on_sequenceSpin_valueChanged(int newSeq)
|
||||
{
|
||||
sequenceNumber = newSeq;
|
||||
ui->textToSendEdit->setFocus();
|
||||
}
|
||||
|
||||
bool cwSender::getCutNumbers()
|
||||
{
|
||||
return ui->cutNumbersChk->isChecked();
|
||||
}
|
||||
|
||||
bool cwSender::getSendImmediate()
|
||||
{
|
||||
return ui->sendImmediateChk->isChecked();
|
||||
}
|
||||
|
||||
bool cwSender::getSidetoneEnable()
|
||||
{
|
||||
return ui->sidetoneEnableChk->isChecked();
|
||||
}
|
||||
|
||||
int cwSender::getSidetoneLevel()
|
||||
{
|
||||
return ui->sidetoneLevelSlider->value();
|
||||
}
|
||||
|
||||
void cwSender::setCutNumbers(bool val)
|
||||
{
|
||||
ui->cutNumbersChk->setChecked(val);
|
||||
}
|
||||
|
||||
void cwSender::setSendImmediate(bool val)
|
||||
{
|
||||
ui->sendImmediateChk->setChecked(val);
|
||||
}
|
||||
|
||||
void cwSender::setSidetoneEnable(bool val)
|
||||
{
|
||||
ui->sidetoneEnableChk->setChecked(val);
|
||||
on_sidetoneEnableChk_clicked(val);
|
||||
}
|
||||
|
||||
void cwSender::setSidetoneLevel(int val)
|
||||
{
|
||||
QMetaObject::invokeMethod(ui->sidetoneLevelSlider, "setValue", Qt::QueuedConnection, Q_ARG(int, val));
|
||||
}
|
||||
|
||||
QStringList cwSender::getMacroText()
|
||||
{
|
||||
// This is for preference saving:
|
||||
QStringList mlist;
|
||||
for(int i=1; i < 11; i++)
|
||||
{
|
||||
mlist << macroText[i];
|
||||
}
|
||||
return mlist;
|
||||
}
|
||||
|
||||
void cwSender::setMacroText(QStringList macros)
|
||||
{
|
||||
if(macros.length() != 10)
|
||||
{
|
||||
qCritical(logCW()) << "Macro list must be exactly 10. Rejecting macro text load.";
|
||||
return;
|
||||
}
|
||||
|
||||
for(int i=0; i < 10; i++)
|
||||
{
|
||||
macroText[i+1] = macros.at(i);
|
||||
}
|
||||
|
||||
setMacroButtonText(macroText[1], ui->macro1btn);
|
||||
setMacroButtonText(macroText[2], ui->macro2btn);
|
||||
setMacroButtonText(macroText[3], ui->macro3btn);
|
||||
setMacroButtonText(macroText[4], ui->macro4btn);
|
||||
setMacroButtonText(macroText[5], ui->macro5btn);
|
||||
setMacroButtonText(macroText[6], ui->macro6btn);
|
||||
setMacroButtonText(macroText[7], ui->macro7btn);
|
||||
setMacroButtonText(macroText[8], ui->macro8btn);
|
||||
setMacroButtonText(macroText[9], ui->macro9btn);
|
||||
setMacroButtonText(macroText[10], ui->macro10btn);
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
#ifndef CWSENDER_H
|
||||
#define CWSENDER_H
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QString>
|
||||
#include <QFont>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QThread>
|
||||
#include <QList>
|
||||
#include <math.h>
|
||||
#include "cwsidetone.h"
|
||||
#include "wfviewtypes.h"
|
||||
|
||||
namespace Ui {
|
||||
class cwSender;
|
||||
}
|
||||
|
||||
class cwSender : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit cwSender(QWidget *parent = 0);
|
||||
~cwSender();
|
||||
QStringList getMacroText();
|
||||
void setMacroText(QStringList macros);
|
||||
void setCutNumbers(bool val);
|
||||
void setSendImmediate(bool val);
|
||||
void setSidetoneEnable(bool val);
|
||||
void setSidetoneLevel(int val);
|
||||
bool getCutNumbers();
|
||||
bool getSendImmediate();
|
||||
bool getSidetoneEnable();
|
||||
int getSidetoneLevel();
|
||||
|
||||
signals:
|
||||
void sendCW(QString cwMessage);
|
||||
void stopCW();
|
||||
void setKeySpeed(unsigned char wpm);
|
||||
void setDashRatio(unsigned char ratio);
|
||||
void setPitch(unsigned char pitch);
|
||||
void setLevel(int level);
|
||||
void setBreakInMode(unsigned char b);
|
||||
void getCWSettings();
|
||||
void sidetone(QString text);
|
||||
void pitchChanged(int val);
|
||||
void dashChanged(int val);
|
||||
void wpmChanged(int val);
|
||||
|
||||
public slots:
|
||||
void handleKeySpeed(unsigned char wpm);
|
||||
void handleDashRatio(unsigned char ratio);
|
||||
void handlePitch(unsigned char pitch);
|
||||
void handleBreakInMode(unsigned char b);
|
||||
void handleCurrentModeUpdate(mode_kind mode);
|
||||
|
||||
private slots:
|
||||
void on_sendBtn_clicked();
|
||||
void showEvent(QShowEvent* event);
|
||||
|
||||
void on_stopBtn_clicked();
|
||||
|
||||
//void on_textToSendEdit_returnPressed();
|
||||
|
||||
void textChanged(QString text);
|
||||
|
||||
void on_breakinCombo_activated(int index);
|
||||
|
||||
void on_wpmSpin_valueChanged(int arg1);
|
||||
|
||||
void on_dashSpin_valueChanged(double arg1);
|
||||
|
||||
void on_pitchSpin_valueChanged(int arg1);
|
||||
|
||||
void on_macro1btn_clicked();
|
||||
|
||||
void on_macro2btn_clicked();
|
||||
|
||||
void on_macro3btn_clicked();
|
||||
|
||||
void on_macro4btn_clicked();
|
||||
|
||||
void on_macro5btn_clicked();
|
||||
|
||||
void on_macro6btn_clicked();
|
||||
|
||||
void on_macro7btn_clicked();
|
||||
|
||||
void on_macro8btn_clicked();
|
||||
|
||||
void on_macro9btn_clicked();
|
||||
|
||||
void on_macro10btn_clicked();
|
||||
|
||||
void on_sequenceSpin_valueChanged(int arg1);
|
||||
|
||||
void on_sidetoneEnableChk_clicked(bool clicked);
|
||||
|
||||
void on_sidetoneLevelSlider_valueChanged(int val);
|
||||
|
||||
private:
|
||||
Ui::cwSender *ui;
|
||||
QString macroText[11];
|
||||
int sequenceNumber = 1;
|
||||
int lastSentPos = 0;
|
||||
mode_kind currentMode;
|
||||
int sidetoneLevel=0;
|
||||
void processMacroButton(int buttonNumber, QPushButton *btn);
|
||||
void runMacroButton(int buttonNumber);
|
||||
void editMacroButton(int buttonNumber, QPushButton *btn);
|
||||
void setMacroButtonText(QString btnText, QPushButton *btn);
|
||||
cwSidetone* tone=Q_NULLPTR;
|
||||
QThread* toneThread = Q_NULLPTR;
|
||||
bool sidetoneWasEnabled=false;
|
||||
QList<QMetaObject::Connection> connections;
|
||||
};
|
||||
|
||||
#endif // CWSENDER_H
|
|
@ -0,0 +1,468 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>cwSender</class>
|
||||
<widget class="QMainWindow" name="cwSender">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>835</width>
|
||||
<height>451</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Send the text in the edit box</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="4" column="0" colspan="12">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Macros</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="4">
|
||||
<widget class="QPushButton" name="macro5btn">
|
||||
<property name="toolTip">
|
||||
<string>Macro Access Button</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Macro 5</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="macro2btn">
|
||||
<property name="toolTip">
|
||||
<string>Macro Access Button</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Macro 2</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QPushButton" name="macro4btn">
|
||||
<property name="toolTip">
|
||||
<string>Macro Access Button</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Macro 4</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="5">
|
||||
<widget class="QCheckBox" name="macroEditChk">
|
||||
<property name="toolTip">
|
||||
<string>Check this box to enter edit mode, where you can then press the macro buttons to edit the macros.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Edit Mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="macro7btn">
|
||||
<property name="toolTip">
|
||||
<string>Macro Access Button</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Macro 7</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="macro3btn">
|
||||
<property name="toolTip">
|
||||
<string>Macro Access Button</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Macro 3</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="macro6btn">
|
||||
<property name="toolTip">
|
||||
<string>Macro Access Button</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Macro 6</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<widget class="QPushButton" name="macro10btn">
|
||||
<property name="toolTip">
|
||||
<string>Macro Access Button</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Macro 10</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="macro9btn">
|
||||
<property name="toolTip">
|
||||
<string>Macro Access Button</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Macro 9</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="macro8btn">
|
||||
<property name="toolTip">
|
||||
<string>Macro Access Button</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Macro 8</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QPushButton" name="macro1btn">
|
||||
<property name="toolTip">
|
||||
<string>Macro Access Button</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Macro 1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="5">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Seq</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="sequenceSpin">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Sequence number, for contests. </p><p>Substitute &quot;%1&quot; in your macro text to use it. </p></body></html></string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>99999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="12">
|
||||
<widget class="QPlainTextEdit" name="transcriptText">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>CW Transmission Transcript</string>
|
||||
</property>
|
||||
<property name="undoRedoEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QPushButton" name="stopBtn">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Stop sending CW</string>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Stop</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Local Sidetone Level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="sidetoneLevelSlider">
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="sidetoneEnableChk">
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="sendBtn">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Send</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Return</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="textToSendEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Type here to send text as CW</string>
|
||||
</property>
|
||||
<property name="inputMethodHints">
|
||||
<set>Qt::ImhUppercaseOnly</set>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="currentText">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="insertPolicy">
|
||||
<enum>QComboBox::InsertAtTop</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="3" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinimumSize</enum>
|
||||
</property>
|
||||
<item row="3" column="0">
|
||||
<widget class="QComboBox" name="breakinCombo">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Set the desired break-in mode:</p><p>1. None: You must manually key and unkey the radio.</p><p>2. Semi: Transmit is automatic and switches to receive at the end of the text.</p><p>3. Full: Same as semi, but with breaks between characters when possible.</p></body></html></string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Off</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Semi</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Full</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>PITCH (Hz):</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>WPM:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QSpinBox" name="pitchSpin">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>300</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>900</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>600</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Break In</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="3">
|
||||
<widget class="QDoubleSpinBox" name="dashSpin">
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>2.800000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>4.500000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>3.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Dash Ratio</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="wpmSpin">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Set the Words Per Minute</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>48</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>20</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<widget class="QCheckBox" name="cutNumbersChk">
|
||||
<property name="text">
|
||||
<string>Cut Num</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="4">
|
||||
<widget class="QCheckBox" name="sendImmediateChk">
|
||||
<property name="text">
|
||||
<string>Send Immed</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -0,0 +1,308 @@
|
|||
#include "cwsidetone.h"
|
||||
|
||||
#include "logcategories.h"
|
||||
|
||||
cwSidetone::cwSidetone(int level, int speed, int freq, double ratio, QWidget* parent) :
|
||||
parent(parent),
|
||||
volume(level),
|
||||
speed(speed),
|
||||
frequency(freq),
|
||||
ratio(ratio)
|
||||
{
|
||||
|
||||
/*
|
||||
* Characters to match Icom table
|
||||
* Unknown characters will return '?'
|
||||
*/
|
||||
cwTable.clear();
|
||||
cwTable['0'] = "-----";
|
||||
cwTable['1'] = ".----";
|
||||
cwTable['2'] = "..---";
|
||||
cwTable['3'] = "...--";
|
||||
cwTable['4'] = "....-";
|
||||
cwTable['5'] = ".....";
|
||||
cwTable['6'] = "-....";
|
||||
cwTable['7'] = "--...";
|
||||
cwTable['8'] = "---..";
|
||||
cwTable['9'] = "----.";
|
||||
|
||||
cwTable['A'] = ".-";
|
||||
cwTable['B'] = "-...";
|
||||
cwTable['C'] = "-.-.";
|
||||
cwTable['D'] = "-..";
|
||||
cwTable['E'] = ".";
|
||||
cwTable['F'] = "..-.";
|
||||
cwTable['G'] = "--.";
|
||||
cwTable['H'] = "....";
|
||||
cwTable['I'] = "..";
|
||||
cwTable['J'] = ".---";
|
||||
cwTable['K'] = "-.-";
|
||||
cwTable['L'] = ".-..";
|
||||
cwTable['M'] = "--";
|
||||
cwTable['N'] = "-.";
|
||||
cwTable['O'] = "---";
|
||||
cwTable['P'] = ".--.";
|
||||
cwTable['Q'] = "--.-";
|
||||
cwTable['R'] = ".-.";
|
||||
cwTable['S'] = "...";
|
||||
cwTable['T'] = "-";
|
||||
cwTable['U'] = "..-";
|
||||
cwTable['V'] = "...-";
|
||||
cwTable['W'] = ".--";
|
||||
cwTable['X'] = "-..-";
|
||||
cwTable['Y'] = "-.--";
|
||||
cwTable['Z'] = "--..";
|
||||
|
||||
cwTable['/'] = "-..-.";
|
||||
cwTable['?'] = "..--..";
|
||||
cwTable['.'] = ".-.-.-";
|
||||
cwTable['-'] = "-....-";
|
||||
cwTable[','] = "--..--";
|
||||
cwTable[':'] = "---...";
|
||||
cwTable['\''] = ".----.";
|
||||
cwTable['('] = "-.--.-";
|
||||
cwTable[')'] = "-.--.-";
|
||||
cwTable['='] = "-...-";
|
||||
cwTable['+'] = ".-.-.";
|
||||
cwTable['"'] = ".-..-.";
|
||||
|
||||
cwTable[' '] = " ";
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
cwSidetone::~cwSidetone()
|
||||
{
|
||||
qInfo(logCW()) << "cwSidetone() finished";
|
||||
this->stop();
|
||||
output->stop();
|
||||
}
|
||||
|
||||
void cwSidetone::init()
|
||||
{
|
||||
format.setSampleRate(44100);
|
||||
format.setChannelCount(1);
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
format.setCodec("audio/pcm");
|
||||
format.setSampleSize(16);
|
||||
format.setByteOrder(QAudioFormat::LittleEndian);
|
||||
format.setSampleType(QAudioFormat::SignedInt);
|
||||
QAudioDeviceInfo device(QAudioDeviceInfo::defaultOutputDevice());
|
||||
#else
|
||||
format.setSampleFormat(QAudioFormat::Int16);
|
||||
QAudioDevice device = QMediaDevices::defaultAudioOutput();
|
||||
#endif
|
||||
if (!device.isNull()) {
|
||||
if (!device.isFormatSupported(format)) {
|
||||
qWarning(logCW()) << "Default format not supported, using preferred";
|
||||
format = device.preferredFormat();
|
||||
}
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
output.reset(new QAudioOutput(device,format));
|
||||
#else
|
||||
output.reset(new QAudioSink(device,format));
|
||||
#endif
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
qInfo(logCW()) << QString("Sidetone Output: %0 (volume: %1 rate: %2 size: %3 type: %4)")
|
||||
.arg(device.deviceName()).arg(volume).arg(format.sampleRate()).arg(format.sampleSize()).arg(format.sampleType());
|
||||
#else
|
||||
qInfo(logCW()) << QString("Sidetone Output: %0 (volume: %1 rate: %2 type: %3")
|
||||
.arg(device.description()).arg(volume).arg(format.sampleRate()).arg(format.sampleFormat());
|
||||
#endif
|
||||
}
|
||||
this->start(); // Start QIODevice
|
||||
}
|
||||
|
||||
void cwSidetone::send(QString text)
|
||||
{
|
||||
text=text.simplified();
|
||||
|
||||
for (int pos=0; pos < text.size(); pos++)
|
||||
{
|
||||
QChar ch = text.at(pos).toUpper();
|
||||
QString currentChar;
|
||||
if (ch == NULL)
|
||||
{
|
||||
currentChar = cwTable[' '];
|
||||
}
|
||||
else if (this->cwTable.contains(ch))
|
||||
{
|
||||
currentChar = cwTable[ch];
|
||||
}
|
||||
else
|
||||
{
|
||||
currentChar=cwTable['?'];
|
||||
}
|
||||
generateMorse(currentChar);
|
||||
}
|
||||
|
||||
if (output->state() == QAudio::StoppedState)
|
||||
{
|
||||
output->start(this);
|
||||
}
|
||||
else if (output->state() == QAudio::IdleState) {
|
||||
output->suspend();
|
||||
output->resume();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void cwSidetone::start()
|
||||
{
|
||||
open(QIODevice::ReadOnly);
|
||||
}
|
||||
|
||||
void cwSidetone::stop()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
qint64 cwSidetone::readData(char *data, qint64 len)
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
const qint64 total = qMin(((qint64)buffer.size()), len);
|
||||
memcpy(data, buffer.constData(), total);
|
||||
buffer.remove(0,total);
|
||||
if (buffer.size() == 0) {
|
||||
emit finished();
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
qint64 cwSidetone::writeData(const char *data, qint64 len)
|
||||
{
|
||||
Q_UNUSED(data);
|
||||
Q_UNUSED(len);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
qint64 cwSidetone::bytesAvailable() const
|
||||
{
|
||||
return buffer.size() + QIODevice::bytesAvailable();
|
||||
}
|
||||
|
||||
void cwSidetone::generateMorse(QString morse)
|
||||
{
|
||||
|
||||
int dit = int(double(SIDETONE_MULTIPLIER / this->speed * SIDETONE_MULTIPLIER));
|
||||
int dah = int(double(dit * this->ratio));
|
||||
|
||||
|
||||
QMutexLocker locker(&mutex);
|
||||
for (int i=0;i<morse.size();i++)
|
||||
{
|
||||
QChar c = morse.at(i);
|
||||
if (c == '-')
|
||||
{
|
||||
buffer.append(generateData(dah,this->frequency));
|
||||
}
|
||||
else if (c == '.')
|
||||
{
|
||||
buffer.append(generateData(dit,this->frequency));
|
||||
}
|
||||
else // Space char
|
||||
{
|
||||
buffer.append(generateData(dit,0));
|
||||
}
|
||||
|
||||
if (i<morse.size()-1)
|
||||
{
|
||||
buffer.append(generateData(dit,0));
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.append(generateData(dit*3,0)); // inter-character space
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray cwSidetone::generateData(qint64 len, qint64 freq)
|
||||
{
|
||||
QByteArray data;
|
||||
|
||||
const int channels = format.channelCount();
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
const int channelBytes = format.sampleSize() / 8;
|
||||
#else
|
||||
const int channelBytes = format.bytesPerSample();
|
||||
#endif
|
||||
|
||||
const int sampleRate = format.sampleRate();
|
||||
//qint64 length = (sampleRate * channels * channelBytes) * len / 100000;
|
||||
qint64 length = format.bytesForDuration(len);
|
||||
const int sampleBytes = channels * channelBytes;
|
||||
|
||||
length -= length % sampleBytes;
|
||||
Q_UNUSED(sampleBytes) // suppress warning in release builds
|
||||
|
||||
data.resize(length);
|
||||
unsigned char *ptr = reinterpret_cast<unsigned char *>(data.data());
|
||||
int sampleIndex = 0;
|
||||
|
||||
while (length) {
|
||||
const qreal x = (qSin(2 * M_PI * freq * qreal(sampleIndex % sampleRate) / sampleRate)) * qreal(volume/100.0);
|
||||
for (int i=0; i<channels; ++i) {
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
if (format.sampleSize() == 8 && format.sampleType() == QAudioFormat::UnSignedInt)
|
||||
*reinterpret_cast<quint8 *>(ptr) = static_cast<quint8>((1.0 + x) / 2 * 255);
|
||||
else if (format.sampleSize() == 16 && format.sampleType() == QAudioFormat::SignedInt)
|
||||
*reinterpret_cast<qint16 *>(ptr) = static_cast<qint16>(x * std::numeric_limits<qint16>::max());
|
||||
else if (format.sampleSize() == 32 && format.sampleType() == QAudioFormat::SignedInt)
|
||||
*reinterpret_cast<qint32 *>(ptr) = static_cast<qint32>(x * std::numeric_limits<qint32>::max());
|
||||
else if (format.sampleType() == QAudioFormat::Float)
|
||||
*reinterpret_cast<float *>(ptr) = x;
|
||||
else
|
||||
qWarning(logCW()) << QString("Unsupported sample size: %0 type: %1").arg(format.sampleSize()).arg(format.sampleType());
|
||||
#else
|
||||
if (format.sampleFormat() == QAudioFormat::UInt8)
|
||||
*reinterpret_cast<quint8 *>(ptr) = static_cast<quint8>((1.0 + x) / 2 * 255);
|
||||
else if (format.sampleFormat() == QAudioFormat::Int16)
|
||||
*reinterpret_cast<qint16 *>(ptr) = static_cast<qint16>(x * std::numeric_limits<qint16>::max());
|
||||
else if (format.sampleFormat() == QAudioFormat::Int32)
|
||||
*reinterpret_cast<qint32 *>(ptr) = static_cast<qint32>(x * std::numeric_limits<qint32>::max());
|
||||
else if (format.sampleFormat() == QAudioFormat::Float)
|
||||
*reinterpret_cast<float *>(ptr) = x;
|
||||
else
|
||||
qWarning(logCW()) << QString("Unsupported sample format: %0").arg(format.sampleFormat());
|
||||
#endif
|
||||
|
||||
ptr += channelBytes;
|
||||
length -= channelBytes;
|
||||
}
|
||||
++sampleIndex;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void cwSidetone::setSpeed(unsigned char speed)
|
||||
{
|
||||
this->speed = (int)speed;
|
||||
}
|
||||
|
||||
void cwSidetone::setFrequency(unsigned char frequency)
|
||||
{
|
||||
this->frequency = round((((600.0 / 255.0) * frequency) + 300) / 5.0) * 5.0;
|
||||
}
|
||||
|
||||
void cwSidetone::setRatio(unsigned char ratio)
|
||||
{
|
||||
this->ratio = (double)ratio/10.0;
|
||||
}
|
||||
|
||||
void cwSidetone::setLevel(int level) {
|
||||
volume = level;
|
||||
}
|
||||
|
||||
void cwSidetone::stopSending() {
|
||||
QMutexLocker locker(&mutex);
|
||||
buffer.clear();
|
||||
emit finished();
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef CWSIDETONE_H
|
||||
#define CWSIDETONE_H
|
||||
|
||||
#include <QApplication>
|
||||
#include <QAudioOutput>
|
||||
#include <QMap>
|
||||
#include <QScopedPointer>
|
||||
#include <QtMath>
|
||||
#include <QMutex>
|
||||
#include <QMutexLocker>
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
#include <QAudioDeviceInfo>
|
||||
#include <QAudioOutput>
|
||||
#else
|
||||
#include <QAudioDevice>
|
||||
#include <QAudioSink>
|
||||
#include <QMediaDevices>
|
||||
#endif
|
||||
|
||||
//#define SIDETONE_MULTIPLIER 386.0
|
||||
#define SIDETONE_MULTIPLIER 1095.46
|
||||
|
||||
class cwSidetone : public QIODevice
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit cwSidetone(int level, int speed, int freq, double ratio, QWidget *parent = 0);
|
||||
~cwSidetone();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
qint64 readData(char *data, qint64 maxlen) override;
|
||||
qint64 writeData(const char *data, qint64 len) override;
|
||||
qint64 bytesAvailable() const override;
|
||||
qint64 size() const override { return buffer.size(); }
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
public slots:
|
||||
void send(QString text);
|
||||
void setSpeed(unsigned char speed);
|
||||
void setFrequency(unsigned char frequency);
|
||||
void setRatio(unsigned char ratio);
|
||||
void setLevel(int level);
|
||||
void stopSending();
|
||||
private:
|
||||
void init();
|
||||
|
||||
void generateMorse(QString morse);
|
||||
QByteArray generateData(qint64 len, qint64 freq);
|
||||
QByteArray buffer;
|
||||
QMap< QChar, QString> cwTable;
|
||||
QWidget* parent;
|
||||
int volume;
|
||||
int speed;
|
||||
int frequency;
|
||||
double ratio;
|
||||
QAudioFormat format;
|
||||
QMutex mutex;
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
QScopedPointer<QAudioOutput> output;
|
||||
#else
|
||||
QScopedPointer<QAudioSink> output;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // CWSIDETONE_H
|
|
@ -0,0 +1,107 @@
|
|||
#ifdef USESQL
|
||||
|
||||
#include "database.h"
|
||||
#include "logcategories.h"
|
||||
|
||||
database::database()
|
||||
{
|
||||
open();
|
||||
}
|
||||
|
||||
database::~database()
|
||||
{
|
||||
}
|
||||
|
||||
bool database::open()
|
||||
{
|
||||
auto name = "my_db_" + QString::number((quint64)QThread::currentThread(), 16);
|
||||
if (QSqlDatabase::contains(name))
|
||||
{
|
||||
db = QSqlDatabase::database(name);
|
||||
qu = QSqlQuery(db);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
qInfo(logCluster()) << "Creating new connection" << name;
|
||||
db = QSqlDatabase::addDatabase("QSQLITE", name);
|
||||
qu = QSqlQuery(db);
|
||||
}
|
||||
|
||||
//QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/" + "wfview.db";
|
||||
QString path = ":memory:";
|
||||
qInfo(logCluster()) << "DB Filename" << path;
|
||||
db.setDatabaseName(path);
|
||||
if (db.isValid())
|
||||
{
|
||||
db.open();
|
||||
if (check()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
qWarning(logCluster()) << "Database is not valid!";
|
||||
return false;
|
||||
}
|
||||
|
||||
void database::close()
|
||||
{
|
||||
auto name = "my_db_" + QString::number((quint64)QThread::currentThread(), 16);
|
||||
qInfo(logCluster()) << "Closing database connection:" << name;
|
||||
db.close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
QSqlQuery database::query(QString query)
|
||||
{
|
||||
if (!db.isOpen())
|
||||
{
|
||||
qWarning(logCluster()) << "Query Database is not open!";
|
||||
db.open();
|
||||
}
|
||||
qu.exec(query);
|
||||
return qu;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool database::check()
|
||||
{
|
||||
if (db.isOpen()) {
|
||||
for (const auto& table : db.tables())
|
||||
{
|
||||
if (table == "spots")
|
||||
{
|
||||
qInfo(logCluster()) << "DB Contains spots table";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
qInfo(logCluster()) << "Creating spots table";
|
||||
// Spots table does not exist, need to create it.
|
||||
/*
|
||||
QString dxcall;
|
||||
double frequency;
|
||||
QString spottercall;
|
||||
QDateTime timestamp;
|
||||
QString mode;
|
||||
QString comment;
|
||||
QCPItemText* text = Q_NULLPTR;*/
|
||||
qu.exec("CREATE TABLE spots "
|
||||
"(id INTEGER PRIMARY KEY, "
|
||||
"type VARCHAR(3),"
|
||||
"dxcall VARCHAR(30),"
|
||||
"spottercall VARCHAR(30),"
|
||||
"frequency DOUBLE,"
|
||||
"timestamp DATETIME,"
|
||||
"mode VARCHAR(30),"
|
||||
"comment VARCHAR(255) )");
|
||||
qu.exec("CREATE INDEX spots_index ON spots(type,dxcall,frequency,timestamp)");
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
qWarning(logCluster()) << "Database is not open";
|
||||
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,36 @@
|
|||
#ifdef USESQL
|
||||
|
||||
#ifndef DATABASE_H
|
||||
#define DATABASE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
#include <QSqlDatabase>
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlRecord>
|
||||
#include <QThread>
|
||||
#include <QStandardPaths>
|
||||
|
||||
class database
|
||||
{
|
||||
public:
|
||||
explicit database();
|
||||
virtual ~database();
|
||||
bool open();
|
||||
void close();
|
||||
QSqlQuery query(QString query);
|
||||
|
||||
signals:
|
||||
|
||||
public slots:
|
||||
private:
|
||||
bool check();
|
||||
QSqlDatabase db;
|
||||
QSqlQuery qu;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -1,7 +1,7 @@
|
|||
#include "freqmemory.h"
|
||||
#include "logcategories.h"
|
||||
|
||||
// Copytight 2017-2020 Elliott H. Liggett
|
||||
// Copyright 2017-2020 Elliott H. Liggett
|
||||
|
||||
freqMemory::freqMemory()
|
||||
{
|
||||
|
|
39
freqmemory.h
|
@ -2,44 +2,7 @@
|
|||
#define FREQMEMORY_H
|
||||
#include <QString>
|
||||
#include <QDebug>
|
||||
|
||||
// 0 1 2 3 4
|
||||
//modes << "LSB" << "USB" << "AM" << "CW" << "RTTY";
|
||||
// 5 6 7 8 9
|
||||
// modes << "FM" << "CW-R" << "RTTY-R" << "LSB-D" << "USB-D";
|
||||
|
||||
enum mode_kind {
|
||||
modeLSB=0x00,
|
||||
modeUSB=0x01,
|
||||
modeAM=0x02,
|
||||
modeCW=0x03,
|
||||
modeRTTY=0x04,
|
||||
modeFM=0x05,
|
||||
modeCW_R=0x07,
|
||||
modeRTTY_R=0x08,
|
||||
modeLSB_D=0x80,
|
||||
modeUSB_D=0x81,
|
||||
modeDV=0x17,
|
||||
modeDD=0x27,
|
||||
modeWFM,
|
||||
modeS_AMD,
|
||||
modeS_AML,
|
||||
modeS_AMU,
|
||||
modeP25,
|
||||
modedPMR,
|
||||
modeNXDN_VN,
|
||||
modeNXDN_N,
|
||||
modeDCR,
|
||||
modePSK,
|
||||
modePSK_R
|
||||
};
|
||||
|
||||
struct mode_info {
|
||||
mode_kind mk;
|
||||
unsigned char reg;
|
||||
unsigned char filter;
|
||||
QString name;
|
||||
};
|
||||
#include "wfviewtypes.h"
|
||||
|
||||
struct preset_kind {
|
||||
// QString name;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
#include <QCoreApplication>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include "keyboard.h"
|
||||
|
||||
keyboard::keyboard(void)
|
||||
{
|
||||
}
|
||||
|
||||
keyboard::~keyboard(void)
|
||||
{
|
||||
}
|
||||
|
||||
void keyboard::run()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
char key = getchar();
|
||||
if (key == 'q') {
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
#include <QCoreApplication>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
class keyboard : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
keyboard(void);
|
||||
~keyboard(void);
|
||||
void run();
|
||||
};
|
|
@ -3,8 +3,16 @@
|
|||
Q_LOGGING_CATEGORY(logSystem, "system")
|
||||
Q_LOGGING_CATEGORY(logSerial, "serial")
|
||||
Q_LOGGING_CATEGORY(logGui, "gui")
|
||||
Q_LOGGING_CATEGORY(logLogger, "log")
|
||||
Q_LOGGING_CATEGORY(logUser, "user")
|
||||
Q_LOGGING_CATEGORY(logRig, "rig")
|
||||
Q_LOGGING_CATEGORY(logRigTraffic, "rigTraffic")
|
||||
Q_LOGGING_CATEGORY(logCW, "cw")
|
||||
Q_LOGGING_CATEGORY(logAudio, "audio")
|
||||
Q_LOGGING_CATEGORY(logUdp, "udp")
|
||||
Q_LOGGING_CATEGORY(logUdpServer, "udp.server")
|
||||
Q_LOGGING_CATEGORY(logRigCtlD, "rigctld")
|
||||
Q_LOGGING_CATEGORY(logTcpServer, "tcpserver")
|
||||
Q_LOGGING_CATEGORY(logUsbControl, "usbcontrol")
|
||||
Q_LOGGING_CATEGORY(logAudioConverter, "audioconverter")
|
||||
Q_LOGGING_CATEGORY(logCluster, "cluster")
|
||||
|
|
|
@ -6,15 +6,28 @@
|
|||
Q_DECLARE_LOGGING_CATEGORY(logSystem)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logSerial)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logGui)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logLogger)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logUser)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logRig)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logRigTraffic)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logCW)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logAudio)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logUdp)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logUdpServer)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logRigCtlD)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logTcpServer)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logUsbControl)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logAudioConverter)
|
||||
Q_DECLARE_LOGGING_CATEGORY(logCluster)
|
||||
|
||||
|
||||
#if defined(Q_OS_WIN) && !defined(__PRETTY_FUNCTION__)
|
||||
#define __PRETTY_FUNCTION__ __FUNCSIG__
|
||||
#endif
|
||||
|
||||
#if QT_VERSION > 0x060000
|
||||
//#define hex Qt::hex
|
||||
//#define bin Qt::bin
|
||||
#endif
|
||||
|
||||
#endif // LOGCATEGORIES_H
|
||||
|
|
|
@ -0,0 +1,214 @@
|
|||
#include "loggingwindow.h"
|
||||
#include "ui_loggingwindow.h"
|
||||
|
||||
loggingWindow::loggingWindow(QString logFilename, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::loggingWindow),
|
||||
logFilename(logFilename)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
this->setWindowTitle("Log");
|
||||
ui->logTextDisplay->setReadOnly(true);
|
||||
ui->userAnnotationText->setFocus();
|
||||
ui->annotateBtn->setDefault(true);
|
||||
ui->logTextDisplay->setFocusPolicy(Qt::NoFocus);
|
||||
ui->annotateBtn->setFocusPolicy(Qt::NoFocus);
|
||||
|
||||
QDir d = QFileInfo(logFilename).absoluteDir();
|
||||
logDirectory = d.absolutePath();
|
||||
|
||||
QFont font("Monospace");
|
||||
font.setStyleHint(QFont::TypeWriter);
|
||||
ui->logTextDisplay->setFont(font);
|
||||
ui->userAnnotationText->setFont(font);
|
||||
|
||||
clipboard = QApplication::clipboard();
|
||||
socket = new QTcpSocket(this);
|
||||
connect(socket, SIGNAL(connected()), this, SLOT(connectedToHost()));
|
||||
connect(socket, SIGNAL(disconnected()), this, SLOT(disconnectedFromHost()));
|
||||
connect(socket, SIGNAL(readyRead()), this, SLOT(handleDataFromLoggingHost()));
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
|
||||
connect(socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, SLOT(handleLoggingHostError(QAbstractSocket::SocketError)));
|
||||
#else
|
||||
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(handleLoggingHostError(QAbstractSocket::SocketError)));
|
||||
#endif
|
||||
vertLogScroll = ui->logTextDisplay->verticalScrollBar();
|
||||
horizLogScroll = ui->logTextDisplay->horizontalScrollBar();
|
||||
|
||||
vertLogScroll->setValue(vertLogScroll->maximum());
|
||||
horizLogScroll->setValue(horizLogScroll->minimum());
|
||||
}
|
||||
|
||||
loggingWindow::~loggingWindow()
|
||||
{
|
||||
QMutexLocker lock(&textMutex);
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void loggingWindow::showEvent(QShowEvent *event)
|
||||
{
|
||||
(void)event;
|
||||
on_toBottomBtn_clicked();
|
||||
}
|
||||
|
||||
void loggingWindow::setInitialDebugState(bool debugModeEnabled)
|
||||
{
|
||||
ui->debugBtn->blockSignals(true);
|
||||
ui->debugBtn->setChecked(debugModeEnabled);
|
||||
ui->debugBtn->blockSignals(false);
|
||||
}
|
||||
|
||||
void loggingWindow::acceptLogText(QString text)
|
||||
{
|
||||
QMutexLocker lock(&textMutex);
|
||||
ui->logTextDisplay->appendPlainText(text);
|
||||
if(vertLogScroll->value() == vertLogScroll->maximum())
|
||||
{
|
||||
horizLogScroll->setValue(horizLogScroll->minimum());
|
||||
}
|
||||
}
|
||||
|
||||
void loggingWindow::sendToTermbin()
|
||||
{
|
||||
qInfo(logLogger()) << "Sending data to termbin.com. Standby.";
|
||||
socket->connectToHost("termbin.com", 9999);
|
||||
ui->sendToPasteBtn->setDisabled(true);
|
||||
}
|
||||
|
||||
void loggingWindow::handleDataFromLoggingHost()
|
||||
{
|
||||
qInfo(logLogger()) << "Receiving data from logging host.";
|
||||
QString URL;
|
||||
QByteArray data = socket->readAll();
|
||||
if(data.length() < 256)
|
||||
{
|
||||
URL = QString(data).trimmed();
|
||||
if(!URL.isEmpty())
|
||||
{
|
||||
clipboard->setText(URL);
|
||||
qInfo(logLogger()) << "Sent log to URL: " << URL;
|
||||
qInfo(logLogger()) << "This address already copied to the clipboard. Please paste this URL in to your support questions.";
|
||||
URLmsgBox.setText("Your log has been posted, and the URL has been copied to the clipboard.");
|
||||
URLmsgBox.setInformativeText("<b>" + URL + "</b>");
|
||||
URLmsgBox.exec();
|
||||
// For whatever reason, showing the message box hides https://termbin.com/ypxbthis window.
|
||||
this->show();
|
||||
this->raise();
|
||||
this->activateWindow();
|
||||
}
|
||||
} else {
|
||||
qDebug(logLogger()) << "Error, return from logging host too large. Received " << data.length() << " bytes.";
|
||||
}
|
||||
}
|
||||
|
||||
void loggingWindow::disconnectedFromHost()
|
||||
{
|
||||
qInfo(logLogger()) << "Disconnected from logging host";
|
||||
ui->sendToPasteBtn->setDisabled(false);
|
||||
}
|
||||
|
||||
void loggingWindow::connectedToHost()
|
||||
{
|
||||
qInfo(logLogger()) << "Connected to logging host";
|
||||
QMutexLocker lock(&textMutex);
|
||||
QTextStream outText(socket);
|
||||
outText << ui->logTextDisplay->toPlainText();
|
||||
outText << "\n----------\nSent from wfview version ";
|
||||
outText << WFVIEW_VERSION << "\n----------\n";
|
||||
outText.flush();
|
||||
}
|
||||
|
||||
void loggingWindow::handleLoggingHostError(QAbstractSocket::SocketError error)
|
||||
{
|
||||
switch(error)
|
||||
{
|
||||
case QAbstractSocket::RemoteHostClosedError:
|
||||
//qInfo(logLogger()) << "Disconnected from logging host.";
|
||||
break;
|
||||
|
||||
default:
|
||||
qWarning(logLogger()) << "Error connecting to logging host. Check internet connection. Error code: " << error;
|
||||
ui->sendToPasteBtn->setDisabled(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void loggingWindow::on_clearDisplayBtn_clicked()
|
||||
{
|
||||
QMutexLocker lock(&textMutex);
|
||||
// Only clears the displayed text, not the log file.
|
||||
ui->logTextDisplay->clear();
|
||||
}
|
||||
|
||||
void loggingWindow::on_openDirBtn_clicked()
|
||||
{
|
||||
QString cmd;
|
||||
bool rtn = false;
|
||||
QStringList arg;
|
||||
const QFileInfo dir(logDirectory);
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
cmd = "xdg-open";
|
||||
#elif defined(Q_OS_WIN)
|
||||
cmd = QStandardPaths::findExecutable("explorer.exe");
|
||||
if (!dir.isDir())
|
||||
arg += QLatin1String("/select,");
|
||||
#else
|
||||
cmd = "open";
|
||||
#endif
|
||||
arg += QDir::toNativeSeparators(dir.canonicalFilePath());;
|
||||
rtn = QProcess::startDetached(cmd, arg);
|
||||
if(!rtn)
|
||||
qInfo(logLogger()) << "Error, open log directory" << logDirectory << "command failed";
|
||||
}
|
||||
|
||||
void loggingWindow::on_openLogFileBtn_clicked()
|
||||
{
|
||||
QString cmd;
|
||||
bool rtn = false;
|
||||
#ifdef Q_OS_LINUX
|
||||
cmd = "xdg-open";
|
||||
#elif defined(Q_OS_WIN)
|
||||
cmd = QStandardPaths::findExecutable("notepad.exe");
|
||||
#else
|
||||
cmd = "open";
|
||||
#endif
|
||||
rtn = QProcess::startDetached(cmd, { logFilename });
|
||||
if(!rtn)
|
||||
qInfo(logLogger()) << "Error, open log file command failed";
|
||||
}
|
||||
|
||||
void loggingWindow::on_sendToPasteBtn_clicked()
|
||||
{
|
||||
sendToTermbin();
|
||||
}
|
||||
|
||||
void loggingWindow::on_annotateBtn_clicked()
|
||||
{
|
||||
QMutexLocker lock(&textMutex);
|
||||
if(ui->userAnnotationText->text().isEmpty())
|
||||
return;
|
||||
qInfo(logUser()) << ui->userAnnotationText->text();
|
||||
ui->userAnnotationText->clear();
|
||||
}
|
||||
|
||||
void loggingWindow::on_userAnnotationText_returnPressed()
|
||||
{
|
||||
on_annotateBtn_clicked();
|
||||
}
|
||||
|
||||
void loggingWindow::on_copyPathBtn_clicked()
|
||||
{
|
||||
clipboard->setText(logFilename);
|
||||
}
|
||||
|
||||
void loggingWindow::on_debugBtn_clicked(bool checked)
|
||||
{
|
||||
emit setDebugMode(checked);
|
||||
}
|
||||
|
||||
void loggingWindow::on_toBottomBtn_clicked()
|
||||
{
|
||||
vertLogScroll->setValue(vertLogScroll->maximum());
|
||||
horizLogScroll->setValue(horizLogScroll->minimum());
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
#ifndef LOGGINGWINDOW_H
|
||||
#define LOGGINGWINDOW_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QMutexLocker>
|
||||
#include <QMutex>
|
||||
#include <QStandardPaths>
|
||||
#include <QClipboard>
|
||||
#include <QTcpSocket>
|
||||
#include <QTextStream>
|
||||
#include <QMessageBox>
|
||||
#include <QScrollBar>
|
||||
#include <QProcess>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
|
||||
#include "logcategories.h"
|
||||
|
||||
namespace Ui {
|
||||
class loggingWindow;
|
||||
}
|
||||
|
||||
class loggingWindow : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit loggingWindow(QString logFilename, QWidget *parent = 0);
|
||||
~loggingWindow();
|
||||
void acceptLogText(QString text);
|
||||
|
||||
public slots:
|
||||
void setInitialDebugState(bool debugModeEnabled);
|
||||
|
||||
private slots:
|
||||
void connectedToHost();
|
||||
void disconnectedFromHost();
|
||||
void handleDataFromLoggingHost();
|
||||
void handleLoggingHostError(QAbstractSocket::SocketError);
|
||||
void showEvent(QShowEvent* event);
|
||||
void on_clearDisplayBtn_clicked();
|
||||
|
||||
void on_openDirBtn_clicked();
|
||||
|
||||
void on_openLogFileBtn_clicked();
|
||||
|
||||
void on_sendToPasteBtn_clicked();
|
||||
|
||||
void on_annotateBtn_clicked();
|
||||
|
||||
void on_userAnnotationText_returnPressed();
|
||||
|
||||
void on_copyPathBtn_clicked();
|
||||
|
||||
void on_debugBtn_clicked(bool checked);
|
||||
|
||||
void on_toBottomBtn_clicked();
|
||||
|
||||
signals:
|
||||
void setDebugMode(bool debugOn);
|
||||
|
||||
private:
|
||||
Ui::loggingWindow* ui;
|
||||
QString logFilename;
|
||||
QString logDirectory;
|
||||
QClipboard *clipboard;
|
||||
QMessageBox URLmsgBox;
|
||||
QScrollBar *vertLogScroll;
|
||||
QScrollBar *horizLogScroll;
|
||||
QMutex textMutex;
|
||||
QTcpSocket *socket;
|
||||
void sendToTermbin();
|
||||
};
|
||||
|
||||
#endif // LOGGINGWINDOW_H
|
|
@ -0,0 +1,191 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>loggingWindow</class>
|
||||
<widget class="QWidget" name="loggingWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>625</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="logTextDisplay">
|
||||
<property name="undoRedoEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="lineWrapMode">
|
||||
<enum>QPlainTextEdit::NoWrap</enum>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="annotateLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>75</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>75</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Annotation:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="userAnnotationText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>290</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>You may enter your own log notes here.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="annotateBtn">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>85</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>85</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Adds user-text to the log.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Annotate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="debugBtn">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Enable or disable debug logging. Use the &quot;-d&quot; or &quot;--debug&quot; flag to open wfview with debug logging enabled on startup. </p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Debug</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="toBottomBtn">
|
||||
<property name="toolTip">
|
||||
<string>Scroll to bottom</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Scroll Down</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="clearDisplayBtn">
|
||||
<property name="toolTip">
|
||||
<string>Clears the display. Does not clear the log file.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="openDirBtn">
|
||||
<property name="toolTip">
|
||||
<string>Makes a best-effort to ask the host system to open the log file directory.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Open Log Directory</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="openLogFileBtn">
|
||||
<property name="toolTip">
|
||||
<string>Makes a best-effort to ask the host system to open the logfile.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Open Log</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="copyPathBtn">
|
||||
<property name="toolTip">
|
||||
<string>Copy the path of the log file to your clipboard.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Copy Path</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="sendToPasteBtn">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Sends text to termbin.com. Some personal information (such as your username) is in the log file, so do not click this button unless you are ok sharing your log file. This is a quick way to receive a URL, pointing to your log file text, that you can send to other people. </p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Send to termbin.com</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
162
main.cpp
|
@ -1,77 +1,107 @@
|
|||
#ifdef BUILD_WFSERVER
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include "keyboard.h"
|
||||
#else
|
||||
#include <QApplication>
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#include <csignal>
|
||||
#endif
|
||||
|
||||
#include <iostream>
|
||||
#include "wfmain.h"
|
||||
|
||||
// Copyright 2017-2022 Elliott H. Liggett
|
||||
#include "logcategories.h"
|
||||
|
||||
// Copytight 2017-2021 Elliott H. Liggett
|
||||
|
||||
#ifdef BUILD_WFSERVER
|
||||
// Smart pointer to log file
|
||||
QScopedPointer<QFile> m_logFile;
|
||||
QMutex logMutex;
|
||||
#endif
|
||||
|
||||
bool debugMode=false;
|
||||
|
||||
#ifdef BUILD_WFSERVER
|
||||
servermain* w=Q_NULLPTR;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
bool __stdcall cleanup(DWORD sig)
|
||||
#else
|
||||
static void cleanup(int sig)
|
||||
#endif
|
||||
{
|
||||
Q_UNUSED(sig)
|
||||
qDebug() << "Exiting via SIGNAL";
|
||||
if (w!=Q_NULLPTR) w->deleteLater();
|
||||
QCoreApplication::quit();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
return true;
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg);
|
||||
#endif
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
//a.setStyle( "Fusion" );
|
||||
|
||||
#ifdef BUILD_WFSERVER
|
||||
QCoreApplication a(argc, argv);
|
||||
a.setOrganizationName("wfview");
|
||||
a.setOrganizationDomain("wfview.org");
|
||||
a.setApplicationName("wfserver");
|
||||
keyboard* kb = new keyboard();
|
||||
kb->start();
|
||||
#else
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QApplication a(argc, argv);
|
||||
a.setOrganizationName("wfview");
|
||||
a.setOrganizationDomain("wfview.org");
|
||||
a.setApplicationName("wfview");
|
||||
#endif
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
debugMode = true;
|
||||
//debugMode = true;
|
||||
#endif
|
||||
|
||||
QString serialPortCL;
|
||||
QString hostCL;
|
||||
QString civCL;
|
||||
#ifdef Q_OS_MAC
|
||||
QString logFilename= QStandardPaths::standardLocations(QStandardPaths::DownloadLocation)[0] + "/wfview.log";
|
||||
#else
|
||||
QString logFilename= QStandardPaths::standardLocations(QStandardPaths::TempLocation)[0] + "/wfview.log";
|
||||
#endif
|
||||
QDateTime date = QDateTime::currentDateTime();
|
||||
QString formattedTime = date.toString("dd.MM.yyyy hh:mm:ss");
|
||||
QString logFilename = (QString("%1/%2-%3.log").arg(QStandardPaths::standardLocations(QStandardPaths::TempLocation)[0]).arg(a.applicationName()).arg(date.toString("yyyyMMddhhmmss")));
|
||||
|
||||
QString settingsFile = NULL;
|
||||
QString currentArg;
|
||||
|
||||
|
||||
const QString helpText = QString("\nUsage: -p --port /dev/port, -h --host remotehostname, -c --civ 0xAddr, -l --logfile filename.log, -s --settings filename.ini, -d --debug\n"); // TODO...
|
||||
|
||||
const QString helpText = QString("\nUsage: -l --logfile filename.log, -s --settings filename.ini, -d --debug, -v --version\n"); // TODO...
|
||||
#ifdef BUILD_WFSERVER
|
||||
const QString version = QString("wfserver version: %1 (Git:%2 on %3 at %4 by %5@%6)\nOperating System: %7 (%8)\nBuild Qt Version %9. Current Qt Version: %10\n")
|
||||
.arg(QString(WFVIEW_VERSION))
|
||||
.arg(GITSHORT).arg(__DATE__).arg(__TIME__).arg(UNAME).arg(HOST)
|
||||
.arg(QSysInfo::prettyProductName()).arg(QSysInfo::buildCpuArchitecture())
|
||||
.arg(QT_VERSION_STR).arg(qVersion());
|
||||
#else
|
||||
const QString version = QString("wfview version: %1 (Git:%2 on %3 at %4 by %5@%6)\nOperating System: %7 (%8)\nBuild Qt Version %9. Current Qt Version: %10\n")
|
||||
.arg(QString(WFVIEW_VERSION))
|
||||
.arg(GITSHORT).arg(__DATE__).arg(__TIME__).arg(UNAME).arg(HOST)
|
||||
.arg(QSysInfo::prettyProductName()).arg(QSysInfo::buildCpuArchitecture())
|
||||
.arg(QT_VERSION_STR).arg(qVersion());
|
||||
#endif
|
||||
for(int c=1; c<argc; c++)
|
||||
{
|
||||
//qInfo() << "Argc: " << c << " argument: " << argv[c];
|
||||
currentArg = QString(argv[c]);
|
||||
|
||||
if ((currentArg == "-p") || (currentArg == "--port"))
|
||||
{
|
||||
if (argc > c)
|
||||
{
|
||||
serialPortCL = argv[c + 1];
|
||||
c += 1;
|
||||
}
|
||||
}
|
||||
else if ((currentArg == "-d") || (currentArg == "--debug"))
|
||||
if ((currentArg == "-d") || (currentArg == "--debug"))
|
||||
{
|
||||
debugMode = true;
|
||||
}
|
||||
else if ((currentArg == "-h") || (currentArg == "--host"))
|
||||
{
|
||||
if(argc > c)
|
||||
{
|
||||
hostCL = argv[c+1];
|
||||
c+=1;
|
||||
}
|
||||
}
|
||||
else if ((currentArg == "-c") || (currentArg == "--civ"))
|
||||
{
|
||||
if (argc > c)
|
||||
{
|
||||
civCL = argv[c + 1];
|
||||
c += 1;
|
||||
}
|
||||
}
|
||||
else if ((currentArg == "-l") || (currentArg == "--logfile"))
|
||||
{
|
||||
if (argc > c)
|
||||
|
@ -88,28 +118,25 @@ int main(int argc, char *argv[])
|
|||
c += 1;
|
||||
}
|
||||
}
|
||||
else if ((currentArg == "--help"))
|
||||
else if ((currentArg == "-?") || (currentArg == "--help"))
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
QMessageBox::information(0, "wfview help", helpText);
|
||||
#else
|
||||
std::cout << helpText.toStdString();
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
else if ((currentArg == "-v") || (currentArg == "--version"))
|
||||
{
|
||||
std::cout << version.toStdString();
|
||||
return 0;
|
||||
} else {
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
QMessageBox::information(0, "wfview unrecognised argument", helpText);
|
||||
#else
|
||||
std::cout << "Unrecognized option: " << currentArg.toStdString();
|
||||
std::cout << helpText.toStdString();
|
||||
#endif
|
||||
|
||||
return -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#ifdef BUILD_WFSERVER
|
||||
|
||||
// Set the logging file before doing anything else.
|
||||
m_logFile.reset(new QFile(logFilename));
|
||||
// Open the file logging
|
||||
|
@ -117,23 +144,29 @@ int main(int argc, char *argv[])
|
|||
// Set handler
|
||||
qInstallMessageHandler(messageHandler);
|
||||
|
||||
qInfo(logSystem()) << QString("Starting wfview: build %1 on %2 at %3 by %5@%6").arg(GITSHORT).arg(__DATE__).arg(__TIME__).arg(UNAME).arg(HOST);
|
||||
qInfo(logSystem()) << QString("Operating System: %1 (%2)").arg(QSysInfo::prettyProductName()).arg(QSysInfo::buildCpuArchitecture());
|
||||
qInfo(logSystem()) << QString("Build Qt Version %1. Current Qt Version: %2").arg(QT_VERSION_STR).arg(qVersion());
|
||||
qDebug(logSystem()) << QString("SerialPortCL as set by parser: %1").arg(serialPortCL);
|
||||
qDebug(logSystem()) << QString("remote host as set by parser: %1").arg(hostCL);
|
||||
qDebug(logSystem()) << QString("CIV as set by parser: %1").arg(civCL);
|
||||
qInfo(logSystem()) << version;
|
||||
|
||||
#endif
|
||||
#ifdef BUILD_WFSERVER
|
||||
#ifdef Q_OS_WIN
|
||||
SetConsoleCtrlHandler((PHANDLER_ROUTINE)cleanup, TRUE);
|
||||
#else
|
||||
signal(SIGINT, cleanup);
|
||||
signal(SIGTERM, cleanup);
|
||||
signal(SIGKILL, cleanup);
|
||||
#endif
|
||||
w = new servermain(settingsFile);
|
||||
#else
|
||||
a.setWheelScrollLines(1); // one line per wheel click
|
||||
wfmain w( serialPortCL, hostCL, settingsFile);
|
||||
|
||||
wfmain w(settingsFile, logFilename, debugMode);
|
||||
w.show();
|
||||
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
return a.exec();
|
||||
|
||||
}
|
||||
|
||||
#ifdef BUILD_WFSERVER
|
||||
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg)
|
||||
{
|
||||
|
@ -144,6 +177,7 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt
|
|||
}
|
||||
QMutexLocker locker(&logMutex);
|
||||
QTextStream out(m_logFile.data());
|
||||
QString text;
|
||||
|
||||
// Write the date of recording
|
||||
out << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz ");
|
||||
|
@ -169,5 +203,7 @@ void messageHandler(QtMsgType type, const QMessageLogContext& context, const QSt
|
|||
}
|
||||
// Write to the output category of the message and the message itself
|
||||
out << context.category << ": " << msg << "\n";
|
||||
std::cout << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz ").toLocal8Bit().toStdString() << msg.toLocal8Bit().toStdString() << "\n";
|
||||
out.flush(); // Clear the buffered data
|
||||
}
|
||||
#endif
|
||||
|
|
162
meter.cpp
|
@ -48,10 +48,30 @@ meter::meter(QWidget *parent) : QWidget(parent)
|
|||
|
||||
}
|
||||
|
||||
void meter::setColors(QColor current, QColor peakScale, QColor peakLevel,
|
||||
QColor average, QColor lowLine,
|
||||
QColor lowText)
|
||||
{
|
||||
currentColor = current;
|
||||
|
||||
peakColor = peakLevel; // color for the peak level indicator
|
||||
highLineColor = peakScale; // color for the red side of the scale
|
||||
highTextColor = peakScale; // color for the red side of the scale's text
|
||||
|
||||
averageColor = average;
|
||||
|
||||
midScaleColor = QColor(Qt::yellow);
|
||||
centerTuningColor = QColor(Qt::green);
|
||||
|
||||
lowLineColor = lowLine;
|
||||
lowTextColor = lowText;
|
||||
this->update();
|
||||
}
|
||||
|
||||
void meter::clearMeterOnPTTtoggle()
|
||||
{
|
||||
// When a meter changes type, such as the fixed S -- TxPo meter,
|
||||
// there is automatic clearing. However, some meters do not switch on thier own,
|
||||
// there is automatic clearing. However, some meters do not switch on their own,
|
||||
// and thus we are providing this clearing method. We are careful
|
||||
// not to clear meters that don't make sense to clear (such as Vd and Id)
|
||||
|
||||
|
@ -110,7 +130,7 @@ void meter::paintEvent(QPaintEvent *)
|
|||
{
|
||||
QPainter painter(this);
|
||||
// This next line sets up a canvis within the
|
||||
// space of the widget, and gives it corrdinates.
|
||||
// space of the widget, and gives it coordinates.
|
||||
// The end effect, is that the drawing functions will all
|
||||
// scale to the window size.
|
||||
painter.setRenderHint(QPainter::SmoothPixmapTransform);
|
||||
|
@ -157,13 +177,29 @@ void meter::paintEvent(QPaintEvent *)
|
|||
drawScaleId(&painter);
|
||||
break;
|
||||
case meterComp:
|
||||
label = "CMP";
|
||||
label = "CMP(dB)";
|
||||
peakRedLevel = 100;
|
||||
drawScaleComp(&painter);
|
||||
break;
|
||||
case meterNone:
|
||||
return;
|
||||
break;
|
||||
case meterAudio:
|
||||
label = "dBfs";
|
||||
peakRedLevel = 241;
|
||||
drawScale_dBFs(&painter);
|
||||
break;
|
||||
case meterRxAudio:
|
||||
label = "Rx(dBfs)";
|
||||
peakRedLevel = 241;
|
||||
drawScale_dBFs(&painter);
|
||||
break;
|
||||
case meterTxMod:
|
||||
label = "Tx(dBfs)";
|
||||
peakRedLevel = 241;
|
||||
drawScale_dBFs(&painter);
|
||||
break;
|
||||
|
||||
default:
|
||||
label = "DN";
|
||||
peakRedLevel = 241;
|
||||
|
@ -196,8 +232,37 @@ void meter::paintEvent(QPaintEvent *)
|
|||
|
||||
painter.drawRect(mXstart+peak-1,mYstart,1,barHeight);
|
||||
|
||||
} else if ( (meterType == meterAudio) ||
|
||||
(meterType == meterTxMod) ||
|
||||
(meterType == meterRxAudio))
|
||||
{
|
||||
// Log scale but still 0-255:
|
||||
int logCurrent = (int)((1-audiopot[255-current])*255);
|
||||
int logAverage = (int)((1-audiopot[255-average])*255);
|
||||
int logPeak = (int)((1-audiopot[255-peak])*255);
|
||||
|
||||
// X, Y, Width, Height
|
||||
painter.drawRect(mXstart,mYstart,logCurrent,barHeight);
|
||||
|
||||
// Average:
|
||||
painter.setPen(averageColor);
|
||||
painter.setBrush(averageColor);
|
||||
painter.drawRect(mXstart+logAverage-1,mYstart,1,barHeight); // bar is 1 pixel wide, height = meter start?
|
||||
|
||||
// Peak:
|
||||
painter.setPen(peakColor);
|
||||
painter.setBrush(peakColor);
|
||||
if(peak > peakRedLevel)
|
||||
{
|
||||
painter.setBrush(Qt::red);
|
||||
painter.setPen(Qt::red);
|
||||
}
|
||||
|
||||
painter.drawRect(mXstart+logPeak-1,mYstart,2,barHeight);
|
||||
|
||||
} else {
|
||||
|
||||
|
||||
// X, Y, Width, Height
|
||||
painter.drawRect(mXstart,mYstart,current,barHeight);
|
||||
|
||||
|
@ -225,7 +290,7 @@ void meter::paintEvent(QPaintEvent *)
|
|||
|
||||
void meter::drawLabel(QPainter *qp)
|
||||
{
|
||||
qp->setPen(lowLineColor);
|
||||
qp->setPen(lowTextColor);
|
||||
qp->drawText(0,scaleTextYstart, label );
|
||||
}
|
||||
|
||||
|
@ -255,6 +320,22 @@ void meter::setLevel(int current)
|
|||
this->update();
|
||||
}
|
||||
|
||||
void meter::setLevels(int current, int peak)
|
||||
{
|
||||
this->current = current;
|
||||
this->peak = peak;
|
||||
|
||||
avgLevels[(avgPosition++)%averageBalisticLength] = current;
|
||||
int sum=0;
|
||||
for(unsigned int i=0; i < (unsigned int)std::min(avgPosition, (int)avgLevels.size()); i++)
|
||||
{
|
||||
sum += avgLevels.at(i);
|
||||
}
|
||||
this->average = sum / std::min(avgPosition, (int)avgLevels.size());
|
||||
|
||||
this->update();
|
||||
}
|
||||
|
||||
void meter::setLevels(int current, int peak, int average)
|
||||
{
|
||||
this->current = current;
|
||||
|
@ -287,9 +368,43 @@ void meter::drawScaleRaw(QPainter *qp)
|
|||
|
||||
// Line: X1, Y1 -->to--> X2, Y2
|
||||
qp->drawLine(mXstart,scaleLineYstart,peakRedLevel+mXstart,scaleLineYstart);
|
||||
qp->setPen(Qt::red);
|
||||
qp->setPen(highLineColor);
|
||||
qp->drawLine(peakRedLevel+mXstart,scaleLineYstart,255+mXstart,scaleLineYstart);
|
||||
}
|
||||
|
||||
void meter::drawScale_dBFs(QPainter *qp)
|
||||
{
|
||||
qp->setPen(lowTextColor);
|
||||
peakRedLevel = 193;
|
||||
|
||||
if(meterType==meterAudio)
|
||||
qp->drawText(20+mXstart,scaleTextYstart, QString("-30"));
|
||||
qp->drawText(38+mXstart+2,scaleTextYstart, QString("-24"));
|
||||
qp->drawText(71+mXstart,scaleTextYstart, QString("-18"));
|
||||
qp->drawText(124+mXstart,scaleTextYstart, QString("-12"));
|
||||
qp->drawText(193+mXstart,scaleTextYstart, QString("-6"));
|
||||
qp->drawText(255+mXstart,scaleTextYstart, QString("0"));
|
||||
|
||||
// Low ticks:
|
||||
qp->setPen(lowLineColor);
|
||||
qp->drawLine(20+mXstart,scaleTextYstart, 20+mXstart, scaleTextYstart+5);
|
||||
qp->drawLine(38+mXstart,scaleTextYstart, 38+mXstart, scaleTextYstart+5);
|
||||
qp->drawLine(71+mXstart,scaleTextYstart, 71+mXstart, scaleTextYstart+5);
|
||||
qp->drawLine(124+mXstart,scaleTextYstart, 124+mXstart, scaleTextYstart+5);
|
||||
|
||||
|
||||
// High ticks:
|
||||
qp->setPen(highLineColor);
|
||||
qp->drawLine(193+mXstart,scaleTextYstart, 193+mXstart, scaleTextYstart+5);
|
||||
qp->drawLine(255+mXstart,scaleTextYstart, 255+mXstart, scaleTextYstart+5);
|
||||
|
||||
// Now the lines:
|
||||
qp->setPen(lowLineColor);
|
||||
|
||||
// Line: X1, Y1 -->to--> X2, Y2
|
||||
qp->drawLine(mXstart,scaleLineYstart,peakRedLevel+mXstart,scaleLineYstart);
|
||||
qp->setPen(highLineColor);
|
||||
qp->drawLine(peakRedLevel+mXstart,scaleLineYstart,255+mXstart,scaleLineYstart);
|
||||
}
|
||||
|
||||
void meter::drawScaleVd(QPainter *qp)
|
||||
|
@ -326,7 +441,7 @@ void meter::drawScaleVd(QPainter *qp)
|
|||
|
||||
// Line: X1, Y1 -->to--> X2, Y2
|
||||
qp->drawLine(mXstart,scaleLineYstart,peakRedLevel+mXstart,scaleLineYstart);
|
||||
qp->setPen(Qt::red);
|
||||
qp->setPen(highLineColor);
|
||||
qp->drawLine(peakRedLevel+mXstart,scaleLineYstart,255+mXstart,scaleLineYstart);
|
||||
|
||||
}
|
||||
|
@ -337,7 +452,7 @@ void meter::drawScaleCenter(QPainter *qp)
|
|||
qp->setPen(lowLineColor);
|
||||
qp->drawText(60+mXstart,scaleTextYstart, QString("-"));
|
||||
|
||||
qp->setPen(Qt::green);
|
||||
qp->setPen(centerTuningColor);
|
||||
// Attempt to draw the zero at the actual center
|
||||
qp->drawText(128-2+mXstart,scaleTextYstart, QString("0"));
|
||||
|
||||
|
@ -348,7 +463,7 @@ void meter::drawScaleCenter(QPainter *qp)
|
|||
qp->setPen(lowLineColor);
|
||||
qp->drawLine(mXstart,scaleLineYstart,128-32+mXstart,scaleLineYstart);
|
||||
|
||||
qp->setPen(Qt::green);
|
||||
qp->setPen(centerTuningColor);
|
||||
qp->drawLine(128-32+mXstart,scaleLineYstart,128+32+mXstart,scaleLineYstart);
|
||||
|
||||
qp->setPen(lowLineColor);
|
||||
|
@ -378,11 +493,11 @@ void meter::drawScalePo(QPainter *qp)
|
|||
|
||||
// Here, P is now 60 watts:
|
||||
// Higher scale:
|
||||
i = i - (int)(10*dnPerWatt); // back one tick first. Otherwise i starts at 178.
|
||||
//i = i - (int)(10*dnPerWatt); // back one tick first. Otherwise i starts at 178. **Not used?**
|
||||
//qDebug() << "meter i: " << i;
|
||||
dnPerWatt = (213-143.0f) / 50.0f; // 1.4 dn per watt
|
||||
// P=5 here.
|
||||
qp->setPen(Qt::yellow);
|
||||
qp->setPen(midScaleColor);
|
||||
int k=0;
|
||||
for(i=mXstart+143; i<mXstart+213; i+=(5*dnPerWatt))
|
||||
{
|
||||
|
@ -392,7 +507,7 @@ void meter::drawScalePo(QPainter *qp)
|
|||
}
|
||||
|
||||
// Now we're out past 100:
|
||||
qp->setPen(Qt::red);
|
||||
qp->setPen(highTextColor);
|
||||
|
||||
for(i=mXstart+213; i<mXstart+255; i+=(10*dnPerWatt))
|
||||
{
|
||||
|
@ -406,7 +521,7 @@ void meter::drawScalePo(QPainter *qp)
|
|||
|
||||
// Line: X1, Y1 -->to--> X2, Y2
|
||||
qp->drawLine(mXstart,scaleLineYstart,213+mXstart,scaleLineYstart);
|
||||
qp->setPen(Qt::red);
|
||||
qp->setPen(highLineColor);
|
||||
qp->drawLine(213+mXstart,scaleLineYstart,255+mXstart,scaleLineYstart);
|
||||
|
||||
(void)qp;
|
||||
|
@ -432,7 +547,7 @@ void meter::drawScaleALC(QPainter *qp)
|
|||
alc +=20;
|
||||
}
|
||||
|
||||
qp->setPen(Qt::red);
|
||||
qp->setPen(highTextColor);
|
||||
|
||||
for(; i<mXstart+120; i+=(int)(10*i))
|
||||
{
|
||||
|
@ -443,7 +558,7 @@ void meter::drawScaleALC(QPainter *qp)
|
|||
|
||||
qp->setPen(lowLineColor);
|
||||
qp->drawLine(mXstart,scaleLineYstart,100+mXstart,scaleLineYstart);
|
||||
qp->setPen(Qt::red);
|
||||
qp->setPen(highLineColor);
|
||||
qp->drawLine(100+mXstart,scaleLineYstart,255+mXstart,scaleLineYstart);
|
||||
|
||||
(void)qp;
|
||||
|
@ -465,6 +580,7 @@ void meter::drawScaleComp(QPainter *qp)
|
|||
float dBperDn = (float)(highPointdB-midPointdB) / float(highPointDn-midPointDn);
|
||||
|
||||
int i=mXstart;
|
||||
i+=midPointDn/4; // skip the 0 for cleaner label space
|
||||
for(; i<mXstart+midPointDn; i+=midPointDn/4)
|
||||
{
|
||||
qp->drawText(i,scaleTextYstart, QString("%1").arg( (int)((i-mXstart) * (float(midPointdB) / float(midPointDn)) )) );
|
||||
|
@ -483,7 +599,7 @@ void meter::drawScaleComp(QPainter *qp)
|
|||
|
||||
// Line: X1, Y1 -->to--> X2, Y2
|
||||
qp->drawLine(mXstart,scaleLineYstart,peakRedLevel+mXstart,scaleLineYstart);
|
||||
qp->setPen(Qt::red);
|
||||
qp->setPen(highLineColor);
|
||||
qp->drawLine(peakRedLevel+mXstart,scaleLineYstart,255+mXstart,scaleLineYstart);
|
||||
}
|
||||
|
||||
|
@ -495,27 +611,29 @@ void meter::drawScaleSWR(QPainter *qp)
|
|||
// 0080=SWR2.0,
|
||||
// 0120=SWR3.0
|
||||
|
||||
qp->setPen(lowTextColor);
|
||||
qp->drawText(mXstart,scaleTextYstart, QString("1.0"));
|
||||
qp->drawText(24+mXstart,scaleTextYstart, QString("1.3"));
|
||||
qp->drawText(48+mXstart,scaleTextYstart, QString("1.5"));
|
||||
qp->drawText(80+mXstart,scaleTextYstart, QString("2.0"));
|
||||
qp->drawText(100+mXstart,scaleTextYstart, QString("2.5"));
|
||||
qp->setPen(highTextColor);
|
||||
qp->drawText(120+mXstart,scaleTextYstart, QString("3.0"));
|
||||
|
||||
qp->setPen(lowLineColor);
|
||||
qp->drawLine( 0+mXstart,scaleTextYstart, 0+mXstart, scaleTextYstart+5);
|
||||
qp->drawLine( 24+mXstart,scaleTextYstart, 24+mXstart, scaleTextYstart+5);
|
||||
qp->drawLine( 48+mXstart,scaleTextYstart, 48+mXstart, scaleTextYstart+5);
|
||||
qp->drawLine( 80+mXstart,scaleTextYstart, 80+mXstart, scaleTextYstart+5);
|
||||
qp->drawLine(100+mXstart,scaleTextYstart,100+mXstart, scaleTextYstart+5); // does not draw?
|
||||
qp->setPen(highLineColor);
|
||||
qp->drawLine(120+mXstart,scaleTextYstart,120+mXstart, scaleTextYstart+5);
|
||||
|
||||
|
||||
qp->setPen(lowLineColor);
|
||||
qp->drawLine(mXstart,scaleLineYstart,100+mXstart,scaleLineYstart);
|
||||
qp->setPen(Qt::red);
|
||||
qp->setPen(highLineColor);
|
||||
qp->drawLine(100+mXstart,scaleLineYstart,255+mXstart,scaleLineYstart);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void meter::drawScaleId(QPainter *qp)
|
||||
|
@ -553,7 +671,7 @@ void meter::drawScaleId(QPainter *qp)
|
|||
|
||||
// Line: X1, Y1 -->to--> X2, Y2
|
||||
qp->drawLine(mXstart,scaleLineYstart,peakRedLevel+mXstart,scaleLineYstart);
|
||||
qp->setPen(Qt::red);
|
||||
qp->setPen(highLineColor);
|
||||
qp->drawLine(peakRedLevel+mXstart,scaleLineYstart,255+mXstart,scaleLineYstart);
|
||||
}
|
||||
|
||||
|
@ -582,7 +700,7 @@ void meter::drawScaleS(QPainter *qp)
|
|||
s = 20;
|
||||
i+=20;
|
||||
|
||||
qp->setPen(Qt::red);
|
||||
qp->setPen(highTextColor);
|
||||
|
||||
for(; i<mXstart+255; i+=40)
|
||||
{
|
||||
|
@ -592,9 +710,7 @@ void meter::drawScaleS(QPainter *qp)
|
|||
}
|
||||
|
||||
qp->setPen(lowLineColor);
|
||||
|
||||
qp->drawLine(mXstart,scaleLineYstart,peakRedLevel+mXstart,scaleLineYstart);
|
||||
qp->setPen(Qt::red);
|
||||
qp->setPen(highLineColor);
|
||||
qp->drawLine(peakRedLevel+mXstart,scaleLineYstart,255+mXstart,scaleLineYstart);
|
||||
|
||||
}
|
||||
|
|
9
meter.h
|
@ -9,6 +9,7 @@
|
|||
#include <cmath>
|
||||
|
||||
#include "rigcommander.h" // for meter types
|
||||
#include "audiotaper.h"
|
||||
|
||||
class meter : public QWidget
|
||||
{
|
||||
|
@ -23,6 +24,7 @@ public slots:
|
|||
|
||||
void updateDrawing(int num);
|
||||
void setLevels(int current, int peak, int average);
|
||||
void setLevels(int current, int peak); // calculate avg
|
||||
void setLevel(int current);
|
||||
void clearMeterOnPTTtoggle();
|
||||
void clearMeter();
|
||||
|
@ -30,6 +32,9 @@ public slots:
|
|||
void setMeterShortString(QString);
|
||||
QString getMeterShortString();
|
||||
meterKind getMeterType();
|
||||
void setColors(QColor current, QColor peakScale, QColor peakLevel,
|
||||
QColor average, QColor lowLine,
|
||||
QColor lowText);
|
||||
|
||||
|
||||
private:
|
||||
|
@ -68,6 +73,7 @@ private:
|
|||
void drawScaleVd(QPainter *qp);
|
||||
void drawScaleId(QPainter *qp);
|
||||
void drawScaleComp(QPainter *qp);
|
||||
void drawScale_dBFs(QPainter *qp);
|
||||
void drawScaleRaw(QPainter *qp);
|
||||
|
||||
void drawLabel(QPainter *qp);
|
||||
|
@ -84,6 +90,9 @@ private:
|
|||
QColor highTextColor;
|
||||
QColor highLineColor;
|
||||
|
||||
QColor midScaleColor;
|
||||
QColor centerTuningColor;
|
||||
|
||||
};
|
||||
|
||||
#endif // METER_H
|
||||
|
|
245
packettypes.h
|
@ -4,6 +4,22 @@
|
|||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
// Various settings used by both client and server
|
||||
#define PURGE_SECONDS 10
|
||||
#define TOKEN_RENEWAL 60000
|
||||
#define PING_PERIOD 500
|
||||
#define IDLE_PERIOD 100
|
||||
#define AREYOUTHERE_PERIOD 500
|
||||
#define WATCHDOG_PERIOD 500
|
||||
#define RETRANSMIT_PERIOD 100 // How often to attempt retransmit
|
||||
#define LOCK_PERIOD 10 // How long to try to lock mutex (ms)
|
||||
#define STALE_CONNECTION 15 // Not heard from in this many seconds
|
||||
#define BUFSIZE 500 // Number of packets to buffer
|
||||
#define MAX_MISSING 50 // More than this indicates serious network problem
|
||||
#define AUDIO_PERIOD 20
|
||||
#define GUIDLEN 16
|
||||
|
||||
|
||||
// Fixed Size Packets
|
||||
#define CONTROL_SIZE 0x10
|
||||
#define WATCHDOG_SIZE 0x14
|
||||
|
@ -15,13 +31,15 @@
|
|||
#define LOGIN_RESPONSE_SIZE 0x60
|
||||
#define LOGIN_SIZE 0x80
|
||||
#define CONNINFO_SIZE 0x90
|
||||
#define CAPABILITIES_SIZE 0xA8
|
||||
#define CAPABILITIES_SIZE 0x42
|
||||
#define RADIO_CAP_SIZE 0x66
|
||||
|
||||
// Variable size packets + payload
|
||||
#define CIV_SIZE 0x15
|
||||
#define AUDIO_SIZE 0x18
|
||||
#define DATA_SIZE 0x15
|
||||
|
||||
|
||||
// 0x10 length control packet (connect/disconnect/idle.)
|
||||
typedef union control_packet {
|
||||
struct {
|
||||
|
@ -103,6 +121,7 @@ typedef union audio_packet {
|
|||
quint16 seq; // 0x06
|
||||
quint32 sentid; // 0x08
|
||||
quint32 rcvdid; // 0x0c
|
||||
|
||||
quint16 ident; // 0x10
|
||||
quint16 sendseq; // 0x12
|
||||
quint16 unused; // 0x14
|
||||
|
@ -147,19 +166,26 @@ typedef union token_packet {
|
|||
quint16 seq; // 0x06
|
||||
quint32 sentid; // 0x08
|
||||
quint32 rcvdid; // 0x0c
|
||||
char unuseda[3]; // 0x10
|
||||
quint16 code; // 0x13
|
||||
quint16 res; // 0x15
|
||||
quint8 innerseq; // 0x17
|
||||
char unusedb; // 0x18
|
||||
char unusedc; // 0x19
|
||||
char unuseda[2]; // 0x10
|
||||
quint16 payloadsize; // 0x12
|
||||
quint8 requestreply; // 0x13
|
||||
quint8 requesttype; // 0x14
|
||||
quint16 innerseq; // 0x16
|
||||
char unusedb[2]; // 0x18
|
||||
quint16 tokrequest; // 0x1a
|
||||
quint32 token; // 0x1c
|
||||
char unusedd[7]; // 0x20
|
||||
quint16 commoncap; // 0x27
|
||||
char unuseddd[2]; // 0x29
|
||||
char identa; // 0x2b
|
||||
quint32 identb; // 0x2c
|
||||
union {
|
||||
struct {
|
||||
quint16 authstartid; // 0x20
|
||||
char unusedg2[2]; // 0x22
|
||||
quint16 resetcap; // 0x24
|
||||
char unusedg1; // 0x26
|
||||
quint16 commoncap; // 0x27
|
||||
char unusedh; // 0x29
|
||||
quint8 macaddress[6]; // 0x2a
|
||||
};
|
||||
quint8 guid[GUIDLEN]; // 0x20
|
||||
};
|
||||
quint32 response; // 0x30
|
||||
char unusede[12]; // 0x34
|
||||
};
|
||||
|
@ -175,20 +201,24 @@ typedef union status_packet {
|
|||
quint16 seq; // 0x06
|
||||
quint32 sentid; // 0x08
|
||||
quint32 rcvdid; // 0x0c
|
||||
char unuseda[3]; // 0x10
|
||||
quint16 code; // 0x13
|
||||
quint16 res; // 0x15
|
||||
quint8 innerseq; // 0x17
|
||||
char unusedb; // 0x18
|
||||
char unusedc; // 0x19
|
||||
char unuseda[2]; // 0x10
|
||||
quint16 payloadsize; // 0x12
|
||||
quint8 requestreply; // 0x13
|
||||
quint8 requesttype; // 0x14
|
||||
quint16 innerseq; // 0x16
|
||||
char unusedb[2]; // 0x18
|
||||
quint16 tokrequest; // 0x1a
|
||||
quint32 token; // 0x1c
|
||||
char unusedd[6]; // 0x20
|
||||
quint16 unknown; // 0x26
|
||||
char unusede; // 0x28
|
||||
char unusedf[2]; // 0x29
|
||||
char identa; // 0x2b
|
||||
quint32 identb; // 0x2c
|
||||
union {
|
||||
struct {
|
||||
quint16 authstartid; // 0x20
|
||||
char unusedd[5]; // 0x22
|
||||
quint16 commoncap; // 0x27
|
||||
char unusede; // 0x29
|
||||
quint8 macaddress[6]; // 0x2a
|
||||
};
|
||||
quint8 guid[GUIDLEN]; // 0x20
|
||||
};
|
||||
quint32 error; // 0x30
|
||||
char unusedg[12]; // 0x34
|
||||
char disc; // 0x40
|
||||
|
@ -210,12 +240,12 @@ typedef union login_response_packet {
|
|||
quint16 seq; // 0x06
|
||||
quint32 sentid; // 0x08
|
||||
quint32 rcvdid; // 0x0c
|
||||
char unuseda[3]; // 0x10
|
||||
quint16 code; // 0x13
|
||||
quint16 res; // 0x15
|
||||
quint8 innerseq; // 0x17
|
||||
char unusedb; // 0x18
|
||||
char unusedc; // 0x19
|
||||
char unuseda[2]; // 0x10
|
||||
quint16 payloadsize; // 0x12
|
||||
quint8 requestreply; // 0x13
|
||||
quint8 requesttype; // 0x14
|
||||
quint16 innerseq; // 0x16
|
||||
char unusedb[2]; // 0x18
|
||||
quint16 tokrequest; // 0x1a
|
||||
quint32 token; // 0x1c
|
||||
quint16 authstartid; // 0x20
|
||||
|
@ -237,12 +267,12 @@ typedef union login_packet {
|
|||
quint16 seq; // 0x06
|
||||
quint32 sentid; // 0x08
|
||||
quint32 rcvdid; // 0x0c
|
||||
char unuseda[3]; // 0x10
|
||||
quint16 code; // 0x13
|
||||
quint16 res; // 0x15
|
||||
quint8 innerseq; // 0x17
|
||||
char unusedaa; // 0x18;
|
||||
char unusedb; // 0x19
|
||||
char unuseda[2]; // 0x10
|
||||
quint16 payloadsize; // 0x12
|
||||
quint8 requestreply; // 0x13
|
||||
quint8 requesttype; // 0x14
|
||||
quint16 innerseq; // 0x16
|
||||
char unusedb[2]; // 0x18
|
||||
quint16 tokrequest; // 0x1a
|
||||
quint32 token; // 0x1c
|
||||
char unusedc[32]; // 0x20
|
||||
|
@ -263,22 +293,26 @@ typedef union conninfo_packet {
|
|||
quint16 seq; // 0x06
|
||||
quint32 sentid; // 0x08
|
||||
quint32 rcvdid; // 0x0c
|
||||
char unuseda[3]; // 0x10
|
||||
quint16 code; // 0x13
|
||||
quint16 res; // 0x15
|
||||
quint8 innerseq; // 0x17
|
||||
char unusedaa; // 0x18
|
||||
char unusedb; // 0x19
|
||||
char unuseda[2]; // 0x10
|
||||
quint16 payloadsize; // 0x12
|
||||
quint8 requestreply; // 0x13
|
||||
quint8 requesttype; // 0x14
|
||||
quint16 innerseq; // 0x16
|
||||
char unusedb[2]; // 0x18
|
||||
quint16 tokrequest; // 0x1a
|
||||
quint32 token; // 0x1c
|
||||
quint16 authstartid; // 0x20
|
||||
char unusedd[5]; // 0x22
|
||||
quint32 commoncap; // 0x27
|
||||
char identa; // 0x2b
|
||||
quint32 identb; // 0x2c
|
||||
char unusedf[16]; // 0x30
|
||||
char name[16]; // 0x40
|
||||
char unusedg[16]; // 0x50
|
||||
union {
|
||||
struct {
|
||||
quint16 authstartid; // 0x20
|
||||
char unusedg[5]; // 0x22
|
||||
quint16 commoncap; // 0x27
|
||||
char unusedh; // 0x29
|
||||
quint8 macaddress[6]; // 0x2a
|
||||
};
|
||||
quint8 guid[GUIDLEN]; // 0x20
|
||||
};
|
||||
char unusedab[16]; // 0x30
|
||||
char name[32]; // 0x40
|
||||
union { // This contains differences between the send/receive packet
|
||||
struct { // Receive
|
||||
quint32 busy; // 0x60
|
||||
|
@ -306,6 +340,41 @@ typedef union conninfo_packet {
|
|||
char packet[CONNINFO_SIZE];
|
||||
} *conninfo_packet_t;
|
||||
|
||||
|
||||
// 0x64 length radio capabilities part of cap packet.
|
||||
|
||||
typedef union radio_cap_packet {
|
||||
struct
|
||||
{
|
||||
union {
|
||||
struct {
|
||||
quint8 unusede[7]; // 0x00
|
||||
quint16 commoncap; // 0x07
|
||||
quint8 unused; // 0x09
|
||||
quint8 macaddress[6]; // 0x0a
|
||||
};
|
||||
quint8 guid[GUIDLEN]; // 0x0
|
||||
};
|
||||
char name[32]; // 0x10
|
||||
char audio[32]; // 0x30
|
||||
quint16 conntype; // 0x50
|
||||
char civ; // 0x52
|
||||
quint16 rxsample; // 0x53
|
||||
quint16 txsample; // 0x55
|
||||
quint8 enablea; // 0x57
|
||||
quint8 enableb; // 0x58
|
||||
quint8 enablec; // 0x59
|
||||
quint32 baudrate; // 0x5a
|
||||
quint16 capf; // 0x5e
|
||||
char unusedi; // 0x60
|
||||
quint16 capg; // 0x61
|
||||
char unusedj[3]; // 0x63
|
||||
};
|
||||
char packet[RADIO_CAP_SIZE];
|
||||
} *radio_cap_packet_t;
|
||||
|
||||
|
||||
|
||||
// 0xA8 length capabilities packet
|
||||
typedef union capabilities_packet {
|
||||
struct
|
||||
|
@ -315,39 +384,63 @@ typedef union capabilities_packet {
|
|||
quint16 seq; // 0x06
|
||||
quint32 sentid; // 0x08
|
||||
quint32 rcvdid; // 0x0c
|
||||
char unuseda[3]; // 0x10
|
||||
quint16 code; // 0x13
|
||||
quint16 res; // 0x15
|
||||
quint8 innerseq; // 0x17
|
||||
char unusedb; // 0x18
|
||||
char unusedc; // 0x19
|
||||
char unuseda[2]; // 0x10
|
||||
quint16 payloadsize; // 0x12
|
||||
quint8 requestreply; // 0x13
|
||||
quint8 requesttype; // 0x14
|
||||
quint16 innerseq; // 0x16
|
||||
char unusedb[2]; // 0x18
|
||||
quint16 tokrequest; // 0x1a
|
||||
quint32 token; // 0x1c
|
||||
char unusedd[33]; // 0x20
|
||||
char capa; // 0x41
|
||||
char unusede[7]; // 0x42
|
||||
quint16 commoncap; // 0x49
|
||||
char unused; // 0x4b
|
||||
char macaddress[6]; // 0x4c
|
||||
char name[32]; // 0x52
|
||||
char audio[32]; // 0x72
|
||||
quint16 conntype; // 0x92
|
||||
char civ; // 0x94
|
||||
quint16 rxsample; // 0x95
|
||||
quint16 txsample; // 0x97
|
||||
quint8 enablea; // 0x99
|
||||
quint8 enableb; // 0x9a
|
||||
quint8 enablec; // 0x9b
|
||||
quint32 baudrate; // 0x9c
|
||||
quint16 capf; // 0xa0
|
||||
char unusedi; // 0xa2
|
||||
quint16 capg; // 0xa3
|
||||
char unusedj[3]; // 0xa5
|
||||
char unusedd[32]; // 0x20
|
||||
quint16 numradios; // 0x40
|
||||
};
|
||||
char packet[CAPABILITIES_SIZE];
|
||||
} *capabilities_packet_t;
|
||||
|
||||
|
||||
typedef union streamdeck_image_header {
|
||||
struct
|
||||
{
|
||||
quint8 cmd;
|
||||
quint8 suffix;
|
||||
quint8 button;
|
||||
quint8 isLast;
|
||||
quint16 length;
|
||||
quint16 index;
|
||||
};
|
||||
char packet[8];
|
||||
} *streamdeck_image_header_t;
|
||||
|
||||
typedef union streamdeck_v1_image_header {
|
||||
struct
|
||||
{
|
||||
quint8 cmd;
|
||||
quint8 suffix;
|
||||
quint16 index;
|
||||
quint8 isLast;
|
||||
quint8 button;
|
||||
quint8 unused[10];
|
||||
};
|
||||
char packet[16];
|
||||
} *streamdeck_v1_image_header_t;
|
||||
|
||||
typedef union streamdeck_lcd_header {
|
||||
struct
|
||||
{
|
||||
quint8 cmd;
|
||||
quint8 suffix;
|
||||
quint16 x;
|
||||
quint16 y;
|
||||
quint16 width;
|
||||
quint16 height;
|
||||
quint8 isLast;
|
||||
quint16 index;
|
||||
quint16 length;
|
||||
quint8 unused;
|
||||
};
|
||||
char packet[16];
|
||||
} *streamdeck_lcd_header_t;
|
||||
|
||||
|
||||
#pragma pack(pop)
|
||||
|
|
|
@ -0,0 +1,343 @@
|
|||
#include "pahandler.h"
|
||||
|
||||
#include "logcategories.h"
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <objbase.h>
|
||||
#endif
|
||||
|
||||
|
||||
paHandler::paHandler(QObject* parent)
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
}
|
||||
|
||||
paHandler::~paHandler()
|
||||
{
|
||||
|
||||
if (converterThread != Q_NULLPTR) {
|
||||
converterThread->quit();
|
||||
converterThread->wait();
|
||||
}
|
||||
|
||||
if (isInitialized) {
|
||||
Pa_StopStream(audio);
|
||||
Pa_CloseStream(audio);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool paHandler::init(audioSetup setup)
|
||||
{
|
||||
if (isInitialized) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->setup = setup;
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "PortAudio handler starting:" << setup.name;
|
||||
|
||||
if (setup.portInt == -1)
|
||||
{
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "No audio device was found.";
|
||||
return false;
|
||||
}
|
||||
|
||||
radioFormat = toQAudioFormat(setup.codec, setup.sampleRate);
|
||||
|
||||
qDebug(logAudio()) << "Creating" << (setup.isinput ? "Input" : "Output") << "audio device:" << setup.name <<
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
", bits" << radioFormat.sampleSize() <<
|
||||
#else
|
||||
", format" << radioFormat.sampleFormat() <<
|
||||
#endif
|
||||
", codec" << setup.codec <<
|
||||
", latency" << setup.latency <<
|
||||
", localAFGain" << setup.localAFgain <<
|
||||
", radioChan" << radioFormat.channelCount() <<
|
||||
", resampleQuality" << setup.resampleQuality <<
|
||||
", samplerate" << radioFormat.sampleRate() <<
|
||||
", uLaw" << setup.ulaw;
|
||||
|
||||
PaError err;
|
||||
#ifdef Q_OS_WIN
|
||||
CoInitialize(0);
|
||||
#endif
|
||||
|
||||
//err = Pa_Initialize();
|
||||
//if (err != paNoError)
|
||||
//{
|
||||
// qDebug(logAudio()) << "Portaudio initialized";
|
||||
//}
|
||||
|
||||
|
||||
codecType codec = LPCM;
|
||||
if (setup.codec == 0x01 || setup.codec == 0x20)
|
||||
codec = PCMU;
|
||||
else if (setup.codec == 0x40 || setup.codec == 0x40)
|
||||
codec = OPUS;
|
||||
|
||||
memset(&aParams, 0, sizeof(PaStreamParameters));
|
||||
|
||||
aParams.device = setup.portInt;
|
||||
info = Pa_GetDeviceInfo(aParams.device);
|
||||
|
||||
qDebug(logAudio()) << "PortAudio" << (setup.isinput ? "Input" : "Output") << setup.portInt << "Input Channels" << info->maxInputChannels << "Output Channels" << info->maxOutputChannels;
|
||||
|
||||
|
||||
if (setup.isinput) {
|
||||
nativeFormat.setChannelCount(info->maxInputChannels);
|
||||
}
|
||||
else {
|
||||
nativeFormat.setChannelCount(info->maxOutputChannels);
|
||||
}
|
||||
|
||||
aParams.suggestedLatency = (float)setup.latency / 1000.0f;
|
||||
nativeFormat.setSampleRate(info->defaultSampleRate);
|
||||
aParams.sampleFormat = paFloat32;
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
nativeFormat.setSampleSize(32);
|
||||
nativeFormat.setSampleType(QAudioFormat::Float);
|
||||
nativeFormat.setByteOrder(QAudioFormat::LittleEndian);
|
||||
nativeFormat.setCodec("audio/pcm");
|
||||
#else
|
||||
nativeFormat.setSampleFormat(QAudioFormat::Float);
|
||||
#endif
|
||||
|
||||
if (nativeFormat.channelCount() > 2) {
|
||||
nativeFormat.setChannelCount(2);
|
||||
}
|
||||
else if (nativeFormat.channelCount() < 1)
|
||||
{
|
||||
qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << "No channels found, aborting setup.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nativeFormat.channelCount() == 1 && radioFormat.channelCount() == 2) {
|
||||
nativeFormat.setChannelCount(2);
|
||||
}
|
||||
|
||||
aParams.channelCount = nativeFormat.channelCount();
|
||||
|
||||
if (nativeFormat.sampleRate() < 44100) {
|
||||
nativeFormat.setSampleRate(48000);
|
||||
}
|
||||
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Selected format: SampleSize" << nativeFormat.sampleSize() << "Channel Count" << nativeFormat.channelCount() <<
|
||||
"Sample Rate" << nativeFormat.sampleRate() << "Codec" << codec << "Sample Type" << nativeFormat.sampleType();
|
||||
#else
|
||||
qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Selected format: SampleFormat" << nativeFormat.sampleFormat() << "Channel Count" << nativeFormat.channelCount() <<
|
||||
"Sample Rate" << nativeFormat.sampleRate() << "Codec" << codec;
|
||||
#endif
|
||||
|
||||
// We "hopefully" now have a valid format that is supported so try connecting
|
||||
|
||||
converter = new audioConverter();
|
||||
converterThread = new QThread(this);
|
||||
if (setup.isinput) {
|
||||
converterThread->setObjectName("audioConvIn()");
|
||||
}
|
||||
else {
|
||||
converterThread->setObjectName("audioConvOut()");
|
||||
}
|
||||
converter->moveToThread(converterThread);
|
||||
|
||||
connect(this, SIGNAL(setupConverter(QAudioFormat, codecType, QAudioFormat, codecType, quint8, quint8)), converter, SLOT(init(QAudioFormat, codecType, QAudioFormat, codecType, quint8, quint8)));
|
||||
connect(converterThread, SIGNAL(finished()), converter, SLOT(deleteLater()));
|
||||
connect(this, SIGNAL(sendToConverter(audioPacket)), converter, SLOT(convert(audioPacket)));
|
||||
converterThread->start(QThread::TimeCriticalPriority);
|
||||
|
||||
aParams.hostApiSpecificStreamInfo = NULL;
|
||||
|
||||
// Per channel chunk size.
|
||||
this->chunkSize = nativeFormat.framesForDuration(setup.blockSize * 1000);
|
||||
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Chunk size" << this->chunkSize;
|
||||
// Check the format is supported
|
||||
|
||||
|
||||
|
||||
err = -1;
|
||||
int errCount = 0;
|
||||
while (err != paNoError) {
|
||||
if (setup.isinput) {
|
||||
err = Pa_IsFormatSupported(&aParams, NULL, nativeFormat.sampleRate());
|
||||
}
|
||||
else
|
||||
{
|
||||
err = Pa_IsFormatSupported(NULL, &aParams, nativeFormat.sampleRate());
|
||||
}
|
||||
if (err == paInvalidChannelCount)
|
||||
{
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Unsupported channel count" << aParams.channelCount;
|
||||
if (aParams.channelCount == 2) {
|
||||
aParams.channelCount = 1;
|
||||
nativeFormat.setChannelCount(1);
|
||||
}
|
||||
else {
|
||||
aParams.channelCount = 2;
|
||||
nativeFormat.setChannelCount(2);
|
||||
}
|
||||
}
|
||||
if (err == paInvalidSampleRate)
|
||||
{
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Unsupported sample rate" << nativeFormat.sampleRate();
|
||||
nativeFormat.setSampleRate(44100);
|
||||
}
|
||||
if (err == paSampleFormatNotSupported)
|
||||
{
|
||||
aParams.sampleFormat = paInt16;
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Unsupported sample Format" << nativeFormat.sampleType();
|
||||
nativeFormat.setSampleType(QAudioFormat::SignedInt);
|
||||
nativeFormat.setSampleSize(16);
|
||||
#else
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Unsupported sample Format" << nativeFormat.sampleFormat();
|
||||
nativeFormat.setSampleFormat(QAudioFormat::Int16);
|
||||
#endif
|
||||
}
|
||||
|
||||
errCount++;
|
||||
if (errCount > 5) {
|
||||
qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << "Cannot find suitable format, aborting:" << Pa_GetErrorText(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (setup.isinput) {
|
||||
|
||||
err = Pa_OpenStream(&audio, &aParams, 0, nativeFormat.sampleRate(), this->chunkSize, paNoFlag, &paHandler::staticWrite, (void*)this);
|
||||
emit setupConverter(nativeFormat, codecType::LPCM, radioFormat, codec, 7, setup.resampleQuality);
|
||||
connect(converter, SIGNAL(converted(audioPacket)), this, SLOT(convertedInput(audioPacket)));
|
||||
}
|
||||
else {
|
||||
err = Pa_OpenStream(&audio, 0, &aParams, nativeFormat.sampleRate(), this->chunkSize, paNoFlag, NULL, NULL);
|
||||
emit setupConverter(radioFormat, codec, nativeFormat, codecType::LPCM, 7, setup.resampleQuality);
|
||||
connect(converter, SIGNAL(converted(audioPacket)), this, SLOT(convertedOutput(audioPacket)));
|
||||
}
|
||||
|
||||
if (err == paNoError) {
|
||||
err = Pa_StartStream(audio);
|
||||
}
|
||||
if (err == paNoError) {
|
||||
isInitialized = true;
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "device successfully opened";
|
||||
}
|
||||
else {
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "failed to open device" << Pa_GetErrorText(err);
|
||||
}
|
||||
|
||||
this->setVolume(setup.localAFgain);
|
||||
|
||||
return isInitialized;
|
||||
}
|
||||
|
||||
|
||||
void paHandler::setVolume(unsigned char volume)
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
this->volume = audiopot[volume] * 5;
|
||||
#else
|
||||
this->volume = audiopot[volume];
|
||||
#endif
|
||||
}
|
||||
|
||||
void paHandler::incomingAudio(audioPacket packet)
|
||||
{
|
||||
packet.volume = volume;
|
||||
if (Pa_IsStreamActive(audio) == 1) {
|
||||
emit sendToConverter(packet);
|
||||
}
|
||||
else
|
||||
{
|
||||
Pa_StartStream(audio);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int paHandler::writeData(const void* inputBuffer, void* outputBuffer,
|
||||
unsigned long nFrames, const PaStreamCallbackTimeInfo * streamTime,
|
||||
PaStreamCallbackFlags status)
|
||||
{
|
||||
Q_UNUSED(outputBuffer);
|
||||
Q_UNUSED(streamTime);
|
||||
Q_UNUSED(status);
|
||||
audioPacket packet;
|
||||
packet.time = QTime::currentTime();
|
||||
packet.sent = 0;
|
||||
packet.volume = volume;
|
||||
memcpy(&packet.guid, setup.guid, GUIDLEN);
|
||||
packet.data.append((char*)inputBuffer, nFrames*nativeFormat.bytesPerFrame());
|
||||
emit sendToConverter(packet);
|
||||
|
||||
if (status == paInputUnderflow) {
|
||||
isUnderrun = true;
|
||||
}
|
||||
else if (status == paInputOverflow) {
|
||||
isOverrun = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isUnderrun = false;
|
||||
isOverrun = false;
|
||||
}
|
||||
|
||||
return paContinue;
|
||||
}
|
||||
|
||||
|
||||
void paHandler::convertedOutput(audioPacket packet) {
|
||||
|
||||
if (packet.data.size() > 0) {
|
||||
|
||||
if (Pa_IsStreamActive(audio) == 1) {
|
||||
PaError err = Pa_WriteStream(audio, (char*)packet.data.data(), packet.data.size() / nativeFormat.bytesPerFrame());
|
||||
|
||||
if (err != paNoError) {
|
||||
qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Error writing audio!";
|
||||
}
|
||||
const PaStreamInfo* info = Pa_GetStreamInfo(audio);
|
||||
currentLatency = packet.time.msecsTo(QTime::currentTime()) + (info->outputLatency * 1000);
|
||||
}
|
||||
|
||||
amplitude = packet.amplitudePeak;
|
||||
emit haveLevels(getAmplitude(), static_cast<quint16>(packet.amplitudeRMS * 255.0), setup.latency, currentLatency, isUnderrun, isOverrun);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void paHandler::convertedInput(audioPacket packet)
|
||||
{
|
||||
if (packet.data.size() > 0) {
|
||||
emit haveAudioData(packet);
|
||||
amplitude = packet.amplitudePeak;
|
||||
const PaStreamInfo* info = Pa_GetStreamInfo(audio);
|
||||
currentLatency = packet.time.msecsTo(QTime::currentTime()) + (info->inputLatency * 1000);
|
||||
emit haveLevels(getAmplitude(), packet.amplitudeRMS, setup.latency, currentLatency, isUnderrun, isOverrun);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void paHandler::changeLatency(const quint16 newSize)
|
||||
{
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Changing latency to: " << newSize << " from " << setup.latency;
|
||||
setup.latency = newSize;
|
||||
latencyAllowance = 0;
|
||||
}
|
||||
|
||||
int paHandler::getLatency()
|
||||
{
|
||||
return currentLatency;
|
||||
}
|
||||
|
||||
|
||||
quint16 paHandler::getAmplitude()
|
||||
{
|
||||
return static_cast<quint16>(amplitude * 255.0);
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
#ifndef PAHANDLER_H
|
||||
#define PAHANDLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QThread>
|
||||
|
||||
#include "portaudio.h"
|
||||
|
||||
#include <QAudioFormat>
|
||||
#include <QTime>
|
||||
#include <QMap>
|
||||
|
||||
|
||||
/* wfview Packet types */
|
||||
#include "packettypes.h"
|
||||
|
||||
/* Logarithmic taper for volume control */
|
||||
#include "audiotaper.h"
|
||||
|
||||
#include "audiohandler.h"
|
||||
|
||||
/* Audio converter class*/
|
||||
#include "audioconverter.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
class paHandler : public audioHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
paHandler(QObject* parent = 0);
|
||||
~paHandler();
|
||||
|
||||
int getLatency();
|
||||
|
||||
using audioHandler::getNextAudioChunk;
|
||||
void getNextAudioChunk(QByteArray& data);
|
||||
quint16 getAmplitude();
|
||||
|
||||
public slots:
|
||||
bool init(audioSetup setup);
|
||||
void changeLatency(const quint16 newSize);
|
||||
void setVolume(unsigned char volume);
|
||||
void convertedInput(audioPacket audio);
|
||||
void convertedOutput(audioPacket audio);
|
||||
void incomingAudio(const audioPacket data);
|
||||
|
||||
|
||||
private slots:
|
||||
|
||||
signals:
|
||||
void audioMessage(QString message);
|
||||
void sendLatency(quint16 newSize);
|
||||
void haveAudioData(const audioPacket& data);
|
||||
void haveLevels(quint16 amplitudePeak, quint16 amplitudeRMS, quint16 latency, quint16 current, bool under, bool over);
|
||||
void setupConverter(QAudioFormat in, codecType codecIn, QAudioFormat out, codecType codecOut, quint8 opus, quint8 resamp);
|
||||
void sendToConverter(audioPacket audio);
|
||||
|
||||
private:
|
||||
|
||||
int writeData(const void* inputBuffer, void* outputBuffer,
|
||||
unsigned long nFrames,
|
||||
const PaStreamCallbackTimeInfo* streamTime,
|
||||
PaStreamCallbackFlags status);
|
||||
static int staticWrite(const void* inputBuffer, void* outputBuffer, unsigned long nFrames, const PaStreamCallbackTimeInfo* streamTime, PaStreamCallbackFlags status, void* userData) {
|
||||
return ((paHandler*)userData)->writeData(inputBuffer, outputBuffer, nFrames, streamTime, status);
|
||||
}
|
||||
|
||||
bool isInitialized = false;
|
||||
PaStream* audio = Q_NULLPTR;
|
||||
PaStreamParameters aParams;
|
||||
const PaDeviceInfo* info;
|
||||
|
||||
quint16 audioLatency;
|
||||
unsigned int chunkSize;
|
||||
|
||||
quint32 lastSeq;
|
||||
quint32 lastSentSeq = 0;
|
||||
|
||||
quint16 currentLatency;
|
||||
float amplitude=0.0;
|
||||
qreal volume = 1.0;
|
||||
|
||||
audioSetup setup;
|
||||
QAudioFormat radioFormat;
|
||||
QAudioFormat nativeFormat;
|
||||
audioConverter* converter = Q_NULLPTR;
|
||||
QThread* converterThread = Q_NULLPTR;
|
||||
bool isUnderrun = false;
|
||||
bool isOverrun = false;
|
||||
int latencyAllowance = 0;
|
||||
};
|
||||
|
||||
#endif // PAHANDLER_H
|
|
@ -0,0 +1,73 @@
|
|||
#ifndef PREFS_H
|
||||
#define PREFS_H
|
||||
|
||||
#include <QString>
|
||||
#include <QColor>
|
||||
#include <QMap>
|
||||
#include "wfviewtypes.h"
|
||||
|
||||
|
||||
struct preferences {
|
||||
// Program:
|
||||
QString version;
|
||||
int majorVersion = 0;
|
||||
int minorVersion = 0;
|
||||
QString gitShort;
|
||||
|
||||
// Interface:
|
||||
bool useFullScreen;
|
||||
bool useSystemTheme;
|
||||
int wfEnable;
|
||||
bool drawPeaks;
|
||||
underlay_t underlayMode = underlayNone;
|
||||
int underlayBufferSize = 64;
|
||||
bool wfAntiAlias;
|
||||
bool wfInterpolate;
|
||||
int wftheme;
|
||||
int plotFloor;
|
||||
int plotCeiling;
|
||||
QString stylesheetPath;
|
||||
unsigned int wflength;
|
||||
bool confirmExit;
|
||||
bool confirmPowerOff;
|
||||
meterKind meter2Type;
|
||||
bool clickDragTuningEnable;
|
||||
|
||||
// Radio:
|
||||
unsigned char radioCIVAddr;
|
||||
bool CIVisRadioModel;
|
||||
bool forceRTSasPTT;
|
||||
int polling_ms;
|
||||
QString serialPortRadio;
|
||||
quint32 serialPortBaud;
|
||||
QString virtualSerialPort;
|
||||
unsigned char localAFgain;
|
||||
audioType audioSystem;
|
||||
|
||||
// Controls:
|
||||
bool enablePTT;
|
||||
bool niceTS;
|
||||
bool automaticSidebandSwitching = true;
|
||||
bool enableUSBControllers;
|
||||
|
||||
// LAN:
|
||||
bool enableLAN;
|
||||
bool enableRigCtlD;
|
||||
quint16 rigCtlPort;
|
||||
int currentColorPresetNumber = 0;
|
||||
quint16 tcpPort;
|
||||
quint8 waterfallFormat;
|
||||
|
||||
// Cluster:
|
||||
bool clusterUdpEnable;
|
||||
bool clusterTcpEnable;
|
||||
int clusterUdpPort;
|
||||
QString clusterTcpServerName;
|
||||
QString clusterTcpUserName;
|
||||
QString clusterTcpPassword;
|
||||
int clusterTimeout;
|
||||
bool clusterSkimmerSpotsEnable;
|
||||
|
||||
};
|
||||
|
||||
#endif // PREFS_H
|
|
@ -0,0 +1,42 @@
|
|||
#ifndef PRINTHEX_H
|
||||
#define PRINTHEX_H
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
|
||||
#include "logcategories.h"
|
||||
|
||||
QString inline getHex(const QByteArray &pdata)
|
||||
{
|
||||
QString head = "---- Begin hex dump -----:\n";
|
||||
QString sdata("DATA: ");
|
||||
QString index("INDEX: ");
|
||||
|
||||
for(int i=0; i < pdata.length(); i++)
|
||||
{
|
||||
sdata.append(QString("%1 ").arg((unsigned char)pdata[i], 2, 16, QChar('0')) );
|
||||
index.append(QString("%1 ").arg(i, 2, 10, QChar('0')));
|
||||
}
|
||||
|
||||
sdata.append("\n");
|
||||
index.append("\n");
|
||||
|
||||
QString tail = "---- End hex dump -----\n";
|
||||
return head + sdata + index + tail;
|
||||
}
|
||||
|
||||
void inline printHexNow(const QByteArray &pdata, const QLoggingCategory &cat)
|
||||
{
|
||||
QString d = getHex(pdata);
|
||||
// These lines are needed to keep the formatting as expected in the log file
|
||||
if(d.endsWith("\n"))
|
||||
{
|
||||
d.chop(1);
|
||||
}
|
||||
QStringList s = d.split("\n");
|
||||
for(int i=0; i < s.length(); i++)
|
||||
{
|
||||
qDebug(cat) << s.at(i);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // PRINTHEX_H
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
// Copyright 2017-2021 Elliott H. Liggett & Phil Taylor
|
||||
|
||||
pttyHandler::pttyHandler(QString pty)
|
||||
pttyHandler::pttyHandler(QString pty, QObject* parent) : QObject(parent)
|
||||
{
|
||||
//constructor
|
||||
if (pty == "" || pty.toLower() == "none")
|
||||
|
@ -73,6 +73,9 @@ void pttyHandler::openPort()
|
|||
// we're good!
|
||||
qInfo(logSerial()) << "Opened pseudoterminal, slave name :" << ptsname(ptfd);
|
||||
|
||||
// Open the slave device to keep alive.
|
||||
ptKeepAlive = open(ptsname(ptfd), O_RDONLY);
|
||||
|
||||
ptReader = new QSocketNotifier(ptfd, QSocketNotifier::Read, this);
|
||||
connect(ptReader, &QSocketNotifier::activated,
|
||||
this, &pttyHandler::receiveDataIn);
|
||||
|
@ -87,7 +90,7 @@ void pttyHandler::openPort()
|
|||
qInfo(logSerial()) << "Could not open pseudo terminal port, please restart.";
|
||||
isConnected = false;
|
||||
serialError = true;
|
||||
emit haveSerialPortError(portName, "Could not open pseudo terminal port. Please restart.");
|
||||
emit havePortError(errorType(portName, "Could not open pseudo terminal port. Please restart."));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -211,16 +214,15 @@ void pttyHandler::receiveDataIn(int fd) {
|
|||
if (civId == 0 && inPortData.length() > lastFE + 2 && (quint8)inPortData[lastFE + 2] > (quint8)0xdf && (quint8)inPortData[lastFE + 2] < (quint8)0xef) {
|
||||
// This is (should be) the remotes CIV id.
|
||||
civId = (quint8)inPortData[lastFE + 2];
|
||||
qInfo(logSerial()) << "pty detected remote CI-V:" << hex << civId;
|
||||
qInfo(logSerial()) << "pty detected remote CI-V:" << QString("0x%1").arg(civId,0,16);
|
||||
}
|
||||
else if (civId != 0 && inPortData.length() > lastFE + 2 && (quint8)inPortData[lastFE + 2] != civId)
|
||||
{
|
||||
civId = (quint8)inPortData[lastFE + 2];
|
||||
qInfo(logSerial()) << "pty remote CI-V changed:" << hex << (quint8)civId;
|
||||
qInfo(logSerial()) << "pty remote CI-V changed:" << QString("0x%1").arg((quint8)civId,0,16);
|
||||
}
|
||||
|
||||
// filter 1A 05 01 12/27 = C-IV transceive command before forwarding on.
|
||||
if (inPortData.contains(QByteArrayLiteral("\x1a\x05\x01\x12")) || inPortData.contains(QByteArrayLiteral("\x1a\x05\x01\x27")))
|
||||
// filter C-IV transceive command before forwarding on.
|
||||
if (inPortData.contains(rigCaps.transceiveCommand))
|
||||
{
|
||||
//qInfo(logSerial()) << "Filtered transceive command";
|
||||
//printHex(inPortData, false, true);
|
||||
|
@ -243,14 +245,14 @@ void pttyHandler::receiveDataIn(int fd) {
|
|||
|
||||
if (rolledBack)
|
||||
{
|
||||
// qInfo(logSerial()) << "Rolled back and was successfull. Length: " << inPortData.length();
|
||||
// qInfo(logSerial()) << "Rolled back and was successful. Length: " << inPortData.length();
|
||||
//printHex(inPortData, false, true);
|
||||
rolledBack = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// did not receive the entire thing so roll back:
|
||||
// qInfo(logSerial()) << "Rolling back transaction. End not detected. Lenth: " << inPortData.length();
|
||||
// qInfo(logSerial()) << "Rolling back transaction. End not detected. Length: " << inPortData.length();
|
||||
//printHex(inPortData, false, true);
|
||||
rolledBack = true;
|
||||
#ifdef Q_OS_WIN
|
||||
|
@ -284,6 +286,10 @@ void pttyHandler::closePort()
|
|||
{
|
||||
QFile::remove(portName);
|
||||
}
|
||||
|
||||
if (ptKeepAlive > 0) {
|
||||
close(ptKeepAlive);
|
||||
}
|
||||
#endif
|
||||
isConnected = false;
|
||||
}
|
||||
|
@ -331,4 +337,10 @@ void pttyHandler::printHex(const QByteArray& pdata, bool printVert, bool printHo
|
|||
qDebug(logSerial()) << "----- End hex dump -----";
|
||||
}
|
||||
|
||||
void pttyHandler::receiveFoundRigID(rigCapabilities rigCaps) {
|
||||
this->rigCaps = rigCaps;
|
||||
qInfo(logSerial) << "Received rigCapabilities for" << rigCaps.modelName;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
#include <QSocketNotifier>
|
||||
#include <QtSerialPort/QSerialPort>
|
||||
|
||||
#include "rigidentities.h"
|
||||
#include "wfviewtypes.h"
|
||||
|
||||
// This class abstracts the comm port in a useful way and connects to
|
||||
// the command creator and command parser.
|
||||
|
||||
|
@ -17,7 +20,7 @@ class pttyHandler : public QObject
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
pttyHandler(QString portName);
|
||||
explicit pttyHandler(QString portName, QObject* parent = nullptr);
|
||||
pttyHandler(QString portName, quint32 baudRate);
|
||||
bool serialError;
|
||||
|
||||
|
@ -27,11 +30,12 @@ private slots:
|
|||
void receiveDataIn(int fd); // from physical port
|
||||
void receiveDataFromRigToPtty(const QByteArray& data);
|
||||
void debugThis();
|
||||
void receiveFoundRigID(rigCapabilities rigCaps);
|
||||
|
||||
signals:
|
||||
void haveTextMessage(QString message); // status, debug only
|
||||
void haveDataFromPort(QByteArray data); // emit this when we have data, connect to rigcommander
|
||||
void haveSerialPortError(const QString port, const QString error);
|
||||
void havePortError(errorType err);
|
||||
void haveStatusUpdate(const QString text);
|
||||
|
||||
private:
|
||||
|
@ -59,6 +63,7 @@ private:
|
|||
bool rolledBack;
|
||||
|
||||
int ptfd; // pseudo-terminal file desc.
|
||||
int ptKeepAlive=0; // Used to keep the pty alive after client disconnects.
|
||||
bool havePt;
|
||||
QString ptDevSlave;
|
||||
|
||||
|
@ -66,8 +71,9 @@ private:
|
|||
mutable QMutex mutex;
|
||||
void printHex(const QByteArray& pdata, bool printVert, bool printHoriz);
|
||||
bool disableTransceive = false;
|
||||
QSocketNotifier *ptReader = nullptr;
|
||||
QSocketNotifier *ptReader = Q_NULLPTR;
|
||||
quint8 civId=0;
|
||||
rigCapabilities rigCaps;
|
||||
};
|
||||
|
||||
#endif // PTTYHANDLER_H
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
QToolTip {
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
background-color: #5A7566;
|
||||
color: white;
|
||||
padding: 0px; /*remove padding, for fix combobox tooltip.*/
|
||||
|
@ -8,7 +8,7 @@ QToolTip {
|
|||
|
||||
QWidget {
|
||||
color: #eff0f1;
|
||||
background-color: #31363b;
|
||||
background-color: #313131;
|
||||
selection-background-color: #3daee9;
|
||||
selection-color: #eff0f1;
|
||||
background-clip: border;
|
||||
|
@ -34,7 +34,7 @@ QCheckBox {
|
|||
}
|
||||
|
||||
QCheckBox:disabled {
|
||||
color: #76797C;
|
||||
color: #767676;
|
||||
}
|
||||
|
||||
QCheckBox::indicator,
|
||||
|
@ -105,7 +105,7 @@ QRadioButton {
|
|||
}
|
||||
|
||||
QRadioButton:disabled {
|
||||
color: #76797C;
|
||||
color: #767676;
|
||||
}
|
||||
|
||||
QRadioButton::indicator {
|
||||
|
@ -149,7 +149,7 @@ QRadioButton::indicator:unchecked:disabled {
|
|||
}
|
||||
|
||||
QMenuBar {
|
||||
background-color: #31363b;
|
||||
background-color: #313131;
|
||||
color: #eff0f1;
|
||||
}
|
||||
|
||||
|
@ -159,11 +159,11 @@ QMenuBar::item {
|
|||
|
||||
QMenuBar::item:selected {
|
||||
background: transparent;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
}
|
||||
|
||||
QMenuBar::item:pressed {
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
background-color: #3daee9;
|
||||
color: #eff0f1;
|
||||
margin-bottom: -1px;
|
||||
|
@ -171,7 +171,7 @@ QMenuBar::item:pressed {
|
|||
}
|
||||
|
||||
QMenu {
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
color: #eff0f1;
|
||||
margin: 2px;
|
||||
}
|
||||
|
@ -248,11 +248,11 @@ QMenu::right-arrow {
|
|||
|
||||
QWidget:disabled {
|
||||
color: #454545;
|
||||
background-color: #31363b;
|
||||
background-color: #313131;
|
||||
}
|
||||
|
||||
QAbstractItemView {
|
||||
alternate-background-color: #31363b;
|
||||
alternate-background-color: #313131;
|
||||
color: #eff0f1;
|
||||
border: 1px solid #3A3939;
|
||||
border-radius: 2px;
|
||||
|
@ -274,7 +274,7 @@ QLineEdit {
|
|||
background-color: #232629;
|
||||
padding: 5px;
|
||||
border-style: solid;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
border-radius: 2px;
|
||||
color: #eff0f1;
|
||||
}
|
||||
|
@ -284,7 +284,7 @@ QAbstractItemView QLineEdit {
|
|||
}
|
||||
|
||||
QGroupBox {
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
border-radius: 2px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
@ -299,7 +299,7 @@ QGroupBox::title {
|
|||
|
||||
QAbstractScrollArea {
|
||||
border-radius: 2px;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
|
@ -426,7 +426,7 @@ QScrollBar::sub-page:vertical {
|
|||
QTextEdit {
|
||||
background-color: #232629;
|
||||
color: #eff0f1;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
}
|
||||
|
||||
QPlainTextEdit {
|
||||
|
@ -434,14 +434,14 @@ QPlainTextEdit {
|
|||
;
|
||||
color: #eff0f1;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
}
|
||||
|
||||
QHeaderView::section {
|
||||
background-color: #76797C;
|
||||
background-color: #767676;
|
||||
color: #eff0f1;
|
||||
padding: 5px;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
}
|
||||
|
||||
QSizeGrip {
|
||||
|
@ -451,24 +451,24 @@ QSizeGrip {
|
|||
}
|
||||
|
||||
QMainWindow::separator {
|
||||
background-color: #31363b;
|
||||
background-color: #313131;
|
||||
color: white;
|
||||
padding-left: 4px;
|
||||
spacing: 2px;
|
||||
border: 1px dashed #76797C;
|
||||
border: 1px dashed #767676;
|
||||
}
|
||||
|
||||
QMainWindow::separator:hover {
|
||||
background-color: #787876;
|
||||
color: white;
|
||||
padding-left: 4px;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
spacing: 2px;
|
||||
}
|
||||
|
||||
QMenu::separator {
|
||||
height: 1px;
|
||||
background-color: #76797C;
|
||||
background-color: #767676;
|
||||
color: white;
|
||||
padding-left: 4px;
|
||||
margin-left: 10px;
|
||||
|
@ -477,12 +477,12 @@ QMenu::separator {
|
|||
|
||||
QFrame {
|
||||
border-radius: 2px;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
}
|
||||
|
||||
QFrame[frameShape="0"] {
|
||||
border-radius: 2px;
|
||||
border: 1px transparent #76797C;
|
||||
border: 1px transparent #767676;
|
||||
}
|
||||
|
||||
QStackedWidget {
|
||||
|
@ -491,7 +491,7 @@ QStackedWidget {
|
|||
|
||||
QToolBar {
|
||||
border: 1px transparent #393838;
|
||||
background: 1px solid #31363b;
|
||||
background: 1px solid #313131;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
@ -512,14 +512,14 @@ QToolBar::separator:vertical {
|
|||
}
|
||||
|
||||
QToolButton#qt_toolbar_ext_button {
|
||||
background: #58595a
|
||||
background: #585858
|
||||
}
|
||||
|
||||
QPushButton {
|
||||
color: #eff0f1;
|
||||
background-color: #31363b;
|
||||
background-color: #313131;
|
||||
border-width: 1px;
|
||||
border-color: #76797C;
|
||||
border-color: #767676;
|
||||
border-style: solid;
|
||||
padding: 5px;
|
||||
border-radius: 2px;
|
||||
|
@ -527,7 +527,7 @@ QPushButton {
|
|||
}
|
||||
|
||||
QPushButton:disabled {
|
||||
background-color: #31363b;
|
||||
background-color: #313131;
|
||||
border-width: 1px;
|
||||
border-color: #454545;
|
||||
border-style: solid;
|
||||
|
@ -553,14 +553,14 @@ QPushButton:pressed {
|
|||
QComboBox {
|
||||
selection-background-color: #3daee9;
|
||||
border-style: solid;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
border-radius: 2px;
|
||||
padding: 5px;
|
||||
min-width: 75px;
|
||||
}
|
||||
|
||||
QPushButton:checked {
|
||||
background-color: #76797C;
|
||||
background-color: #767676;
|
||||
border-color: #6A6969;
|
||||
}
|
||||
|
||||
|
@ -585,7 +585,7 @@ QComboBox:on {
|
|||
QComboBox QAbstractItemView {
|
||||
background-color: #232629;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
selection-background-color: #18465d;
|
||||
}
|
||||
|
||||
|
@ -612,7 +612,7 @@ QComboBox::down-arrow:focus {
|
|||
|
||||
QAbstractSpinBox {
|
||||
padding: 5px;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
background-color: #232629;
|
||||
color: #eff0f1;
|
||||
border-radius: 2px;
|
||||
|
@ -664,7 +664,7 @@ QTabWidget {
|
|||
}
|
||||
|
||||
QTabWidget::pane {
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
padding: 5px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
@ -702,9 +702,9 @@ QTabBar::close-button:pressed {
|
|||
|
||||
QTabBar::tab:top {
|
||||
color: #eff0f1;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
border-bottom: 1px transparent black;
|
||||
background-color: #31363b;
|
||||
background-color: #313131;
|
||||
padding: 5px;
|
||||
min-width: 50px;
|
||||
border-top-left-radius: 2px;
|
||||
|
@ -714,7 +714,7 @@ QTabBar::tab:top {
|
|||
QTabBar::tab:top:selected {
|
||||
color: #eff0f1;
|
||||
background-color: #54575B;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
border-bottom: 2px solid #3daee9;
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
|
@ -729,9 +729,9 @@ QTabBar::tab:top:!selected:hover {
|
|||
|
||||
QTabBar::tab:bottom {
|
||||
color: #eff0f1;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
border-top: 1px transparent black;
|
||||
background-color: #31363b;
|
||||
background-color: #313131;
|
||||
padding: 5px;
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
|
@ -741,7 +741,7 @@ QTabBar::tab:bottom {
|
|||
QTabBar::tab:bottom:selected {
|
||||
color: #eff0f1;
|
||||
background-color: #54575B;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
border-top: 2px solid #3daee9;
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
|
@ -756,9 +756,9 @@ QTabBar::tab:bottom:!selected:hover {
|
|||
|
||||
QTabBar::tab:left {
|
||||
color: #eff0f1;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
border-left: 1px transparent black;
|
||||
background-color: #31363b;
|
||||
background-color: #313131;
|
||||
padding: 5px;
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
|
@ -768,7 +768,7 @@ QTabBar::tab:left {
|
|||
QTabBar::tab:left:selected {
|
||||
color: #eff0f1;
|
||||
background-color: #54575B;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
border-left: 2px solid #3daee9;
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
|
@ -783,9 +783,9 @@ QTabBar::tab:left:!selected:hover {
|
|||
|
||||
QTabBar::tab:right {
|
||||
color: #eff0f1;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
border-right: 1px transparent black;
|
||||
background-color: #31363b;
|
||||
background-color: #313131;
|
||||
padding: 5px;
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
|
@ -795,7 +795,7 @@ QTabBar::tab:right {
|
|||
QTabBar::tab:right:selected {
|
||||
color: #eff0f1;
|
||||
background-color: #54575B;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
border-right: 2px solid #3daee9;
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
|
@ -822,7 +822,7 @@ QTabBar QToolButton::left-arrow:disabled {
|
|||
}
|
||||
|
||||
QDockWidget {
|
||||
background: #31363b;
|
||||
background: #313131;
|
||||
border: 1px solid #403F3F;
|
||||
titlebar-close-icon: url(:/qss_icons/rc/close.png);
|
||||
titlebar-normal-icon: url(:/qss_icons/rc/undock.png);
|
||||
|
@ -848,7 +848,7 @@ QDockWidget::float-button:pressed {
|
|||
|
||||
QTreeView,
|
||||
QListView {
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
background-color: #232629;
|
||||
}
|
||||
|
||||
|
@ -980,7 +980,7 @@ QSlider::handle:vertical {
|
|||
|
||||
QToolButton {
|
||||
background-color: transparent;
|
||||
border: 1px transparent #76797C;
|
||||
border: 1px transparent #767676;
|
||||
border-radius: 2px;
|
||||
margin: 3px;
|
||||
padding: 5px;
|
||||
|
@ -990,7 +990,7 @@ QToolButton[popupMode="1"] {
|
|||
/* only for MenuButtonPopup */
|
||||
padding-right: 20px;
|
||||
/* make way for the popup button */
|
||||
border: 1px #76797C;
|
||||
border: 1px #767676;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
@ -998,7 +998,7 @@ QToolButton[popupMode="2"] {
|
|||
/* only for InstantPopup */
|
||||
padding-right: 10px;
|
||||
/* make way for the popup button */
|
||||
border: 1px #76797C;
|
||||
border: 1px #767676;
|
||||
}
|
||||
|
||||
QToolButton:hover,
|
||||
|
@ -1030,7 +1030,7 @@ QToolButton::menu-indicator {
|
|||
/* the subcontrols below are used only in the MenuButtonPopup mode */
|
||||
|
||||
QToolButton::menu-button {
|
||||
border: 1px transparent #76797C;
|
||||
border: 1px transparent #767676;
|
||||
border-top-right-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
/* 16px width + 4px for border = 20px allocated above */
|
||||
|
@ -1043,7 +1043,7 @@ QToolButton::menu-arrow {
|
|||
}
|
||||
|
||||
QToolButton::menu-arrow:open {
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
}
|
||||
|
||||
QPushButton::menu-indicator {
|
||||
|
@ -1053,8 +1053,8 @@ QPushButton::menu-indicator {
|
|||
}
|
||||
|
||||
QTableView {
|
||||
border: 1px solid #76797C;
|
||||
gridline-color: #31363b;
|
||||
border: 1px solid #767676;
|
||||
gridline-color: #313131;
|
||||
background-color: #232629;
|
||||
}
|
||||
|
||||
|
@ -1078,7 +1078,7 @@ QListView::item:selected:active {
|
|||
}
|
||||
|
||||
QHeaderView {
|
||||
background-color: #31363b;
|
||||
background-color: #313131;
|
||||
border: 1px transparent;
|
||||
border-radius: 0px;
|
||||
margin: 0px;
|
||||
|
@ -1086,17 +1086,17 @@ QHeaderView {
|
|||
}
|
||||
|
||||
QHeaderView::section {
|
||||
background-color: #31363b;
|
||||
background-color: #313131;
|
||||
color: #eff0f1;
|
||||
padding: 5px;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
border-radius: 0px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
QHeaderView::section::vertical::first,
|
||||
QHeaderView::section::vertical::only-one {
|
||||
border-top: 1px solid #76797C;
|
||||
border-top: 1px solid #767676;
|
||||
}
|
||||
|
||||
QHeaderView::section::vertical {
|
||||
|
@ -1105,7 +1105,7 @@ QHeaderView::section::vertical {
|
|||
|
||||
QHeaderView::section::horizontal::first,
|
||||
QHeaderView::section::horizontal::only-one {
|
||||
border-left: 1px solid #76797C;
|
||||
border-left: 1px solid #767676;
|
||||
}
|
||||
|
||||
QHeaderView::section::horizontal {
|
||||
|
@ -1129,8 +1129,8 @@ QHeaderView::up-arrow {
|
|||
}
|
||||
|
||||
QTableCornerButton::section {
|
||||
background-color: #31363b;
|
||||
border: 1px transparent #76797C;
|
||||
background-color: #313131;
|
||||
border: 1px transparent #767676;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
|
@ -1141,9 +1141,9 @@ QToolBox {
|
|||
|
||||
QToolBox::tab {
|
||||
color: #eff0f1;
|
||||
background-color: #31363b;
|
||||
border: 1px solid #76797C;
|
||||
border-bottom: 1px transparent #31363b;
|
||||
background-color: #313131;
|
||||
border: 1px solid #767676;
|
||||
border-bottom: 1px transparent #313131;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
}
|
||||
|
@ -1151,7 +1151,7 @@ QToolBox::tab {
|
|||
QToolBox::tab:selected {
|
||||
/* italicize selected tabs */
|
||||
font: italic;
|
||||
background-color: #31363b;
|
||||
background-color: #313131;
|
||||
border-color: #3daee9;
|
||||
}
|
||||
|
||||
|
@ -1161,16 +1161,16 @@ QStatusBar::item {
|
|||
|
||||
QFrame[height="3"],
|
||||
QFrame[width="3"] {
|
||||
background-color: #76797C;
|
||||
background-color: #767676;
|
||||
}
|
||||
|
||||
QSplitter::handle {
|
||||
border: 1px dashed #76797C;
|
||||
border: 1px dashed #767676;
|
||||
}
|
||||
|
||||
QSplitter::handle:hover {
|
||||
background-color: #787876;
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
}
|
||||
|
||||
QSplitter::handle:horizontal {
|
||||
|
@ -1182,7 +1182,7 @@ QSplitter::handle:vertical {
|
|||
}
|
||||
|
||||
QProgressBar {
|
||||
border: 1px solid #76797C;
|
||||
border: 1px solid #767676;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
@ -4,20 +4,23 @@
|
|||
static const int SIZE = 16;
|
||||
static const QString greenSS = QString("color: white;border-radius: %1;background-color: qlineargradient(spread:pad, x1:0.145, y1:0.16, x2:1, y2:1, stop:0 rgba(20, 252, 7, 255), stop:1 rgba(25, 134, 5, 255));").arg(SIZE / 2);
|
||||
static const QString redSS = QString("color: white;border-radius: %1;background-color: qlineargradient(spread:pad, x1:0.145, y1:0.16, x2:0.92, y2:0.988636, stop:0 rgba(255, 12, 12, 255), stop:0.869347 rgba(103, 0, 0, 255));").arg(SIZE / 2);
|
||||
static const QString rgSplitSS = QString("color: white;border-radius: %1;background-color: qlineargradient(spread:pad, x1:0.4, y1:0.5, x2:0.6, y2:0.5, stop:0 rgba(255, 0, 0, 255), stop:1.0 rgba(0, 255, 0, 255));").arg(SIZE / 2);
|
||||
static const QString orangeSS = QString("color: white;border-radius: %1;background-color: qlineargradient(spread:pad, x1:0.232, y1:0.272, x2:0.98, y2:0.959773, stop:0 rgba(255, 113, 4, 255), stop:1 rgba(91, 41, 7, 255))").arg(SIZE / 2);
|
||||
static const QString blueSS = QString("color: white;border-radius: %1;background-color: qlineargradient(spread:pad, x1:0.04, y1:0.0565909, x2:0.799, y2:0.795, stop:0 rgba(203, 220, 255, 255), stop:0.41206 rgba(0, 115, 255, 255), stop:1 rgba(0, 49, 109, 255));").arg(SIZE / 2);
|
||||
static const QString blankSS = QString("color: white;border-radius: %1;background-color: qlineargradient(spread:pad, x1:0.04, y1:0.0565909, x2:0.799, y2:0.795, stop:0 rgba(203, 220, 255, 0), stop:0.41206 rgba(0, 115, 255, 0), stop:1 rgba(0, 49, 109, 0));").arg(SIZE / 2);
|
||||
|
||||
QLedLabel::QLedLabel(QWidget* parent) :
|
||||
QLabel(parent)
|
||||
{
|
||||
//Set to ok by default
|
||||
setState(StateOkBlue);
|
||||
baseColor = QColor(0, 115, 255, 255);
|
||||
setFixedSize(SIZE, SIZE);
|
||||
}
|
||||
|
||||
void QLedLabel::setState(State state)
|
||||
{
|
||||
qInfo() << "setState" << state;
|
||||
//Debug() << "LED: setState" << state;
|
||||
switch (state) {
|
||||
case StateOk:
|
||||
setStyleSheet(greenSS);
|
||||
|
@ -29,6 +32,14 @@ void QLedLabel::setState(State state)
|
|||
setStyleSheet(redSS);
|
||||
break;
|
||||
case StateOkBlue:
|
||||
setStyleSheet(blueSS);
|
||||
break;
|
||||
case StateBlank:
|
||||
setStyleSheet(blankSS);
|
||||
break;
|
||||
case StateSplitErrorOk:
|
||||
setStyleSheet(rgSplitSS);
|
||||
break;
|
||||
default:
|
||||
setStyleSheet(blueSS);
|
||||
break;
|
||||
|
@ -38,4 +49,62 @@ void QLedLabel::setState(State state)
|
|||
void QLedLabel::setState(bool state)
|
||||
{
|
||||
setState(state ? StateOk : StateError);
|
||||
}
|
||||
}
|
||||
|
||||
void QLedLabel::setColor(QColor customColor, bool applyGradient=true)
|
||||
{
|
||||
QColor top,middle,bottom;
|
||||
this->baseColor = customColor;
|
||||
|
||||
if(applyGradient)
|
||||
{
|
||||
top = customColor.lighter(200);
|
||||
middle = customColor;
|
||||
bottom = customColor.darker(200);
|
||||
} else {
|
||||
top = customColor;
|
||||
middle = customColor;
|
||||
bottom = customColor;
|
||||
}
|
||||
|
||||
// Stop 0 is the upper corner color, white-ish
|
||||
// Stop 0.4 is the middle color
|
||||
// Stop 1 is the bottom-right corner, darker.
|
||||
|
||||
QString colorSS = QString("color: white;\
|
||||
border-radius: %1;background-color: \
|
||||
qlineargradient(spread:pad, x1:0.04, \
|
||||
y1:0.0565909, x2:0.799, y2:0.795, \
|
||||
stop:0 \
|
||||
rgba(%2, %3, %4, %5), \
|
||||
stop:0.41206 \
|
||||
rgba(%6, %7, %8, %9), \
|
||||
stop:1 \
|
||||
rgba(%10, %11, %12, %13));").arg(SIZE / 2)
|
||||
.arg(top.red()).arg(top.green()).arg(top.blue()).arg(top.alpha())
|
||||
.arg(middle.red()).arg(middle.green()).arg(middle.blue()).arg(middle.alpha())
|
||||
.arg(bottom.red()).arg(bottom.green()).arg(bottom.blue()).arg(bottom.alpha());
|
||||
|
||||
setStyleSheet(colorSS);
|
||||
}
|
||||
|
||||
void QLedLabel::setColor(QString colorString, bool applyGradient=true)
|
||||
{
|
||||
QColor c = QColor(colorString);
|
||||
setColor(c, applyGradient);
|
||||
}
|
||||
|
||||
void QLedLabel::setColor(QColor c)
|
||||
{
|
||||
this->setColor(c, true);
|
||||
}
|
||||
|
||||
void QLedLabel::setColor(QString s)
|
||||
{
|
||||
this->setColor(s, true);
|
||||
}
|
||||
|
||||
QColor QLedLabel::getColor()
|
||||
{
|
||||
return baseColor;
|
||||
}
|
||||
|
|
12
qledlabel.h
|
@ -13,7 +13,9 @@ public:
|
|||
StateOk,
|
||||
StateOkBlue,
|
||||
StateWarning,
|
||||
StateError
|
||||
StateError,
|
||||
StateSplitErrorOk,
|
||||
StateBlank
|
||||
};
|
||||
|
||||
|
||||
|
@ -22,6 +24,14 @@ signals:
|
|||
public slots:
|
||||
void setState(State state);
|
||||
void setState(bool state);
|
||||
void setColor(QColor customColor, bool applyGradient);
|
||||
void setColor(QString colorString, bool applyGradient);
|
||||
void setColor(QColor c);
|
||||
void setColor(QString s);
|
||||
QColor getColor();
|
||||
|
||||
private:
|
||||
QColor baseColor;
|
||||
};
|
||||
|
||||
#endif // QLEDLABEL_H
|
||||
|
|
|
@ -2,31 +2,7 @@
|
|||
#define REPEATERATTRIBUTES_H
|
||||
#include <QMetaType>
|
||||
|
||||
enum duplexMode {
|
||||
dmSplitOff=0x00,
|
||||
dmSplitOn=0x01,
|
||||
dmSimplex=0x10,
|
||||
dmDupMinus=0x11,
|
||||
dmDupPlus=0x12,
|
||||
dmDupRPS=0x13,
|
||||
dmDupAutoOn=0x26,
|
||||
dmDupAutoOff=0x36
|
||||
};
|
||||
// TODO: Remove this file as it does nothing.
|
||||
|
||||
// Here, T=tone, D=DCS, N=none
|
||||
// And the naming convention order is Transmit Receive
|
||||
enum rptAccessTxRx {
|
||||
ratrNN=0x00,
|
||||
ratrTN=0x01, // "TONE" (T only)
|
||||
ratrNT=0x02, // "TSQL" (R only)
|
||||
ratrDD=0x03, // "DTCS" (TR)
|
||||
ratrDN=0x06, // "DTCS(T)"
|
||||
ratrTD=0x07, // "TONE(T) / TSQL(R)"
|
||||
ratrDT=0x08, // "DTCS(T) / TSQL(R)"
|
||||
ratrTT=0x09 // "TONE(T) / TSQL(R)"
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(enum duplexMode)
|
||||
Q_DECLARE_METATYPE(enum rptAccessTxRx)
|
||||
|
||||
#endif // REPEATERATTRIBUTES_H
|
||||
|
|
|
@ -7,25 +7,18 @@ repeaterSetup::repeaterSetup(QWidget *parent) :
|
|||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->autoTrackLiveBtn->setEnabled(false); // until we set split enabled.
|
||||
ui->warningFMLabel->setVisible(false);
|
||||
// populate the CTCSS combo box:
|
||||
populateTones();
|
||||
|
||||
// populate the DCS combo box:
|
||||
populateDTCS();
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
ui->debugBtn->setVisible(true);
|
||||
ui->rptReadRigBtn->setVisible(true);
|
||||
#else
|
||||
ui->debugBtn->setVisible(false);
|
||||
ui->rptReadRigBtn->setVisible(false);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
repeaterSetup::~repeaterSetup()
|
||||
{
|
||||
// Trying this for more consistant destruction
|
||||
// Trying this for more consistent destruction
|
||||
rig.inputs.clear();
|
||||
rig.preamps.clear();
|
||||
rig.attenuators.clear();
|
||||
|
@ -48,7 +41,6 @@ void repeaterSetup::setRig(rigCapabilities inRig)
|
|||
ui->toneTone->setDisabled(true);
|
||||
ui->toneTSQL->setDisabled(true);
|
||||
}
|
||||
|
||||
if(rig.hasDTCS)
|
||||
{
|
||||
ui->rptDTCSCombo->setDisabled(false);
|
||||
|
@ -61,6 +53,55 @@ void repeaterSetup::setRig(rigCapabilities inRig)
|
|||
ui->rptDTCSInvertRx->setDisabled(true);
|
||||
ui->rptDTCSInvertTx->setDisabled(true);
|
||||
}
|
||||
if(rig.hasVFOAB)
|
||||
{
|
||||
ui->selABtn->setDisabled(false);
|
||||
ui->selBBtn->setDisabled(false);
|
||||
ui->aEqBBtn->setDisabled(false);
|
||||
ui->swapABBtn->setDisabled(false);
|
||||
} else {
|
||||
ui->selABtn->setDisabled(true);
|
||||
ui->selBBtn->setDisabled(true);
|
||||
ui->aEqBBtn->setDisabled(true);
|
||||
ui->swapABBtn->setDisabled(true);
|
||||
}
|
||||
if(rig.hasVFOMS)
|
||||
{
|
||||
ui->selMainBtn->setDisabled(false);
|
||||
ui->selSubBtn->setDisabled(false);
|
||||
ui->mEqSBtn->setDisabled(false);
|
||||
ui->swapMSBtn->setDisabled(false);
|
||||
} else {
|
||||
ui->selMainBtn->setDisabled(true);
|
||||
ui->selSubBtn->setDisabled(true);
|
||||
ui->mEqSBtn->setDisabled(true);
|
||||
ui->swapMSBtn->setDisabled(true);
|
||||
}
|
||||
if(rig.hasVFOMS && rig.hasVFOAB)
|
||||
{
|
||||
// Rigs that have both AB and MS
|
||||
// do not have a swap AB command.
|
||||
ui->swapABBtn->setDisabled(true);
|
||||
}
|
||||
if(rig.hasSpecifyMainSubCmd)
|
||||
{
|
||||
ui->setRptrSubVFOBtn->setEnabled(true);
|
||||
ui->setToneSubVFOBtn->setEnabled(true);
|
||||
ui->setSplitRptrToneChk->setEnabled(true);
|
||||
} else {
|
||||
ui->setRptrSubVFOBtn->setDisabled(true);
|
||||
ui->setToneSubVFOBtn->setDisabled(true);
|
||||
ui->setSplitRptrToneChk->setDisabled(true);
|
||||
}
|
||||
ui->rptAutoBtn->setEnabled(rig.hasRepeaterModes);
|
||||
ui->rptDupMinusBtn->setEnabled(rig.hasRepeaterModes);
|
||||
ui->rptDupPlusBtn->setEnabled(rig.hasRepeaterModes);
|
||||
ui->rptSimplexBtn->setEnabled(rig.hasRepeaterModes);
|
||||
ui->rptrOffsetEdit->setEnabled(rig.hasRepeaterModes);
|
||||
ui->rptrOffsetSetBtn->setEnabled(rig.hasRepeaterModes);
|
||||
ui->setToneSubVFOBtn->setEnabled(rig.hasSpecifyMainSubCmd);
|
||||
ui->setRptrSubVFOBtn->setEnabled(rig.hasSpecifyMainSubCmd);
|
||||
ui->quickSplitChk->setVisible(rig.hasQuickSplitCommand);
|
||||
}
|
||||
|
||||
void repeaterSetup::populateTones()
|
||||
|
@ -235,44 +276,81 @@ void repeaterSetup::populateDTCS()
|
|||
void repeaterSetup::receiveDuplexMode(duplexMode dm)
|
||||
{
|
||||
currentdm = dm;
|
||||
ui->splitEnableChk->blockSignals(true);
|
||||
switch(dm)
|
||||
{
|
||||
case dmSimplex:
|
||||
case dmSplitOff:
|
||||
ui->splitOffBtn->setChecked(true);
|
||||
ui->autoTrackLiveBtn->setDisabled(true);
|
||||
break;
|
||||
case dmSplitOn:
|
||||
ui->splitEnableChk->setChecked(true);
|
||||
ui->rptSimplexBtn->setChecked(false);
|
||||
ui->rptDupPlusBtn->setChecked(false);
|
||||
ui->autoTrackLiveBtn->setEnabled(true);
|
||||
ui->rptDupMinusBtn->setChecked(false);
|
||||
break;
|
||||
case dmSimplex:
|
||||
ui->rptSimplexBtn->setChecked(true);
|
||||
//ui->splitEnableChk->setChecked(false);
|
||||
ui->autoTrackLiveBtn->setDisabled(true);
|
||||
break;
|
||||
case dmDupPlus:
|
||||
ui->rptDupPlusBtn->setChecked(true);
|
||||
//ui->splitEnableChk->setChecked(false);
|
||||
ui->autoTrackLiveBtn->setDisabled(true);
|
||||
break;
|
||||
case dmDupMinus:
|
||||
ui->rptDupMinusBtn->setChecked(true);
|
||||
//ui->splitEnableChk->setChecked(false);
|
||||
ui->autoTrackLiveBtn->setDisabled(true);
|
||||
break;
|
||||
default:
|
||||
qDebug() << "Did not understand duplex/split/repeater value of " << (unsigned char)dm;
|
||||
break;
|
||||
}
|
||||
ui->splitEnableChk->blockSignals(false);
|
||||
}
|
||||
|
||||
void repeaterSetup::handleRptAccessMode(rptAccessTxRx tmode)
|
||||
{
|
||||
// ratrXY
|
||||
// X = Transmit (T)one or (N)one or (D)CS
|
||||
// Y = Receive (T)sql or (N)one or (D)CS
|
||||
switch(tmode)
|
||||
{
|
||||
case ratrNN:
|
||||
ui->toneNone->setChecked(true);
|
||||
break;
|
||||
case ratrTT:
|
||||
ui->toneTSQL->setChecked(true);
|
||||
break;
|
||||
case ratrTN:
|
||||
ui->toneTone->setChecked(true);
|
||||
break;
|
||||
case ratrDD:
|
||||
ui->toneDTCS->setChecked(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case ratrNN:
|
||||
ui->toneNone->setChecked(true);
|
||||
break;
|
||||
case ratrTT:
|
||||
case ratrNT:
|
||||
ui->toneTSQL->setChecked(true);
|
||||
break;
|
||||
case ratrTN:
|
||||
ui->toneTone->setChecked(true);
|
||||
break;
|
||||
case ratrDD:
|
||||
ui->toneDTCS->setChecked(true);
|
||||
break;
|
||||
case ratrTONEoff:
|
||||
ui->toneTone->setChecked(false);
|
||||
break;
|
||||
case ratrTONEon:
|
||||
ui->toneTone->setChecked(true);
|
||||
break;
|
||||
case ratrTSQLoff:
|
||||
ui->toneTSQL->setChecked(false);
|
||||
break;
|
||||
case ratrTSQLon:
|
||||
ui->toneTSQL->setChecked(true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if( !ui->toneTSQL->isChecked() && !ui->toneTone->isChecked() && !ui->toneDTCS->isChecked())
|
||||
{
|
||||
ui->toneNone->setChecked(true);
|
||||
}
|
||||
|
||||
(void)tmode;
|
||||
}
|
||||
|
||||
void repeaterSetup::handleTone(quint16 tone)
|
||||
|
@ -296,11 +374,112 @@ void repeaterSetup::handleDTCS(quint16 dcode, bool tinv, bool rinv)
|
|||
ui->rptDTCSInvertRx->setChecked(rinv);
|
||||
}
|
||||
|
||||
void repeaterSetup::handleUpdateCurrentMainFrequency(freqt mainfreq)
|
||||
{
|
||||
if(amTransmitting)
|
||||
return;
|
||||
|
||||
// Track if autoTrack enabled, split enabled, and there's a split defined.
|
||||
if(ui->autoTrackLiveBtn->isChecked() && (currentdm == dmSplitOn) && !ui->splitOffsetEdit->text().isEmpty())
|
||||
{
|
||||
if(currentMainFrequency.Hz != mainfreq.Hz)
|
||||
{
|
||||
this->currentMainFrequency = mainfreq;
|
||||
if(!ui->splitTransmitFreqEdit->hasFocus())
|
||||
{
|
||||
if(usedPlusSplit)
|
||||
{
|
||||
on_splitPlusButton_clicked();
|
||||
} else {
|
||||
on_splitMinusBtn_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
if(ui->setSplitRptrToneChk->isChecked())
|
||||
{
|
||||
// TODO, not really needed if the op
|
||||
// just sets the tone when needed, as it will do both bands.
|
||||
}
|
||||
}
|
||||
this->currentMainFrequency = mainfreq;
|
||||
}
|
||||
|
||||
void repeaterSetup::handleUpdateCurrentMainMode(mode_info m)
|
||||
{
|
||||
// Used to set the secondary VFO to the same mode
|
||||
// (generally FM)
|
||||
// NB: We don't accept values during transmit as they
|
||||
// may represent the inactive VFO
|
||||
if(!amTransmitting)
|
||||
{
|
||||
this->currentModeMain = m;
|
||||
this->modeTransmitVFO = m;
|
||||
this->modeTransmitVFO.VFO = inactiveVFO;
|
||||
if(m.mk == modeFM)
|
||||
ui->warningFMLabel->setVisible(false);
|
||||
else
|
||||
ui->warningFMLabel->setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
void repeaterSetup::handleRptOffsetFrequency(freqt f)
|
||||
{
|
||||
// Called when a new offset is available from the radio.
|
||||
QString offsetstr = QString::number(f.Hz / double(1E6), 'f', 4);
|
||||
|
||||
if(!ui->rptrOffsetEdit->hasFocus())
|
||||
{
|
||||
ui->rptrOffsetEdit->setText(offsetstr);
|
||||
currentOffset = f;
|
||||
}
|
||||
}
|
||||
|
||||
void repeaterSetup::handleTransmitStatus(bool amTransmitting)
|
||||
{
|
||||
this->amTransmitting = amTransmitting;
|
||||
}
|
||||
|
||||
void repeaterSetup::showEvent(QShowEvent *event)
|
||||
{
|
||||
emit getDuplexMode();
|
||||
emit getSplitModeEnabled();
|
||||
if(rig.hasRepeaterModes)
|
||||
emit getRptDuplexOffset();
|
||||
QMainWindow::showEvent(event);
|
||||
(void)event;
|
||||
}
|
||||
|
||||
void repeaterSetup::on_splitEnableChk_clicked()
|
||||
{
|
||||
emit setDuplexMode(dmSplitOn);
|
||||
ui->autoTrackLiveBtn->setEnabled(true);
|
||||
|
||||
if(ui->autoTrackLiveBtn->isChecked() && !ui->splitOffsetEdit->text().isEmpty())
|
||||
{
|
||||
if(usedPlusSplit)
|
||||
{
|
||||
on_splitPlusButton_clicked();
|
||||
} else {
|
||||
on_splitMinusBtn_clicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void repeaterSetup::on_splitOffBtn_clicked()
|
||||
{
|
||||
emit setDuplexMode(dmSplitOff);
|
||||
ui->autoTrackLiveBtn->setDisabled(true);
|
||||
}
|
||||
|
||||
void repeaterSetup::on_rptSimplexBtn_clicked()
|
||||
{
|
||||
// Simplex
|
||||
emit setDuplexMode(dmDupAutoOff);
|
||||
emit setDuplexMode(dmSimplex);
|
||||
emit setDuplexMode(dmSplitOff);
|
||||
if(rig.hasRepeaterModes)
|
||||
{
|
||||
emit setDuplexMode(dmDupAutoOff);
|
||||
emit setDuplexMode(dmSimplex);
|
||||
}
|
||||
}
|
||||
|
||||
void repeaterSetup::on_rptDupPlusBtn_clicked()
|
||||
|
@ -324,20 +503,29 @@ void repeaterSetup::on_rptAutoBtn_clicked()
|
|||
emit setDuplexMode(dmDupAutoOn);
|
||||
}
|
||||
|
||||
void repeaterSetup::on_rptReadRigBtn_clicked()
|
||||
{
|
||||
emit getDuplexMode();
|
||||
}
|
||||
|
||||
void repeaterSetup::on_rptToneCombo_activated(int tindex)
|
||||
{
|
||||
quint16 tone=0;
|
||||
tone = (quint16)ui->rptToneCombo->itemData(tindex).toUInt();
|
||||
rptrTone_t rt;
|
||||
rt.tone = tone;
|
||||
bool updateSub = ui->setSplitRptrToneChk->isEnabled() && ui->setSplitRptrToneChk->isChecked();
|
||||
if(ui->toneTone->isChecked())
|
||||
{
|
||||
emit setTone(tone);
|
||||
emit setTone(rt);
|
||||
if(updateSub)
|
||||
{
|
||||
rt.useSecondaryVFO = true;
|
||||
emit setTone(rt);
|
||||
}
|
||||
|
||||
} else if (ui->toneTSQL->isChecked()) {
|
||||
emit setTSQL(tone);
|
||||
emit setTSQL(rt);
|
||||
if(updateSub)
|
||||
{
|
||||
rt.useSecondaryVFO = true;
|
||||
emit setTone(rt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -353,45 +541,304 @@ void repeaterSetup::on_rptDTCSCombo_activated(int index)
|
|||
void repeaterSetup::on_toneNone_clicked()
|
||||
{
|
||||
rptAccessTxRx rm;
|
||||
rptrAccessData_t rd;
|
||||
rm = ratrNN;
|
||||
emit setRptAccessMode(rm);
|
||||
rd.accessMode = rm;
|
||||
emit setRptAccessMode(rd);
|
||||
bool updateSub = ui->setSplitRptrToneChk->isEnabled() && ui->setSplitRptrToneChk->isChecked();
|
||||
|
||||
if(updateSub)
|
||||
{
|
||||
rd.useSecondaryVFO = true;
|
||||
emit setRptAccessMode(rd);
|
||||
}
|
||||
}
|
||||
|
||||
void repeaterSetup::on_toneTone_clicked()
|
||||
{
|
||||
rptAccessTxRx rm;
|
||||
rptrAccessData_t rd;
|
||||
rm = ratrTN;
|
||||
emit setRptAccessMode(rm);
|
||||
emit setTone((quint16)ui->rptToneCombo->currentData().toUInt());
|
||||
rd.accessMode = rm;
|
||||
rptrTone_t rt;
|
||||
rt.tone = (quint16)ui->rptToneCombo->currentData().toUInt();
|
||||
emit setRptAccessMode(rd);
|
||||
emit setTone(rt);
|
||||
|
||||
bool updateSub = ui->setSplitRptrToneChk->isEnabled() && ui->setSplitRptrToneChk->isChecked();
|
||||
|
||||
if(updateSub)
|
||||
{
|
||||
rd.useSecondaryVFO = true;
|
||||
rt.useSecondaryVFO = true;
|
||||
emit setRptAccessMode(rd);
|
||||
emit setTone(rt);
|
||||
}
|
||||
}
|
||||
|
||||
void repeaterSetup::on_toneTSQL_clicked()
|
||||
{
|
||||
rptAccessTxRx rm;
|
||||
rptrAccessData_t rd;
|
||||
rm = ratrTT;
|
||||
emit setRptAccessMode(rm);
|
||||
emit setTSQL((quint16)ui->rptToneCombo->currentData().toUInt());
|
||||
rptrTone_t rt;
|
||||
rt.tone = (quint16)ui->rptToneCombo->currentData().toUInt();
|
||||
rd.accessMode = rm;
|
||||
emit setRptAccessMode(rd);
|
||||
emit setTSQL(rt);
|
||||
bool updateSub = ui->setSplitRptrToneChk->isEnabled() && ui->setSplitRptrToneChk->isChecked();
|
||||
|
||||
if(updateSub)
|
||||
{
|
||||
rd.useSecondaryVFO = true;
|
||||
rt.useSecondaryVFO = true;
|
||||
emit setRptAccessMode(rd);
|
||||
emit setTone(rt);
|
||||
}
|
||||
}
|
||||
|
||||
void repeaterSetup::on_toneDTCS_clicked()
|
||||
{
|
||||
rptAccessTxRx rm;
|
||||
rptrAccessData_t rd;
|
||||
quint16 dcode=0;
|
||||
|
||||
rm = ratrDD;
|
||||
emit setRptAccessMode(rm);
|
||||
rd.accessMode = ratrDD;
|
||||
emit setRptAccessMode(rd);
|
||||
|
||||
bool tinv = ui->rptDTCSInvertTx->isChecked();
|
||||
bool rinv = ui->rptDTCSInvertRx->isChecked();
|
||||
dcode = (quint16)ui->rptDTCSCombo->currentData().toUInt();
|
||||
emit setDTCS(dcode, tinv, rinv);
|
||||
// TODO: DTCS with subband
|
||||
}
|
||||
|
||||
void repeaterSetup::on_debugBtn_clicked()
|
||||
quint64 repeaterSetup::getFreqHzFromKHzString(QString khz)
|
||||
{
|
||||
// TODO: Move these four commands to wfview's startup command list (place at the end)
|
||||
//emit getTone();
|
||||
//emit getTSQL();
|
||||
//emit getDTCS();
|
||||
emit getRptAccessMode();
|
||||
// This function takes a string containing a number in KHz,
|
||||
// and creates an accurate quint64 in Hz.
|
||||
quint64 fhz = 0;
|
||||
bool ok = true;
|
||||
if(khz.isEmpty())
|
||||
{
|
||||
qWarning() << "KHz offset was empty!";
|
||||
return fhz;
|
||||
}
|
||||
if(khz.contains("."))
|
||||
{
|
||||
// "600.245" becomes "600"
|
||||
khz.chop(khz.indexOf("."));
|
||||
}
|
||||
fhz = 1E3 * khz.toUInt(&ok);
|
||||
if(!ok)
|
||||
{
|
||||
qWarning() << "Could not understand user KHz text";
|
||||
}
|
||||
return fhz;
|
||||
}
|
||||
|
||||
quint64 repeaterSetup::getFreqHzFromMHzString(QString MHz)
|
||||
{
|
||||
// This function takes a string containing a number in KHz,
|
||||
// and creates an accurate quint64 in Hz.
|
||||
quint64 fhz = 0;
|
||||
bool ok = true;
|
||||
if(MHz.isEmpty())
|
||||
{
|
||||
qWarning() << "MHz string was empty!";
|
||||
return fhz;
|
||||
}
|
||||
if(MHz.contains("."))
|
||||
{
|
||||
int decimalPtIndex = MHz.indexOf(".");
|
||||
// "29.623"
|
||||
// indexOf(".") = 2
|
||||
// length = 6
|
||||
// We want the right 4xx 3 characters.
|
||||
QString KHz = MHz.right(MHz.length() - decimalPtIndex - 1);
|
||||
MHz.chop(MHz.length() - decimalPtIndex);
|
||||
if(KHz.length() != 6)
|
||||
{
|
||||
QString zeros = QString("000000");
|
||||
zeros.chop(KHz.length());
|
||||
KHz.append(zeros);
|
||||
}
|
||||
//qInfo() << "KHz string: " << KHz;
|
||||
fhz = MHz.toUInt(&ok) * 1E6; if(!ok) goto handleError;
|
||||
fhz += KHz.toUInt(&ok) * 1; if(!ok) goto handleError;
|
||||
//qInfo() << "Fhz: " << fhz;
|
||||
} else {
|
||||
// Frequency was already MHz (unlikely but what can we do?)
|
||||
fhz = MHz.toUInt(&ok) * 1E6; if(!ok) goto handleError;
|
||||
}
|
||||
return fhz;
|
||||
|
||||
handleError:
|
||||
qWarning() << "Could not understand user MHz text " << MHz;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void repeaterSetup::on_splitPlusButton_clicked()
|
||||
{
|
||||
quint64 hzOffset = getFreqHzFromKHzString(ui->splitOffsetEdit->text());
|
||||
quint64 txfreqhz;
|
||||
freqt f;
|
||||
QString txString;
|
||||
if(hzOffset)
|
||||
{
|
||||
txfreqhz = currentMainFrequency.Hz + hzOffset;
|
||||
f.Hz = txfreqhz;
|
||||
f.VFO = inactiveVFO;
|
||||
f.MHzDouble = f.Hz/1E6;
|
||||
txString = QString::number(f.Hz / double(1E6), 'f', 6);
|
||||
ui->splitTransmitFreqEdit->setText(txString);
|
||||
usedPlusSplit = true;
|
||||
emit setTransmitFrequency(f);
|
||||
emit setTransmitMode(modeTransmitVFO);
|
||||
}
|
||||
}
|
||||
|
||||
void repeaterSetup::on_splitMinusBtn_clicked()
|
||||
{
|
||||
quint64 hzOffset = getFreqHzFromKHzString(ui->splitOffsetEdit->text());
|
||||
quint64 txfreqhz;
|
||||
freqt f;
|
||||
QString txString;
|
||||
if(hzOffset)
|
||||
{
|
||||
txfreqhz = currentMainFrequency.Hz - hzOffset;
|
||||
f.Hz = txfreqhz;
|
||||
f.VFO = inactiveVFO;
|
||||
f.MHzDouble = f.Hz/1E6;
|
||||
txString = QString::number(f.Hz / double(1E6), 'f', 6);
|
||||
ui->splitTransmitFreqEdit->setText(txString);
|
||||
usedPlusSplit = false;
|
||||
emit setTransmitFrequency(f);
|
||||
emit setTransmitMode(modeTransmitVFO);
|
||||
}
|
||||
}
|
||||
|
||||
void repeaterSetup::on_splitTxFreqSetBtn_clicked()
|
||||
{
|
||||
quint64 fHz = getFreqHzFromMHzString(ui->splitTransmitFreqEdit->text());
|
||||
freqt f;
|
||||
if(fHz)
|
||||
{
|
||||
f.Hz = fHz;
|
||||
f.VFO = inactiveVFO;
|
||||
f.MHzDouble = f.Hz/1E6;
|
||||
emit setTransmitFrequency(f);
|
||||
emit setTransmitMode(modeTransmitVFO);
|
||||
}
|
||||
}
|
||||
|
||||
void repeaterSetup::on_splitTransmitFreqEdit_returnPressed()
|
||||
{
|
||||
this->on_splitTxFreqSetBtn_clicked();
|
||||
ui->splitTransmitFreqEdit->clearFocus();
|
||||
}
|
||||
|
||||
void repeaterSetup::on_selABtn_clicked()
|
||||
{
|
||||
vfo_t v = vfoA;
|
||||
emit selectVFO(v);
|
||||
}
|
||||
|
||||
void repeaterSetup::on_selBBtn_clicked()
|
||||
{
|
||||
vfo_t v = vfoB;
|
||||
emit selectVFO(v);
|
||||
}
|
||||
|
||||
void repeaterSetup::on_aEqBBtn_clicked()
|
||||
{
|
||||
emit equalizeVFOsAB();
|
||||
}
|
||||
|
||||
void repeaterSetup::on_swapABBtn_clicked()
|
||||
{
|
||||
emit swapVFOs();
|
||||
}
|
||||
|
||||
void repeaterSetup::on_selMainBtn_clicked()
|
||||
{
|
||||
vfo_t v = vfoMain;
|
||||
emit selectVFO(v);
|
||||
}
|
||||
|
||||
void repeaterSetup::on_selSubBtn_clicked()
|
||||
{
|
||||
vfo_t v = vfoSub;
|
||||
emit selectVFO(v);
|
||||
}
|
||||
|
||||
void repeaterSetup::on_mEqSBtn_clicked()
|
||||
{
|
||||
emit equalizeVFOsMS();
|
||||
}
|
||||
|
||||
void repeaterSetup::on_swapMSBtn_clicked()
|
||||
{
|
||||
emit swapVFOs();
|
||||
}
|
||||
|
||||
void repeaterSetup::on_setToneSubVFOBtn_clicked()
|
||||
{
|
||||
// Perhaps not needed
|
||||
// Set the secondary VFO to the selected tone
|
||||
// TODO: DTCS
|
||||
rptrTone_t rt;
|
||||
rt.tone = (quint16)ui->rptToneCombo->currentData().toUInt();
|
||||
rt.useSecondaryVFO = true;
|
||||
emit setTone(rt);
|
||||
}
|
||||
|
||||
void repeaterSetup::on_setRptrSubVFOBtn_clicked()
|
||||
{
|
||||
// Perhaps not needed
|
||||
// Set the secondary VFO to the selected repeater mode
|
||||
rptrAccessData_t rd;
|
||||
rd.useSecondaryVFO = true;
|
||||
|
||||
if(ui->toneTone->isChecked())
|
||||
rd.accessMode=ratrTN;
|
||||
if(ui->toneNone->isChecked())
|
||||
rd.accessMode=ratrNN;
|
||||
if(ui->toneTSQL->isChecked())
|
||||
rd.accessMode=ratrTT;
|
||||
if(ui->toneDTCS->isChecked())
|
||||
rd.accessMode=ratrDD;
|
||||
|
||||
emit setRptAccessMode(rd);
|
||||
}
|
||||
|
||||
void repeaterSetup::on_rptrOffsetSetBtn_clicked()
|
||||
{
|
||||
freqt f;
|
||||
f.Hz = getFreqHzFromMHzString(ui->rptrOffsetEdit->text());
|
||||
f.MHzDouble = f.Hz/1E6;
|
||||
f.VFO=activeVFO;
|
||||
if(f.Hz != 0)
|
||||
{
|
||||
emit setRptDuplexOffset(f);
|
||||
}
|
||||
ui->rptrOffsetEdit->clearFocus();
|
||||
}
|
||||
|
||||
void repeaterSetup::on_rptrOffsetEdit_returnPressed()
|
||||
{
|
||||
this->on_rptrOffsetSetBtn_clicked();
|
||||
}
|
||||
|
||||
void repeaterSetup::on_setSplitRptrToneChk_clicked(bool checked)
|
||||
{
|
||||
if(checked)
|
||||
{
|
||||
on_setRptrSubVFOBtn_clicked();
|
||||
on_setToneSubVFOBtn_clicked();
|
||||
}
|
||||
}
|
||||
|
||||
void repeaterSetup::on_quickSplitChk_clicked(bool checked)
|
||||
{
|
||||
emit setQuickSplit(checked);
|
||||
}
|
||||
|
|
|
@ -23,14 +23,29 @@ public:
|
|||
signals:
|
||||
void getDuplexMode();
|
||||
void setDuplexMode(duplexMode dm);
|
||||
void setTone(quint16 tone);
|
||||
void setTSQL(quint16 tsql);
|
||||
void setTone(rptrTone_t tone);
|
||||
void setTSQL(rptrTone_t tsql);
|
||||
void setDTCS(quint16 dcode, bool tinv, bool rinv);
|
||||
void getTone();
|
||||
void getTSQL();
|
||||
void getDTCS();
|
||||
void setRptAccessMode(rptAccessTxRx tmode);
|
||||
void setRptAccessMode(rptrAccessData_t rd);
|
||||
void getRptAccessMode();
|
||||
void setRptDuplexOffset(freqt f);
|
||||
void getRptDuplexOffset();
|
||||
// Split:
|
||||
void getSplitModeEnabled();
|
||||
void setQuickSplit(bool qsOn);
|
||||
void getTransmitFrequency();
|
||||
// Use the duplexMode to communicate split.
|
||||
// void setSplitModeEnabled(bool splitEnabled);
|
||||
void setTransmitFrequency(freqt transmitFreq);
|
||||
void setTransmitMode(mode_info m);
|
||||
// VFO:
|
||||
void selectVFO(vfo_t v); // A,B,M,S
|
||||
void equalizeVFOsAB();
|
||||
void equalizeVFOsMS();
|
||||
void swapVFOs();
|
||||
|
||||
public slots:
|
||||
void receiveDuplexMode(duplexMode dm);
|
||||
|
@ -38,29 +53,80 @@ public slots:
|
|||
void handleTone(quint16 tone);
|
||||
void handleTSQL(quint16 tsql);
|
||||
void handleDTCS(quint16 dcscode, bool tinv, bool rinv);
|
||||
// void handleSplitMode(bool splitEnabled);
|
||||
// void handleSplitFrequency(freqt transmitFreq);
|
||||
void handleUpdateCurrentMainFrequency(freqt mainfreq);
|
||||
void handleUpdateCurrentMainMode(mode_info m);
|
||||
void handleTransmitStatus(bool amTransmitting);
|
||||
void handleRptOffsetFrequency(freqt f);
|
||||
|
||||
private slots:
|
||||
void showEvent(QShowEvent *event);
|
||||
void on_rptSimplexBtn_clicked();
|
||||
void on_rptDupPlusBtn_clicked();
|
||||
void on_rptDupMinusBtn_clicked();
|
||||
void on_rptAutoBtn_clicked();
|
||||
void on_rptReadRigBtn_clicked();
|
||||
void on_rptToneCombo_activated(int index);
|
||||
void on_rptDTCSCombo_activated(int index);
|
||||
void on_debugBtn_clicked();
|
||||
void on_toneNone_clicked();
|
||||
void on_toneTone_clicked();
|
||||
void on_toneTSQL_clicked();
|
||||
void on_toneDTCS_clicked();
|
||||
void on_toneDTCS_clicked();
|
||||
void on_splitPlusButton_clicked();
|
||||
void on_splitMinusBtn_clicked();
|
||||
|
||||
void on_splitTxFreqSetBtn_clicked();
|
||||
|
||||
void on_selABtn_clicked();
|
||||
|
||||
void on_selBBtn_clicked();
|
||||
|
||||
void on_aEqBBtn_clicked();
|
||||
|
||||
void on_swapABBtn_clicked();
|
||||
|
||||
void on_selMainBtn_clicked();
|
||||
|
||||
void on_selSubBtn_clicked();
|
||||
|
||||
void on_mEqSBtn_clicked();
|
||||
|
||||
void on_swapMSBtn_clicked();
|
||||
|
||||
void on_setToneSubVFOBtn_clicked();
|
||||
|
||||
void on_setRptrSubVFOBtn_clicked();
|
||||
|
||||
void on_rptrOffsetSetBtn_clicked();
|
||||
|
||||
void on_splitOffBtn_clicked();
|
||||
|
||||
void on_splitEnableChk_clicked();
|
||||
|
||||
void on_rptrOffsetEdit_returnPressed();
|
||||
|
||||
void on_splitTransmitFreqEdit_returnPressed();
|
||||
|
||||
void on_setSplitRptrToneChk_clicked(bool checked);
|
||||
|
||||
void on_quickSplitChk_clicked(bool checked);
|
||||
|
||||
private:
|
||||
Ui::repeaterSetup *ui;
|
||||
|
||||
freqt currentMainFrequency;
|
||||
void populateTones();
|
||||
void populateDTCS();
|
||||
quint64 getFreqHzFromKHzString(QString khz);
|
||||
quint64 getFreqHzFromMHzString(QString MHz);
|
||||
|
||||
rigCapabilities rig;
|
||||
bool haveRig = false;
|
||||
duplexMode currentdm;
|
||||
mode_info currentModeMain;
|
||||
mode_info modeTransmitVFO;
|
||||
freqt currentOffset;
|
||||
bool usedPlusSplit = false;
|
||||
bool amTransmitting = false;
|
||||
};
|
||||
|
||||
#endif // REPEATERSETUP_H
|
||||
|
|
410
repeatersetup.ui
|
@ -6,62 +6,27 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>217</height>
|
||||
<width>1071</width>
|
||||
<height>231</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>1071</width>
|
||||
<height>230</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>1071</width>
|
||||
<height>231</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Repeater Setup</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="rptReadRigBtn">
|
||||
<property name="text">
|
||||
<string>Read Current Settings</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="debugBtn">
|
||||
<property name="text">
|
||||
<string>Debug</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
|
@ -71,7 +36,13 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<widget class="QGroupBox" name="rptGrpbox">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>180</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Repeater Duplex</string>
|
||||
</property>
|
||||
|
@ -116,15 +87,328 @@
|
|||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_9">
|
||||
<item>
|
||||
<widget class="QPushButton" name="rptrOffsetSetBtn">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Set the repeater offset for radios using Repeater modes. Only available on radios that have repeater modes. Radios using Split should instead use the provided Split Mode section with a custom Offset. </p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Set Offset (MHz):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="rptrOffsetEdit">
|
||||
<property name="placeholderText">
|
||||
<string>Rpt Offset (MHz)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="splitModeGrpbox">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>400</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Split Mode</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="splitEnableChk">
|
||||
<property name="toolTip">
|
||||
<string>Turn on Split</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Split On</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">rptDuplexBtns</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="splitOffBtn">
|
||||
<property name="toolTip">
|
||||
<string>Turn off Split</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Split Off</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">rptDuplexBtns</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="quickSplitChk">
|
||||
<property name="text">
|
||||
<string>QuickSplit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_10">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="setSplitRptrToneChk">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Click here to automatically set the sub VFO (transmit VFO) tone. Only available on some radios. Other radios may take care of this for you.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Set Rpt Tone</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="autoTrackLiveBtn">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Click here to continually set the transmit VFO to match the receive VFO with the offset accounted for. </p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>AutoTrack</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Offset (KHz):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="splitOffsetEdit">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Enter the desired split offset in KHz.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="splitPlusButton">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>55</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Set the transmit frequency to the receive frequency PLUS the offset. Sets the radio sub VFO and also populates the wfview text box (as a convenience). </p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Split+</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="splitMinusBtn">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>55</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Set the transmit frequency to the receive frequency PLUS the offset. Sets the radio sub VFO and also populates the wfview text box (as a convenience). </p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Split-</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Tx Freq (MHz):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="splitTransmitFreqEdit">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>120</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="splitTxFreqSetBtn">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>116</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Set the transmit frequency manually. Not needed if the Split+ or Split- button was used. </p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Set</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>220</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>VFO</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="swapABBtn">
|
||||
<property name="toolTip">
|
||||
<string>Swap VFO A with VFO B. Some radios do not support this.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Swap AB</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="selSubBtn">
|
||||
<property name="toolTip">
|
||||
<string>Select the Sub VFO</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Sel Sub</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QPushButton" name="selMainBtn">
|
||||
<property name="toolTip">
|
||||
<string>Select the Main VFO</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Sel Main</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="selBBtn">
|
||||
<property name="toolTip">
|
||||
<string>Select VFO B</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Sel B</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QPushButton" name="selABtn">
|
||||
<property name="toolTip">
|
||||
<string>Select VFO A</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Sel A</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="aEqBBtn">
|
||||
<property name="toolTip">
|
||||
<string>Set VFO B to VFO A</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>A=B</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QPushButton" name="mEqSBtn">
|
||||
<property name="toolTip">
|
||||
<string>Set the SUB VFO to match the Main VFO</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>M=>S</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="swapMSBtn">
|
||||
<property name="toolTip">
|
||||
<string>Swap the Main VFO and Sub VFO</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Swap MS</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Repeater Tone Type</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="warningFMLabel">
|
||||
<property name="text">
|
||||
<string>Only available in FM</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="toneNone">
|
||||
<property name="text">
|
||||
|
@ -165,11 +449,27 @@
|
|||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="setRptrSubVFOBtn">
|
||||
<property name="toolTip">
|
||||
<string>Set the Tone Mode for the Sub VFO. Not available on all radios.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Set Sub VFO</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Tone Selection</string>
|
||||
</property>
|
||||
|
@ -220,6 +520,16 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="setToneSubVFOBtn">
|
||||
<property name="toolTip">
|
||||
<string>Set the Sub VFO to the selected Tone. Not available on all radios and not available for DTCS.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Set Sub VFO</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
|
|
@ -96,15 +96,15 @@ static void speex_free(void* ptr) { free(ptr); }
|
|||
#define UINT32_MAX 4294967295U
|
||||
#endif
|
||||
|
||||
#ifdef USE_SSE
|
||||
#if defined(USE_SSE) && !defined(__aarch64__)
|
||||
#include "resample_sse.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_NEON
|
||||
#if defined(USE_NEON) || defined(__aarch64__)
|
||||
#include "resample_neon.h"
|
||||
#endif
|
||||
|
||||
/* Numer of elements to allocate on the stack */
|
||||
/* Number of elements to allocate on the stack */
|
||||
#ifdef VAR_ARRAYS
|
||||
#define FIXED_STACK_ALLOC 8192
|
||||
#else
|
||||
|
@ -294,7 +294,7 @@ static spx_word16_t sinc(float cutoff, float x, int N, const struct FuncDef* win
|
|||
else if (fabs(x) > .5 * N)
|
||||
return 0;
|
||||
/*FIXME: Can it really be any slower than this? */
|
||||
return cutoff * sin(M_PI * xx) / (M_PI * xx) * compute_func(fabs(2. * x / N), window_func);
|
||||
return (spx_word16_t)(cutoff * (float)(sin(M_PI * xx) / (M_PI * xx) * compute_func((float)fabs(2. * x / N), window_func)));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -324,7 +324,7 @@ static void cubic_coef(spx_word16_t frac, spx_word16_t interp[4])
|
|||
/*interp[2] = 1.f - 0.5f*frac - frac*frac + 0.5f*frac*frac*frac;*/
|
||||
interp[3] = -0.33333f * frac + 0.5f * frac * frac - 0.16667f * frac * frac * frac;
|
||||
/* Just to make sure we don't have rounding problems */
|
||||
interp[2] = 1. - interp[0] - interp[1] - interp[3];
|
||||
interp[2] = (spx_word16_t) (1. - interp[0] - interp[1] - interp[3]);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -419,7 +419,7 @@ static int resampler_basic_direct_double(SpeexResamplerState* st, spx_uint32_t c
|
|||
sum = inner_product_double(sinct, iptr, N);
|
||||
#endif
|
||||
|
||||
out[out_stride * out_sample++] = PSHR32(sum, 15);
|
||||
out[out_stride * out_sample++] = (spx_word16_t)PSHR32(sum, 15);
|
||||
last_sample += int_advance;
|
||||
samp_frac_num += frac_advance;
|
||||
if (samp_frac_num >= den_rate)
|
||||
|
@ -539,7 +539,7 @@ static int resampler_basic_interpolate_double(SpeexResamplerState* st, spx_uint3
|
|||
sum = MULT16_32_Q15(interp[0], accum[0]) + MULT16_32_Q15(interp[1], accum[1]) + MULT16_32_Q15(interp[2], accum[2]) + MULT16_32_Q15(interp[3], accum[3]);
|
||||
#else
|
||||
cubic_coef(frac, interp);
|
||||
sum = interpolate_product_double(iptr, st->sinc_table + st->oversample + 4 - offset - 2, N, st->oversample, interp);
|
||||
sum = (spx_word16_t)interpolate_product_double(iptr, st->sinc_table + st->oversample + 4 - offset - 2, N, st->oversample, interp);
|
||||
#endif
|
||||
|
||||
out[out_stride * out_sample++] = PSHR32(sum, 15);
|
||||
|
@ -673,7 +673,7 @@ static int update_filter(SpeexResamplerState* st)
|
|||
for (i = 0; i < st->den_rate; i++)
|
||||
{
|
||||
spx_int32_t j;
|
||||
for (j = 0; j < st->filt_len; j++)
|
||||
for (j = 0; j < (spx_int32_t)st->filt_len; j++)
|
||||
{
|
||||
st->sinc_table[i * st->filt_len + j] = sinc(st->cutoff, ((j - (spx_int32_t)st->filt_len / 2 + 1) - ((float)i) / st->den_rate), st->filt_len, quality_map[st->quality].window_func);
|
||||
}
|
||||
|
@ -710,7 +710,7 @@ static int update_filter(SpeexResamplerState* st)
|
|||
/* Adding buffer_size to filt_len won't overflow here because filt_len
|
||||
could be multiplied by sizeof(spx_word16_t) above. */
|
||||
min_alloc_size = st->filt_len - 1 + st->buffer_size;
|
||||
if (min_alloc_size > st->mem_alloc_size)
|
||||
if (min_alloc_size > st->mem_alloc_size && st->nb_channels>0)
|
||||
{
|
||||
spx_word16_t* mem;
|
||||
if (INT_MAX / sizeof(spx_word16_t) / st->nb_channels < min_alloc_size)
|
||||
|
@ -934,7 +934,7 @@ EXPORT int speex_resampler_process_int(SpeexResamplerState* st, spx_uint32_t cha
|
|||
EXPORT int speex_resampler_process_float(SpeexResamplerState* st, spx_uint32_t channel_index, const float* in, spx_uint32_t* in_len, float* out, spx_uint32_t* out_len)
|
||||
#endif
|
||||
{
|
||||
int j;
|
||||
spx_uint32_t j;
|
||||
spx_uint32_t ilen = *in_len;
|
||||
spx_uint32_t olen = *out_len;
|
||||
spx_word16_t* x = st->mem + channel_index * st->mem_alloc_size;
|
||||
|
@ -976,7 +976,7 @@ EXPORT int speex_resampler_process_float(SpeexResamplerState* st, spx_uint32_t c
|
|||
EXPORT int speex_resampler_process_int(SpeexResamplerState* st, spx_uint32_t channel_index, const spx_int16_t* in, spx_uint32_t* in_len, spx_int16_t* out, spx_uint32_t* out_len)
|
||||
#endif
|
||||
{
|
||||
int j;
|
||||
spx_uint32_t j;
|
||||
const int istride_save = st->in_stride;
|
||||
const int ostride_save = st->out_stride;
|
||||
spx_uint32_t ilen = *in_len;
|
||||
|
@ -1246,4 +1246,4 @@ EXPORT const char* speex_resampler_strerror(int err)
|
|||
default:
|
||||
return "Unknown error. Bad error code or strange version mismatch.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
#define OVERRIDE_INNER_PRODUCT_SINGLE
|
||||
static inline float inner_product_single(const float* a, const float* b, unsigned int len)
|
||||
{
|
||||
int i;
|
||||
unsigned int i;
|
||||
float ret;
|
||||
__m128 sum = _mm_setzero_ps();
|
||||
for (i = 0; i < len; i += 8)
|
||||
|
@ -55,7 +55,7 @@ static inline float inner_product_single(const float* a, const float* b, unsigne
|
|||
|
||||
#define OVERRIDE_INTERPOLATE_PRODUCT_SINGLE
|
||||
static inline float interpolate_product_single(const float* a, const float* b, unsigned int len, const spx_uint32_t oversample, float* frac) {
|
||||
int i;
|
||||
unsigned int i;
|
||||
float ret;
|
||||
__m128 sum = _mm_setzero_ps();
|
||||
__m128 f = _mm_loadu_ps(frac);
|
||||
|
@ -71,13 +71,13 @@ static inline float interpolate_product_single(const float* a, const float* b, u
|
|||
return ret;
|
||||
}
|
||||
|
||||
#ifdef USE_SSE2
|
||||
#if defined(USE_SSE2) && !defined(__aarch64__)
|
||||
#include <emmintrin.h>
|
||||
#define OVERRIDE_INNER_PRODUCT_DOUBLE
|
||||
|
||||
static inline double inner_product_double(const float* a, const float* b, unsigned int len)
|
||||
{
|
||||
int i;
|
||||
unsigned int i;
|
||||
double ret;
|
||||
__m128d sum = _mm_setzero_pd();
|
||||
__m128 t;
|
||||
|
@ -98,7 +98,7 @@ static inline double inner_product_double(const float* a, const float* b, unsign
|
|||
|
||||
#define OVERRIDE_INTERPOLATE_PRODUCT_DOUBLE
|
||||
static inline double interpolate_product_double(const float* a, const float* b, unsigned int len, const spx_uint32_t oversample, float* frac) {
|
||||
int i;
|
||||
unsigned int i;
|
||||
double ret;
|
||||
__m128d sum;
|
||||
__m128d sum1 = _mm_setzero_pd();
|
||||
|
@ -125,4 +125,4 @@ static inline double interpolate_product_double(const float* a, const float* b,
|
|||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
|
Po Szerokość: | Wysokość: | Rozmiar: 321 KiB |
|
@ -1,10 +1,11 @@
|
|||
#!/bin/bash
|
||||
echo "This script copies the following items into your system:"
|
||||
echo ""
|
||||
echo "icon: wfview.png to /usr/share/pixmaps/"
|
||||
echo "icon: unix_icons/wfview.png to /usr/share/icons/hicolor/256x256/apps/"
|
||||
echo "wfview application to /usr/local/bin/"
|
||||
echo "wfview.desktop to /usr/share/applications/"
|
||||
echo "qdarkstyle stylesheet to /usr/share/wfview/stylesheets"
|
||||
echo "org.wfview.wfview.metainfo.xml metadata file to /usr/share/metainfo/"
|
||||
echo "qdarkstyle stylesheet to /usr/share/wfview/stylesheets/"
|
||||
|
||||
echo ""
|
||||
echo "This script MUST be run from the build directory. Do not run it from the source directory!"
|
||||
|
@ -30,7 +31,8 @@ echo ""
|
|||
|
||||
cp wfview /usr/local/bin/wfview
|
||||
cp wfview.desktop /usr/share/applications/
|
||||
cp wfview.png /usr/share/pixmaps/
|
||||
cp unix_icons/wfview.png /usr/share/icons/hicolor/256x256/apps/
|
||||
cp org.wfview.wfview.metainfo.xml /usr/share/metainfo/
|
||||
mkdir -p /usr/share/wfview/stylesheets
|
||||
cp -r qdarkstyle /usr/share/wfview/stylesheets/
|
||||
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>org.wfview.wfview</id>
|
||||
<launchable type="desktop-id">wfview.desktop</launchable>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0-or-later</project_license>
|
||||
<name>wfview</name>
|
||||
<developer_name>Elliott H. Liggett</developer_name>
|
||||
<summary>Open Source interface for Icom transceivers</summary>
|
||||
<description>
|
||||
<p>
|
||||
wfview is a program developed by amateur radio enthusiasts to control modern Icom ham radios. It provides the user with controls
|
||||
that may be comfortably operated from a keyboard, mouse, or touch screen interface. wfview is free and open source software.
|
||||
</p>
|
||||
<p>
|
||||
wfview has been developed with an eye towards compatibility. Even though our target platform consists of modern-era transceivers,
|
||||
wfview’s command dictionary is focused on commands with the most compatibility. Many of Icom’s transceivers from the last 20 years
|
||||
will work to some degree with wfview, and we are always adding more.
|
||||
</p>
|
||||
</description>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image type="source">https://wfview.org/wp-content/uploads/2021/11/7300-remote-screenshot.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<url type="homepage">https://wfview.org</url>
|
||||
<url type="bugtracker">https://gitlab.com/eliggett/wfview/-/issues</url>
|
||||
<content_rating type="oars-1.1"/>
|
||||
</component>
|
Po Szerokość: | Wysokość: | Rozmiar: 113 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 106 KiB |
|
@ -1,5 +1,16 @@
|
|||
<RCC>
|
||||
<qresource prefix="resources">
|
||||
<file>wfview.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="/resources">
|
||||
<file>wfview.png</file>
|
||||
<file>shuttlexpress.png</file>
|
||||
<file>shuttlepro.png</file>
|
||||
<file>rc28.png</file>
|
||||
<file>ecoder.png</file>
|
||||
<file>quickkeys.png</file>
|
||||
<file>xbox.png</file>
|
||||
<file>streamdeck.png</file>
|
||||
<file>streamdeckmini.png</file>
|
||||
<file>streamdeckxl.png</file>
|
||||
<file>streamdeckpedal.png</file>
|
||||
<file>streamdeckplus.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
Po Szerokość: | Wysokość: | Rozmiar: 185 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 428 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 179 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 331 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 396 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 217 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 412 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 53 KiB |
|
@ -18,5 +18,7 @@
|
|||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.usb</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
Po Szerokość: | Wysokość: | Rozmiar: 240 KiB |
2220
rigcommander.cpp
262
rigcommander.h
|
@ -2,14 +2,19 @@
|
|||
#define RIGCOMMANDER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutexLocker>
|
||||
#include <QDebug>
|
||||
|
||||
#include "wfviewtypes.h"
|
||||
#include "commhandler.h"
|
||||
#include "pttyhandler.h"
|
||||
#include "udphandler.h"
|
||||
#include "rigidentities.h"
|
||||
#include "repeaterattributes.h"
|
||||
#include "freqmemory.h"
|
||||
#include "tcpserver.h"
|
||||
|
||||
#include "rigstate.h"
|
||||
|
||||
// This file figures out what to send to the comm and also
|
||||
// parses returns into useful things.
|
||||
|
@ -18,133 +23,26 @@
|
|||
// note: using a define because switch case doesn't even work with const unsigned char. Surprised me.
|
||||
#define compCivAddr 0xE1
|
||||
|
||||
enum meterKind {
|
||||
meterNone=0,
|
||||
meterS,
|
||||
meterCenter,
|
||||
meterSWR,
|
||||
meterPower,
|
||||
meterALC,
|
||||
meterComp,
|
||||
meterVoltage,
|
||||
meterCurrent,
|
||||
meterRxdB,
|
||||
meterTxMod,
|
||||
meterRxAudio,
|
||||
meterLatency
|
||||
};
|
||||
|
||||
enum spectrumMode {
|
||||
spectModeCenter=0x00,
|
||||
spectModeFixed=0x01,
|
||||
spectModeScrollC=0x02,
|
||||
spectModeScrollF=0x03,
|
||||
spectModeUnknown=0xff
|
||||
};
|
||||
|
||||
struct freqt {
|
||||
quint64 Hz;
|
||||
double MHzDouble;
|
||||
};
|
||||
|
||||
struct datekind {
|
||||
uint16_t year;
|
||||
unsigned char month;
|
||||
unsigned char day;
|
||||
};
|
||||
|
||||
struct timekind {
|
||||
unsigned char hours;
|
||||
unsigned char minutes;
|
||||
bool isMinus;
|
||||
};
|
||||
|
||||
struct rigStateStruct {
|
||||
freqt vfoAFreq;
|
||||
freqt vfoBFreq;
|
||||
unsigned char currentVfo;
|
||||
bool ptt;
|
||||
unsigned char mode;
|
||||
unsigned char filter;
|
||||
duplexMode duplex;
|
||||
bool datamode;
|
||||
unsigned char antenna;
|
||||
bool rxAntenna;
|
||||
// Tones
|
||||
quint16 ctcss;
|
||||
quint16 tsql;
|
||||
quint16 dtcs;
|
||||
quint16 csql;
|
||||
// Levels
|
||||
unsigned char preamp;
|
||||
unsigned char attenuator;
|
||||
unsigned char modInput;
|
||||
unsigned char afGain;
|
||||
unsigned char rfGain;
|
||||
unsigned char squelch;
|
||||
unsigned char txPower;
|
||||
unsigned char micGain;
|
||||
unsigned char compLevel;
|
||||
unsigned char monitorLevel;
|
||||
unsigned char voxGain;
|
||||
unsigned char antiVoxGain;
|
||||
// Meters
|
||||
unsigned char sMeter;
|
||||
unsigned char powerMeter;
|
||||
unsigned char swrMeter;
|
||||
unsigned char alcMeter;
|
||||
unsigned char compMeter;
|
||||
unsigned char voltageMeter;
|
||||
unsigned char currentMeter;
|
||||
// Functions
|
||||
bool fagcFunc=false;
|
||||
bool nbFunc=false;
|
||||
bool compFunc=false;
|
||||
bool voxFunc = false;
|
||||
bool toneFunc = false;
|
||||
bool tsqlFunc = false;
|
||||
bool sbkinFunc = false;
|
||||
bool fbkinFunc = false;
|
||||
bool anfFunc = false;
|
||||
bool nrFunc = false;
|
||||
bool aipFunc = false;
|
||||
bool apfFunc = false;
|
||||
bool monFunc = false;
|
||||
bool mnFunc = false;
|
||||
bool rfFunc = false;
|
||||
bool aroFunc = false;
|
||||
bool muteFunc = false;
|
||||
bool vscFunc = false;
|
||||
bool revFunc = false;
|
||||
bool sqlFunc = false;
|
||||
bool abmFunc = false;
|
||||
bool bcFunc = false;
|
||||
bool mbcFunc = false;
|
||||
bool ritFunc = false;
|
||||
bool afcFunc = false;
|
||||
bool satmodeFunc = false;
|
||||
bool scopeFunc = false;
|
||||
bool resumeFunc = false;
|
||||
bool tburstFunc = false;
|
||||
bool tunerFunc = false;
|
||||
bool lockFunc = false;
|
||||
};
|
||||
|
||||
class rigCommander : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
rigCommander();
|
||||
explicit rigCommander(QObject* parent=nullptr);
|
||||
explicit rigCommander(quint8 guid[GUIDLEN], QObject* parent = nullptr);
|
||||
~rigCommander();
|
||||
|
||||
bool usingLAN();
|
||||
|
||||
quint8* getGUID();
|
||||
|
||||
public slots:
|
||||
void process();
|
||||
void commSetup(unsigned char rigCivAddr, QString rigSerialPort, quint32 rigBaudRate,QString vsp);
|
||||
void commSetup(unsigned char rigCivAddr, udpPreferences prefs, audioSetup rxSetup, audioSetup txSetup, QString vsp);
|
||||
void commSetup(unsigned char rigCivAddr, QString rigSerialPort, quint32 rigBaudRate, QString vsp, quint16 tcp, quint8 wf);
|
||||
void commSetup(unsigned char rigCivAddr, udpPreferences prefs, audioSetup rxSetup, audioSetup txSetup, QString vsp, quint16 tcp);
|
||||
void closeComm();
|
||||
void stateUpdated();
|
||||
void setRTSforPTT(bool enabled);
|
||||
|
||||
// Power:
|
||||
void powerOn();
|
||||
|
@ -171,22 +69,32 @@ public slots:
|
|||
void getScopeMode();
|
||||
|
||||
// Frequency, Mode, BSR:
|
||||
void setFrequency(freqt freq);
|
||||
void setFrequency(unsigned char vfo, freqt freq);
|
||||
void getFrequency(unsigned char vfo);
|
||||
void getFrequency();
|
||||
void selectVFO(vfo_t vfo);
|
||||
void equalizeVFOsAB();
|
||||
void equalizeVFOsMS();
|
||||
void exchangeVFOs();
|
||||
void setMode(unsigned char mode, unsigned char modeFilter);
|
||||
void setMode(mode_info);
|
||||
void getMode();
|
||||
void setDataMode(bool dataOn, unsigned char filter);
|
||||
void getDataMode();
|
||||
void getSplit();
|
||||
void setSplit(bool splitEnabled);
|
||||
void getBandStackReg(char band, char regCode);
|
||||
void getRitEnabled();
|
||||
void getRitValue();
|
||||
void setRitValue(int ritValue);
|
||||
void setRitEnable(bool ritEnabled);
|
||||
void setPassband(quint16 pass);
|
||||
|
||||
// PTT, ATU, ATT, Antenna, and Preamp:
|
||||
void getPTT();
|
||||
void setPTT(bool pttOn);
|
||||
void sendCW(QString textToSend);
|
||||
void sendStopCW();
|
||||
void startATU();
|
||||
void setATU(bool enabled);
|
||||
void getATUStatus();
|
||||
|
@ -196,29 +104,71 @@ public slots:
|
|||
void setAttenuator(unsigned char att);
|
||||
void setPreamp(unsigned char pre);
|
||||
void setAntenna(unsigned char ant, bool rx);
|
||||
void setNB(bool enabled);
|
||||
void getNB();
|
||||
void setNR(bool enabled);
|
||||
void getNR();
|
||||
void setAutoNotch(bool enabled);
|
||||
void getAutoNotch();
|
||||
void setToneEnabled(bool enabled);
|
||||
void getToneEnabled();
|
||||
void setToneSql(bool enabled);
|
||||
void getToneSqlEnabled();
|
||||
void setCompressor(bool enabled);
|
||||
void getCompressor();
|
||||
void setMonitor(bool enabled);
|
||||
void getMonitor();
|
||||
void setVox(bool enabled);
|
||||
void getVox();
|
||||
void setBreakIn(unsigned char type);
|
||||
void getBreakIn();
|
||||
void setKeySpeed(unsigned char wpm);
|
||||
void getKeySpeed();
|
||||
void setManualNotch(bool enabled);
|
||||
void getManualNotch();
|
||||
|
||||
void getPassband();
|
||||
void getNBLevel();
|
||||
void getNRLevel(); void getCwPitch();
|
||||
void setCwPitch(unsigned char pitch);
|
||||
void getDashRatio();
|
||||
void setDashRatio(unsigned char ratio);
|
||||
void getPskTone();
|
||||
void setPskTone(unsigned char tone);
|
||||
void getRttyMark();
|
||||
void setRttyMark(unsigned char mark);
|
||||
|
||||
// Repeater:
|
||||
void setDuplexMode(duplexMode dm);
|
||||
void getDuplexMode();
|
||||
void setQuickSplit(bool qsOn);
|
||||
void getTransmitFrequency();
|
||||
void setTone(quint16 tone);
|
||||
void setTSQL(quint16 tsql);
|
||||
void setTone(rptrTone_t t);
|
||||
void setTSQL(rptrTone_t t);
|
||||
void setTone(quint16 t);
|
||||
void setTSQL(quint16 t);
|
||||
void getTSQL();
|
||||
void getTone();
|
||||
void setDTCS(quint16 dcscode, bool tinv, bool rinv);
|
||||
void getDTCS();
|
||||
void setRptAccessMode(rptAccessTxRx ratr);
|
||||
void setRptAccessMode(rptrAccessData_t ratr);
|
||||
void getRptAccessMode();
|
||||
void setRptDuplexOffset(freqt f);
|
||||
void getRptDuplexOffset();
|
||||
|
||||
// Get Levels:
|
||||
void getLevels(); // all supported levels
|
||||
void getRfGain();
|
||||
void getAfGain();
|
||||
void getSql();
|
||||
void getIFShift();
|
||||
void getTPBFInner();
|
||||
void getTPBFOuter();
|
||||
void getTxLevel();
|
||||
void getMicGain();
|
||||
void getCompLevel();
|
||||
void getMonitorLevel();
|
||||
void getMonitorGain();
|
||||
void getVoxGain();
|
||||
void getAntiVoxGain();
|
||||
void getUSBGain();
|
||||
|
@ -227,11 +177,16 @@ public slots:
|
|||
void getACCGain(unsigned char ab);
|
||||
void getModInput(bool dataOn);
|
||||
void getModInputLevel(rigInput input);
|
||||
void getAfMute();
|
||||
void getDialLock();
|
||||
|
||||
// Set Levels:
|
||||
void setSquelch(unsigned char level);
|
||||
void setRfGain(unsigned char level);
|
||||
void setAfGain(unsigned char level);
|
||||
void setIFShift(unsigned char level);
|
||||
void setTPBFInner(unsigned char level);
|
||||
void setTPBFOuter(unsigned char level);
|
||||
void setTxPower(unsigned char power);
|
||||
void setMicGain(unsigned char gain);
|
||||
void setUSBGain(unsigned char gain);
|
||||
|
@ -239,11 +194,15 @@ public slots:
|
|||
void setACCGain(unsigned char gain);
|
||||
void setACCGain(unsigned char gain, unsigned char ab);
|
||||
void setCompLevel(unsigned char compLevel);
|
||||
void setMonitorLevel(unsigned char monitorLevel);
|
||||
void setMonitorGain(unsigned char monitorLevel);
|
||||
void setVoxGain(unsigned char gain);
|
||||
void setAntiVoxGain(unsigned char gain);
|
||||
void setModInput(rigInput input, bool dataOn);
|
||||
void setModInputLevel(rigInput input, unsigned char level);
|
||||
void setAfMute(bool muteOn);
|
||||
void setDialLock(bool lockOn);
|
||||
void setNBLevel(unsigned char level);
|
||||
void setNRLevel(unsigned char level);
|
||||
|
||||
// NB, NR, IP+:
|
||||
void setIPP(bool enabled);
|
||||
|
@ -274,6 +233,7 @@ public slots:
|
|||
// Rig ID and CIV:
|
||||
void getRigID();
|
||||
void findRigs();
|
||||
void setRigID(unsigned char rigID);
|
||||
void setCIVAddr(unsigned char civAddr);
|
||||
|
||||
// Calibration:
|
||||
|
@ -295,7 +255,7 @@ public slots:
|
|||
// UDP:
|
||||
void handleNewData(const QByteArray& data);
|
||||
void receiveAudioData(const audioPacket& data);
|
||||
void handleSerialPortError(const QString port, const QString errorText);
|
||||
void handlePortError(errorType err);
|
||||
void changeLatency(const quint16 value);
|
||||
void dataFromServer(QByteArray data);
|
||||
void receiveBaudRate(quint32 baudrate);
|
||||
|
@ -306,16 +266,22 @@ public slots:
|
|||
void sayAll();
|
||||
|
||||
// Housekeeping:
|
||||
void handleStatusUpdate(const QString text);
|
||||
void handleStatusUpdate(const networkStatus status);
|
||||
void handleNetworkAudioLevels(networkAudioLevels);
|
||||
void radioSelection(QList<radio_cap_packet> radios);
|
||||
void radioUsage(quint8 radio, quint8 busy, QString name, QString ip);
|
||||
void setCurrentRadio(quint8 radio);
|
||||
void sendState();
|
||||
void getDebug();
|
||||
|
||||
signals:
|
||||
// Communication:
|
||||
void commReady();
|
||||
void haveSerialPortError(const QString port, const QString errorText);
|
||||
void haveStatusUpdate(const QString text);
|
||||
void havePortError(errorType err);
|
||||
void haveStatusUpdate(const networkStatus status);
|
||||
void haveNetworkAudioLevels(const networkAudioLevels l);
|
||||
void dataForComm(const QByteArray &outData);
|
||||
void toggleRTS(bool rtsOn);
|
||||
|
||||
// UDP:
|
||||
void haveChangeLatency(quint16 value);
|
||||
|
@ -332,6 +298,7 @@ signals:
|
|||
void haveSpectrumMode(spectrumMode spectmode);
|
||||
void haveScopeEdge(char edge);
|
||||
void haveSpectrumRefLevel(int level);
|
||||
void haveScopeOutOfRange(bool outOfRange);
|
||||
|
||||
// Rig ID:
|
||||
void haveRigID(rigCapabilities rigCaps);
|
||||
|
@ -344,6 +311,10 @@ signals:
|
|||
void haveBandStackReg(freqt f, char mode, char filter, bool dataOn);
|
||||
void haveRitEnabled(bool ritEnabled);
|
||||
void haveRitFrequency(int ritHz);
|
||||
void havePassband(quint16 pass);
|
||||
void haveCwPitch(unsigned char pitch);
|
||||
void havePskTone(unsigned char tone);
|
||||
void haveRttyMark(unsigned char mark);
|
||||
|
||||
// Repeater:
|
||||
void haveDuplexMode(duplexMode);
|
||||
|
@ -351,17 +322,28 @@ signals:
|
|||
void haveTone(quint16 tone);
|
||||
void haveTSQL(quint16 tsql);
|
||||
void haveDTCS(quint16 dcscode, bool tinv, bool rinv);
|
||||
void haveRptOffsetFrequency(freqt f);
|
||||
|
||||
// Levels:
|
||||
void haveRfGain(unsigned char level);
|
||||
void haveAfGain(unsigned char level);
|
||||
void haveSql(unsigned char level);
|
||||
void haveTPBFInner(unsigned char level);
|
||||
void haveTPBFOuter(unsigned char level);
|
||||
void haveIFShift(unsigned char level);
|
||||
void haveTxPower(unsigned char level);
|
||||
void haveMicGain(unsigned char level);
|
||||
void haveCompLevel(unsigned char level);
|
||||
void haveMonitorLevel(unsigned char level);
|
||||
void haveMonitorGain(unsigned char level);
|
||||
void haveVoxGain(unsigned char gain);
|
||||
void haveAntiVoxGain(unsigned char gain);
|
||||
void haveNBLevel(unsigned char level);
|
||||
void haveNRLevel(unsigned char level);
|
||||
void haveVox(bool en);
|
||||
void haveMonitor(bool en);
|
||||
void haveComp(bool en);
|
||||
void haveNB(bool en);
|
||||
void haveNR(bool en);
|
||||
|
||||
// Modulation source and gain:
|
||||
void haveModInput(rigInput input, bool isData);
|
||||
|
@ -391,10 +373,19 @@ signals:
|
|||
void havePreamp(unsigned char pre);
|
||||
void haveAntenna(unsigned char ant,bool rx);
|
||||
|
||||
// CW:
|
||||
void haveKeySpeed(unsigned char wpm);
|
||||
void haveCWBreakMode(unsigned char bmode);
|
||||
void haveDashRatio(unsigned char ratio);
|
||||
|
||||
|
||||
// Rig State
|
||||
void stateInfo(rigStateStruct* state);
|
||||
void stateInfo(rigstate* state);
|
||||
|
||||
// Housekeeping:
|
||||
void requestRadioSelection(QList<radio_cap_packet> radios);
|
||||
void setRadioUsage(quint8 radio, quint8 busy, QString user, QString ip);
|
||||
void selectedRadio(quint8 radio);
|
||||
void getMoreDebug();
|
||||
void finished();
|
||||
|
||||
|
@ -402,13 +393,15 @@ private:
|
|||
void setup();
|
||||
QByteArray stripData(const QByteArray &data, unsigned char cutPosition);
|
||||
void parseData(QByteArray data); // new data come here
|
||||
void parseCommand();
|
||||
void parseCommand(); // Entry point for complete commands
|
||||
unsigned char bcdHexToUChar(unsigned char in);
|
||||
unsigned char bcdHexToUChar(unsigned char hundreds, unsigned char tensunits);
|
||||
unsigned int bcdHexToUInt(unsigned char hundreds, unsigned char tensunits);
|
||||
QByteArray bcdEncodeInt(unsigned int);
|
||||
void parseFrequency();
|
||||
freqt parseFrequency(QByteArray data, unsigned char lastPosition); // supply index where Mhz is found
|
||||
freqt parseFrequencyRptOffset(QByteArray data);
|
||||
QByteArray makeFreqPayloadRptOffset(freqt freq);
|
||||
QByteArray makeFreqPayload(double frequency);
|
||||
QByteArray makeFreqPayload(freqt freq);
|
||||
QByteArray encodeTone(quint16 tone, bool tinv, bool rinv);
|
||||
|
@ -417,6 +410,11 @@ private:
|
|||
quint16 decodeTone(QByteArray eTone);
|
||||
quint16 decodeTone(QByteArray eTone, bool &tinv, bool &rinv);
|
||||
|
||||
unsigned char audioLevelRxMean[50];
|
||||
unsigned char audioLevelRxPeak[50];
|
||||
unsigned char audioLevelTxMean[50];
|
||||
unsigned char audioLevelTxPeak[50];
|
||||
|
||||
void parseMode();
|
||||
void parseSpectrum();
|
||||
void parseWFData();
|
||||
|
@ -446,6 +444,7 @@ private:
|
|||
|
||||
commHandler* comm = Q_NULLPTR;
|
||||
pttyHandler* ptty = Q_NULLPTR;
|
||||
tcpServer* tcp = Q_NULLPTR;
|
||||
udpHandler* udp=Q_NULLPTR;
|
||||
QThread* udpHandlerThread = Q_NULLPTR;
|
||||
|
||||
|
@ -466,7 +465,7 @@ private:
|
|||
|
||||
struct rigCapabilities rigCaps;
|
||||
|
||||
rigStateStruct rigState;
|
||||
rigstate state;
|
||||
|
||||
bool haveRigCaps;
|
||||
model_kind model;
|
||||
|
@ -474,6 +473,7 @@ private:
|
|||
quint16 spectAmpMax;
|
||||
quint16 spectLenMax;
|
||||
spectrumMode oldScopeMode;
|
||||
bool wasOutOfRange = false;
|
||||
|
||||
bool usingNativeLAN; // indicates using OEM LAN connection (705,7610,9700,7850)
|
||||
bool lookingForRig;
|
||||
|
@ -483,6 +483,9 @@ private:
|
|||
unsigned char civAddr;
|
||||
unsigned char incomingCIVAddr; // place to store the incoming CIV.
|
||||
bool pttAllowed;
|
||||
bool useRTSforPTT_isSet = false;
|
||||
bool useRTSforPTT_manual = false;
|
||||
|
||||
|
||||
QString rigSerialPort;
|
||||
quint32 rigBaudRate;
|
||||
|
@ -495,7 +498,8 @@ private:
|
|||
QString password;
|
||||
|
||||
QString serialPortError;
|
||||
|
||||
unsigned char localVolume=0;
|
||||
quint8 guid[GUIDLEN] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
|
||||
|
||||
};
|
||||
|
||||
|
|
2235
rigctld.cpp
70
rigctld.h
|
@ -19,6 +19,7 @@
|
|||
#include <typeindex>
|
||||
|
||||
#include "rigcommander.h"
|
||||
#include "rigstate.h"
|
||||
|
||||
#define CONSTANT_64BIT_FLAG(BIT) (1ull << (BIT))
|
||||
|
||||
|
@ -325,7 +326,7 @@ class rigCtlD : public QTcpServer
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit rigCtlD(QObject *parent=Q_NULLPTR);
|
||||
explicit rigCtlD(QObject *parent=nullptr);
|
||||
virtual ~rigCtlD();
|
||||
|
||||
int startServer(qint16 port);
|
||||
|
@ -336,43 +337,43 @@ signals:
|
|||
void onStarted();
|
||||
void onStopped();
|
||||
void sendData(QString data);
|
||||
void setFrequency(freqt freq);
|
||||
void setFrequency(quint8 vfo, freqt freq);
|
||||
void setPTT(bool state);
|
||||
void setMode(unsigned char mode, unsigned char modeFilter);
|
||||
void setDataMode(bool dataOn, unsigned char modeFilter);
|
||||
void setVFO(unsigned char vfo);
|
||||
void setSplit(unsigned char split);
|
||||
void setMode(quint8 mode, quint8 modeFilter);
|
||||
void setDataMode(bool dataOn, quint8 modeFilter);
|
||||
void setVFO(quint8 vfo);
|
||||
void setSplit(quint8 split);
|
||||
void setDuplexMode(duplexMode dm);
|
||||
|
||||
void stateUpdated();
|
||||
// Power
|
||||
void sendPowerOn();
|
||||
void sendPowerOff();
|
||||
|
||||
// Att/preamp
|
||||
void setAttenuator(unsigned char att);
|
||||
void setPreamp(unsigned char pre);
|
||||
void setAttenuator(quint8 att);
|
||||
void setPreamp(quint8 pre);
|
||||
|
||||
//Level set
|
||||
void setRfGain(unsigned char level);
|
||||
void setAfGain(unsigned char level);
|
||||
void setSql(unsigned char level);
|
||||
void setMicGain(unsigned char);
|
||||
void setCompLevel(unsigned char);
|
||||
void setTxPower(unsigned char);
|
||||
void setMonitorLevel(unsigned char);
|
||||
void setVoxGain(unsigned char);
|
||||
void setAntiVoxGain(unsigned char);
|
||||
void setRfGain(quint8 level);
|
||||
void setAfGain(quint8 level);
|
||||
void setSql(quint8 level);
|
||||
void setMicGain(quint8);
|
||||
void setCompLevel(quint8);
|
||||
void setTxPower(quint8);
|
||||
void setMonitorGain(quint8);
|
||||
void setVoxGain(quint8);
|
||||
void setAntiVoxGain(quint8);
|
||||
void setSpectrumRefLevel(int);
|
||||
|
||||
|
||||
public slots:
|
||||
virtual void incomingConnection(qintptr socketDescriptor);
|
||||
void receiveRigCaps(rigCapabilities caps);
|
||||
void receiveStateInfo(rigStateStruct* state);
|
||||
void receiveFrequency(freqt freq);
|
||||
void receiveStateInfo(rigstate* state);
|
||||
// void receiveFrequency(freqt freq);
|
||||
|
||||
private:
|
||||
rigStateStruct* rigState = Q_NULLPTR;
|
||||
rigstate* rigState = Q_NULLPTR;
|
||||
};
|
||||
|
||||
|
||||
|
@ -382,7 +383,7 @@ class rigCtlClient : public QObject
|
|||
|
||||
public:
|
||||
|
||||
explicit rigCtlClient(int socket, rigCapabilities caps, rigStateStruct *state, rigCtlD* parent = Q_NULLPTR);
|
||||
explicit rigCtlClient(int socket, rigCapabilities caps, rigstate *state, rigCtlD* parent = Q_NULLPTR);
|
||||
int getSocketId();
|
||||
|
||||
|
||||
|
@ -399,16 +400,23 @@ protected:
|
|||
|
||||
private:
|
||||
rigCapabilities rigCaps;
|
||||
rigStateStruct* rigState = Q_NULLPTR;
|
||||
rigstate* rigState = Q_NULLPTR;
|
||||
rigCtlD* parent;
|
||||
QString getMode(unsigned char mode, bool datamode);
|
||||
unsigned char getMode(QString modeString);
|
||||
QString getFilter(unsigned char mode, unsigned char filter);
|
||||
QString generateFreqRange(bandType band);
|
||||
unsigned char getAntennas();
|
||||
quint64 getRadioModes();
|
||||
QString getAntName(unsigned char ant);
|
||||
int getCalibratedValue(unsigned char meter,cal_table_t cal);
|
||||
bool chkVfoEecuted=false;
|
||||
unsigned long crcTable[256];
|
||||
unsigned long doCrc(unsigned char* p, size_t n);
|
||||
void genCrc(unsigned long crcTable[]);
|
||||
QString getMode(quint8 mode, bool datamode);
|
||||
quint8 getMode(QString modeString);
|
||||
QString getFilter(quint8 mode, quint8 filter);
|
||||
quint8 getAntennas();
|
||||
quint64 getRadioModes(QString mode = "");
|
||||
QString getAntName(quint8 ant);
|
||||
quint8 antFromName(QString name);
|
||||
quint8 vfoFromName(QString vfo);
|
||||
QString getVfoName(quint8 vfo);
|
||||
|
||||
int getCalibratedValue(quint8 meter,cal_table_t cal);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1,81 +1,13 @@
|
|||
#include "rigidentities.h"
|
||||
#include "logcategories.h"
|
||||
|
||||
// Copytight 2017-2021 Elliott H. Liggett
|
||||
// Copyright 2017-2021 Elliott H. Liggett
|
||||
|
||||
model_kind determineRadioModel(unsigned char rigID)
|
||||
{
|
||||
|
||||
model_kind rig;
|
||||
|
||||
switch(rigID)
|
||||
{
|
||||
case model7100:
|
||||
rig = model7100;
|
||||
break;
|
||||
case model7200:
|
||||
rig = model7200;
|
||||
break;
|
||||
case model7300:
|
||||
rig = model7300;
|
||||
break;
|
||||
case modelR8600:
|
||||
rig = modelR8600;
|
||||
break;
|
||||
case model7000:
|
||||
rig = model7000;
|
||||
break;
|
||||
case model7410:
|
||||
rig = model7410;
|
||||
break;
|
||||
case model7600:
|
||||
rig = model7600;
|
||||
break;
|
||||
case model7610:
|
||||
rig = model7610;
|
||||
break;
|
||||
case model7700:
|
||||
rig = model7700;
|
||||
break;
|
||||
case model7800:
|
||||
rig = model7800;
|
||||
break;
|
||||
case model7850:
|
||||
rig = model7850;
|
||||
break;
|
||||
case model9700:
|
||||
rig = model9700;
|
||||
break;
|
||||
case model706:
|
||||
rig = model706;
|
||||
break;
|
||||
case model705:
|
||||
rig = model705;
|
||||
break;
|
||||
case model718:
|
||||
rig = model718;
|
||||
break;
|
||||
case model910h:
|
||||
rig = model910h;
|
||||
break;
|
||||
case model756pro:
|
||||
rig = model756pro;
|
||||
break;
|
||||
case model756proii:
|
||||
rig = model756proii;
|
||||
break;
|
||||
case model756proiii:
|
||||
rig = model756proiii;
|
||||
break;
|
||||
default:
|
||||
rig = modelUnknown;
|
||||
break;
|
||||
}
|
||||
rig = (model_kind)rigID;
|
||||
|
||||
return rig;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -7,12 +7,14 @@
|
|||
#include <vector>
|
||||
|
||||
#include "freqmemory.h"
|
||||
#include "packettypes.h"
|
||||
|
||||
// Credit for parts of CIV list:
|
||||
// http://www.docksideradio.com/Icom%20Radio%20Hex%20Addresses.htm
|
||||
|
||||
// 7850 and 7851 have the same commands and are essentially identical
|
||||
|
||||
|
||||
enum model_kind {
|
||||
model7100 = 0x88,
|
||||
model7200 = 0x76,
|
||||
|
@ -26,13 +28,21 @@ enum model_kind {
|
|||
model7410 = 0x80,
|
||||
model7850 = 0x8E,
|
||||
model9700 = 0xA2,
|
||||
model703 = 0x68,
|
||||
model705 = 0xA4,
|
||||
model706 = 0x58,
|
||||
model718 = 0x5E,
|
||||
model736 = 0x40,
|
||||
model737 = 0x3C,
|
||||
model738 = 0x44,
|
||||
model746 = 0x56,
|
||||
model756 = 0x50,
|
||||
model756pro = 0x5C,
|
||||
model756proii = 0x64,
|
||||
model756proiii = 0x6E,
|
||||
model905 = 0xAC,
|
||||
model910h = 0x60,
|
||||
model9100 = 0x7C,
|
||||
modelUnknown = 0xFF
|
||||
};
|
||||
|
||||
|
@ -46,7 +56,12 @@ enum rigInput{ inputMic=0,
|
|||
inputUnknown=0xff
|
||||
};
|
||||
|
||||
enum bandType { band23cm=0,
|
||||
enum availableBands {
|
||||
band3cm = 0,
|
||||
band6cm,
|
||||
band9cm,
|
||||
band13cm,
|
||||
band23cm,
|
||||
band70cm,
|
||||
band2m,
|
||||
bandAir,
|
||||
|
@ -78,7 +93,10 @@ enum centerSpansType {
|
|||
cs250k = 6,
|
||||
cs500k = 7,
|
||||
cs1M = 8,
|
||||
cs2p5M = 9
|
||||
cs2p5M = 9,
|
||||
cs5M = 10,
|
||||
cs10M = 11,
|
||||
cs25M = 12,
|
||||
};
|
||||
|
||||
struct centerSpanData {
|
||||
|
@ -86,6 +104,18 @@ struct centerSpanData {
|
|||
QString name;
|
||||
};
|
||||
|
||||
struct bandType {
|
||||
bandType(availableBands band, quint64 lowFreq, quint64 highFreq, mode_kind defaultMode) :
|
||||
band(band), lowFreq(lowFreq), highFreq(highFreq), defaultMode(defaultMode) {}
|
||||
|
||||
bandType() {}
|
||||
|
||||
availableBands band;
|
||||
quint64 lowFreq;
|
||||
quint64 highFreq;
|
||||
mode_kind defaultMode;
|
||||
};
|
||||
|
||||
model_kind determineRadioModel(unsigned char rigID);
|
||||
|
||||
struct rigCapabilities {
|
||||
|
@ -102,35 +132,54 @@ struct rigCapabilities {
|
|||
|
||||
QVector<rigInput> inputs;
|
||||
|
||||
bool hasSpectrum;
|
||||
bool hasSpectrum=true;
|
||||
quint8 spectSeqMax;
|
||||
quint16 spectAmpMax;
|
||||
quint16 spectLenMax;
|
||||
|
||||
bool hasNB = false;
|
||||
QByteArray nbCommand;
|
||||
|
||||
bool hasDD;
|
||||
bool hasDV;
|
||||
bool hasATU;
|
||||
|
||||
bool hasCTCSS;
|
||||
bool hasDTCS;
|
||||
bool hasRepeaterModes = false;
|
||||
|
||||
bool hasTransmit;
|
||||
bool hasPTTCommand;
|
||||
bool useRTSforPTT;
|
||||
bool hasAttenuator;
|
||||
bool hasPreamp;
|
||||
bool hasAntennaSel;
|
||||
bool hasDataModes;
|
||||
bool hasIFShift;
|
||||
bool hasTBPF;
|
||||
|
||||
bool hasRXAntenna;
|
||||
|
||||
bool hasSpecifyMainSubCmd = false; // 0x29
|
||||
bool hasVFOMS = false;
|
||||
bool hasVFOAB = true; // 0x07 [00||01]
|
||||
|
||||
bool hasAdvancedRptrToneCmds = false;
|
||||
bool hasQuickSplitCommand = false;
|
||||
QByteArray quickSplitCommand;
|
||||
|
||||
std::vector <unsigned char> attenuators;
|
||||
std::vector <unsigned char> preamps;
|
||||
std::vector <unsigned char> antennas;
|
||||
std::vector <centerSpanData> scopeCenterSpans;
|
||||
std::vector <bandType> bands;
|
||||
unsigned char bsr[20] = {0};
|
||||
unsigned char bsr[24] = {0};
|
||||
|
||||
std::vector <mode_info> modes;
|
||||
|
||||
QByteArray transceiveCommand;
|
||||
quint8 guid[GUIDLEN] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
|
||||
quint32 baudRate;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
#ifndef RIGSTATEH
|
||||
#define RIGSTATEH
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
#include <QDateTime>
|
||||
#include <QVariant>
|
||||
#include <QMap>
|
||||
#include <QCache>
|
||||
|
||||
#include "rigcommander.h"
|
||||
#include "rigidentities.h"
|
||||
|
||||
// Meters at the end as they are ALWAYS updated from the rig!
|
||||
enum stateTypes { VFOAFREQ, VFOBFREQ, CURRENTVFO, PTT, MODE, FILTER, PASSBAND, DUPLEX, DATAMODE, ANTENNA, RXANTENNA, CTCSS, TSQL, DTCS, CSQL,
|
||||
PREAMP, AGC, ATTENUATOR, MODINPUT, AFGAIN, RFGAIN, SQUELCH, RFPOWER, MICGAIN, COMPLEVEL, MONITORLEVEL, BAL, KEYSPD,
|
||||
VOXGAIN, ANTIVOXGAIN, CWPITCH, NOTCHF, IF, PBTIN, PBTOUT, APF, NR, NB, NBDEPTH, NBWIDTH, RIGINPUT, POWERONOFF, RITVALUE,
|
||||
FAGCFUNC, NBFUNC, COMPFUNC, VOXFUNC, TONEFUNC, TSQLFUNC, SBKINFUNC, FBKINFUNC, ANFFUNC, NRFUNC, AIPFUNC, APFFUNC, MONFUNC, MNFUNC,RFFUNC,
|
||||
AROFUNC, MUTEFUNC, VSCFUNC, REVFUNC, SQLFUNC, ABMFUNC, BCFUNC, MBCFUNC, RITFUNC, AFCFUNC, SATMODEFUNC, SCOPEFUNC,
|
||||
ANN, APO, BACKLIGHT, BEEP, TIME, BAT, KEYLIGHT,
|
||||
RESUMEFUNC, TBURSTFUNC, TUNERFUNC, LOCKFUNC, SMETER, POWERMETER, SWRMETER, ALCMETER, COMPMETER, VOLTAGEMETER, CURRENTMETER,
|
||||
};
|
||||
|
||||
struct value {
|
||||
quint64 _value=0;
|
||||
bool _valid = false;
|
||||
bool _updated = false;
|
||||
QDateTime _dateUpdated;
|
||||
};
|
||||
|
||||
class rigstate
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
void invalidate(stateTypes s) { map[s]._valid = false; }
|
||||
bool isValid(stateTypes s) { return map[s]._valid; }
|
||||
bool isUpdated(stateTypes s) { return map[s]._updated; }
|
||||
QDateTime whenUpdated(stateTypes s) { return map[s]._dateUpdated; }
|
||||
|
||||
void set(stateTypes s, quint64 x, bool u) {
|
||||
if ((x != (quint64)map[s]._value) && ((!u && !map[s]._updated) || (u))) {
|
||||
_mutex.lock();
|
||||
map[s]._value = quint64(x);
|
||||
map[s]._valid = true;
|
||||
map[s]._updated = u;
|
||||
map[s]._dateUpdated = QDateTime::currentDateTime();
|
||||
_mutex.unlock();
|
||||
}
|
||||
}
|
||||
void set(stateTypes s, qint32 x, bool u) {
|
||||
if ((x != (qint32)map[s]._value) && ((!u && !map[s]._updated) || (u))) {
|
||||
_mutex.lock();
|
||||
map[s]._value = quint64(x);
|
||||
map[s]._valid = true;
|
||||
map[s]._updated = u;
|
||||
map[s]._dateUpdated = QDateTime::currentDateTime();
|
||||
_mutex.unlock();
|
||||
}
|
||||
}
|
||||
void set(stateTypes s, qint16 x, bool u) {
|
||||
if ((x != (qint16)map[s]._value) && ((!u && !map[s]._updated) || (u))) {
|
||||
_mutex.lock();
|
||||
map[s]._value = quint64(x);
|
||||
map[s]._valid = true;
|
||||
map[s]._updated = u;
|
||||
map[s]._dateUpdated = QDateTime::currentDateTime();
|
||||
_mutex.unlock();
|
||||
}
|
||||
}
|
||||
void set(stateTypes s, quint16 x, bool u) {
|
||||
if ((x != (quint16)map[s]._value) && ((!u && !map[s]._updated) || (u))) {
|
||||
_mutex.lock();
|
||||
map[s]._value = quint64(x);
|
||||
map[s]._valid = true;
|
||||
map[s]._updated = u;
|
||||
map[s]._dateUpdated = QDateTime::currentDateTime();
|
||||
_mutex.unlock();
|
||||
}
|
||||
}
|
||||
void set(stateTypes s, quint8 x, bool u) {
|
||||
if ((x != (quint8)map[s]._value) && ((!u && !map[s]._updated) || (u))) {
|
||||
_mutex.lock();
|
||||
map[s]._value = quint64(x);
|
||||
map[s]._valid = true;
|
||||
map[s]._updated = u;
|
||||
map[s]._dateUpdated = QDateTime::currentDateTime();
|
||||
_mutex.unlock();
|
||||
}
|
||||
}
|
||||
void set(stateTypes s, bool x, bool u) {
|
||||
if ((x != (bool)map[s]._value) && ((!u && !map[s]._updated) || (u))) {
|
||||
_mutex.lock();
|
||||
map[s]._value = quint64(x);
|
||||
map[s]._valid = true;
|
||||
map[s]._updated = u;
|
||||
map[s]._dateUpdated = QDateTime::currentDateTime();
|
||||
_mutex.unlock();
|
||||
}
|
||||
}
|
||||
void set(stateTypes s, duplexMode x, bool u) {
|
||||
if ((x != (duplexMode)map[s]._value) && ((!u && !map[s]._updated) || (u))) {
|
||||
_mutex.lock();
|
||||
map[s]._value = quint64(x);
|
||||
map[s]._valid = true;
|
||||
map[s]._updated = u;
|
||||
map[s]._dateUpdated = QDateTime::currentDateTime();
|
||||
_mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void set(stateTypes s, rigInput x, bool u) {
|
||||
if ((x != map[s]._value) && ((!u && !map[s]._updated) || (u))) {
|
||||
_mutex.lock();
|
||||
map[s]._value = quint64(x);
|
||||
map[s]._valid = true;
|
||||
map[s]._updated = u;
|
||||
map[s]._dateUpdated = QDateTime::currentDateTime();
|
||||
_mutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
bool getBool(stateTypes s) { return map[s]._value != 0; }
|
||||
quint8 getChar(stateTypes s) { return quint8(map[s]._value); }
|
||||
qint16 getInt16(stateTypes s) { return qint16(map[s]._value); }
|
||||
quint16 getUInt16(stateTypes s) { return quint16(map[s]._value); }
|
||||
qint32 getInt32(stateTypes s) { return qint32(map[s]._value); }
|
||||
quint32 getUInt32(stateTypes s) { return quint32(map[s]._value); }
|
||||
quint64 getInt64(stateTypes s) { return map[s]._value; }
|
||||
duplexMode getDuplex(stateTypes s) { return duplexMode(map[s]._value); }
|
||||
rigInput getInput(stateTypes s) { return rigInput(map[s]._value); }
|
||||
QMap<stateTypes, value> map;
|
||||
|
||||
|
||||
private:
|
||||
//std::map<stateTypes, std::unique_ptr<valueBase> > values;
|
||||
QMutex _mutex;
|
||||
};
|
||||
|
||||
#endif
|
21
ring/LICENSE
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018 Trevor Wilson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -1,10 +0,0 @@
|
|||
# Ring Library
|
||||
|
||||
## Overview
|
||||
|
||||
This library provides source for a multi-producer multi-consumer lock-free ring buffer. It provides a very simple interface for writing and reading from the buffer. The source includes a `Ring_` class, that provides the raw implementation and C-like facilities, as well as a templated `Ring<T>` class for typed reads and writes.
|
||||
|
||||
|
||||
## Contact
|
||||
|
||||
If you have any questions, concerns, or recommendations please feel free to e-mail me at kmdreko@gmail.com. If you notice a bug or defect, create an issue to report it.
|
290
ring/ring.cpp
|
@ -1,290 +0,0 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
// FILE: ring.cpp
|
||||
// DATE: 2016-02-25
|
||||
// AUTH: Trevor Wilson
|
||||
// DESC: Implements a lock-free, multi-consumer, multi-producer ring buffer
|
||||
// class
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) 2016 Trevor Wilson
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files(the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions :
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#include "ring.h"
|
||||
using namespace wilt;
|
||||
|
||||
#include <cstring>
|
||||
// - std::memcpy
|
||||
|
||||
Ring_::Ring_()
|
||||
: beg_(nullptr)
|
||||
, end_(nullptr)
|
||||
{
|
||||
std::atomic_init(&used_, static_cast<std::ptrdiff_t>(0));
|
||||
std::atomic_init(&free_, static_cast<std::ptrdiff_t>(0));
|
||||
std::atomic_init(&rbuf_, static_cast<char*>(0));
|
||||
std::atomic_init(&rptr_, static_cast<char*>(0));
|
||||
std::atomic_init(&wptr_, static_cast<char*>(0));
|
||||
std::atomic_init(&wbuf_, static_cast<char*>(0));
|
||||
}
|
||||
|
||||
Ring_::Ring_(std::size_t size)
|
||||
: beg_(new char[size])
|
||||
, end_(beg_ + size)
|
||||
{
|
||||
std::atomic_init(&used_, static_cast<std::ptrdiff_t>(0));
|
||||
std::atomic_init(&free_, static_cast<std::ptrdiff_t>(size));
|
||||
std::atomic_init(&rbuf_, beg_);
|
||||
std::atomic_init(&rptr_, beg_);
|
||||
std::atomic_init(&wptr_, beg_);
|
||||
std::atomic_init(&wbuf_, beg_);
|
||||
}
|
||||
|
||||
Ring_::Ring_(Ring_&& ring)
|
||||
: beg_(ring.beg_)
|
||||
, end_(ring.end_)
|
||||
{
|
||||
std::atomic_init(&used_, ring.used_.load());
|
||||
std::atomic_init(&free_, ring.free_.load());
|
||||
std::atomic_init(&rbuf_, ring.rbuf_.load());
|
||||
std::atomic_init(&rptr_, ring.rptr_.load());
|
||||
std::atomic_init(&wptr_, ring.wptr_.load());
|
||||
std::atomic_init(&wbuf_, ring.wbuf_.load());
|
||||
|
||||
ring.beg_ = nullptr;
|
||||
ring.end_ = nullptr;
|
||||
|
||||
ring.used_.store(0);
|
||||
ring.free_.store(0);
|
||||
ring.rbuf_.store(nullptr);
|
||||
ring.rptr_.store(nullptr);
|
||||
ring.wptr_.store(nullptr);
|
||||
ring.wbuf_.store(nullptr);
|
||||
}
|
||||
|
||||
Ring_& Ring_::operator= (Ring_&& ring)
|
||||
{
|
||||
delete[] beg_;
|
||||
|
||||
beg_ = ring.beg_;
|
||||
end_ = ring.end_;
|
||||
|
||||
used_.store(ring.used_.load());
|
||||
free_.store(ring.free_.load());
|
||||
rbuf_.store(ring.rbuf_.load());
|
||||
rptr_.store(ring.rptr_.load());
|
||||
wptr_.store(ring.wptr_.load());
|
||||
wbuf_.store(ring.wbuf_.load());
|
||||
|
||||
ring.beg_ = nullptr;
|
||||
ring.end_ = nullptr;
|
||||
|
||||
ring.used_.store(0);
|
||||
ring.free_.store(0);
|
||||
ring.rbuf_.store(nullptr);
|
||||
ring.rptr_.store(nullptr);
|
||||
ring.wptr_.store(nullptr);
|
||||
ring.wbuf_.store(nullptr);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Ring_::~Ring_()
|
||||
{
|
||||
delete[] beg_;
|
||||
}
|
||||
|
||||
std::size_t Ring_::size() const
|
||||
{
|
||||
// The 'used' space can be negative in an over-reserved case, but it can be
|
||||
// clamped to 0 for simplicity.
|
||||
|
||||
auto s = used_.load();
|
||||
return s < 0 ? 0 : static_cast<std::size_t>(s);
|
||||
}
|
||||
|
||||
std::size_t Ring_::capacity() const
|
||||
{
|
||||
return static_cast<std::size_t>(end_ - beg_);
|
||||
}
|
||||
|
||||
void Ring_::read(void* data, std::size_t length) noexcept
|
||||
{
|
||||
auto block = acquire_read_block_(length);
|
||||
|
||||
copy_read_block_(block, (char*)data, length);
|
||||
release_read_block_(block, length);
|
||||
}
|
||||
|
||||
void Ring_::write(const void* data, std::size_t length) noexcept
|
||||
{
|
||||
auto block = acquire_write_block_(length);
|
||||
|
||||
copy_write_block_(block, (const char*)data, length);
|
||||
release_write_block_(block, length);
|
||||
}
|
||||
|
||||
bool Ring_::try_read(void* data, std::size_t length) noexcept
|
||||
{
|
||||
auto block = try_acquire_read_block_(length);
|
||||
if (block == nullptr)
|
||||
return false;
|
||||
|
||||
copy_read_block_(block, (char*)data, length);
|
||||
release_read_block_(block, length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Ring_::try_write(const void* data, std::size_t length) noexcept
|
||||
{
|
||||
auto block = try_acquire_write_block_(length);
|
||||
if (block == nullptr)
|
||||
return false;
|
||||
|
||||
copy_write_block_(block, (const char*)data, length);
|
||||
release_write_block_(block, length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
char* Ring_::normalize_(char* ptr)
|
||||
{
|
||||
return ptr < end_ ? ptr : ptr - capacity();
|
||||
}
|
||||
|
||||
char* Ring_::acquire_read_block_(std::size_t length)
|
||||
{
|
||||
auto size = static_cast<std::ptrdiff_t>(length);
|
||||
while (true) // loop while conflict
|
||||
{
|
||||
auto old_rptr = rptr_.load(std::memory_order_consume); // read rptr
|
||||
while (used_.load(std::memory_order_consume) < size) // check for data
|
||||
; // spin until success
|
||||
|
||||
auto new_rptr = normalize_(old_rptr + size); // get block end
|
||||
used_.fetch_sub(size); // reserve
|
||||
if (rptr_.compare_exchange_strong(old_rptr, new_rptr)) // try commit
|
||||
return old_rptr; // committed
|
||||
|
||||
used_.fetch_add(size, std::memory_order_relaxed); // un-reserve
|
||||
}
|
||||
}
|
||||
|
||||
char* Ring_::try_acquire_read_block_(std::size_t length)
|
||||
{
|
||||
auto size = static_cast<std::ptrdiff_t>(length);
|
||||
while (true) // loop while conflict
|
||||
{
|
||||
auto old_rptr = rptr_.load(std::memory_order_consume); // read rptr
|
||||
if (used_.load(std::memory_order_consume) < size) // check for data
|
||||
return nullptr; // return failure
|
||||
|
||||
auto new_rptr = normalize_(old_rptr + size); // get block end
|
||||
used_.fetch_sub(size); // reserve
|
||||
if (rptr_.compare_exchange_strong(old_rptr, new_rptr)) // try commit
|
||||
return old_rptr; // committed
|
||||
|
||||
used_.fetch_add(size, std::memory_order_relaxed); // un-reserve
|
||||
}
|
||||
}
|
||||
|
||||
void Ring_::copy_read_block_(const char* block, char* data, std::size_t length)
|
||||
{
|
||||
if (block + length < end_)
|
||||
{
|
||||
std::memcpy(data, block, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto first = end_ - block;
|
||||
std::memcpy(data, block, first);
|
||||
std::memcpy(data + first, beg_, length - first);
|
||||
}
|
||||
}
|
||||
|
||||
void Ring_::release_read_block_(char* old_rptr, std::size_t length)
|
||||
{
|
||||
auto new_rptr = normalize_(old_rptr + length); // get block end
|
||||
while (rbuf_.load() != old_rptr) // check for earlier reads
|
||||
; // spin until reads complete
|
||||
|
||||
rbuf_.store(new_rptr); // finish commit
|
||||
free_.fetch_add(length, std::memory_order_relaxed); // add to free space
|
||||
}
|
||||
|
||||
char* Ring_::acquire_write_block_(std::size_t length)
|
||||
{
|
||||
auto size = static_cast<std::ptrdiff_t>(length);
|
||||
while (true) // loop while conflict
|
||||
{
|
||||
auto old_wbuf = wbuf_.load(std::memory_order_consume); // read wbuf
|
||||
while (free_.load(std::memory_order_consume) < size) // check for space
|
||||
; // spin until success
|
||||
|
||||
auto new_wbuf = normalize_(old_wbuf + size); // get block end
|
||||
free_.fetch_sub(size); // reserve
|
||||
if (wbuf_.compare_exchange_strong(old_wbuf, new_wbuf)) // try commit
|
||||
return old_wbuf; // committed
|
||||
|
||||
free_.fetch_add(size, std::memory_order_relaxed); // un-reserve
|
||||
}
|
||||
}
|
||||
|
||||
char* Ring_::try_acquire_write_block_(std::size_t length)
|
||||
{
|
||||
auto size = static_cast<std::ptrdiff_t>(length);
|
||||
while (true) // loop while conflict
|
||||
{
|
||||
auto old_wbuf = wbuf_.load(std::memory_order_consume); // read wbuf
|
||||
if (free_.load(std::memory_order_consume) < size) // check for space
|
||||
return nullptr; // return failure
|
||||
|
||||
auto new_wbuf = normalize_(old_wbuf + size); // get block end
|
||||
free_.fetch_sub(size); // reserve
|
||||
if (wbuf_.compare_exchange_strong(old_wbuf, new_wbuf)) // try commit
|
||||
return old_wbuf; // committed
|
||||
|
||||
free_.fetch_add(size, std::memory_order_relaxed); // un-reserve
|
||||
}
|
||||
}
|
||||
|
||||
void Ring_::copy_write_block_(char* block, const char* data, std::size_t length)
|
||||
{
|
||||
if (block + length < end_)
|
||||
{
|
||||
std::memcpy(block, data, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto first = end_ - block;
|
||||
std::memcpy(block, data, first);
|
||||
std::memcpy(beg_, data + first, length - first);
|
||||
}
|
||||
}
|
||||
|
||||
void Ring_::release_write_block_(char* old_wbuf, std::size_t length)
|
||||
{
|
||||
auto new_wbuf = normalize_(old_wbuf + length); // get block end
|
||||
while (wptr_.load() != old_wbuf) // wait for earlier writes
|
||||
; // spin until writes complete
|
||||
|
||||
wptr_.store(new_wbuf); // finish commit
|
||||
used_.fetch_add(length, std::memory_order_relaxed); // add to used space
|
||||
}
|
440
ring/ring.h
|
@ -1,440 +0,0 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
// FILE: ring.h
|
||||
// DATE: 2016-02-25
|
||||
// AUTH: Trevor Wilson
|
||||
// DESC: Defines a lock-free, multi-consumer, multi-producer ring buffer class
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (c) 2016 Trevor Wilson
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files(the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions :
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
#ifndef WILT_RING_H
|
||||
#define WILT_RING_H
|
||||
|
||||
#include <atomic>
|
||||
// - std::atomic
|
||||
#include <cstddef>
|
||||
// - std::size_t
|
||||
// - std::ptrdiff_t
|
||||
#include <new>
|
||||
// - ::new(ptr)
|
||||
#include <type_traits>
|
||||
// - std::is_nothrow_copy_constructible
|
||||
// - std::is_nothrow_move_constructible
|
||||
// - std::is_nothrow_move_assignable
|
||||
// - std::is_nothrow_destructible
|
||||
#include <utility>
|
||||
// - std::move
|
||||
|
||||
namespace wilt
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// This structure aims to access elements in a ring buffer from multiple
|
||||
// concurrent readers and writers in a lock-free manner.
|
||||
//
|
||||
// The class works by allocating the array and storing two pointers (for the
|
||||
// beginning and end of the allocated space). Two atomic pointers are used to
|
||||
// track the beginning and end of the currently used storage space. To
|
||||
// facilitate concurrent reads and writes, theres a read buffer pointer before
|
||||
// the read pointer for data currently being read, and a corresponding write
|
||||
// buffer pointer beyond the write pointer for data currently being written.
|
||||
// These buffer pointers cannot overlap. Just using these pointers suffer from
|
||||
// some minute inefficiencies and a few ABA problems. Therfore, atomic
|
||||
// integers are used to store the currently used and currently free sizes.
|
||||
//
|
||||
// It allows multiple readers and multiple writers by implementing a reserve-
|
||||
// commit system. A thread wanting to read will check the used size to see if
|
||||
// there's enough data. If there is, it subtracts from the used size to
|
||||
// 'reserve' the read. It then does a compare-exchange to 'commit' by
|
||||
// increasing the read pointer. If that fails, then it backs out ('un-
|
||||
// reserves') by adding back to the used size and tries again. If it
|
||||
// succeeds, then it proceeds to read the data. In order to complete, the
|
||||
// reader must update the read buffer pointer to where it just finished
|
||||
// reading from. However, because other readers that started before may not be
|
||||
// done yet, the reader must wait until the read buffer pointer points to
|
||||
// where the read started. Only, then is the read buffer pointer updated, and
|
||||
// the free size increased. So while this implementation is lock-free, it is
|
||||
// not wait-free. This same principle works the same when writing (ammended
|
||||
// for the appropriate pointers).
|
||||
//
|
||||
// If two readers try to read at the same time and there is only enough data
|
||||
// for one of them. The used size MAY be negative because they both 'reserve'
|
||||
// the data. This is an over-reserved state. But the compare-exchange will
|
||||
// only allow one reader to 'commit' to the read and the other will 'un-
|
||||
// reserve' the read.
|
||||
//
|
||||
// |beg |rptr used=5 |wbuf - unused
|
||||
// |----|----|++++|====|====|====|====|====|++++|----| + modifying
|
||||
// free=3 |rbuf |wptr |end = used
|
||||
//
|
||||
// The diagram above shows a buffer of size 10 storing 5 bytes with a reader
|
||||
// reading one byte and one writer reading one byte.
|
||||
//
|
||||
// Out of the box, the class works by reading and writing raw bytes from POD
|
||||
// data types and arrays. A wrapper could allow for a nicer interface for
|
||||
// pushing and popping elements. As it stands, this structure cannot be easily
|
||||
// modified to store types of variable size.
|
||||
|
||||
class Ring_
|
||||
{
|
||||
private:
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// TYPE DEFINITIONS
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef char* data_ptr;
|
||||
typedef std::atomic<std::ptrdiff_t> size_type;
|
||||
typedef std::atomic<char*> atom_ptr;
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE MEMBERS
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Beginning and end pointers don't need to be atomic because they don't
|
||||
// change. used_ and free_ can be negative in certain cases (and that's ok).
|
||||
|
||||
data_ptr beg_; // pointer to beginning of data block
|
||||
data_ptr end_; // pointer to end of data block
|
||||
|
||||
alignas(64)
|
||||
size_type used_; // size of unreserved used space
|
||||
|
||||
alignas(64)
|
||||
size_type free_; // size of unreserved free space
|
||||
|
||||
alignas(64)
|
||||
atom_ptr rbuf_; // pointer to beginning of data being read
|
||||
atom_ptr rptr_; // pointer to beginning of data
|
||||
|
||||
alignas(64)
|
||||
atom_ptr wptr_; // pointer to end of data
|
||||
atom_ptr wbuf_; // pointer to end of data being written
|
||||
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// CONSTRUCTORS AND DESTRUCTORS
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Constructs a ring without a buffer (capacity() == 0)
|
||||
Ring_();
|
||||
|
||||
// Constructs a ring with a buffer with a size
|
||||
Ring_(std::size_t size);
|
||||
|
||||
// Moves the buffer between rings, assumes no concurrent operations
|
||||
Ring_(Ring_&& ring);
|
||||
|
||||
// Moves the buffer between rings, assumes no concurrent operations on
|
||||
// either ring. Deallocates the buffer
|
||||
Ring_& operator= (Ring_&& ring);
|
||||
|
||||
// No copying
|
||||
Ring_(const Ring_&) = delete;
|
||||
Ring_& operator= (const Ring_&) = delete;
|
||||
|
||||
// Deallocates the buffer
|
||||
~Ring_();
|
||||
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// QUERY FUNCTIONS
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Functions only report on the state of the ring
|
||||
|
||||
// Returns the current amount of non-reserved used space (amount of written
|
||||
// data that a read hasn't yet reserved). Over-reserved scenarios mean this
|
||||
// number is not the ultimate source of truth with concurrent operations,
|
||||
// but its the closest safe approximation. This, of course, doesn't report
|
||||
// writes that have not completed.
|
||||
std::size_t size() const;
|
||||
|
||||
// Maximum amount of data that can be held
|
||||
std::size_t capacity() const;
|
||||
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// ACCESSORS AND MODIFIERS
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// All operations assume object has not been moved. Blocking operations run
|
||||
// until operation is completed. Non-blocking operations fail if there is
|
||||
// not enough space
|
||||
|
||||
void read(void* data, std::size_t length) noexcept;
|
||||
void write(const void* data, std::size_t length) noexcept;
|
||||
bool try_read(void* data, std::size_t length) noexcept;
|
||||
bool try_write(const void* data, std::size_t length) noexcept;
|
||||
|
||||
protected:
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// PROTECTED FUNCTIONS
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Helper functions
|
||||
|
||||
// Wraps a pointer within the array. Assumes 'beg_ <= ptr < end_+capacity()'
|
||||
char* normalize_(char*);
|
||||
|
||||
char* acquire_read_block_(std::size_t length);
|
||||
char* try_acquire_read_block_(std::size_t length);
|
||||
void copy_read_block_(const char* block, char* data, std::size_t length);
|
||||
void release_read_block_(char* block, std::size_t length);
|
||||
|
||||
char* acquire_write_block_(std::size_t length);
|
||||
char* try_acquire_write_block_(std::size_t length);
|
||||
void copy_write_block_(char* block, const char* data, std::size_t length);
|
||||
void release_write_block_(char* block, std::size_t length);
|
||||
|
||||
char* begin_alloc_() { return beg_; }
|
||||
const char* begin_alloc_() const { return beg_; }
|
||||
char* end_alloc_() { return end_; }
|
||||
const char* end_alloc_() const { return end_; }
|
||||
char* begin_data_() { return rptr_; }
|
||||
const char* begin_data_() const { return rptr_; }
|
||||
char* end_data_() { return wptr_; }
|
||||
const char* end_data_() const { return wptr_; }
|
||||
|
||||
}; // class Ring_
|
||||
|
||||
template <class T>
|
||||
class Ring : protected Ring_
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// CONSTRUCTORS AND DESTRUCTORS
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Constructs a ring without a buffer (capacity() == 0)
|
||||
Ring();
|
||||
|
||||
// Constructs a ring with a buffer with a size
|
||||
Ring(std::size_t size);
|
||||
|
||||
// Moves the buffer between rings, assumes no concurrent operations
|
||||
Ring(Ring<T>&& ring);
|
||||
|
||||
// Moves the buffer between rings, assumes no concurrent operations on
|
||||
// either ring. Deallocates the buffer
|
||||
Ring<T>& operator= (Ring<T>&& ring);
|
||||
|
||||
// No copying
|
||||
Ring(const Ring_&) = delete;
|
||||
Ring& operator= (const Ring_&) = delete;
|
||||
|
||||
// Deallocates the buffer, destructs stored data.
|
||||
~Ring();
|
||||
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// QUERY FUNCTIONS
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Functions only report on the state of the ring
|
||||
|
||||
// Returns the current amount of non-reserved used space (amount of written
|
||||
// data that a read hasn't yet reserved). Over-reserved scenarios mean this
|
||||
// number is not the ultimate source of truth with concurrent operations,
|
||||
// but its the closest safe approximation. This, of course, doesn't report
|
||||
// writes that have not completed.
|
||||
std::size_t size() const;
|
||||
|
||||
// Maximum amount of data that can be held
|
||||
std::size_t capacity() const;
|
||||
|
||||
public:
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// ACCESSORS AND MODIFIERS
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// All operations assume object has not been moved. Blocking operations run
|
||||
// until operation is completed. Non-blocking operations fail if there is
|
||||
// not enough space
|
||||
|
||||
void read(T& data) noexcept; // blocking read
|
||||
void write(const T& data) noexcept; // blocking write
|
||||
void write(T&& data) noexcept; // blocking write
|
||||
bool try_read(T& data) noexcept; // non-blocking read
|
||||
bool try_write(const T& data) noexcept; // non-blocking write
|
||||
bool try_write(T&& data) noexcept; // non-blocking write
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// PRIVATE HELPER FUNCTIONS
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void destruct_();
|
||||
|
||||
}; // class Ring<T>
|
||||
|
||||
template <class T>
|
||||
Ring<T>::Ring()
|
||||
: Ring_()
|
||||
{ }
|
||||
|
||||
template <class T>
|
||||
Ring<T>::Ring(std::size_t size)
|
||||
: Ring_(size * sizeof(T))
|
||||
{ }
|
||||
|
||||
template <class T>
|
||||
Ring<T>::Ring(Ring<T>&& ring)
|
||||
: Ring_(std::move(ring))
|
||||
{ }
|
||||
|
||||
template <class T>
|
||||
Ring<T>& Ring<T>::operator= (Ring<T>&& ring)
|
||||
{
|
||||
destruct_();
|
||||
|
||||
Ring_::operator= (ring);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Ring<T>::~Ring()
|
||||
{
|
||||
destruct_();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Ring<T>::destruct_()
|
||||
{
|
||||
if (size() == 0)
|
||||
return;
|
||||
|
||||
auto itr = begin_data_();
|
||||
auto end = end_data_();
|
||||
do
|
||||
{
|
||||
auto t = reinterpret_cast<T*>(itr);
|
||||
t->~T();
|
||||
|
||||
itr = normalize_(itr + sizeof(T));
|
||||
} while (itr != end);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::size_t Ring<T>::size() const
|
||||
{
|
||||
return Ring_::size() / sizeof(T);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::size_t Ring<T>::capacity() const
|
||||
{
|
||||
return Ring_::capacity() / sizeof(T);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Ring<T>::read(T& data) noexcept
|
||||
{
|
||||
static_assert(std::is_nothrow_move_assignable<T>::value, "T move assignment must not throw");
|
||||
static_assert(std::is_nothrow_destructible<T>::value, "T destructor must not throw");
|
||||
|
||||
auto block = acquire_read_block_(sizeof(T));
|
||||
|
||||
// critical section
|
||||
auto t = reinterpret_cast<T*>(block);
|
||||
data = std::move(*t);
|
||||
t->~T();
|
||||
|
||||
release_read_block_(block, sizeof(T));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Ring<T>::write(const T& data) noexcept
|
||||
{
|
||||
static_assert(std::is_nothrow_copy_constructible<T>::value, "T copy constructor must not throw");
|
||||
|
||||
auto block = acquire_write_block_(sizeof(T));
|
||||
|
||||
// critical section
|
||||
new(block) T(data);
|
||||
|
||||
release_write_block_(block, sizeof(T));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void Ring<T>::write(T&& data) noexcept
|
||||
{
|
||||
static_assert(std::is_nothrow_move_constructible<T>::value, "T move constructor must not throw");
|
||||
|
||||
auto block = acquire_write_block_(sizeof(T));
|
||||
|
||||
// critical section
|
||||
new(block) T(std::move(data));
|
||||
|
||||
release_write_block_(block, sizeof(T));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool Ring<T>::try_read(T& data) noexcept
|
||||
{
|
||||
static_assert(std::is_nothrow_move_assignable<T>::value, "T move assignment must not throw");
|
||||
static_assert(std::is_nothrow_destructible<T>::value, "T destructor must not throw");
|
||||
|
||||
auto block = try_acquire_read_block_(sizeof(T));
|
||||
if (block == nullptr)
|
||||
return false;
|
||||
|
||||
// critical section
|
||||
auto t = reinterpret_cast<T*>(block);
|
||||
data = std::move(*t);
|
||||
t->~T();
|
||||
|
||||
release_read_block_(block, sizeof(T));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool Ring<T>::try_write(const T& data) noexcept
|
||||
{
|
||||
static_assert(std::is_nothrow_copy_constructible<T>::value, "T copy constructor must not throw");
|
||||
|
||||
auto block = try_acquire_write_block_(sizeof(T));
|
||||
if (block == nullptr)
|
||||
return false;
|
||||
|
||||
// critical section
|
||||
new(block) T(data);
|
||||
|
||||
release_write_block_(block, sizeof(T));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
bool Ring<T>::try_write(T&& data) noexcept
|
||||
{
|
||||
static_assert(std::is_nothrow_move_constructible<T>::value, "T move constructor must not throw");
|
||||
|
||||
auto block = try_acquire_write_block_(sizeof(T));
|
||||
if (block == nullptr)
|
||||
return false;
|
||||
|
||||
// critical section
|
||||
new(block) T(std::move(data));
|
||||
|
||||
release_write_block_(block, sizeof(T));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wilt
|
||||
|
||||
#endif // !WILT_RING_H
|
|
@ -0,0 +1,401 @@
|
|||
#include "rthandler.h"
|
||||
|
||||
#include "logcategories.h"
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#include <objbase.h>
|
||||
#endif
|
||||
|
||||
#undef RT_EXCEPTION
|
||||
|
||||
rtHandler::rtHandler(QObject* parent)
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
}
|
||||
|
||||
rtHandler::~rtHandler()
|
||||
{
|
||||
|
||||
if (converterThread != Q_NULLPTR) {
|
||||
converterThread->quit();
|
||||
converterThread->wait();
|
||||
}
|
||||
|
||||
if (isInitialized) {
|
||||
|
||||
#ifdef RT_EXCEPTION
|
||||
try {
|
||||
#endif
|
||||
audio->abortStream();
|
||||
audio->closeStream();
|
||||
#ifdef RT_EXCEPTION
|
||||
}
|
||||
catch (RtAudioError& e) {
|
||||
qInfo(logAudio()) << "Error closing stream:" << aParams.deviceId << ":" << QString::fromStdString(e.getMessage());
|
||||
}
|
||||
#endif
|
||||
delete audio;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool rtHandler::init(audioSetup setup)
|
||||
{
|
||||
if (isInitialized) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this->setup = setup;
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "RTAudio handler starting:" << setup.name;
|
||||
|
||||
if (setup.portInt==-1)
|
||||
{
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "No audio device was found.";
|
||||
return false;
|
||||
}
|
||||
|
||||
radioFormat = toQAudioFormat(setup.codec, setup.sampleRate);
|
||||
|
||||
qDebug(logAudio()) << "Creating" << (setup.isinput ? "Input" : "Output") << "audio device:" << setup.name <<
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
", bits" << radioFormat.sampleSize() <<
|
||||
#else
|
||||
", format" << radioFormat.sampleFormat() <<
|
||||
#endif
|
||||
", codec" << setup.codec <<
|
||||
", latency" << setup.latency <<
|
||||
", localAFGain" << setup.localAFgain <<
|
||||
", radioChan" << radioFormat.channelCount() <<
|
||||
", resampleQuality" << setup.resampleQuality <<
|
||||
", samplerate" << radioFormat.sampleRate() <<
|
||||
", uLaw" << setup.ulaw;
|
||||
|
||||
#if !defined(Q_OS_MACX)
|
||||
options.flags = ((!RTAUDIO_HOG_DEVICE) | (RTAUDIO_MINIMIZE_LATENCY));
|
||||
//options.flags = RTAUDIO_MINIMIZE_LATENCY;
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
audio = new RtAudio(RtAudio::Api::LINUX_ALSA);
|
||||
#elif defined(Q_OS_WIN)
|
||||
audio = new RtAudio(RtAudio::Api::WINDOWS_WASAPI);
|
||||
#elif defined(Q_OS_MACX)
|
||||
audio = new RtAudio(RtAudio::Api::MACOSX_CORE);
|
||||
#endif
|
||||
|
||||
codecType codec = LPCM;
|
||||
if (setup.codec == 0x01 || setup.codec == 0x20)
|
||||
codec = PCMU;
|
||||
else if (setup.codec == 0x40 || setup.codec == 0x40)
|
||||
codec = OPUS;
|
||||
|
||||
options.numberOfBuffers = int(setup.latency/setup.blockSize);
|
||||
|
||||
if (setup.portInt > 0) {
|
||||
aParams.deviceId = setup.portInt;
|
||||
}
|
||||
else if (setup.isinput) {
|
||||
aParams.deviceId = audio->getDefaultInputDevice();
|
||||
}
|
||||
else {
|
||||
aParams.deviceId = audio->getDefaultOutputDevice();
|
||||
}
|
||||
aParams.firstChannel = 0;
|
||||
|
||||
#ifdef RT_EXCEPTION
|
||||
try {
|
||||
#endif
|
||||
info = audio->getDeviceInfo(aParams.deviceId);
|
||||
#ifdef RT_EXCEPTION
|
||||
}
|
||||
catch (RtAudioError e) {
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Device exception:" << aParams.deviceId << ":" << QString::fromStdString(e.getMessage());
|
||||
goto errorHandler;
|
||||
}
|
||||
#endif
|
||||
#ifdef RT_EXCEPTION
|
||||
if (info.probed)
|
||||
{
|
||||
#endif
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << QString::fromStdString(info.name) << "(" << aParams.deviceId << ") successfully probed";
|
||||
|
||||
RtAudioFormat sampleFormat;
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
nativeFormat.setByteOrder(QAudioFormat::LittleEndian);
|
||||
nativeFormat.setCodec("audio/pcm");
|
||||
#endif
|
||||
|
||||
if (info.nativeFormats == 0)
|
||||
{
|
||||
qCritical(logAudio()) << " No natively supported data formats!";
|
||||
goto errorHandler;
|
||||
}
|
||||
else {
|
||||
qDebug(logAudio()) << " Supported formats:" <<
|
||||
(info.nativeFormats & RTAUDIO_SINT8 ? "8-bit int," : "") <<
|
||||
(info.nativeFormats & RTAUDIO_SINT16 ? "16-bit int," : "") <<
|
||||
(info.nativeFormats & RTAUDIO_SINT24 ? "24-bit int," : "") <<
|
||||
(info.nativeFormats & RTAUDIO_SINT32 ? "32-bit int," : "") <<
|
||||
(info.nativeFormats & RTAUDIO_FLOAT32 ? "32-bit float," : "") <<
|
||||
(info.nativeFormats & RTAUDIO_FLOAT64 ? "64-bit float," : "");
|
||||
|
||||
qInfo(logAudio()) << " Preferred sample rate:" << info.preferredSampleRate;
|
||||
if (setup.isinput) {
|
||||
nativeFormat.setChannelCount(info.inputChannels);
|
||||
}
|
||||
else {
|
||||
nativeFormat.setChannelCount(info.outputChannels);
|
||||
}
|
||||
|
||||
qInfo(logAudio()) << " Channels:" << nativeFormat.channelCount();
|
||||
|
||||
if (nativeFormat.channelCount() > 2) {
|
||||
nativeFormat.setChannelCount(2);
|
||||
}
|
||||
else if (nativeFormat.channelCount() < 1)
|
||||
{
|
||||
qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << "No channels found, aborting setup.";
|
||||
goto errorHandler;
|
||||
}
|
||||
|
||||
if (nativeFormat.channelCount() == 1 && radioFormat.channelCount() == 2) {
|
||||
nativeFormat.setChannelCount(2);
|
||||
}
|
||||
|
||||
aParams.nChannels = nativeFormat.channelCount();
|
||||
|
||||
|
||||
nativeFormat.setSampleRate(info.preferredSampleRate);
|
||||
|
||||
if (nativeFormat.sampleRate() < 44100) {
|
||||
nativeFormat.setSampleRate(48000);
|
||||
}
|
||||
|
||||
if (info.nativeFormats & RTAUDIO_FLOAT32) {
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
nativeFormat.setSampleType(QAudioFormat::Float);
|
||||
nativeFormat.setSampleSize(32);
|
||||
#else
|
||||
nativeFormat.setSampleFormat(QAudioFormat::Float);
|
||||
#endif
|
||||
sampleFormat = RTAUDIO_FLOAT32;
|
||||
}
|
||||
else if (info.nativeFormats & RTAUDIO_SINT32) {
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
nativeFormat.setSampleType(QAudioFormat::SignedInt);
|
||||
nativeFormat.setSampleSize(32);
|
||||
#else
|
||||
nativeFormat.setSampleFormat(QAudioFormat::Int32);
|
||||
#endif
|
||||
sampleFormat = RTAUDIO_SINT32;
|
||||
}
|
||||
else if (info.nativeFormats & RTAUDIO_SINT16) {
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
nativeFormat.setSampleType(QAudioFormat::SignedInt);
|
||||
nativeFormat.setSampleSize(16);
|
||||
#else
|
||||
nativeFormat.setSampleFormat(QAudioFormat::Int16);
|
||||
#endif
|
||||
sampleFormat = RTAUDIO_SINT16;
|
||||
}
|
||||
else {
|
||||
qCritical(logAudio()) << "Cannot find supported sample format!";
|
||||
goto errorHandler;
|
||||
}
|
||||
}
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6,0,0))
|
||||
qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Selected format: SampleSize" << nativeFormat.sampleSize() << "Channel Count" << nativeFormat.channelCount() <<
|
||||
"Sample Rate" << nativeFormat.sampleRate() << "Codec" << nativeFormat.codec() << "Sample Type" << nativeFormat.sampleType();
|
||||
#else
|
||||
qDebug(logAudio()) << (setup.isinput ? "Input" : "Output") << "Selected format: SampleFormat" << nativeFormat.sampleFormat() << "Channel Count" << nativeFormat.channelCount() <<
|
||||
"Sample Rate" << nativeFormat.sampleRate() << "Codec" << codec;
|
||||
#endif
|
||||
|
||||
// We "hopefully" now have a valid format that is supported so try connecting
|
||||
converter = new audioConverter();
|
||||
converterThread = new QThread(this);
|
||||
if (setup.isinput) {
|
||||
converterThread->setObjectName("audioConvIn()");
|
||||
}
|
||||
else {
|
||||
converterThread->setObjectName("audioConvOut()");
|
||||
}
|
||||
converter->moveToThread(converterThread);
|
||||
|
||||
connect(this, SIGNAL(setupConverter(QAudioFormat, codecType, QAudioFormat, codecType, quint8, quint8)), converter, SLOT(init(QAudioFormat, codecType, QAudioFormat, codecType, quint8, quint8)));
|
||||
connect(converterThread, SIGNAL(finished()), converter, SLOT(deleteLater()));
|
||||
connect(this, SIGNAL(sendToConverter(audioPacket)), converter, SLOT(convert(audioPacket)));
|
||||
converterThread->start(QThread::TimeCriticalPriority);
|
||||
|
||||
|
||||
// Per channel chunk size.
|
||||
this->chunkSize = nativeFormat.framesForDuration(setup.blockSize * 1000);
|
||||
|
||||
#ifdef RT_EXCEPTION
|
||||
try {
|
||||
#endif
|
||||
if (setup.isinput) {
|
||||
audio->openStream(NULL, &aParams, sampleFormat, nativeFormat.sampleRate(), &this->chunkSize, &staticWrite, this, &options);
|
||||
emit setupConverter(nativeFormat, codecType::LPCM, radioFormat, codec, 7, setup.resampleQuality);
|
||||
connect(converter, SIGNAL(converted(audioPacket)), this, SLOT(convertedInput(audioPacket)));
|
||||
}
|
||||
else {
|
||||
audio->openStream(&aParams, NULL, sampleFormat, nativeFormat.sampleRate(), &this->chunkSize, &staticRead, this , &options);
|
||||
emit setupConverter(radioFormat, codec, nativeFormat, codecType::LPCM, 7, setup.resampleQuality);
|
||||
connect(converter, SIGNAL(converted(audioPacket)), this, SLOT(convertedOutput(audioPacket)));
|
||||
}
|
||||
audio->startStream();
|
||||
isInitialized = true;
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "device successfully opened";
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "detected latency:" << audio->getStreamLatency();
|
||||
#ifdef RT_EXCEPTION
|
||||
}
|
||||
catch (RtAudioError& e) {
|
||||
qInfo(logAudio()) << "Error opening:" << QString::fromStdString(e.getMessage());
|
||||
// Try again?
|
||||
goto errorHandler;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef RT_EXCEPTION
|
||||
}
|
||||
else
|
||||
{
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << QString::fromStdString(info.name) << "(" << aParams.deviceId << ") could not be probed, check audio configuration!";
|
||||
goto errorHandler;
|
||||
}
|
||||
#endif
|
||||
this->setVolume(setup.localAFgain);
|
||||
|
||||
|
||||
return isInitialized;
|
||||
|
||||
errorHandler:
|
||||
if (retryConnectCount < 10) {
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "*** Attempting to reconnect to audio device in 500ms";
|
||||
QTimer::singleShot(500, this, std::bind(&rtHandler::init, this, setup));
|
||||
retryConnectCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
qCritical(logAudio()) << (setup.isinput ? "Input" : "Output") << "*** Retry count exceeded, giving up!";
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
|
||||
void rtHandler::setVolume(unsigned char volume)
|
||||
{
|
||||
this->volume = audiopot[volume];
|
||||
}
|
||||
|
||||
void rtHandler::incomingAudio(audioPacket packet)
|
||||
{
|
||||
packet.volume = volume;
|
||||
emit sendToConverter(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
int rtHandler::readData(void* outputBuffer, void* inputBuffer,
|
||||
unsigned int nFrames, double streamTime, RtAudioStreamStatus status)
|
||||
{
|
||||
Q_UNUSED(inputBuffer);
|
||||
Q_UNUSED(streamTime);
|
||||
int nBytes = nFrames * nativeFormat.bytesPerFrame();
|
||||
//lastSentSeq = packet.seq;
|
||||
if (arrayBuffer.length() >= nBytes) {
|
||||
if (audioMutex.tryLock(0)) {
|
||||
std::memcpy(outputBuffer, arrayBuffer.constData(), nBytes);
|
||||
arrayBuffer.remove(0, nBytes);
|
||||
audioMutex.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
if (status == RTAUDIO_INPUT_OVERFLOW) {
|
||||
isUnderrun = true;
|
||||
}
|
||||
else if (status == RTAUDIO_OUTPUT_UNDERFLOW) {
|
||||
isOverrun = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isUnderrun = false;
|
||||
isOverrun = false;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int rtHandler::writeData(void* outputBuffer, void* inputBuffer,
|
||||
unsigned int nFrames, double streamTime, RtAudioStreamStatus status)
|
||||
{
|
||||
Q_UNUSED(outputBuffer);
|
||||
Q_UNUSED(streamTime);
|
||||
Q_UNUSED(status);
|
||||
audioPacket packet;
|
||||
packet.time = QTime::currentTime();
|
||||
packet.sent = 0;
|
||||
packet.volume = volume;
|
||||
memcpy(&packet.guid, setup.guid, GUIDLEN);
|
||||
packet.data.append((char*)inputBuffer, nFrames * nativeFormat.bytesPerFrame());
|
||||
emit sendToConverter(packet);
|
||||
|
||||
if (status == RTAUDIO_INPUT_OVERFLOW) {
|
||||
isUnderrun = true;
|
||||
}
|
||||
else if (status == RTAUDIO_OUTPUT_UNDERFLOW) {
|
||||
isOverrun = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
isUnderrun = false;
|
||||
isOverrun = false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void rtHandler::convertedOutput(audioPacket packet)
|
||||
{
|
||||
audioMutex.lock();
|
||||
arrayBuffer.append(packet.data);
|
||||
audioMutex.unlock();
|
||||
amplitude = packet.amplitudePeak;
|
||||
currentLatency = packet.time.msecsTo(QTime::currentTime()) + (nativeFormat.durationForBytes(audio->getStreamLatency() * nativeFormat.bytesPerFrame()) / 1000);
|
||||
emit haveLevels(getAmplitude(), packet.amplitudeRMS, setup.latency, currentLatency, isUnderrun, isOverrun);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void rtHandler::convertedInput(audioPacket packet)
|
||||
{
|
||||
if (packet.data.size() > 0) {
|
||||
emit haveAudioData(packet);
|
||||
amplitude = packet.amplitudePeak;
|
||||
currentLatency = packet.time.msecsTo(QTime::currentTime()) + (nativeFormat.durationForBytes(audio->getStreamLatency() * nativeFormat.bytesPerFrame()) / 1000);
|
||||
emit haveLevels(getAmplitude(), static_cast<quint16>(packet.amplitudeRMS * 255.0), setup.latency, currentLatency, isUnderrun, isOverrun);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void rtHandler::changeLatency(const quint16 newSize)
|
||||
{
|
||||
qInfo(logAudio()) << (setup.isinput ? "Input" : "Output") << "Changing latency to: " << newSize << " from " << setup.latency;
|
||||
}
|
||||
|
||||
int rtHandler::getLatency()
|
||||
{
|
||||
return currentLatency;
|
||||
}
|
||||
|
||||
|
||||
quint16 rtHandler::getAmplitude()
|
||||
{
|
||||
return static_cast<quint16>(amplitude * 255.0);
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
#ifndef rtHandler_H
|
||||
#define rtHandler_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QThread>
|
||||
#include <QMutex>
|
||||
|
||||
#ifndef Q_OS_LINUX
|
||||
#include "RtAudio.h"
|
||||
#else
|
||||
#include "rtaudio/RtAudio.h"
|
||||
#endif
|
||||
|
||||
|
||||
#include <QAudioFormat>
|
||||
#include <QTime>
|
||||
#include <QMap>
|
||||
#include <QTimer>
|
||||
|
||||
|
||||
/* wfview Packet types */
|
||||
#include "packettypes.h"
|
||||
|
||||
/* Logarithmic taper for volume control */
|
||||
#include "audiotaper.h"
|
||||
|
||||
#include "audiohandler.h"
|
||||
/* Audio converter class*/
|
||||
#include "audioconverter.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
|
||||
class rtHandler : public audioHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
rtHandler(QObject* parent = 0);
|
||||
~rtHandler();
|
||||
|
||||
int getLatency();
|
||||
|
||||
using audioHandler::getNextAudioChunk;
|
||||
void getNextAudioChunk(QByteArray& data);
|
||||
quint16 getAmplitude();
|
||||
|
||||
public slots:
|
||||
bool init(audioSetup setup);
|
||||
void changeLatency(const quint16 newSize);
|
||||
void setVolume(unsigned char volume);
|
||||
void convertedInput(audioPacket audio);
|
||||
void convertedOutput(audioPacket audio);
|
||||
void incomingAudio(const audioPacket data);
|
||||
|
||||
|
||||
private slots:
|
||||
|
||||
signals:
|
||||
void audioMessage(QString message);
|
||||
void sendLatency(quint16 newSize);
|
||||
void haveAudioData(const audioPacket& data);
|
||||
void haveLevels(quint16 amplitudePeak, quint16 amplitudeRMS, quint16 latency, quint16 current, bool under, bool over);
|
||||
void setupConverter(QAudioFormat in, codecType codecIn, QAudioFormat out, codecType codecOut, quint8 opus, quint8 resamp);
|
||||
void sendToConverter(audioPacket audio);
|
||||
|
||||
private:
|
||||
|
||||
|
||||
int readData(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status);
|
||||
|
||||
static int staticRead(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void* userData) {
|
||||
return static_cast<rtHandler*>(userData)->readData(outputBuffer, inputBuffer, nFrames, streamTime, status);
|
||||
}
|
||||
|
||||
|
||||
int writeData(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status);
|
||||
|
||||
static int staticWrite(void* outputBuffer, void* inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void* userData) {
|
||||
return static_cast<rtHandler*>(userData)->writeData(outputBuffer, inputBuffer, nFrames, streamTime, status);
|
||||
}
|
||||
|
||||
|
||||
bool isInitialized = false;
|
||||
|
||||
RtAudio* audio = Q_NULLPTR;
|
||||
int audioDevice = 0;
|
||||
RtAudio::StreamParameters aParams;
|
||||
RtAudio::StreamOptions options;
|
||||
RtAudio::DeviceInfo info;
|
||||
|
||||
quint16 audioLatency;
|
||||
unsigned int chunkSize;
|
||||
|
||||
quint32 lastSeq;
|
||||
quint32 lastSentSeq = 0;
|
||||
|
||||
quint16 currentLatency;
|
||||
float amplitude = 0.0;
|
||||
qreal volume = 1.0;
|
||||
|
||||
audioSetup setup;
|
||||
QAudioFormat radioFormat;
|
||||
QAudioFormat nativeFormat;
|
||||
audioConverter* converter = Q_NULLPTR;
|
||||
QThread* converterThread = Q_NULLPTR;
|
||||
QByteArray arrayBuffer;
|
||||
bool isUnderrun = false;
|
||||
bool isOverrun = false;
|
||||
QMutex audioMutex;
|
||||
int retryConnectCount = 0;
|
||||
};
|
||||
|
||||
#endif // rtHandler_H
|
|
@ -0,0 +1,85 @@
|
|||
#include "logcategories.h"
|
||||
#include "selectradio.h"
|
||||
#include "ui_selectradio.h"
|
||||
|
||||
|
||||
selectRadio::selectRadio(QWidget* parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::selectRadio)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
}
|
||||
|
||||
selectRadio::~selectRadio()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void selectRadio::populate(QList<radio_cap_packet> radios)
|
||||
{
|
||||
ui->table->clearContents();
|
||||
for (int row = ui->table->rowCount() - 1;row>=0; row--) {
|
||||
ui->table->removeRow(row);
|
||||
}
|
||||
|
||||
for (int row = 0; row < radios.count(); row++) {
|
||||
ui->table->insertRow(ui->table->rowCount());
|
||||
ui->table->setItem(row, 0, new QTableWidgetItem(QString(radios[row].name)));
|
||||
ui->table->setItem(row, 1, new QTableWidgetItem(QString("%1").arg((unsigned char)radios[row].civ, 2, 16, QLatin1Char('0')).toUpper()));
|
||||
ui->table->setItem(row, 2, new QTableWidgetItem(QString::number(qFromBigEndian(radios[row].baudrate))));
|
||||
}
|
||||
if (radios.count() > 1) {
|
||||
this->setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
void selectRadio::setInUse(quint8 radio, quint8 busy, QString user, QString ip)
|
||||
{
|
||||
//if ((radio > 0)&& !this->isVisible()) {
|
||||
// qInfo() << "setInUse: radio:" << radio <<"busy" << busy << "user" << user << "ip"<<ip;
|
||||
// this->setVisible(true);
|
||||
//}
|
||||
ui->table->setItem(radio, 3, new QTableWidgetItem(user));
|
||||
ui->table->setItem(radio, 4, new QTableWidgetItem(ip));
|
||||
for (int f = 0; f < 5; f++) {
|
||||
if (busy == 1)
|
||||
{
|
||||
ui->table->item(radio, f)->setBackground(Qt::darkGreen);
|
||||
}
|
||||
else if (busy == 2)
|
||||
{
|
||||
ui->table->item(radio, f)->setBackground(Qt::red);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->table->item(radio, f)->setBackground(Qt::black);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void selectRadio::on_table_cellClicked(int row, int col) {
|
||||
qInfo() << "Clicked on " << row << "," << col;
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5,11,0))
|
||||
if (ui->table->item(row, col)->backgroundColor() != Qt::darkGreen) {
|
||||
#else
|
||||
if (ui->table->item(row, col)->background() != Qt::darkGreen) {
|
||||
#endif
|
||||
ui->table->selectRow(row);
|
||||
emit selectedRadio(row);
|
||||
this->setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void selectRadio::on_cancelButton_clicked() {
|
||||
this->setVisible(false);
|
||||
}
|
||||
|
||||
void selectRadio::audioOutputLevel(quint16 level) {
|
||||
ui->afLevel->setValue(level);
|
||||
}
|
||||
|
||||
void selectRadio::audioInputLevel(quint16 level) {
|
||||
ui->modLevel->setValue(level);
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef SELECTRADIO_H
|
||||
#define SELECTRADIO_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QList>
|
||||
#include <QtEndian>
|
||||
#include <QHostInfo>
|
||||
#include "packettypes.h"
|
||||
|
||||
namespace Ui {
|
||||
class selectRadio;
|
||||
}
|
||||
|
||||
class selectRadio : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit selectRadio(QWidget* parent = 0);
|
||||
~selectRadio();
|
||||
void populate(QList<radio_cap_packet> radios);
|
||||
void audioOutputLevel(quint16 level);
|
||||
void audioInputLevel(quint16 level);
|
||||
|
||||
public slots:
|
||||
void on_table_cellClicked(int row, int col);
|
||||
void setInUse(quint8 radio, quint8 busy, QString user, QString ip);
|
||||
void on_cancelButton_clicked();
|
||||
|
||||
signals:
|
||||
void selectedRadio(quint8 radio);
|
||||
|
||||
private:
|
||||
Ui::selectRadio* ui;
|
||||
|
||||
};
|
||||
|
||||
#endif // SELECTRADIO_H
|
|
@ -0,0 +1,157 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>selectRadio</class>
|
||||
<widget class="QDialog" name="selectRadio">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>590</width>
|
||||
<height>206</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Select Radio From List</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancelButton">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTableWidget" name="table">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderStretchLastSection">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Rig Name</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>CI-V</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Baud Rate</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Current User</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>User IP Address</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>AF</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="afLevel">
|
||||
<property name="maximum">
|
||||
<number>255</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>MOD</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QProgressBar" name="modLevel">
|
||||
<property name="maximum">
|
||||
<number>255</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="textVisible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -0,0 +1,646 @@
|
|||
#include "servermain.h"
|
||||
|
||||
#include "commhandler.h"
|
||||
#include "rigidentities.h"
|
||||
#include "logcategories.h"
|
||||
#include <iostream>
|
||||
|
||||
// This code is copyright 2017-2020 Elliott H. Liggett
|
||||
// All rights reserved
|
||||
|
||||
servermain::servermain(const QString settingsFile)
|
||||
{
|
||||
|
||||
qRegisterMetaType <udpPreferences>(); // Needs to be registered early.
|
||||
qRegisterMetaType <rigCapabilities>();
|
||||
qRegisterMetaType <duplexMode>();
|
||||
qRegisterMetaType <rptAccessTxRx>();
|
||||
qRegisterMetaType <rptrAccessData_t>();
|
||||
qRegisterMetaType <rptAccessTxRx>();
|
||||
qRegisterMetaType <rigInput>();
|
||||
qRegisterMetaType <meterKind>();
|
||||
qRegisterMetaType <spectrumMode>();
|
||||
qRegisterMetaType <freqt>();
|
||||
qRegisterMetaType <mode_info>();
|
||||
qRegisterMetaType <audioPacket>();
|
||||
qRegisterMetaType <audioSetup>();
|
||||
qRegisterMetaType <SERVERCONFIG>();
|
||||
qRegisterMetaType <timekind>();
|
||||
qRegisterMetaType <datekind>();
|
||||
qRegisterMetaType<rigstate*>();
|
||||
qRegisterMetaType<QList<radio_cap_packet>>();
|
||||
qRegisterMetaType<networkStatus>();
|
||||
qRegisterMetaType<codecType>();
|
||||
qRegisterMetaType<errorType>();
|
||||
|
||||
setDefPrefs();
|
||||
|
||||
getSettingsFilePath(settingsFile);
|
||||
|
||||
loadSettings(); // Look for saved preferences
|
||||
|
||||
audioDev = new audioDevices(prefs.audioSystem, QFontMetrics(QFont()));
|
||||
connect(audioDev, SIGNAL(updated()), this, SLOT(updateAudioDevices()));
|
||||
audioDev->enumerate();
|
||||
|
||||
setInitialTiming();
|
||||
|
||||
openRig();
|
||||
|
||||
setServerToPrefs();
|
||||
|
||||
amTransmitting = false;
|
||||
|
||||
}
|
||||
|
||||
servermain::~servermain()
|
||||
{
|
||||
for (RIGCONFIG* radio : serverConfig.rigs)
|
||||
{
|
||||
if (radio->rigThread != Q_NULLPTR)
|
||||
{
|
||||
radio->rigThread->quit();
|
||||
radio->rigThread->wait();
|
||||
}
|
||||
delete radio; // This has been created by new in loadSettings();
|
||||
}
|
||||
serverConfig.rigs.clear();
|
||||
if (serverThread != Q_NULLPTR) {
|
||||
serverThread->quit();
|
||||
serverThread->wait();
|
||||
}
|
||||
|
||||
if (audioDev != Q_NULLPTR) {
|
||||
delete audioDev;
|
||||
}
|
||||
|
||||
delete settings;
|
||||
|
||||
#if defined(PORTAUDIO)
|
||||
Pa_Terminate();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void servermain::openRig()
|
||||
{
|
||||
// This function is intended to handle opening a connection to the rig.
|
||||
// the connection can be either serial or network,
|
||||
// and this function is also responsible for initiating the search for a rig model and capabilities.
|
||||
// Any errors, such as unable to open connection or unable to open port, are to be reported to the user.
|
||||
|
||||
|
||||
makeRig();
|
||||
|
||||
for (RIGCONFIG* radio : serverConfig.rigs)
|
||||
{
|
||||
//qInfo(logSystem()) << "Opening rig";
|
||||
if (radio->rigThread != Q_NULLPTR)
|
||||
{
|
||||
//qInfo(logSystem()) << "Got rig";
|
||||
QMetaObject::invokeMethod(radio->rig, [=]() {
|
||||
radio->rig->commSetup(radio->civAddr, radio->serialPort, radio->baudRate, QString("none"),0 ,radio->waterfallFormat);
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void servermain::makeRig()
|
||||
{
|
||||
for (RIGCONFIG* radio : serverConfig.rigs)
|
||||
{
|
||||
if (radio->rigThread == Q_NULLPTR)
|
||||
{
|
||||
qInfo(logSystem()) << "Creating new rigThread()";
|
||||
radio->rig = new rigCommander(radio->guid);
|
||||
radio->rigThread = new QThread(this);
|
||||
radio->rigThread->setObjectName("rigCommander()");
|
||||
|
||||
// Thread:
|
||||
radio->rig->moveToThread(radio->rigThread);
|
||||
connect(radio->rigThread, SIGNAL(started()), radio->rig, SLOT(process()));
|
||||
connect(radio->rigThread, SIGNAL(finished()), radio->rig, SLOT(deleteLater()));
|
||||
radio->rigThread->start();
|
||||
// Rig status and Errors:
|
||||
connect(radio->rig, SIGNAL(havePortError(errorType)), this, SLOT(receivePortError(errorType)));
|
||||
connect(radio->rig, SIGNAL(haveStatusUpdate(networkStatus)), this, SLOT(receiveStatusUpdate(networkStatus)));
|
||||
|
||||
// Rig comm setup:
|
||||
connect(this, SIGNAL(setRTSforPTT(bool)), radio->rig, SLOT(setRTSforPTT(bool)));
|
||||
|
||||
connect(radio->rig, SIGNAL(haveBaudRate(quint32)), this, SLOT(receiveBaudRate(quint32)));
|
||||
|
||||
connect(this, SIGNAL(sendCloseComm()), radio->rig, SLOT(closeComm()));
|
||||
connect(this, SIGNAL(sendChangeLatency(quint16)), radio->rig, SLOT(changeLatency(quint16)));
|
||||
//connect(this, SIGNAL(getRigCIV()), radio->rig, SLOT(findRigs()));
|
||||
//connect(this, SIGNAL(setRigID(unsigned char)), radio->rig, SLOT(setRigID(unsigned char)));
|
||||
connect(radio->rig, SIGNAL(discoveredRigID(rigCapabilities)), this, SLOT(receiveFoundRigID(rigCapabilities)));
|
||||
connect(radio->rig, SIGNAL(commReady()), this, SLOT(receiveCommReady()));
|
||||
|
||||
connect(this, SIGNAL(requestRigState()), radio->rig, SLOT(sendState()));
|
||||
connect(this, SIGNAL(stateUpdated()), radio->rig, SLOT(stateUpdated()));
|
||||
connect(radio->rig, SIGNAL(stateInfo(rigstate*)), this, SLOT(receiveStateInfo(rigstate*)));
|
||||
|
||||
//Other connections
|
||||
connect(this, SIGNAL(setCIVAddr(unsigned char)), radio->rig, SLOT(setCIVAddr(unsigned char)));
|
||||
|
||||
connect(radio->rig, SIGNAL(havePTTStatus(bool)), this, SLOT(receivePTTstatus(bool)));
|
||||
connect(this, SIGNAL(setPTT(bool)), radio->rig, SLOT(setPTT(bool)));
|
||||
connect(this, SIGNAL(getPTT()), radio->rig, SLOT(getPTT()));
|
||||
connect(this, SIGNAL(getDebug()), radio->rig, SLOT(getDebug()));
|
||||
if (radio->rigThread->isRunning()) {
|
||||
qInfo(logSystem()) << "Rig thread is running";
|
||||
}
|
||||
else {
|
||||
qInfo(logSystem()) << "Rig thread is not running";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void servermain::removeRig()
|
||||
{
|
||||
for (RIGCONFIG* radio : serverConfig.rigs)
|
||||
{
|
||||
if (radio->rigThread != Q_NULLPTR)
|
||||
{
|
||||
radio->rigThread->disconnect();
|
||||
radio->rig->disconnect();
|
||||
delete radio->rigThread;
|
||||
delete radio->rig;
|
||||
radio->rig = Q_NULLPTR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void servermain::findSerialPort()
|
||||
{
|
||||
// Find the ICOM radio connected, or, if none, fall back to OS default.
|
||||
// qInfo(logSystem()) << "Searching for serial port...";
|
||||
QDirIterator it73("/dev/serial/by-id", QStringList() << "*IC-7300*", QDir::Files, QDirIterator::Subdirectories);
|
||||
QDirIterator it97("/dev/serial", QStringList() << "*IC-9700*A*", QDir::Files, QDirIterator::Subdirectories);
|
||||
QDirIterator it785x("/dev/serial", QStringList() << "*IC-785*A*", QDir::Files, QDirIterator::Subdirectories);
|
||||
QDirIterator it705("/dev/serial", QStringList() << "*IC-705*A", QDir::Files, QDirIterator::Subdirectories);
|
||||
QDirIterator it7610("/dev/serial", QStringList() << "*IC-7610*A", QDir::Files, QDirIterator::Subdirectories);
|
||||
QDirIterator itR8600("/dev/serial", QStringList() << "*IC-R8600*A", QDir::Files, QDirIterator::Subdirectories);
|
||||
|
||||
if(!it73.filePath().isEmpty())
|
||||
{
|
||||
// IC-7300
|
||||
serialPortRig = it73.filePath(); // first
|
||||
} else if(!it97.filePath().isEmpty())
|
||||
{
|
||||
// IC-9700
|
||||
serialPortRig = it97.filePath();
|
||||
} else if(!it785x.filePath().isEmpty())
|
||||
{
|
||||
// IC-785x
|
||||
serialPortRig = it785x.filePath();
|
||||
} else if(!it705.filePath().isEmpty())
|
||||
{
|
||||
// IC-705
|
||||
serialPortRig = it705.filePath();
|
||||
} else if(!it7610.filePath().isEmpty())
|
||||
{
|
||||
// IC-7610
|
||||
serialPortRig = it7610.filePath();
|
||||
} else if(!itR8600.filePath().isEmpty())
|
||||
{
|
||||
// IC-R8600
|
||||
serialPortRig = itR8600.filePath();
|
||||
} else {
|
||||
//fall back:
|
||||
qInfo(logSystem()) << "Could not find Icom serial port. Falling back to OS default. Use --port to specify, or modify preferences.";
|
||||
#ifdef Q_OS_MAC
|
||||
serialPortRig = QString("/dev/tty.SLAB_USBtoUART");
|
||||
#endif
|
||||
#ifdef Q_OS_LINUX
|
||||
serialPortRig = QString("/dev/ttyUSB0");
|
||||
#endif
|
||||
#ifdef Q_OS_WIN
|
||||
serialPortRig = QString("COM1");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void servermain::receiveStatusUpdate(networkStatus status)
|
||||
{
|
||||
if (status.message != lastMessage) {
|
||||
std::cout << status.message.toLocal8Bit().toStdString() << "\n";
|
||||
lastMessage = status.message;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void servermain::receiveCommReady()
|
||||
{
|
||||
rigCommander* sender = qobject_cast<rigCommander*>(QObject::sender());
|
||||
|
||||
// Use the GUID to determine which radio the response is from
|
||||
|
||||
for (RIGCONFIG* radio : serverConfig.rigs)
|
||||
{
|
||||
if (sender != Q_NULLPTR && radio->rig != Q_NULLPTR && !memcmp(sender->getGUID(), radio->guid, GUIDLEN))
|
||||
{
|
||||
|
||||
qInfo(logSystem()) << "Received CommReady!! ";
|
||||
if (radio->civAddr == 0)
|
||||
{
|
||||
// tell rigCommander to broadcast a request for all rig IDs.
|
||||
// qInfo(logSystem()) << "Beginning search from wfview for rigCIV (auto-detection broadcast)";
|
||||
if (!radio->rigAvailable) {
|
||||
if (radio->connectTimer == Q_NULLPTR) {
|
||||
radio->connectTimer = new QTimer();
|
||||
connect(radio->connectTimer, &QTimer::timeout, this, std::bind(&servermain::connectToRig, this, radio));
|
||||
}
|
||||
radio->connectTimer->start(500);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// don't bother, they told us the CIV they want, stick with it.
|
||||
// We still query the rigID to find the model, but at least we know the CIV.
|
||||
qInfo(logSystem()) << "Skipping automatic CIV, using user-supplied value of " << radio->civAddr;
|
||||
QMetaObject::invokeMethod(radio->rig, [=]() {
|
||||
radio->rig->setRigID(radio->civAddr);
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void servermain::connectToRig(RIGCONFIG* rig)
|
||||
{
|
||||
if (!rig->rigAvailable) {
|
||||
qDebug(logSystem()) << "Searching for rig on" << rig->serialPort;
|
||||
QMetaObject::invokeMethod(rig->rig, [=]() {
|
||||
rig->rig->findRigs();
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
else {
|
||||
rig->connectTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void servermain::receiveFoundRigID(rigCapabilities rigCaps)
|
||||
{
|
||||
// Entry point for unknown rig being identified at the start of the program.
|
||||
//now we know what the rig ID is:
|
||||
|
||||
rigCommander* sender = qobject_cast<rigCommander*>(QObject::sender());
|
||||
|
||||
// Use the GUID to determine which radio the response is from
|
||||
for (RIGCONFIG* radio : serverConfig.rigs)
|
||||
{
|
||||
|
||||
if (sender != Q_NULLPTR && radio->rig != Q_NULLPTR && !radio->rigAvailable && !memcmp(sender->getGUID(), radio->guid, GUIDLEN))
|
||||
{
|
||||
|
||||
qDebug(logSystem()) << "Rig name: " << rigCaps.modelName;
|
||||
qDebug(logSystem()) << "Has LAN capabilities: " << rigCaps.hasLan;
|
||||
qDebug(logSystem()) << "Rig ID received into servermain: spectLenMax: " << rigCaps.spectLenMax;
|
||||
qDebug(logSystem()) << "Rig ID received into servermain: spectAmpMax: " << rigCaps.spectAmpMax;
|
||||
qDebug(logSystem()) << "Rig ID received into servermain: spectSeqMax: " << rigCaps.spectSeqMax;
|
||||
qDebug(logSystem()) << "Rig ID received into servermain: hasSpectrum: " << rigCaps.hasSpectrum;
|
||||
qDebug(logSystem()).noquote() << QString("Rig ID received into servermain: GUID: {%1%2%3%4-%5%6-%7%8-%9%10-%11%12%13%14%15%16}")
|
||||
.arg(rigCaps.guid[0], 2, 16, QLatin1Char('0'))
|
||||
.arg(rigCaps.guid[1], 2, 16, QLatin1Char('0'))
|
||||
.arg(rigCaps.guid[2], 2, 16, QLatin1Char('0'))
|
||||
.arg(rigCaps.guid[3], 2, 16, QLatin1Char('0'))
|
||||
.arg(rigCaps.guid[4], 2, 16, QLatin1Char('0'))
|
||||
.arg(rigCaps.guid[5], 2, 16, QLatin1Char('0'))
|
||||
.arg(rigCaps.guid[6], 2, 16, QLatin1Char('0'))
|
||||
.arg(rigCaps.guid[7], 2, 16, QLatin1Char('0'))
|
||||
.arg(rigCaps.guid[8], 2, 16, QLatin1Char('0'))
|
||||
.arg(rigCaps.guid[9], 2, 16, QLatin1Char('0'))
|
||||
.arg(rigCaps.guid[10], 2, 16, QLatin1Char('0'))
|
||||
.arg(rigCaps.guid[11], 2, 16, QLatin1Char('0'))
|
||||
.arg(rigCaps.guid[12], 2, 16, QLatin1Char('0'))
|
||||
.arg(rigCaps.guid[13], 2, 16, QLatin1Char('0'))
|
||||
.arg(rigCaps.guid[14], 2, 16, QLatin1Char('0'))
|
||||
.arg(rigCaps.guid[15], 2, 16, QLatin1Char('0'))
|
||||
;
|
||||
radio->rigCaps = rigCaps;
|
||||
// Added so that server receives rig capabilities.
|
||||
emit sendRigCaps(rigCaps);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void servermain::receivePortError(errorType err)
|
||||
{
|
||||
qInfo(logSystem()) << "servermain: received error for device: " << err.device << " with message: " << err.message;
|
||||
}
|
||||
|
||||
|
||||
void servermain::getSettingsFilePath(QString settingsFile)
|
||||
{
|
||||
if (settingsFile.isNull()) {
|
||||
settings = new QSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
QString file = settingsFile;
|
||||
QFile info(settingsFile);
|
||||
QString path="";
|
||||
if (!QFileInfo(info).isAbsolute())
|
||||
{
|
||||
path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||
if (path.isEmpty())
|
||||
{
|
||||
path = QDir::homePath();
|
||||
}
|
||||
path = path + "/";
|
||||
file = info.fileName();
|
||||
}
|
||||
|
||||
qInfo(logSystem()) << "Loading settings from:" << path + file;
|
||||
settings = new QSettings(path + file, QSettings::Format::IniFormat);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void servermain::setInitialTiming()
|
||||
{
|
||||
loopTickCounter = 0;
|
||||
pttTimer = new QTimer(this);
|
||||
pttTimer->setInterval(180*1000); // 3 minute max transmit time in ms
|
||||
pttTimer->setSingleShot(true);
|
||||
connect(pttTimer, SIGNAL(timeout()), this, SLOT(handlePttLimit()));
|
||||
}
|
||||
|
||||
void servermain::setServerToPrefs()
|
||||
{
|
||||
|
||||
// Start server if enabled in config
|
||||
if (serverThread != Q_NULLPTR) {
|
||||
serverThread->quit();
|
||||
serverThread->wait();
|
||||
serverThread = Q_NULLPTR;
|
||||
udp = Q_NULLPTR;
|
||||
}
|
||||
|
||||
udp = new udpServer(&serverConfig);
|
||||
|
||||
serverThread = new QThread(this);
|
||||
serverThread->setObjectName("udpServer()");
|
||||
|
||||
udp->moveToThread(serverThread);
|
||||
|
||||
|
||||
connect(this, SIGNAL(initServer()), udp, SLOT(init()));
|
||||
connect(serverThread, SIGNAL(finished()), udp, SLOT(deleteLater()));
|
||||
|
||||
// Step through all radios and connect them to the server,
|
||||
// server will then use GUID to determine which actual radio it belongs to.
|
||||
|
||||
for (RIGCONFIG* radio : serverConfig.rigs)
|
||||
{
|
||||
if (radio->rigThread != Q_NULLPTR)
|
||||
{
|
||||
|
||||
if (radio->rig != Q_NULLPTR) {
|
||||
connect(radio->rig, SIGNAL(haveAudioData(audioPacket)), udp, SLOT(receiveAudioData(audioPacket)));
|
||||
connect(radio->rig, SIGNAL(haveDataForServer(QByteArray)), udp, SLOT(dataForServer(QByteArray)));
|
||||
//connect(udp, SIGNAL(haveDataFromServer(QByteArray)), radio->rig, SLOT(dataFromServer(QByteArray)));
|
||||
connect(this, SIGNAL(sendRigCaps(rigCapabilities)), udp, SLOT(receiveRigCaps(rigCapabilities)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connect(udp, SIGNAL(haveNetworkStatus(networkStatus)), this, SLOT(receiveStatusUpdate(networkStatus)));
|
||||
|
||||
serverThread->start();
|
||||
|
||||
emit initServer();
|
||||
}
|
||||
|
||||
|
||||
void servermain::setDefPrefs()
|
||||
{
|
||||
defPrefs.radioCIVAddr = 0x00; // previously was 0x94 for 7300.
|
||||
defPrefs.CIVisRadioModel = false;
|
||||
defPrefs.forceRTSasPTT = false;
|
||||
defPrefs.serialPortRadio = QString("auto");
|
||||
defPrefs.serialPortBaud = 115200;
|
||||
defPrefs.localAFgain = 255;
|
||||
defPrefs.tcpPort = 0;
|
||||
defPrefs.audioSystem = qtAudio;
|
||||
defPrefs.rxAudio.name = QString("default");
|
||||
defPrefs.txAudio.name = QString("default");
|
||||
|
||||
udpDefPrefs.ipAddress = QString("");
|
||||
udpDefPrefs.controlLANPort = 50001;
|
||||
udpDefPrefs.serialLANPort = 50002;
|
||||
udpDefPrefs.audioLANPort = 50003;
|
||||
udpDefPrefs.username = QString("");
|
||||
udpDefPrefs.password = QString("");
|
||||
udpDefPrefs.clientName = QHostInfo::localHostName();
|
||||
|
||||
}
|
||||
|
||||
void servermain::loadSettings()
|
||||
{
|
||||
qInfo(logSystem()) << "Loading settings from " << settings->fileName();
|
||||
prefs.audioSystem = static_cast<audioType>(settings->value("AudioSystem", defPrefs.audioSystem).toInt());
|
||||
|
||||
int numRadios = settings->beginReadArray("Radios");
|
||||
if (numRadios == 0) {
|
||||
settings->endArray();
|
||||
|
||||
// We assume that QSettings is empty as there are no radios configured, create new:
|
||||
qInfo(logSystem()) << "Creating new settings file " << settings->fileName();
|
||||
settings->setValue("AudioSystem", defPrefs.audioSystem);
|
||||
numRadios = 1;
|
||||
settings->beginWriteArray("Radios");
|
||||
for (int i = 0; i < numRadios; i++)
|
||||
{
|
||||
settings->setArrayIndex(i);
|
||||
settings->setValue("RigCIVuInt", defPrefs.radioCIVAddr);
|
||||
settings->setValue("ForceRTSasPTT", defPrefs.forceRTSasPTT);
|
||||
settings->setValue("SerialPortRadio", defPrefs.serialPortRadio);
|
||||
settings->setValue("RigName", "<NONE>");
|
||||
settings->setValue("SerialPortBaud", defPrefs.serialPortBaud);
|
||||
settings->setValue("AudioInput", "default");
|
||||
settings->setValue("AudioOutput", "default");
|
||||
settings->setValue("WaterfallFormat", 0);
|
||||
}
|
||||
settings->endArray();
|
||||
|
||||
settings->beginGroup("Server");
|
||||
settings->setValue("ServerEnabled", true);
|
||||
settings->setValue("ServerControlPort", udpDefPrefs.controlLANPort);
|
||||
settings->setValue("ServerCivPort", udpDefPrefs.serialLANPort);
|
||||
settings->setValue("ServerAudioPort", udpDefPrefs.audioLANPort);
|
||||
|
||||
settings->beginWriteArray("Users");
|
||||
settings->setArrayIndex(0);
|
||||
settings->setValue("Username", "user");
|
||||
QByteArray pass;
|
||||
passcode("password", pass);
|
||||
settings->setValue("Password", QString(pass));
|
||||
settings->setValue("UserType", 0);
|
||||
|
||||
settings->endArray();
|
||||
|
||||
settings->endGroup();
|
||||
settings->sync();
|
||||
|
||||
} else {
|
||||
settings->endArray();
|
||||
}
|
||||
|
||||
numRadios = settings->beginReadArray("Radios");
|
||||
int tempNum = numRadios;
|
||||
|
||||
for (int i = 0; i < numRadios; i++) {
|
||||
settings->setArrayIndex(i);
|
||||
RIGCONFIG* tempPrefs = new RIGCONFIG();
|
||||
tempPrefs->civAddr = (unsigned char)settings->value("RigCIVuInt", defPrefs.radioCIVAddr).toInt();
|
||||
tempPrefs->forceRTSasPTT = (bool)settings->value("ForceRTSasPTT", defPrefs.forceRTSasPTT).toBool();
|
||||
tempPrefs->serialPort = settings->value("SerialPortRadio", defPrefs.serialPortRadio).toString();
|
||||
tempPrefs->rigName = settings->value("RigName", "<NONE>").toString();
|
||||
tempPrefs->baudRate = (quint32)settings->value("SerialPortBaud", defPrefs.serialPortBaud).toInt();
|
||||
tempPrefs->rxAudioSetup.name = settings->value("AudioInput", "default").toString();
|
||||
tempPrefs->txAudioSetup.name = settings->value("AudioOutput", "default").toString();
|
||||
tempPrefs->waterfallFormat = settings->value("WaterfallFormat", 0).toInt();
|
||||
tempPrefs->rxAudioSetup.type = prefs.audioSystem;
|
||||
tempPrefs->txAudioSetup.type = prefs.audioSystem;
|
||||
|
||||
if (tempPrefs->serialPort == "auto") {
|
||||
foreach(const QSerialPortInfo & serialPortInfo, QSerialPortInfo::availablePorts())
|
||||
{
|
||||
qDebug(logSystem()) << "Serial Port found: " << serialPortInfo.portName() << "Manufacturer:" << serialPortInfo.manufacturer() << "Product ID" << serialPortInfo.description() << "S/N" << serialPortInfo.serialNumber();
|
||||
if (serialPortInfo.serialNumber().startsWith("IC-") && tempPrefs->serialPort == "auto") {
|
||||
tempPrefs->rigName = serialPortInfo.serialNumber();
|
||||
tempPrefs->serialPort = serialPortInfo.portName();
|
||||
}
|
||||
}
|
||||
}
|
||||
QString guid = settings->value("GUID", "").toString();
|
||||
if (guid.isEmpty()) {
|
||||
guid = QUuid::createUuid().toString();
|
||||
settings->setValue("GUID", guid);
|
||||
}
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5,10,0))
|
||||
memcpy(tempPrefs->guid, QUuid::fromString(guid).toRfc4122().constData(), GUIDLEN);
|
||||
#endif
|
||||
tempPrefs->rxAudioSetup.isinput = true;
|
||||
tempPrefs->txAudioSetup.isinput = false;
|
||||
tempPrefs->rxAudioSetup.localAFgain = 255;
|
||||
tempPrefs->txAudioSetup.localAFgain = 255;
|
||||
tempPrefs->rxAudioSetup.resampleQuality = 4;
|
||||
tempPrefs->txAudioSetup.resampleQuality = 4;
|
||||
|
||||
tempPrefs->rig = Q_NULLPTR;
|
||||
tempPrefs->rigThread = Q_NULLPTR;
|
||||
|
||||
serverConfig.rigs.append(tempPrefs);
|
||||
if (tempNum == 0) {
|
||||
settings->endGroup();
|
||||
}
|
||||
}
|
||||
if (tempNum > 0) {
|
||||
settings->endArray();
|
||||
}
|
||||
|
||||
|
||||
settings->beginGroup("Server");
|
||||
serverConfig.enabled = settings->value("ServerEnabled", false).toBool();
|
||||
serverConfig.controlPort = settings->value("ServerControlPort", 50001).toInt();
|
||||
serverConfig.civPort = settings->value("ServerCivPort", 50002).toInt();
|
||||
serverConfig.audioPort = settings->value("ServerAudioPort", 50003).toInt();
|
||||
|
||||
serverConfig.users.clear();
|
||||
|
||||
int numUsers = settings->beginReadArray("Users");
|
||||
if (numUsers > 0) {
|
||||
{
|
||||
for (int f = 0; f < numUsers; f++)
|
||||
{
|
||||
settings->setArrayIndex(f);
|
||||
SERVERUSER user;
|
||||
user.username = settings->value("Username", "").toString();
|
||||
user.password = settings->value("Password", "").toString();
|
||||
user.userType = settings->value("UserType", 0).toInt();
|
||||
serverConfig.users.append(user);
|
||||
}
|
||||
}
|
||||
settings->endArray();
|
||||
}
|
||||
|
||||
settings->endGroup();
|
||||
settings->sync();
|
||||
|
||||
#if defined(RTAUDIO)
|
||||
delete audio;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void servermain::updateAudioDevices()
|
||||
{
|
||||
|
||||
for (RIGCONFIG* rig : serverConfig.rigs)
|
||||
{
|
||||
qDebug(logAudio()) << "Rig" << rig->rigName << "configured rxAudio device:" << rig->rxAudioSetup.name;
|
||||
qDebug(logAudio()) << "Rig" << rig->rigName << "configured txAudio device:" << rig->txAudioSetup.name;
|
||||
|
||||
int inputNum = audioDev->findInput(rig->rigName, rig->rxAudioSetup.name);
|
||||
int outputNum = audioDev->findOutput(rig->rigName, rig->txAudioSetup.name);
|
||||
|
||||
if (prefs.audioSystem == qtAudio) {
|
||||
rig->rxAudioSetup.port = audioDev->getInputDeviceInfo(inputNum);
|
||||
rig->txAudioSetup.port = audioDev->getOutputDeviceInfo(outputNum);
|
||||
}
|
||||
else {
|
||||
rig->rxAudioSetup.portInt = audioDev->getInputDeviceInt(inputNum);
|
||||
rig->txAudioSetup.portInt = audioDev->getOutputDeviceInt(outputNum);
|
||||
}
|
||||
rig->rxAudioSetup.name = audioDev->getInputName(inputNum);
|
||||
rig->txAudioSetup.name = audioDev->getOutputName(outputNum);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void servermain::receivePTTstatus(bool pttOn)
|
||||
{
|
||||
// This is the only place where amTransmitting and the transmit button text should be changed:
|
||||
//qInfo(logSystem()) << "PTT status: " << pttOn;
|
||||
amTransmitting = pttOn;
|
||||
}
|
||||
|
||||
void servermain::handlePttLimit()
|
||||
{
|
||||
// ptt time exceeded!
|
||||
std::cout << "Transmit timeout at 3 minutes. Sending PTT OFF command now.\n";
|
||||
emit setPTT(false);
|
||||
emit getPTT();
|
||||
}
|
||||
|
||||
void servermain::receiveBaudRate(quint32 baud)
|
||||
{
|
||||
qInfo() << "Received serial port baud rate from remote server:" << baud;
|
||||
prefs.serialPortBaud = baud;
|
||||
}
|
||||
|
||||
void servermain::powerRigOn()
|
||||
{
|
||||
emit sendPowerOn();
|
||||
}
|
||||
|
||||
void servermain::powerRigOff()
|
||||
{
|
||||
emit sendPowerOff();
|
||||
}
|
||||
|
||||
void servermain::receiveStateInfo(rigstate* state)
|
||||
{
|
||||
qInfo("Received rig state for wfmain");
|
||||
Q_UNUSED(state);
|
||||
//rigState = state;
|
||||
}
|
|
@ -0,0 +1,315 @@
|
|||
#ifndef WFMAIN_H
|
||||
#define WFMAIN_H
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QDirIterator>
|
||||
#include <QtCore/QStandardPaths>
|
||||
|
||||
#include <QThread>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QTimer>
|
||||
#include <QSettings>
|
||||
#include <QShortcut>
|
||||
#include <QMetaType>
|
||||
|
||||
#include "logcategories.h"
|
||||
#include "commhandler.h"
|
||||
#include "rigcommander.h"
|
||||
#include "rigstate.h"
|
||||
#include "freqmemory.h"
|
||||
#include "rigidentities.h"
|
||||
#include "repeaterattributes.h"
|
||||
#include "audiodevices.h"
|
||||
|
||||
#include "udpserver.h"
|
||||
#include "rigctld.h"
|
||||
#include "signal.h"
|
||||
|
||||
#include <qserialportinfo.h>
|
||||
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
|
||||
#include <portaudio.h>
|
||||
#ifdef Q_OS_WIN
|
||||
#include "RtAudio.h"
|
||||
#else
|
||||
#include "rtaudio/RtAudio.h"
|
||||
#endif
|
||||
|
||||
namespace Ui {
|
||||
class wfmain;
|
||||
}
|
||||
|
||||
class servermain : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
servermain(const QString logFile);
|
||||
~servermain();
|
||||
|
||||
signals:
|
||||
// Basic to rig:
|
||||
void setCIVAddr(unsigned char newRigCIVAddr);
|
||||
void setRigID(unsigned char rigID);
|
||||
void setRTSforPTT(bool enabled);
|
||||
|
||||
// Power
|
||||
void sendPowerOn();
|
||||
void sendPowerOff();
|
||||
|
||||
// Frequency, mode, band:
|
||||
void getFrequency();
|
||||
void setFrequency(unsigned char vfo, freqt freq);
|
||||
void getMode();
|
||||
void setMode(unsigned char modeIndex, unsigned char modeFilter);
|
||||
void setMode(mode_info);
|
||||
void setDataMode(bool dataOn, unsigned char filter);
|
||||
void getDataMode();
|
||||
void getModInput(bool dataOn);
|
||||
void setModInput(rigInput input, bool dataOn);
|
||||
void getBandStackReg(char band, char regCode);
|
||||
void getDebug();
|
||||
void getRitEnabled();
|
||||
void getRitValue();
|
||||
void setRitValue(int ritValue);
|
||||
void setRitEnable(bool ritEnabled);
|
||||
|
||||
// Repeater:
|
||||
void getDuplexMode();
|
||||
void getTone();
|
||||
void getTSQL();
|
||||
void getDTCS();
|
||||
void getRptAccessMode();
|
||||
|
||||
// Level get:
|
||||
void getLevels(); // get all levels
|
||||
void getRfGain();
|
||||
void getAfGain();
|
||||
void getSql();
|
||||
void getIfShift();
|
||||
void getTPBFInner();
|
||||
void getTPBFOuter();
|
||||
void getTxPower();
|
||||
void getMicGain();
|
||||
void getSpectrumRefLevel();
|
||||
void getModInputLevel(rigInput input);
|
||||
|
||||
// Level set:
|
||||
void setRfGain(unsigned char level);
|
||||
void setAfGain(unsigned char level);
|
||||
void setSql(unsigned char level);
|
||||
void setIFShift(unsigned char level);
|
||||
void setTPBFInner(unsigned char level);
|
||||
void setTPBFOuter(unsigned char level);
|
||||
|
||||
void setIFShiftWindow(unsigned char level);
|
||||
void setTPBFInnerWindow(unsigned char level);
|
||||
void setTPBFOuterWindow(unsigned char level);
|
||||
void setMicGain(unsigned char);
|
||||
void setCompLevel(unsigned char);
|
||||
void setTxPower(unsigned char);
|
||||
void setMonitorLevel(unsigned char);
|
||||
void setVoxGain(unsigned char);
|
||||
void setAntiVoxGain(unsigned char);
|
||||
void setSpectrumRefLevel(int);
|
||||
|
||||
void setModLevel(rigInput input, unsigned char level);
|
||||
void setACCGain(unsigned char level);
|
||||
void setACCAGain(unsigned char level);
|
||||
void setACCBGain(unsigned char level);
|
||||
void setUSBGain(unsigned char level);
|
||||
void setLANGain(unsigned char level);
|
||||
|
||||
void getMeters(meterKind meter);
|
||||
|
||||
|
||||
// PTT, ATU, ATT, Antenna, Preamp:
|
||||
void getPTT();
|
||||
void setPTT(bool pttOn);
|
||||
void getAttenuator();
|
||||
void getPreamp();
|
||||
void getAntenna();
|
||||
void setAttenuator(unsigned char att);
|
||||
void setPreamp(unsigned char pre);
|
||||
void setAntenna(unsigned char ant, bool rx);
|
||||
void startATU();
|
||||
void setATU(bool atuEnabled);
|
||||
void getATUStatus();
|
||||
|
||||
// Time and date:
|
||||
void setTime(timekind t);
|
||||
void setDate(datekind d);
|
||||
void setUTCOffset(timekind t);
|
||||
|
||||
void getRigID(); // this is the model of the rig
|
||||
void getRigCIV(); // get the rig's CIV addr
|
||||
void spectOutputEnable();
|
||||
void spectOutputDisable();
|
||||
void scopeDisplayEnable();
|
||||
void scopeDisplayDisable();
|
||||
void setScopeMode(spectrumMode spectMode);
|
||||
void setScopeSpan(char span);
|
||||
void setScopeEdge(char edge);
|
||||
void setScopeFixedEdge(double startFreq, double endFreq, unsigned char edgeNumber);
|
||||
void getScopeMode();
|
||||
void getScopeEdge();
|
||||
void getScopeSpan();
|
||||
void sayFrequency();
|
||||
void sayMode();
|
||||
void sayAll();
|
||||
void sendCommSetup(unsigned char rigCivAddr, QString rigSerialPort, quint32 rigBaudRate,QString vsp, quint16 tcp, quint8 wf);
|
||||
void sendCommSetup(unsigned char rigCivAddr, udpPreferences prefs, audioSetup rxSetup, audioSetup txSetup, QString vsp, quint16 tcp);
|
||||
void sendCloseComm();
|
||||
void sendChangeLatency(quint16 latency);
|
||||
void initServer();
|
||||
void sendRigCaps(rigCapabilities caps);
|
||||
void requestRigState();
|
||||
void stateUpdated();
|
||||
|
||||
private slots:
|
||||
|
||||
void receiveCommReady();
|
||||
void receivePTTstatus(bool pttOn);
|
||||
|
||||
void receiveFoundRigID(rigCapabilities rigCaps);
|
||||
void receivePortError(errorType err);
|
||||
void receiveBaudRate(quint32 baudrate);
|
||||
|
||||
void handlePttLimit();
|
||||
void receiveStatusUpdate(networkStatus status);
|
||||
void receiveStateInfo(rigstate* state);
|
||||
void connectToRig(RIGCONFIG* rig);
|
||||
void updateAudioDevices();
|
||||
|
||||
private:
|
||||
QSettings *settings=Q_NULLPTR;
|
||||
void loadSettings();
|
||||
|
||||
void openRig();
|
||||
void powerRigOff();
|
||||
void powerRigOn();
|
||||
QStringList portList;
|
||||
QString serialPortRig;
|
||||
|
||||
QTimer * delayedCommand;
|
||||
QTimer * pttTimer;
|
||||
uint16_t loopTickCounter;
|
||||
uint16_t slowCmdNum;
|
||||
QString lastMessage="";
|
||||
|
||||
void makeRig();
|
||||
void removeRig();
|
||||
void findSerialPort();
|
||||
|
||||
void setServerToPrefs();
|
||||
void setInitialTiming();
|
||||
void getSettingsFilePath(QString settingsFile);
|
||||
|
||||
QStringList modes;
|
||||
int currentModeIndex;
|
||||
QStringList spans;
|
||||
QStringList edges;
|
||||
QStringList commPorts;
|
||||
|
||||
quint16 spectWidth;
|
||||
quint16 wfLength;
|
||||
bool spectrumDrawLock;
|
||||
|
||||
QByteArray spectrumPeaks;
|
||||
|
||||
QVector <QByteArray> wfimage;
|
||||
unsigned int wfLengthMax;
|
||||
|
||||
bool onFullscreen;
|
||||
bool drawPeaks;
|
||||
bool freqTextSelected;
|
||||
void checkFreqSel();
|
||||
|
||||
double oldLowerFreq;
|
||||
double oldUpperFreq;
|
||||
freqt freq;
|
||||
float tsKnobMHz;
|
||||
|
||||
unsigned char setModeVal=0;
|
||||
unsigned char setFilterVal=0;
|
||||
|
||||
bool usingLAN = false;
|
||||
|
||||
struct preferences {
|
||||
unsigned char radioCIVAddr;
|
||||
bool CIVisRadioModel;
|
||||
bool forceRTSasPTT;
|
||||
QString serialPortRadio;
|
||||
quint32 serialPortBaud;
|
||||
unsigned char localAFgain;
|
||||
audioSetup rxAudio;
|
||||
audioSetup txAudio;
|
||||
rigCapabilities rigCaps;
|
||||
bool haveRigCaps = false;
|
||||
quint16 tcpPort;
|
||||
audioType audioSystem;
|
||||
} prefs;
|
||||
|
||||
preferences defPrefs;
|
||||
udpPreferences udpPrefs;
|
||||
udpPreferences udpDefPrefs;
|
||||
|
||||
// Configuration for audio output and input.
|
||||
audioSetup rxSetup;
|
||||
audioSetup txSetup;
|
||||
|
||||
audioSetup serverRxSetup;
|
||||
audioSetup serverTxSetup;
|
||||
|
||||
|
||||
void setDefPrefs(); // populate default values to default prefs
|
||||
|
||||
bool amTransmitting;
|
||||
bool usingDataMode = false;
|
||||
|
||||
unsigned char micGain=0;
|
||||
unsigned char accAGain=0;
|
||||
unsigned char accBGain=0;
|
||||
unsigned char accGain=0;
|
||||
unsigned char usbGain=0;
|
||||
unsigned char lanGain=0;
|
||||
|
||||
|
||||
udpServer* udp = Q_NULLPTR;
|
||||
rigCtlD* rigCtl = Q_NULLPTR;
|
||||
QThread* serverThread = Q_NULLPTR;
|
||||
|
||||
rigstate* rigState = Q_NULLPTR;
|
||||
|
||||
audioDevices* audioDev = Q_NULLPTR;
|
||||
|
||||
SERVERCONFIG serverConfig;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(struct rigCapabilities)
|
||||
Q_DECLARE_METATYPE(struct freqt)
|
||||
Q_DECLARE_METATYPE(struct mode_info)
|
||||
Q_DECLARE_METATYPE(struct udpPreferences)
|
||||
Q_DECLARE_METATYPE(struct audioPacket)
|
||||
Q_DECLARE_METATYPE(struct audioSetup)
|
||||
Q_DECLARE_METATYPE(struct SERVERCONFIG)
|
||||
Q_DECLARE_METATYPE(struct timekind)
|
||||
Q_DECLARE_METATYPE(struct datekind)
|
||||
Q_DECLARE_METATYPE(struct networkStatus)
|
||||
Q_DECLARE_METATYPE(enum rigInput)
|
||||
Q_DECLARE_METATYPE(QList<radio_cap_packet>)
|
||||
Q_DECLARE_METATYPE(enum meterKind)
|
||||
Q_DECLARE_METATYPE(enum spectrumMode)
|
||||
Q_DECLARE_METATYPE(rigstate*)
|
||||
Q_DECLARE_METATYPE(codecType)
|
||||
Q_DECLARE_METATYPE(errorType)
|
||||
Q_DECLARE_METATYPE(enum duplexMode)
|
||||
Q_DECLARE_METATYPE(enum rptAccessTxRx)
|
||||
Q_DECLARE_METATYPE(struct rptrTone_t)
|
||||
Q_DECLARE_METATYPE(struct rptrAccessData_t)
|
||||
|
||||
|
||||
#endif // WFMAIN_H
|
|
@ -0,0 +1,39 @@
|
|||
#ifndef SIDEBANDCHOOSER_H
|
||||
#define SIDEBANDCHOOSER_H
|
||||
|
||||
#include "wfviewtypes.h"
|
||||
|
||||
class sidebandChooser
|
||||
{
|
||||
public:
|
||||
sidebandChooser();
|
||||
|
||||
static inline mode_kind getMode(freqt f, mode_kind currentMode = modeUSB) {
|
||||
|
||||
if((currentMode == modeLSB) || (currentMode == modeUSB) )
|
||||
{
|
||||
if(f.Hz < 5000000)
|
||||
{
|
||||
return modeLSB;
|
||||
}
|
||||
if( (f.Hz >= 5000000) && (f.Hz < 5600000) )
|
||||
{
|
||||
return modeUSB;
|
||||
}
|
||||
if( (f.Hz >= 5600000) && (f.Hz < 10000000) )
|
||||
{
|
||||
return modeLSB;
|
||||
}
|
||||
|
||||
return modeUSB;
|
||||
} else {
|
||||
return currentMode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
#endif // SIDEBANDCHOOSER_H
|
|
@ -0,0 +1,99 @@
|
|||
#include "tcpserver.h"
|
||||
|
||||
#include "logcategories.h"
|
||||
|
||||
tcpServer::tcpServer(QObject* parent) : QTcpServer(parent)
|
||||
{
|
||||
}
|
||||
|
||||
tcpServer::~tcpServer()
|
||||
{
|
||||
qInfo(logTcpServer()) << "closing tcpServer";
|
||||
}
|
||||
|
||||
int tcpServer::startServer(qint16 port) {
|
||||
|
||||
if (!this->listen(QHostAddress::Any, port)) {
|
||||
qInfo(logTcpServer()) << "could not start on port " << port;
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
qInfo(logTcpServer()) << "started on port " << port;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tcpServer::incomingConnection(qintptr socket) {
|
||||
tcpServerClient* client = new tcpServerClient(socket, this);
|
||||
connect(this, SIGNAL(onStopped()), client, SLOT(closeSocket()));
|
||||
emit newClient(socket); // Signal par
|
||||
}
|
||||
|
||||
void tcpServer::stopServer()
|
||||
{
|
||||
qInfo(logTcpServer()) << "stopping server";
|
||||
emit onStopped();
|
||||
}
|
||||
|
||||
|
||||
void tcpServer::receiveDataFromClient(QByteArray data)
|
||||
{
|
||||
emit receiveData(data);
|
||||
}
|
||||
|
||||
void tcpServer::sendData(QByteArray data) {
|
||||
|
||||
emit sendDataToClient(data);
|
||||
|
||||
}
|
||||
|
||||
tcpServerClient::tcpServerClient(int socketId, tcpServer* parent) : QObject(parent)
|
||||
{
|
||||
sessionId = socketId;
|
||||
socket = new QTcpSocket(this);
|
||||
this->parent = parent;
|
||||
if (!socket->setSocketDescriptor(sessionId))
|
||||
{
|
||||
qInfo(logTcpServer()) << " error binding socket: " << sessionId;
|
||||
return;
|
||||
}
|
||||
connect(socket, SIGNAL(readyRead()), this, SLOT(socketReadyRead()), Qt::DirectConnection);
|
||||
connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()), Qt::DirectConnection);
|
||||
connect(parent, SIGNAL(sendDataToClient(QByteArray)), this, SLOT(receiveDataToClient(QByteArray)), Qt::DirectConnection);
|
||||
connect(this, SIGNAL(sendDataFromClient(QByteArray)), parent, SLOT(receiveDataFromClient(QByteArray)), Qt::DirectConnection);
|
||||
qInfo(logTcpServer()) << " session connected: " << sessionId;
|
||||
|
||||
}
|
||||
|
||||
void tcpServerClient::socketReadyRead() {
|
||||
QByteArray data;
|
||||
if (socket->bytesAvailable()) {
|
||||
data=socket->readAll();
|
||||
emit sendDataFromClient(data);
|
||||
}
|
||||
}
|
||||
|
||||
void tcpServerClient::socketDisconnected() {
|
||||
qInfo(logTcpServer()) << sessionId << "disconnected";
|
||||
socket->deleteLater();
|
||||
this->deleteLater();
|
||||
}
|
||||
|
||||
void tcpServerClient::closeSocket()
|
||||
{
|
||||
socket->close();
|
||||
}
|
||||
|
||||
void tcpServerClient::receiveDataToClient(QByteArray data) {
|
||||
|
||||
if (socket != Q_NULLPTR && socket->isValid() && socket->isOpen())
|
||||
{
|
||||
socket->write(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
qInfo(logTcpServer()) << "socket not open!";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
#ifndef TCPSERVER_H
|
||||
#define TCPSERVER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QDebug>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QSet>
|
||||
#include <QDataStream>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <typeindex>
|
||||
|
||||
class tcpServer : public QTcpServer
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit tcpServer(QObject* parent = Q_NULLPTR);
|
||||
~tcpServer();
|
||||
int startServer(qint16 port);
|
||||
void stopServer();
|
||||
|
||||
public slots:
|
||||
virtual void incomingConnection(qintptr socketDescriptor);
|
||||
void receiveDataFromClient(QByteArray data);
|
||||
void sendData(QByteArray data);
|
||||
signals:
|
||||
void onStarted();
|
||||
void onStopped();
|
||||
void receiveData(QByteArray data); // emit this when we have data from tcp client, connect to rigcommander
|
||||
void sendDataToClient(QByteArray data);
|
||||
void newClient(int socketId);
|
||||
|
||||
private:
|
||||
QTcpServer* server;
|
||||
QTcpSocket* socket = Q_NULLPTR;
|
||||
};
|
||||
|
||||
class tcpServerClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit tcpServerClient(int socket, tcpServer* parent = Q_NULLPTR);
|
||||
public slots:
|
||||
void socketReadyRead();
|
||||
void socketDisconnected();
|
||||
void closeSocket();
|
||||
void receiveDataToClient(QByteArray);
|
||||
|
||||
signals:
|
||||
void sendDataFromClient(QByteArray data);
|
||||
protected:
|
||||
int sessionId;
|
||||
QTcpSocket* socket = Q_NULLPTR;
|
||||
|
||||
private:
|
||||
tcpServer* parent;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -6,11 +6,153 @@ transceiverAdjustments::transceiverAdjustments(QWidget *parent) :
|
|||
ui(new Ui::transceiverAdjustments)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
// request level updates
|
||||
|
||||
// ---- These controls aren't finished yet:
|
||||
ui->transmitterControlsGroupBox->setVisible(false); // no controls available so far
|
||||
ui->bassRxLabel->setVisible(false);
|
||||
ui->bassRxSlider->setVisible(false);
|
||||
ui->trebleRxLabel->setVisible(false);
|
||||
ui->trebleRxSlider->setVisible(false);
|
||||
|
||||
ui->NRRxCheckBox->setVisible(false);
|
||||
ui->NRRxSlider->setVisible(false);
|
||||
ui->notchRxChkBox->setVisible(false);
|
||||
ui->notchRxSlider->setVisible(false);
|
||||
ui->NBRxChkBox->setVisible(false);
|
||||
ui->NBRxSlider->setVisible(false);
|
||||
ui->bandwidthGroupBox->setVisible(false);
|
||||
ui->otherGrpBox->setVisible(false);
|
||||
// ----
|
||||
|
||||
// Resize to fit new visible contents:
|
||||
this->window()->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
this->window()->resize(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
|
||||
this->setWindowTitle("TransceiverAdjustments");
|
||||
}
|
||||
|
||||
transceiverAdjustments::~transceiverAdjustments()
|
||||
{
|
||||
rigCaps.inputs.clear();
|
||||
rigCaps.preamps.clear();
|
||||
rigCaps.attenuators.clear();
|
||||
rigCaps.antennas.clear();
|
||||
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void transceiverAdjustments::setMaxPassband(quint16 maxHzAllowed)
|
||||
{
|
||||
if( (maxHzAllowed <= 10E3) && (maxHzAllowed != 0) )
|
||||
{
|
||||
maxHz = maxHzAllowed;
|
||||
updatePassband(lastKnownPassband);
|
||||
}
|
||||
}
|
||||
|
||||
void transceiverAdjustments::on_IFShiftSlider_valueChanged(int value)
|
||||
{
|
||||
if(rigCaps.hasIFShift)
|
||||
{
|
||||
emit setIFShift(value);
|
||||
} else {
|
||||
unsigned char inner = ui->TPBFInnerSlider->value();
|
||||
unsigned char outer = ui->TPBFOuterSlider->value();
|
||||
int shift = value - previousIFShift;
|
||||
inner = qMax( 0, qMin(255,int (inner + shift)) );
|
||||
outer = qMax( 0, qMin(255,int (outer + shift)) );
|
||||
|
||||
ui->TPBFInnerSlider->setValue(inner);
|
||||
ui->TPBFOuterSlider->setValue(outer);
|
||||
previousIFShift = value;
|
||||
}
|
||||
}
|
||||
|
||||
void transceiverAdjustments::on_TPBFInnerSlider_valueChanged(int value)
|
||||
{
|
||||
emit setTPBFInner(value);
|
||||
}
|
||||
|
||||
void transceiverAdjustments::on_TPBFOuterSlider_valueChanged(int value)
|
||||
{
|
||||
emit setTPBFOuter(value);
|
||||
}
|
||||
|
||||
void transceiverAdjustments::setRig(rigCapabilities rig)
|
||||
{
|
||||
this->rigCaps = rig;
|
||||
if(!rigCaps.hasIFShift)
|
||||
updateIFShift(128);
|
||||
//ui->IFShiftSlider->setVisible(rigCaps.hasIFShift);
|
||||
//ui->IFShiftLabel->setVisible(rigCaps.hasIFShift);
|
||||
|
||||
ui->TPBFInnerSlider->setVisible(rigCaps.hasTBPF);
|
||||
ui->TPBFInnerLabel->setVisible(rigCaps.hasTBPF);
|
||||
|
||||
ui->TPBFOuterSlider->setVisible(rigCaps.hasTBPF);
|
||||
ui->TPBFInnerLabel->setVisible(rigCaps.hasTBPF);
|
||||
|
||||
haveRigCaps = true;
|
||||
}
|
||||
|
||||
// These are accessed by wfmain when we receive new values from rigCommander:
|
||||
void transceiverAdjustments::updateIFShift(unsigned char level)
|
||||
{
|
||||
ui->IFShiftSlider->blockSignals(true);
|
||||
ui->IFShiftSlider->setValue(level);
|
||||
ui->IFShiftSlider->blockSignals(false);
|
||||
}
|
||||
|
||||
void transceiverAdjustments::updateTPBFInner(unsigned char level)
|
||||
{
|
||||
ui->TPBFInnerSlider->blockSignals(true);
|
||||
ui->TPBFInnerSlider->setValue(level);
|
||||
ui->TPBFInnerSlider->blockSignals(false);
|
||||
}
|
||||
|
||||
void transceiverAdjustments::updateTPBFOuter(unsigned char level)
|
||||
{
|
||||
ui->TPBFOuterSlider->blockSignals(true);
|
||||
ui->TPBFOuterSlider->setValue(level);
|
||||
ui->TPBFOuterSlider->blockSignals(false);
|
||||
}
|
||||
|
||||
void transceiverAdjustments::updatePassband(quint16 passbandHz)
|
||||
{
|
||||
lastKnownPassband = passbandHz;
|
||||
float l = 2.0*passbandHz/maxHz;
|
||||
#ifdef Q_OS_LINUX
|
||||
int val = exp10f(l);
|
||||
#else
|
||||
int val = pow(10, l);
|
||||
#endif
|
||||
|
||||
//qDebug() << "Updating slider passband to " << passbandHz << "Hz using 1-100 value:" << val << "with l=" << l << "and max=" << maxHz;
|
||||
ui->passbandWidthSlider->blockSignals(true);
|
||||
ui->passbandWidthSlider->setValue(val);
|
||||
ui->passbandWidthSlider->blockSignals(false);
|
||||
}
|
||||
|
||||
void transceiverAdjustments::on_resetPBTbtn_clicked()
|
||||
{
|
||||
ui->TPBFInnerSlider->setValue(128);
|
||||
ui->TPBFOuterSlider->setValue(128);
|
||||
ui->IFShiftSlider->blockSignals(true);
|
||||
ui->IFShiftSlider->setValue(128);
|
||||
ui->IFShiftSlider->blockSignals(false);
|
||||
}
|
||||
|
||||
void transceiverAdjustments::on_passbandWidthSlider_valueChanged(int value)
|
||||
{
|
||||
// value is 1-100
|
||||
//float maxHz = 10E3;
|
||||
float l = log10f(value);
|
||||
float p = l*maxHz/2.0;
|
||||
quint16 pbHz = (quint16)p;
|
||||
if(pbHz != 0)
|
||||
{
|
||||
//qDebug() << "Setting passband, maxHZ" << maxHz << ", value: " << value << ", l:" << l << ", p:" << p << ", pbHz: " << pbHz;
|
||||
lastKnownPassband = pbHz;
|
||||
emit setPassband(pbHz);
|
||||
}
|
||||
}
|
||||
|
|