PyFT8 2.4.0__tar.gz → 2.4.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-2.4.0 → pyft8-2.4.2}/PKG-INFO +17 -4
- {pyft8-2.4.0 → pyft8-2.4.2}/PyFT8/pyft8.py +16 -13
- {pyft8-2.4.0 → pyft8-2.4.2}/PyFT8/receiver.py +57 -60
- {pyft8-2.4.0 → pyft8-2.4.2}/PyFT8/transmitter.py +8 -6
- {pyft8-2.4.0 → pyft8-2.4.2}/PyFT8.egg-info/PKG-INFO +17 -4
- {pyft8-2.4.0 → pyft8-2.4.2}/PyFT8.egg-info/SOURCES.txt +3 -1
- {pyft8-2.4.0 → pyft8-2.4.2}/README.md +16 -3
- {pyft8-2.4.0 → pyft8-2.4.2}/pyproject.toml +1 -1
- pyft8-2.4.2/tests/dev/CQ AAAA.py +30 -0
- pyft8-2.4.2/tests/dev/view_worked_before.py +10 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/LICENSE +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/MANIFEST.in +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/PyFT8/__init__.py +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/PyFT8/callhashes.py +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/PyFT8/gui.py +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/PyFT8/pskr_upload.py +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/PyFT8/rigctrl.py +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/PyFT8/time_utils.py +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/PyFT8.egg-info/dependency_links.txt +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/PyFT8.egg-info/entry_points.txt +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/PyFT8.egg-info/requires.txt +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/PyFT8.egg-info/top_level.txt +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/setup.cfg +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/tests/dev/osd.py +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/tests/dev/test_generate_wav.py +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/tests/dev/test_loopback_performance.py +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/tests/plot_baseline.py +0 -0
- {pyft8-2.4.0 → pyft8-2.4.2}/tests/spare.py +0 -0
- {pyft8-2.4.0 → pyft8-2.4.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: 2.4.
|
|
3
|
+
Version: 2.4.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
|
|
@@ -17,7 +17,7 @@ Requires-Dist: matplotlib
|
|
|
17
17
|
Requires-Dist: pyaudio
|
|
18
18
|
Dynamic: license-file
|
|
19
19
|
|
|
20
|
-
# PyFT8 [](https://pepy.tech/projects/pyft8)
|
|
20
|
+
# PyFT8 [](https://pepy.tech/projects/pyft8) [](https://pepy.tech/projects/pyft8)
|
|
21
21
|
# All-Python FT8 Transceiver GUI / Command Line Modem
|
|
22
22
|
|
|
23
23
|
This repository contains the source code for PyFT8, an all-Python open source FT8 transceiver that you can run as a basic GUI or from the command line to receive and transmit. Decoding performance (number of decodes) is about 70% of that achieved by WSJT-x in NORM mode, but (tbc) slightly above ft8_lib.
|
|
@@ -86,9 +86,22 @@ The image below shows the number of decodes from PyFT8, WSJT-x V2.7.0 running in
|
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
## Limitations
|
|
89
|
+
PyFT8 doesn't decode / encode *all* message types. The table below shows which are handled.
|
|
90
|
+
|
|
91
|
+
|i3.n3|Known as|Rx|Tx|notes|
|
|
92
|
+
|------|--------|----|----|-----|
|
|
93
|
+
|0.0|Free Text | | | |
|
|
94
|
+
|0.1|DXpedition | | | Call1 RR73; Call2 +07|
|
|
95
|
+
|0.3|Field Day | | | |
|
|
96
|
+
|0.4|Field Day | | | |
|
|
97
|
+
|0.5|Telemetry | | | |
|
|
98
|
+
|1|Std Msg |Y| Y |Standard <=6 char callsigns, can include /R |
|
|
99
|
+
|2|EU VHF |Y|Y| Standard <=6 char callsigns, can include /P |
|
|
100
|
+
|3|RTTY RU | | | |
|
|
101
|
+
|4|NonStd Call |Y|Y| <=11 char callsigns + hashed call|
|
|
102
|
+
|5|EU VHF | | | |
|
|
103
|
+
|
|
89
104
|
|
|
90
|
-
In pursuit of tight code, I've concentrated on core standard messages, leaving out some of the less-used features. The receive part of the
|
|
91
|
-
code doesn't (yet) have the full capability of the advanced decoders used in WSJT-x, and so gets fewer decodes than WSJT-x gets, depending on band conditions (on a quiet band with only good signals PyFT8 will get close to 100%).
|
|
92
105
|
|
|
93
106
|
## Acknowledgements
|
|
94
107
|
This project implements a decoder for the FT8 digital mode.
|
|
@@ -11,7 +11,7 @@ from PyFT8.transmitter import AudioOut
|
|
|
11
11
|
from PyFT8.time_utils import global_time_utils
|
|
12
12
|
from PyFT8.rigctrl import Rig
|
|
13
13
|
|
|
14
|
-
VER = '2.4.
|
|
14
|
+
VER = '2.4.2'
|
|
15
15
|
|
|
16
16
|
MAX_TX_START_SECONDS = 2.5
|
|
17
17
|
rig, gui, qso, worked_before, pskr_upload = None, None, None, None, None
|
|
@@ -50,16 +50,16 @@ class Logging:
|
|
|
50
50
|
f.write("header <eoh>")
|
|
51
51
|
if(not os.path.exists(self.worked_before_file)):
|
|
52
52
|
with open(f"{self.worked_before_file}","wb") as f:
|
|
53
|
-
pickle.dump({'dummy':
|
|
53
|
+
pickle.dump({'dummy':0}, f)
|
|
54
54
|
self.load_wb()
|
|
55
|
+
console_print(f"Logging to {self.adif_log_file}")
|
|
55
56
|
|
|
56
57
|
def load_wb(self):
|
|
57
58
|
global worked_before
|
|
58
59
|
with open(f"{self.worked_before_file}","rb") as f:
|
|
59
60
|
worked_before = pickle.load(f)
|
|
60
|
-
#self.load_wb_from_txt()
|
|
61
61
|
|
|
62
|
-
def
|
|
62
|
+
def merge_adif_to_wb_not_used(self, file = 'c:/users/drala/recent_log.adi'):
|
|
63
63
|
import datetime
|
|
64
64
|
with open(file, 'r') as f:
|
|
65
65
|
for l in f.readlines():
|
|
@@ -69,14 +69,18 @@ class Logging:
|
|
|
69
69
|
t = parse_from_adif_rec(l, 'time_on')
|
|
70
70
|
d = parse_from_adif_rec(l, 'qso_date')
|
|
71
71
|
tm = time.mktime(datetime.datetime.strptime(d+t, "%Y%m%d%H%M%S").timetuple())
|
|
72
|
+
if callsign in worked_before:
|
|
73
|
+
if tm < worked_before[callsign]:
|
|
74
|
+
continue
|
|
72
75
|
self.update_worked_before(callsign, tm)
|
|
73
76
|
|
|
74
|
-
def update_worked_before(self, callsign, tm):
|
|
77
|
+
def update_worked_before(self, callsign, band, mode, tm):
|
|
75
78
|
global worked_before
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
self.load_wb()
|
|
80
|
+
worked_before[callsign] = tm
|
|
81
|
+
cbm = callsign + "_"+band+"_"+mode
|
|
79
82
|
worked_before[callsign] = tm
|
|
83
|
+
worked_before[cbm] = tm
|
|
80
84
|
with open(f"{self.worked_before_file}","wb") as f:
|
|
81
85
|
pickle.dump(worked_before, f)
|
|
82
86
|
|
|
@@ -93,7 +97,7 @@ class Logging:
|
|
|
93
97
|
v = str(v)
|
|
94
98
|
f.write(f"<{k}:{len(v)}>{v} ")
|
|
95
99
|
f.write(f"<eor>\n")
|
|
96
|
-
self.update_worked_before(oStation['c'], time.time())
|
|
100
|
+
self.update_worked_before(oStation['c'], band_info['b'], 'FT8', time.time())
|
|
97
101
|
console_print(f"Logged QSO with {oStation['c']}")
|
|
98
102
|
|
|
99
103
|
|
|
@@ -178,6 +182,7 @@ class FT8_QSO:
|
|
|
178
182
|
|
|
179
183
|
def log(self):
|
|
180
184
|
if self.logging is not None:
|
|
185
|
+
self.times['time_off'] = time.gmtime()
|
|
181
186
|
self.logging.log(self.times, self.band_info, self.mStation, self.oStation, self.rpts)
|
|
182
187
|
|
|
183
188
|
def isReport(grid_rpt): return "+" in grid_rpt or "-" in grid_rpt
|
|
@@ -220,14 +225,12 @@ def progress_qso(clicked_message):
|
|
|
220
225
|
qso.rpts['rcvd'] = grid_rpt[-3:]
|
|
221
226
|
if isRReport(grid_rpt) or isRRR(grid_rpt):
|
|
222
227
|
reply = f"{qso.oStation['c']} {my_station['c']} RR73"
|
|
228
|
+
qso.log()
|
|
223
229
|
if isRR73(grid_rpt):
|
|
224
230
|
reply = f"{qso.oStation['c']} {my_station['c']} 73"
|
|
231
|
+
qso.log()
|
|
225
232
|
qso.set_tx_message(reply)
|
|
226
233
|
|
|
227
|
-
if is73(grid_rpt) or " 73" in reply or isRR73(grid_rpt):
|
|
228
|
-
qso.times['time_off'] = time.gmtime()
|
|
229
|
-
qso.log()
|
|
230
|
-
|
|
231
234
|
def make_wav(msg, wave_output_file): # move to transmitter.py?
|
|
232
235
|
symbols = audio_out.create_ft8_symbols(msg)
|
|
233
236
|
audio_data = audio_out.create_ft8_wave(symbols)
|
|
@@ -33,92 +33,89 @@ HOPS_PER_GRID = 2 * HOPS_PER_CYCLE
|
|
|
33
33
|
global_time_utils.set_cycle_length(T_CYC)
|
|
34
34
|
|
|
35
35
|
#=========== Unpacking functions ========================================
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
def get_bitfields(bits, lengths):
|
|
37
|
+
fields = []
|
|
38
|
+
for n in lengths:
|
|
39
|
+
mask = (1 << n) - 1
|
|
40
|
+
fields.append(bits & mask)
|
|
41
|
+
bits >>= n
|
|
42
|
+
return *fields, bits
|
|
41
43
|
|
|
42
44
|
def unpack(bits):
|
|
43
|
-
|
|
44
|
-
i3, bits = get_bits(bits,3)
|
|
45
|
-
# print(i3)
|
|
45
|
+
i3, bits74 = get_bitfields(bits,[3])
|
|
46
46
|
if i3 == 0:
|
|
47
|
-
n3,
|
|
47
|
+
n3, bits71 = get_bitfields(bits74,[3])
|
|
48
48
|
if n3 == 0:
|
|
49
49
|
return ('Free text','not','implemented')
|
|
50
50
|
else:
|
|
51
51
|
return (['DXpedition','Field Day', 'Field Day', 'Telemetry'][n3-1],'not','implemented')
|
|
52
52
|
elif i3 == 1 or i3 == 2: # 1 = Std Msg incl /R 2 = 'EU VHF' = Std Msg incl /P
|
|
53
|
-
|
|
54
|
-
cb, bits = get_bits(bits,29)
|
|
55
|
-
ca, bits = get_bits(bits,29)
|
|
56
|
-
return (call_28(ca, i3), call_28(cb, i3), decode_grid(gr))
|
|
53
|
+
return unpack_std(bits74, i3)
|
|
57
54
|
elif i3 == 3:
|
|
58
55
|
return ('RTTY RU','not','implemented')
|
|
59
56
|
elif i3 == 4:
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
cb =
|
|
57
|
+
cq, rrr, swp, c58, hsh, _ = get_bitfields(bits74, [1,2,1,58,12])
|
|
58
|
+
ca = "CQ" if cq else call_hashes.get((hsh,12), '<....>')
|
|
59
|
+
cb = ""
|
|
60
|
+
for i in range(12):
|
|
61
|
+
cb = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/"[c58 % 38] + cb
|
|
62
|
+
c58 = c58 // 38
|
|
63
|
+
cb = cb.strip()
|
|
64
|
+
add_call_hashes(cb)
|
|
67
65
|
(ca, cb) = (cb, ca) if swp else (ca, cb)
|
|
68
66
|
return (ca, cb, ('', 'RRR', 'RR73', '73')[rrr])
|
|
69
67
|
elif i3 == 5:
|
|
70
68
|
return ('EU VHF','not','implemented')
|
|
71
69
|
|
|
72
|
-
def
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
70
|
+
def unpack_std(bits74, i3):
|
|
71
|
+
g16, cb29, ca29, _ = get_bitfields(bits74,[16,29,29])
|
|
72
|
+
g15 = g16 & 0x7FFF
|
|
73
|
+
if g15 < 32400:
|
|
74
|
+
a, nn = divmod(g15, 1800)
|
|
75
|
+
b, nn = divmod(nn, 100)
|
|
76
|
+
c, d = divmod(nn, 10)
|
|
77
|
+
grid_rpt = chr(65+a) + chr(65+b) + str(c) + str(d)
|
|
78
|
+
elif g15 - 32400 <= 4:
|
|
79
|
+
grid_rpt = ('', '', 'RRR', 'RR73', '73')[g15 - 32400]
|
|
80
|
+
else:
|
|
81
|
+
prefix = 'R' if (g16 >> 15) else ''
|
|
82
|
+
grid_rpt = prefix + f"{(g15 - 32435):+03d}"
|
|
83
|
+
return (call_29(ca29, i3), call_29(cb29, i3), grid_rpt)
|
|
81
84
|
|
|
82
|
-
def
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
def call_29(call_int29, i3):
|
|
86
|
+
portable_rover = call_int29 & 1
|
|
87
|
+
call_int28 = call_int29>>1
|
|
88
|
+
if call_int28 < 3:
|
|
89
|
+
return ['DE', 'QRZ', 'CQ'][call_int28]
|
|
90
|
+
elif call_int28 < 1004:
|
|
91
|
+
return f"CQ {call_int28 - 3:03d}"
|
|
92
|
+
elif call_int28 < 21443:
|
|
93
|
+
x, txt = call_int28 - 1003, ''
|
|
94
|
+
for i in range(4):
|
|
95
|
+
txt = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"[int(x % 27)] + txt
|
|
96
|
+
x //= 27
|
|
97
|
+
return f"CQ {txt.strip()}"
|
|
98
|
+
elif call_int28 < 2063592+4194303:
|
|
99
|
+
return call_hashes.get((call_int28 - 2063592, 22), '<....>')
|
|
100
|
+
else:
|
|
101
|
+
call = standard_call28(call_int28, i3)
|
|
102
|
+
if portable_rover:
|
|
103
|
+
call = call + ('/P' if i3 == 2 else '/R')
|
|
104
|
+
add_call_hashes(call)
|
|
105
|
+
return call
|
|
106
|
+
|
|
107
|
+
def standard_call28(call_int28, i3):
|
|
108
|
+
nn = call_int28 - (2063592 + 4194304)
|
|
89
109
|
from string import ascii_uppercase as ltrs, digits as digs
|
|
90
110
|
call_fields = [ (' ' + digs + ltrs, 36*10*27**3), (digs + ltrs, 10*27**3), (digs + ' ' * 17, 27**3),
|
|
91
111
|
(' ' + ltrs, 27**2), (' ' + ltrs, 27), (' ' + ltrs, 1) ]
|
|
92
|
-
portable_rover = call_int & 1
|
|
93
|
-
call_int >>= 1
|
|
94
|
-
t7 = get_table_7(call_int)
|
|
95
|
-
if t7 is not None:
|
|
96
|
-
return t7 if t7 != 'hash' else call_hashes.get((call_int - 2063592, 22), '<....>')
|
|
97
|
-
call_int -= (2063592 + 4194304)
|
|
98
112
|
chars = []
|
|
99
113
|
for alphabet, div in call_fields:
|
|
100
|
-
idx,
|
|
114
|
+
idx, nn = divmod(nn, div)
|
|
101
115
|
chars.append(alphabet[idx])
|
|
102
116
|
call = ''.join(chars).strip()
|
|
103
|
-
if portable_rover:
|
|
104
|
-
call = call + ('/P' if i3 == 2 else '/R')
|
|
105
|
-
add_call_hashes(call)
|
|
106
117
|
return call
|
|
107
118
|
|
|
108
|
-
def decode_grid(grid_int):
|
|
109
|
-
g15 = grid_int & 0x7FFF
|
|
110
|
-
if g15 < 32400:
|
|
111
|
-
a, nn = divmod(g15, 1800)
|
|
112
|
-
b, nn = divmod(nn, 100)
|
|
113
|
-
c, d = divmod(nn, 10)
|
|
114
|
-
return chr(65+a) + chr(65+b) + str(c) + str(d)
|
|
115
|
-
r = g15 - 32400
|
|
116
|
-
if r <= 4:
|
|
117
|
-
return ('', '', 'RRR', 'RR73', '73')[r]
|
|
118
|
-
snr = r - 35
|
|
119
|
-
ir = grid_int >> 15
|
|
120
|
-
prefix = 'R' if ir else ''
|
|
121
|
-
return prefix + f"{snr:+03d}"
|
|
122
119
|
#============== CRC ===========================================================
|
|
123
120
|
def check_crc(bits91_int):
|
|
124
121
|
bits77_int = bits91_int >> 14
|
|
@@ -68,13 +68,12 @@ def _pack_message(c1, c2, gr):
|
|
|
68
68
|
c28a, p1a = pack_ft8_c28(c1)
|
|
69
69
|
c28b, p1b = pack_ft8_c28(c2)
|
|
70
70
|
g15, ir = pack_ft8_g15(gr)
|
|
71
|
-
i3 = 2 if
|
|
71
|
+
i3 = 2 if c1.endswith('/P') or c2.endswith('/P') else 1
|
|
72
72
|
n3 = 0
|
|
73
|
-
symbols, bits77 = [], 0
|
|
74
73
|
if(c28a>=0 and c28b>=0):
|
|
75
74
|
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)
|
|
76
75
|
symbols = encode_bits77(bits77)
|
|
77
|
-
|
|
76
|
+
else:
|
|
78
77
|
i3 = 4
|
|
79
78
|
full_call = c1 if c28b>0 else c2
|
|
80
79
|
hash_call = c2 if c28b>0 else c1
|
|
@@ -103,8 +102,8 @@ def pack_ft8_c28(call):
|
|
|
103
102
|
if (call in tkns):
|
|
104
103
|
c28, p1 = tkns.index(call), 0
|
|
105
104
|
else:
|
|
106
|
-
p1 = 1 if call[-2:]
|
|
107
|
-
call = call.replace('/P','')
|
|
105
|
+
p1 = 1 if call[-2:] in ('/P', '/R') else 0
|
|
106
|
+
call = call.replace('/P','').replace('/R','')
|
|
108
107
|
if len(call) > 6:
|
|
109
108
|
return -1, 0
|
|
110
109
|
prepend_space = '' if call[2].isdigit() else ' '
|
|
@@ -185,7 +184,8 @@ def append_crc(bits77_int):
|
|
|
185
184
|
if __name__ == "__main__":
|
|
186
185
|
OK = True
|
|
187
186
|
msgs = [("G1OJS/P", "G1OJS/P", "IO90"),("WM3PEN","EA6VQ","+08"),("E67A/P","EA6VQ","R-08"),
|
|
188
|
-
("CQ","CT7ARQ/P","
|
|
187
|
+
("CQ","CT7ARQ/P","JO03"), ("EC5A","9A5E","RR73"), ("EC5A/P","9A5E","73"), ("EC5A/MM","9A5E","73"),
|
|
188
|
+
("CQ","CT7ARQ/R","JO03")]
|
|
189
189
|
for msg_tx in msgs:
|
|
190
190
|
symbols, bits77 = _pack_message(*msg_tx)
|
|
191
191
|
from PyFT8.receiver import unpack
|
|
@@ -194,4 +194,6 @@ if __name__ == "__main__":
|
|
|
194
194
|
OK = OK and (msg_tx == msg_rx) or 'implemented' in msg_rx
|
|
195
195
|
#print(''.join([str(s) for s in symbols]))
|
|
196
196
|
print("\nPASSED" if OK else "\nFAILED")
|
|
197
|
+
|
|
198
|
+
print(unpack(int('00000000000000000100011011110000010010000000000111000001100011111000010010001',2)))
|
|
197
199
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyFT8
|
|
3
|
-
Version: 2.4.
|
|
3
|
+
Version: 2.4.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
|
|
@@ -17,7 +17,7 @@ Requires-Dist: matplotlib
|
|
|
17
17
|
Requires-Dist: pyaudio
|
|
18
18
|
Dynamic: license-file
|
|
19
19
|
|
|
20
|
-
# PyFT8 [](https://pepy.tech/projects/pyft8)
|
|
20
|
+
# PyFT8 [](https://pepy.tech/projects/pyft8) [](https://pepy.tech/projects/pyft8)
|
|
21
21
|
# All-Python FT8 Transceiver GUI / Command Line Modem
|
|
22
22
|
|
|
23
23
|
This repository contains the source code for PyFT8, an all-Python open source FT8 transceiver that you can run as a basic GUI or from the command line to receive and transmit. Decoding performance (number of decodes) is about 70% of that achieved by WSJT-x in NORM mode, but (tbc) slightly above ft8_lib.
|
|
@@ -86,9 +86,22 @@ The image below shows the number of decodes from PyFT8, WSJT-x V2.7.0 running in
|
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
## Limitations
|
|
89
|
+
PyFT8 doesn't decode / encode *all* message types. The table below shows which are handled.
|
|
90
|
+
|
|
91
|
+
|i3.n3|Known as|Rx|Tx|notes|
|
|
92
|
+
|------|--------|----|----|-----|
|
|
93
|
+
|0.0|Free Text | | | |
|
|
94
|
+
|0.1|DXpedition | | | Call1 RR73; Call2 +07|
|
|
95
|
+
|0.3|Field Day | | | |
|
|
96
|
+
|0.4|Field Day | | | |
|
|
97
|
+
|0.5|Telemetry | | | |
|
|
98
|
+
|1|Std Msg |Y| Y |Standard <=6 char callsigns, can include /R |
|
|
99
|
+
|2|EU VHF |Y|Y| Standard <=6 char callsigns, can include /P |
|
|
100
|
+
|3|RTTY RU | | | |
|
|
101
|
+
|4|NonStd Call |Y|Y| <=11 char callsigns + hashed call|
|
|
102
|
+
|5|EU VHF | | | |
|
|
103
|
+
|
|
89
104
|
|
|
90
|
-
In pursuit of tight code, I've concentrated on core standard messages, leaving out some of the less-used features. The receive part of the
|
|
91
|
-
code doesn't (yet) have the full capability of the advanced decoders used in WSJT-x, and so gets fewer decodes than WSJT-x gets, depending on band conditions (on a quiet band with only good signals PyFT8 will get close to 100%).
|
|
92
105
|
|
|
93
106
|
## Acknowledgements
|
|
94
107
|
This project implements a decoder for the FT8 digital mode.
|
|
@@ -20,6 +20,8 @@ PyFT8.egg-info/top_level.txt
|
|
|
20
20
|
tests/plot_baseline.py
|
|
21
21
|
tests/spare.py
|
|
22
22
|
tests/test_batch_and_live.py
|
|
23
|
+
tests/dev/CQ AAAA.py
|
|
23
24
|
tests/dev/osd.py
|
|
24
25
|
tests/dev/test_generate_wav.py
|
|
25
|
-
tests/dev/test_loopback_performance.py
|
|
26
|
+
tests/dev/test_loopback_performance.py
|
|
27
|
+
tests/dev/view_worked_before.py
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PyFT8 [](https://pepy.tech/projects/pyft8)
|
|
1
|
+
# PyFT8 [](https://pepy.tech/projects/pyft8) [](https://pepy.tech/projects/pyft8)
|
|
2
2
|
# All-Python FT8 Transceiver GUI / Command Line Modem
|
|
3
3
|
|
|
4
4
|
This repository contains the source code for PyFT8, an all-Python open source FT8 transceiver that you can run as a basic GUI or from the command line to receive and transmit. Decoding performance (number of decodes) is about 70% of that achieved by WSJT-x in NORM mode, but (tbc) slightly above ft8_lib.
|
|
@@ -67,9 +67,22 @@ The image below shows the number of decodes from PyFT8, WSJT-x V2.7.0 running in
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
## Limitations
|
|
70
|
+
PyFT8 doesn't decode / encode *all* message types. The table below shows which are handled.
|
|
71
|
+
|
|
72
|
+
|i3.n3|Known as|Rx|Tx|notes|
|
|
73
|
+
|------|--------|----|----|-----|
|
|
74
|
+
|0.0|Free Text | | | |
|
|
75
|
+
|0.1|DXpedition | | | Call1 RR73; Call2 +07|
|
|
76
|
+
|0.3|Field Day | | | |
|
|
77
|
+
|0.4|Field Day | | | |
|
|
78
|
+
|0.5|Telemetry | | | |
|
|
79
|
+
|1|Std Msg |Y| Y |Standard <=6 char callsigns, can include /R |
|
|
80
|
+
|2|EU VHF |Y|Y| Standard <=6 char callsigns, can include /P |
|
|
81
|
+
|3|RTTY RU | | | |
|
|
82
|
+
|4|NonStd Call |Y|Y| <=11 char callsigns + hashed call|
|
|
83
|
+
|5|EU VHF | | | |
|
|
84
|
+
|
|
70
85
|
|
|
71
|
-
In pursuit of tight code, I've concentrated on core standard messages, leaving out some of the less-used features. The receive part of the
|
|
72
|
-
code doesn't (yet) have the full capability of the advanced decoders used in WSJT-x, and so gets fewer decodes than WSJT-x gets, depending on band conditions (on a quiet band with only good signals PyFT8 will get close to 100%).
|
|
73
86
|
|
|
74
87
|
## Acknowledgements
|
|
75
88
|
This project implements a decoder for the FT8 digital mode.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
def meth1(a):
|
|
3
|
+
c = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
4
|
+
x = a-1003
|
|
5
|
+
ci1 = x // (27*27*27)
|
|
6
|
+
x %= 27*27*27
|
|
7
|
+
ci2 = x // (27*27)
|
|
8
|
+
x %= 27*27
|
|
9
|
+
ci3 = x // 27
|
|
10
|
+
x %= 27
|
|
11
|
+
ci4 = x
|
|
12
|
+
aaaa = c[ci1] + c[ci2] + c[ci3] + c[ci4]
|
|
13
|
+
return f"CQ {aaaa}"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
import numpy
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def meth2(a):
|
|
20
|
+
c = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
21
|
+
x = int(a - 1003)
|
|
22
|
+
print(x)
|
|
23
|
+
txt = ''
|
|
24
|
+
for i in range(4):
|
|
25
|
+
txt = c[int(x % 27)] + txt
|
|
26
|
+
x /= 27
|
|
27
|
+
return f"CQ {txt}"
|
|
28
|
+
|
|
29
|
+
for a in [1004, 1029, 1031, 1731, 1760, 20685, 21443, 532443, 1135]:
|
|
30
|
+
print(meth1(a), meth2(a))
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import pickle
|
|
2
|
+
def flat_list(worked_before_file = 'C:/Users/drala/Documents/Projects/GitHub/G1OJS/PyFT8_cfg/PyFT8_wb.pkl'):
|
|
3
|
+
with open(f"{worked_before_file}","rb") as f:
|
|
4
|
+
worked_before = pickle.load(f)
|
|
5
|
+
worked_before['dummy']=0
|
|
6
|
+
wb = sorted(worked_before.items(), key=lambda x: x[1])
|
|
7
|
+
for c in wb:
|
|
8
|
+
print(c)
|
|
9
|
+
|
|
10
|
+
flat_list()
|
|
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
|