PyFT8 3.0.0__tar.gz → 3.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyft8-3.0.0 → pyft8-3.0.2}/PKG-INFO +4 -3
- {pyft8-3.0.0 → pyft8-3.0.2}/PyFT8/databases.py +3 -3
- {pyft8-3.0.0 → pyft8-3.0.2}/PyFT8/pskreporter.py +2 -1
- {pyft8-3.0.0 → pyft8-3.0.2}/PyFT8/pyft8.py +1 -1
- {pyft8-3.0.0 → pyft8-3.0.2}/PyFT8/transmitter.py +68 -36
- {pyft8-3.0.0 → pyft8-3.0.2}/PyFT8.egg-info/PKG-INFO +4 -3
- {pyft8-3.0.0 → pyft8-3.0.2}/README.md +3 -2
- {pyft8-3.0.0 → pyft8-3.0.2}/pyproject.toml +1 -1
- {pyft8-3.0.0 → pyft8-3.0.2}/LICENSE +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/MANIFEST.in +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/PyFT8/__init__.py +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/PyFT8/gui.py +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/PyFT8/receiver.py +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/PyFT8/rigctrl.py +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/PyFT8/time_utils.py +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/PyFT8.egg-info/SOURCES.txt +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/PyFT8.egg-info/dependency_links.txt +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/PyFT8.egg-info/entry_points.txt +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/PyFT8.egg-info/requires.txt +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/PyFT8.egg-info/top_level.txt +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/setup.cfg +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/tests/dev/CQ AAAA.py +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/tests/dev/osd.py +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/tests/dev/test_generate_wav.py +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/tests/dev/test_loopback_performance.py +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/tests/dev/view_worked_before.py +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/tests/plot_baseline.py +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/tests/spare.py +0 -0
- {pyft8-3.0.0 → pyft8-3.0.2}/tests/test_batch_and_live.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyFT8
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.2
|
|
4
4
|
Summary: FT8 Decoding and Encoding in Python with test/loopback code
|
|
5
5
|
Author-email: G1OJS <g1ojs@yahoo.com>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -27,7 +27,8 @@ PyFT8 is somewhat experimental, with a focus on demonstrating FT8 written in Pyt
|
|
|
27
27
|
If you're interested in how this works, maybe have a look at [MiniPyFT8](https://github.com/G1OJS/MiniPyFT8) which puts all of the receive code in a single 300 line Python file.
|
|
28
28
|
|
|
29
29
|
## Features
|
|
30
|
-
- Rx and Tx of standard messages with optional /P and /R
|
|
30
|
+
- Rx and Tx of standard messages with optional /P and /R
|
|
31
|
+
- RX of nonstandard calls plus hashed calls
|
|
31
32
|
- Launches quickly (~2 seconds on my old Dell Optiplex 790)
|
|
32
33
|
- Use with or without gui (receive and send messages via command line commands)
|
|
33
34
|
- Automatically chooses clearest Tx frequency
|
|
@@ -127,7 +128,7 @@ PyFT8 doesn't decode / encode *all* message types. The table below shows which a
|
|
|
127
128
|
|1|Std Msg |Y| Y |Standard <=6 char callsigns, can include /R |
|
|
128
129
|
|2|EU VHF |Y|Y| Standard <=6 char callsigns, can include /P |
|
|
129
130
|
|3|RTTY RU | | | |
|
|
130
|
-
|4|NonStd Call |Y|
|
|
131
|
+
|4|NonStd Call |Y|N| <=11 char callsigns + hashed call|
|
|
131
132
|
|5|EU VHF | | | |
|
|
132
133
|
|
|
133
134
|
## Acknowledgements
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from PyFT8.pskreporter import PSKR_MQTT_listener
|
|
2
2
|
import threading, time, os, pickle, json
|
|
3
3
|
|
|
4
|
-
call_hashes = {}
|
|
4
|
+
call_hashes, hashes_for_calls = {}, {}
|
|
5
5
|
|
|
6
6
|
def add_call_hashes(call):
|
|
7
7
|
global call_hashes
|
|
@@ -17,9 +17,9 @@ def add_call_hashes(call):
|
|
|
17
17
|
x = x * 47055833459
|
|
18
18
|
x = x & ((1 << 64) - 1)
|
|
19
19
|
x = x >> (64 - m)
|
|
20
|
-
hashes.append(x)
|
|
20
|
+
hashes.append((x, m))
|
|
21
21
|
call_hashes[(x, m)] = call
|
|
22
|
-
|
|
22
|
+
hashes_for_calls[call] = hashes
|
|
23
23
|
|
|
24
24
|
def grid_to_latlong(grid, centre = True):
|
|
25
25
|
lat, lon = -90, -180
|
|
@@ -9,7 +9,8 @@ class PSKR_MQTT_listener:
|
|
|
9
9
|
def __init__(self, home_square, on_spot):
|
|
10
10
|
self.home_square = home_square
|
|
11
11
|
self.on_spot = on_spot
|
|
12
|
-
|
|
12
|
+
client_id = "PyFT8_" + ''.join(random.choice('0123456789ABCDEF') for i in range(16))
|
|
13
|
+
mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id)
|
|
13
14
|
mqttc.on_connect = self.on_connect
|
|
14
15
|
mqttc.on_message = self.on_message
|
|
15
16
|
try:
|
|
@@ -2,7 +2,7 @@ import numpy as np
|
|
|
2
2
|
import wave
|
|
3
3
|
import pyaudio
|
|
4
4
|
import time
|
|
5
|
-
from PyFT8.databases import add_call_hashes
|
|
5
|
+
from PyFT8.databases import hashes_for_calls, add_call_hashes
|
|
6
6
|
|
|
7
7
|
#==================== AUDIO OUT ================================================================
|
|
8
8
|
|
|
@@ -56,35 +56,42 @@ class AudioOut:
|
|
|
56
56
|
stream.stop_stream()
|
|
57
57
|
stream.close()
|
|
58
58
|
|
|
59
|
-
#==================== PACK
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
#==================== PACK ================================================================
|
|
60
|
+
|
|
61
|
+
def ifindex(arr, val, default = None):
|
|
62
|
+
return arr.index(val) if val in arr else default
|
|
62
63
|
|
|
63
64
|
def pack_message(c1, c2, gr):
|
|
64
65
|
symbols, bits77 = _pack_message(c1, c2, gr)
|
|
65
66
|
return symbols
|
|
66
67
|
|
|
67
68
|
def _pack_message(c1, c2, gr):
|
|
68
|
-
|
|
69
|
-
c28b, p1b = pack_ft8_c28(c2)
|
|
69
|
+
c29a, c29b = pack_ft8_c29(c1), pack_ft8_c29(c2)
|
|
70
70
|
g15, ir = pack_ft8_g15(gr)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
if c29a and c29b:
|
|
72
|
+
c28a, p1a = c29a
|
|
73
|
+
c28b, p1b = c29b
|
|
74
|
+
i3 = 2 if c1.endswith('/P') or c2.endswith('/P') else 1
|
|
75
|
+
n3 = 0
|
|
76
|
+
bits77 = (c28a<<28+1+1+1+15+3) | (p1a<<28+1+1+15+3) | (c28b<<1+1+15+3) | (p1b <<1+15+3) | (ir<<15+3) | (g15<< 3) | (i3)
|
|
77
|
+
symbols = encode_bits77(bits77)
|
|
78
|
+
if c29b and not c29a:
|
|
79
|
+
add_call_hashes(c1)
|
|
80
|
+
c28a, p1a = 2063592 + hashes_for_calls[c1][2][0], 0
|
|
81
|
+
c28b, p1b = c29b
|
|
82
|
+
i3 = 2 if c2.endswith('/P') else 1
|
|
83
|
+
n3 = 0
|
|
74
84
|
bits77 = (c28a<<28+1+1+1+15+3) | (p1a<<28+1+1+15+3) | (c28b<<1+1+15+3) | (p1b <<1+15+3) | (ir<<15+3) | (g15<< 3) | (i3)
|
|
75
85
|
symbols = encode_bits77(bits77)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
rrr, cq_ = 0, 0
|
|
84
|
-
if gr in ('RRR', 'RR73', '73'):
|
|
85
|
-
rrr = ('', 'RRR', 'RR73', '73').index(gr)
|
|
86
|
-
bits77 = (hashes[1]<<58+1+2+1+3) | (c58 <<1+2+1+3) | (swp<<2+1+3) | (rrr<<1+3) | (cq_<<3) | (i3)
|
|
86
|
+
if c29a and not c29b:
|
|
87
|
+
add_call_hashes(c2)
|
|
88
|
+
c28b, p1b = 2063592 + hashes_for_calls[c2][2][0], 0
|
|
89
|
+
c28a, p1a = c29a
|
|
90
|
+
i3 = 2 if c1.endswith('/P') else 1
|
|
91
|
+
n3 = 0
|
|
92
|
+
bits77 = (c28a<<28+1+1+1+15+3) | (p1a<<28+1+1+15+3) | (c28b<<1+1+15+3) | (p1b <<1+15+3) | (ir<<15+3) | (g15<< 3) | (i3)
|
|
87
93
|
symbols = encode_bits77(bits77)
|
|
94
|
+
|
|
88
95
|
return symbols, bits77
|
|
89
96
|
|
|
90
97
|
|
|
@@ -96,24 +103,23 @@ def pack_ft8_c58(call):
|
|
|
96
103
|
n58 = n58*38 + chars.index(call[i])
|
|
97
104
|
return n58
|
|
98
105
|
|
|
99
|
-
def
|
|
100
|
-
if '/' in call
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
else:
|
|
106
|
+
def pack_ft8_c29(call):
|
|
107
|
+
if '/' not in call or call.endswith("P") or call.endswith("R"):
|
|
108
|
+
t = ifindex(['DE','QRZ','CQ'], call)
|
|
109
|
+
if t is not None:
|
|
110
|
+
return t, 0
|
|
105
111
|
p1 = 1 if call[-2:] in ('/P', '/R') else 0
|
|
106
112
|
call = call.replace('/P','').replace('/R','')
|
|
107
|
-
if len(call)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
if len(call) <= 6:
|
|
114
|
+
prepend_space = '' if call[2].isdigit() else ' '
|
|
115
|
+
call = (prepend_space + call + ' ')[:6]
|
|
116
|
+
a = ' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
117
|
+
b = ' ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
118
|
+
x = [ifindex(a, call[0]), ifindex(a[1:], call[1]), ifindex(a[1:], call[2]), ifindex(b, call[3]), ifindex(b, call[4]), ifindex(b, call[5]), 1 ]
|
|
119
|
+
if not None in x:
|
|
120
|
+
y = [36*10*27*27*27, 10*27*27*27, 27*27*27, 27*27, 27, 1, 2_063_592 + 4_194_304]
|
|
121
|
+
c28 = int(np.dot(x,y))
|
|
122
|
+
return c28, p1
|
|
117
123
|
|
|
118
124
|
def pack_ft8_g15(txt):
|
|
119
125
|
ir = 0
|
|
@@ -138,6 +144,11 @@ def pack_ft8_g15(txt):
|
|
|
138
144
|
v = v * 10 + int(txt[3])
|
|
139
145
|
return int(v), ir
|
|
140
146
|
|
|
147
|
+
#============ ENCODE ========================================================================================================================
|
|
148
|
+
|
|
149
|
+
generator_matrix_rows = ["8329ce11bf31eaf509f27fc", "761c264e25c259335493132", "dc265902fb277c6410a1bdc", "1b3f417858cd2dd33ec7f62", "09fda4fee04195fd034783a", "077cccc11b8873ed5c3d48a", "29b62afe3ca036f4fe1a9da", "6054faf5f35d96d3b0c8c3e", "e20798e4310eed27884ae90", "775c9c08e80e26ddae56318", "b0b811028c2bf997213487c", "18a0c9231fc60adf5c5ea32", "76471e8302a0721e01b12b8", "ffbccb80ca8341fafb47b2e", "66a72a158f9325a2bf67170", "c4243689fe85b1c51363a18", "0dff739414d1a1b34b1c270", "15b48830636c8b99894972e", "29a89c0d3de81d665489b0e", "4f126f37fa51cbe61bd6b94", "99c47239d0d97d3c84e0940", "1919b75119765621bb4f1e8", "09db12d731faee0b86df6b8", "488fc33df43fbdeea4eafb4", "827423ee40b675f756eb5fe", "abe197c484cb74757144a9a", "2b500e4bc0ec5a6d2bdbdd0", "c474aa53d70218761669360", "8eba1a13db3390bd6718cec", "753844673a27782cc42012e", "06ff83a145c37035a5c1268", "3b37417858cc2dd33ec3f62", "9a4a5a28ee17ca9c324842c", "bc29f465309c977e89610a4", "2663ae6ddf8b5ce2bb29488", "46f231efe457034c1814418", "3fb2ce85abe9b0c72e06fbe", "de87481f282c153971a0a2e", "fcd7ccf23c69fa99bba1412", "f0261447e9490ca8e474cec", "4410115818196f95cdd7012", "088fc31df4bfbde2a4eafb4", "b8fef1b6307729fb0a078c0", "5afea7acccb77bbc9d99a90", "49a7016ac653f65ecdc9076", "1944d085be4e7da8d6cc7d0", "251f62adc4032f0ee714002", "56471f8702a0721e00b12b8", "2b8e4923f2dd51e2d537fa0", "6b550a40a66f4755de95c26", "a18ad28d4e27fe92a4f6c84", "10c2e586388cb82a3d80758", "ef34a41817ee02133db2eb0", "7e9c0c54325a9c15836e000", "3693e572d1fde4cdf079e86", "bfb2cec5abe1b0c72e07fbe", "7ee18230c583cccc57d4b08", "a066cb2fedafc9f52664126", "bb23725abc47cc5f4cc4cd2", "ded9dba3bee40c59b5609b4", "d9a7016ac653e6decdc9036", "9ad46aed5f707f280ab5fc4", "e5921c77822587316d7d3c2", "4f14da8242a8b86dca73352", "8b8b507ad467d4441df770e", "22831c9cf1169467ad04b68", "213b838fe2ae54c38ee7180", "5d926b6dd71f085181a4e12", "66ab79d4b29ee6e69509e56", "958148682d748a38dd68baa", "b8ce020cf069c32a723ab14", "f4331d6d461607e95752746", "6da23ba424b9596133cf9c8", "a636bcbc7b30c5fbeae67fe", "5cb0d86a07df654a9089a20", "f11f106848780fc9ecdd80a", "1fbb5364fb8d2c9d730d5ba", "fcb86bc70a50c9d02a5d034", "a534433029eac15f322e34c", "c989d9c7c3d3b8c55d75130", "7bb38b2f0186d46643ae962", "2644ebadeb44b9467d1f42c", "608cc857594bfbb55d69600"]
|
|
150
|
+
kGEN = np.array([int(row,16)>>1 for row in generator_matrix_rows])
|
|
151
|
+
|
|
141
152
|
def ldpc_encode(msg_crc: int) -> int:
|
|
142
153
|
msg_crc = int(msg_crc)
|
|
143
154
|
parity_bits = 0
|
|
@@ -182,6 +193,8 @@ def append_crc(bits77_int):
|
|
|
182
193
|
#==================== TESTS ================================================================
|
|
183
194
|
|
|
184
195
|
if __name__ == "__main__":
|
|
196
|
+
|
|
197
|
+
print("Test standard calls")
|
|
185
198
|
OK = True
|
|
186
199
|
msgs = [("G1OJS/P", "G1OJS/P", "IO90"),("WM3PEN","EA6VQ","+08"),("E67A/P","EA6VQ","R-08"),
|
|
187
200
|
("CQ","CT7ARQ/P","JO03"), ("EC5A","9A5E","RR73"), ("EC5A/P","9A5E","73"), ("EC5A/MM","9A5E","73"),
|
|
@@ -195,5 +208,24 @@ if __name__ == "__main__":
|
|
|
195
208
|
#print(''.join([str(s) for s in symbols]))
|
|
196
209
|
print("\nPASSED" if OK else "\nFAILED")
|
|
197
210
|
|
|
211
|
+
print("\nTest non-standard calls")
|
|
212
|
+
OK = True
|
|
213
|
+
msgs = [("SX200M","G1OJS","IO90"),
|
|
214
|
+
("G1OJS","SX200M","CB21"),
|
|
215
|
+
("CQ","SX200M","CB21")]
|
|
216
|
+
expected_symbols = ["3140652203407700750145313654745000003140652276403052651370607066332604363140652",
|
|
217
|
+
"",
|
|
218
|
+
"3140652564261623472565070174400214333140652601351750040163007617513443213140652"]
|
|
219
|
+
for i, msg_tx in enumerate(msgs):
|
|
220
|
+
symbols, bits77 = _pack_message(*msg_tx)
|
|
221
|
+
from PyFT8.receiver import unpack
|
|
222
|
+
msg_rx = unpack(bits77)
|
|
223
|
+
print(f"\n{msg_tx}\n{msg_rx}")
|
|
224
|
+
OK = OK and (msg_tx == msg_rx) or 'implemented' in msg_rx
|
|
225
|
+
print(f"exp: {expected_symbols[i]}")
|
|
226
|
+
print(f"gen: {''.join([str(s) for s in symbols])}")
|
|
227
|
+
print("\nPASSED" if OK else "\nFAILED")
|
|
228
|
+
|
|
229
|
+
print("\nUnpack 'CQ DX G1OJS IO90'")
|
|
198
230
|
print(unpack(int('00000000000000000100011011110000010010000000000111000001100011111000010010001',2)))
|
|
199
231
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyFT8
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.2
|
|
4
4
|
Summary: FT8 Decoding and Encoding in Python with test/loopback code
|
|
5
5
|
Author-email: G1OJS <g1ojs@yahoo.com>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -27,7 +27,8 @@ PyFT8 is somewhat experimental, with a focus on demonstrating FT8 written in Pyt
|
|
|
27
27
|
If you're interested in how this works, maybe have a look at [MiniPyFT8](https://github.com/G1OJS/MiniPyFT8) which puts all of the receive code in a single 300 line Python file.
|
|
28
28
|
|
|
29
29
|
## Features
|
|
30
|
-
- Rx and Tx of standard messages with optional /P and /R
|
|
30
|
+
- Rx and Tx of standard messages with optional /P and /R
|
|
31
|
+
- RX of nonstandard calls plus hashed calls
|
|
31
32
|
- Launches quickly (~2 seconds on my old Dell Optiplex 790)
|
|
32
33
|
- Use with or without gui (receive and send messages via command line commands)
|
|
33
34
|
- Automatically chooses clearest Tx frequency
|
|
@@ -127,7 +128,7 @@ PyFT8 doesn't decode / encode *all* message types. The table below shows which a
|
|
|
127
128
|
|1|Std Msg |Y| Y |Standard <=6 char callsigns, can include /R |
|
|
128
129
|
|2|EU VHF |Y|Y| Standard <=6 char callsigns, can include /P |
|
|
129
130
|
|3|RTTY RU | | | |
|
|
130
|
-
|4|NonStd Call |Y|
|
|
131
|
+
|4|NonStd Call |Y|N| <=11 char callsigns + hashed call|
|
|
131
132
|
|5|EU VHF | | | |
|
|
132
133
|
|
|
133
134
|
## Acknowledgements
|
|
@@ -8,7 +8,8 @@ PyFT8 is somewhat experimental, with a focus on demonstrating FT8 written in Pyt
|
|
|
8
8
|
If you're interested in how this works, maybe have a look at [MiniPyFT8](https://github.com/G1OJS/MiniPyFT8) which puts all of the receive code in a single 300 line Python file.
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
|
-
- Rx and Tx of standard messages with optional /P and /R
|
|
11
|
+
- Rx and Tx of standard messages with optional /P and /R
|
|
12
|
+
- RX of nonstandard calls plus hashed calls
|
|
12
13
|
- Launches quickly (~2 seconds on my old Dell Optiplex 790)
|
|
13
14
|
- Use with or without gui (receive and send messages via command line commands)
|
|
14
15
|
- Automatically chooses clearest Tx frequency
|
|
@@ -108,7 +109,7 @@ PyFT8 doesn't decode / encode *all* message types. The table below shows which a
|
|
|
108
109
|
|1|Std Msg |Y| Y |Standard <=6 char callsigns, can include /R |
|
|
109
110
|
|2|EU VHF |Y|Y| Standard <=6 char callsigns, can include /P |
|
|
110
111
|
|3|RTTY RU | | | |
|
|
111
|
-
|4|NonStd Call |Y|
|
|
112
|
+
|4|NonStd Call |Y|N| <=11 char callsigns + hashed call|
|
|
112
113
|
|5|EU VHF | | | |
|
|
113
114
|
|
|
114
115
|
## Acknowledgements
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|