PyFT8 2.3.0__tar.gz → 2.3.1__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.3.0 → pyft8-2.3.1}/PKG-INFO +14 -4
- {pyft8-2.3.0 → pyft8-2.3.1}/PyFT8/pskr_upload.py +19 -17
- {pyft8-2.3.0 → pyft8-2.3.1}/PyFT8/pyft8.py +23 -7
- {pyft8-2.3.0 → pyft8-2.3.1}/PyFT8/receiver.py +7 -7
- {pyft8-2.3.0 → pyft8-2.3.1}/PyFT8/transmitter.py +1 -1
- {pyft8-2.3.0 → pyft8-2.3.1}/PyFT8.egg-info/PKG-INFO +14 -4
- {pyft8-2.3.0 → pyft8-2.3.1}/README.md +13 -3
- {pyft8-2.3.0 → pyft8-2.3.1}/pyproject.toml +1 -1
- {pyft8-2.3.0 → pyft8-2.3.1}/LICENSE +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/MANIFEST.in +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/PyFT8/__init__.py +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/PyFT8/gui.py +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/PyFT8/rigctrl.py +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/PyFT8/time_utils.py +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/PyFT8.egg-info/SOURCES.txt +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/PyFT8.egg-info/dependency_links.txt +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/PyFT8.egg-info/entry_points.txt +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/PyFT8.egg-info/requires.txt +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/PyFT8.egg-info/top_level.txt +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/setup.cfg +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/tests/dev/osd.py +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/tests/dev/test_generate_wav.py +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/tests/dev/test_loopback_performance.py +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/tests/plot_baseline.py +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/tests/spare.py +0 -0
- {pyft8-2.3.0 → pyft8-2.3.1}/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.3.
|
|
3
|
+
Version: 2.3.1
|
|
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
|
|
@@ -18,7 +18,7 @@ Requires-Dist: pyaudio
|
|
|
18
18
|
Dynamic: license-file
|
|
19
19
|
|
|
20
20
|
# PyFT8 [](https://pepy.tech/projects/pyft8)
|
|
21
|
-
# All-Python FT8 Transceiver
|
|
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.
|
|
24
24
|
|
|
@@ -30,11 +30,19 @@ If you're interested in how this works, maybe have a look at [MiniPyFT8](https:/
|
|
|
30
30
|
- Doesn't try to do everything, so launches quickly (~2 seconds on my old Dell Optiplex 790)
|
|
31
31
|
- Use with or without gui (receive and send messages via command line commands)
|
|
32
32
|
- GUI provides simultaneous views of odd and even cycles
|
|
33
|
-
- GUI shows worked-before info on CQ calls
|
|
33
|
+
- GUI shows worked-before info on CQ calls
|
|
34
34
|
- Messages overlaid on waterfall signals that produce them
|
|
35
35
|
- Automatically chooses clearest Tx frequency
|
|
36
36
|
- Modern programming language throughout
|
|
37
37
|
- Finds sound cards by keywords so follows them if windows moves them ...
|
|
38
|
+
- Logs QSOs to ADIF file and all spots to WSJTX-style ALL.txt file
|
|
39
|
+
- Uploads spots to pskreporter
|
|
40
|
+
|
|
41
|
+
To enable uploading of spots to pskreporter, make sure that your .ini file includes
|
|
42
|
+
```
|
|
43
|
+
[pskreporter]
|
|
44
|
+
upload = Y
|
|
45
|
+
```
|
|
38
46
|
|
|
39
47
|
<img width="1003" height="1020" alt="image" src="https://github.com/user-attachments/assets/bf6e3f78-531a-4c9b-ab2b-b51cc04ad980" />
|
|
40
48
|
|
|
@@ -112,6 +120,7 @@ Note - section 9 of the QEX paper states that the above two WSJT-X resources are
|
|
|
112
120
|
**FT8 decoding explorations / explanations**
|
|
113
121
|
- [VK3JPK's FT8 notes](https://github.com/vk3jpk/ft8-notes) including comprehensive [Python source code](https://github.com/vk3jpk/ft8-notes/blob/master/ft8.py)
|
|
114
122
|
- [G4JNT notes on LDPC coding process](http://www.g4jnt.com/WSJT-X_LdpcModesCodingProcess.pdf)
|
|
123
|
+
- [Kristian Glass's 2025-05-06 Understanding the FT8 binary protocol](https://notes.doismellburning.co.uk/notebook/2025-05-06-understanding-the-ft8-binary-protocol/)
|
|
115
124
|
|
|
116
125
|
**FT8 decoding in hardware**
|
|
117
126
|
- [Optimizing the (Web-888) FT8 Skimmer Experience](https://www.rx-888.com/web/design/digi.html) (see also [RX-888 project](https://www.rx-888.com/) )
|
|
@@ -122,7 +131,8 @@ Note - section 9 of the QEX paper states that the above two WSJT-X resources are
|
|
|
122
131
|
- [Post about ft8play](https://groups.io/g/FT8-Digital-Mode/topic/i_made_a_thing_ft8play/107846361)
|
|
123
132
|
|
|
124
133
|
**Browser-based decoder/encoders**
|
|
125
|
-
- [ft8js](https://e04.github.io/ft8js/example/browser/index.html) - source [github](https://github.com/e04/ft8js?tab=readme-ov-file), uses [FT8_lib](https://github.com/kgoba/ft8_lib)
|
|
134
|
+
- [ft8js by e04](https://e04.github.io/ft8js/example/browser/index.html) - source [github](https://github.com/e04/ft8js?tab=readme-ov-file), uses web-assembled version of [FT8_lib](https://github.com/kgoba/ft8_lib)
|
|
135
|
+
- [ft8ts by e04](https://github.com/e04/ft8ts), - pure TypeScript implementation
|
|
126
136
|
- [ChromeFT8 Browser Extension](https://github.com/Transwarp8/ChromeFT8), decoder adapted from [ft8js](https://e04.github.io/ft8js/example/browser/index.html)
|
|
127
137
|
|
|
128
138
|
<script data-goatcounter="https://g1ojs-github.goatcounter.com/count" async src="//gc.zgo.at/count.js"></script>
|
|
@@ -11,7 +11,6 @@ class PSKR_upload:
|
|
|
11
11
|
# https://pskreporter.info/cgi-bin/psk-analysis.pl
|
|
12
12
|
def __init__(self, mycall, mygrid, software, tt, console_print):
|
|
13
13
|
self.RxInfoRecDescriptor_CallLocSoft = b"\x00\x03\x00\x24\x99\x92\x00\x03\x00\x01\x80\x02\xFF\xFF\x00\x00\x76\x8F\x80\x04\xFF\xFF\x00\x00\x76\x8F\x80\x08\xFF\xFF\x00\x00\x76\x8F\x00\x00"
|
|
14
|
-
self.SenderInfoRecDescriptor_CallFreqSourceStart = b"\x00\x02\x00\x2C\x99\x93\x00\x05\x80\x01\xFF\xFF\x00\x00\x76\x8F\x80\x05\x00\x04\x00\x00\x76\x8F\x80\x0A\xFF\xFF\x00\x00\x76\x8F\x80\x0B\x00\x01\x00\x00\x76\x8F\x00\x96\x00\x04"
|
|
15
14
|
self.SenderInfoRecDescriptor_SenderFreqSNRiMDModeSourceTime = b"\x00\x02\x00\x3C\x99\x93\x00\x07\x80\x01\xFF\xFF\x00\x00\x76\x8F\x80\x05\x00\x04\x00\x00\x76\x8F\x80\x06\x00\x01\x00\x00\x76\x8F\x80\x07\x00\x01\x00\x00\x76\x8F\x80\x0A\xFF\xFF\x00\x00\x76\x8F\x80\x0B\x00\x01\x00\x00\x76\x8F\x00\x96\x00\x04"
|
|
16
15
|
self.tt = tt
|
|
17
16
|
self.includeDescriptors = 0
|
|
@@ -20,10 +19,12 @@ class PSKR_upload:
|
|
|
20
19
|
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
21
20
|
self.session_id = random.getrandbits(32)
|
|
22
21
|
self.seq = 1
|
|
23
|
-
self.reports
|
|
22
|
+
self.reports = {}
|
|
24
23
|
rx = self._enc_str(mycall) + self._enc_str(mygrid) + self._enc_str(software)
|
|
25
24
|
self.rx_block = self._block(b"\x99\x92", rx)
|
|
26
25
|
self.console_print = console_print
|
|
26
|
+
self.lock = threading.Lock()
|
|
27
|
+
threading.Thread(target = self._check_for_send, daemon = True).start()
|
|
27
28
|
|
|
28
29
|
def _enc_str(self, s):
|
|
29
30
|
b = s.encode("ascii")
|
|
@@ -36,21 +37,21 @@ class PSKR_upload:
|
|
|
36
37
|
blk = block_type + struct.pack("!H", len_with_pad) + payload + b"\x00" * pad_len
|
|
37
38
|
return blk
|
|
38
39
|
|
|
39
|
-
# need to modify this to keep only the latest report for each dxcall
|
|
40
|
-
# also to check if packet is full and if so send
|
|
41
40
|
def add_report(self, dxcall, freq_hz, snr, mode, source, tt):
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
self.reports.append(report)
|
|
45
|
-
self.dxcalls.append(dxcall)
|
|
46
|
-
n_reports = len(self.reports)
|
|
47
|
-
if n_reports >= MAX_REPORTS or (time.time() - self.last_report_time) > 300:
|
|
48
|
-
self.send(includeDescriptors = (time.time() - self.includeDescriptors) > 3600)
|
|
49
|
-
self.includeDescriptors = time.time()
|
|
50
|
-
self.last_report_time = time.time()
|
|
51
|
-
#self.console_print(f"[pskr_upload] Added report {report} (now {len(self.reports)} reports)")
|
|
41
|
+
with self.lock:
|
|
42
|
+
self.reports[dxcall] = (dxcall, freq_hz, snr, mode, source, (tt // 15) * 15)
|
|
52
43
|
|
|
53
|
-
def
|
|
44
|
+
def _check_for_send(self):
|
|
45
|
+
while True:
|
|
46
|
+
time.sleep(60)
|
|
47
|
+
with self.lock:
|
|
48
|
+
n_reports = len(self.reports)
|
|
49
|
+
if n_reports >= MAX_REPORTS or (time.time() - self.last_report_time) > 300:
|
|
50
|
+
self._send(includeDescriptors = (time.time() - self.includeDescriptors) > 3600)
|
|
51
|
+
self.includeDescriptors = time.time()
|
|
52
|
+
self.last_report_time = time.time()
|
|
53
|
+
|
|
54
|
+
def _send(self, includeDescriptors = False):
|
|
54
55
|
if not self.reports:
|
|
55
56
|
return
|
|
56
57
|
ipfx_header = struct.pack("!H", 10) + b"\x00\x00" + struct.pack("!I", self.tt) + struct.pack("!I", self.seq) + struct.pack("!I", self.session_id)
|
|
@@ -58,7 +59,8 @@ class PSKR_upload:
|
|
|
58
59
|
if includeDescriptors:
|
|
59
60
|
header = header + self.RxInfoRecDescriptor_CallLocSoft + self.SenderInfoRecDescriptor_SenderFreqSNRiMDModeSourceTime
|
|
60
61
|
senders = bytearray()
|
|
61
|
-
for dxcall, freq_hz, snr, mode, source, tt in self.reports:
|
|
62
|
+
for dxcall, freq_hz, snr, mode, source, tt in self.reports.values():
|
|
63
|
+
print(f"Sending report {dxcall}, {freq_hz}, {snr}, {source}, {tt}")
|
|
62
64
|
sender = self._enc_str(dxcall) + struct.pack("!I", int(freq_hz)) + struct.pack("b", int(snr)) + struct.pack("b", 0) + self._enc_str(mode) + struct.pack("B", source) + struct.pack("!I", tt)
|
|
63
65
|
senders += sender
|
|
64
66
|
packet = bytearray(header + self.rx_block + self._block(b"\x99\x93", senders))
|
|
@@ -66,7 +68,7 @@ class PSKR_upload:
|
|
|
66
68
|
self.seq += len(self.reports)
|
|
67
69
|
self.sock.sendto(packet, self.addr)
|
|
68
70
|
self.console_print(f"[pskr_upload] Sent packet with {len(self.reports)} reports")
|
|
69
|
-
self.reports
|
|
71
|
+
self.reports = {}
|
|
70
72
|
|
|
71
73
|
|
|
72
74
|
#pskr = PSKReporter('G1OJS', 'IO90ju', software = 'PyFT8', tt = int(time.time()))
|
|
@@ -11,11 +11,13 @@ 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.3.1'
|
|
15
|
+
|
|
14
16
|
MAX_TX_START_SECONDS = 2.5
|
|
15
17
|
T_CYC = 15
|
|
16
18
|
rig, gui, qso, worked_before, pskr_upload = None, None, None, None, None
|
|
17
19
|
|
|
18
|
-
def get_config(
|
|
20
|
+
def get_config():
|
|
19
21
|
import configparser
|
|
20
22
|
global config
|
|
21
23
|
config = configparser.ConfigParser()
|
|
@@ -41,7 +43,7 @@ def parse_from_adif_rec(rec, field):
|
|
|
41
43
|
return rec[p2+1: p2+1+n]
|
|
42
44
|
|
|
43
45
|
class Logging:
|
|
44
|
-
def __init__(self
|
|
46
|
+
def __init__(self):
|
|
45
47
|
self.adif_log_file = f"{config_folder}/PyFT8.adi"
|
|
46
48
|
self.worked_before_file = f"{config_folder}/PyFT8_wb.pkl"
|
|
47
49
|
if(not os.path.exists(self.adif_log_file)):
|
|
@@ -117,6 +119,10 @@ class Message:
|
|
|
117
119
|
def wsjtx_screen_format(self):
|
|
118
120
|
return f"{self.cyclestart['string']} {self.snr:+03d} {self.dt:4.1f} {self.fHz:4.0f} ~ {self.msg}"
|
|
119
121
|
|
|
122
|
+
def wsjtx_all_txt_format(self):
|
|
123
|
+
fMHz = float(qso.band_info['fMHz']) if qso.band_info['fMHz'] is not None else 0
|
|
124
|
+
return f"{self.cyclestart['string']} {fMHz:8.3f} Rx FT8 {self.snr:+03d} {self.dt:4.1f} {self.fHz:4.0f} ~ {self.msg}"
|
|
125
|
+
|
|
120
126
|
|
|
121
127
|
class FT8_QSO:
|
|
122
128
|
def __init__(self, logging):
|
|
@@ -234,6 +240,13 @@ def wait_for_keyboard():
|
|
|
234
240
|
except KeyboardInterrupt:
|
|
235
241
|
pass
|
|
236
242
|
|
|
243
|
+
def write_all_txt_row(message):
|
|
244
|
+
all_file = f"{config_folder}/ALL.txt"
|
|
245
|
+
mode = 'w' if not os.path.exists(all_file) else 'a'
|
|
246
|
+
row = message.wsjtx_all_txt_format()
|
|
247
|
+
with open(all_file, mode) as f:
|
|
248
|
+
f.write(f"{row}\n")
|
|
249
|
+
|
|
237
250
|
#============= Callbacks for GUI ==========================================================
|
|
238
251
|
def on_decode(c):
|
|
239
252
|
message = Message(c)
|
|
@@ -242,8 +255,9 @@ def on_decode(c):
|
|
|
242
255
|
if qso.band_info['b'] is not None and pskr_upload is not None:
|
|
243
256
|
dx_call = c.msg_tuple[1]
|
|
244
257
|
if dx_call != 'not':
|
|
245
|
-
pskr_upload.add_report(dx_call, int(1000000*float(qso.band_info['fMHz'])) + c.fHz, c.snr, 'FT8',
|
|
258
|
+
pskr_upload.add_report(dx_call, int(1000000*float(qso.band_info['fMHz'])) + c.fHz, c.snr, 'FT8', 1, int(time.time()))
|
|
246
259
|
print(message.wsjtx_screen_format())
|
|
260
|
+
write_all_txt_row(message)
|
|
247
261
|
|
|
248
262
|
def on_busy_profile(busy_profile, cycle):
|
|
249
263
|
if output_device_idx is None:
|
|
@@ -253,6 +267,8 @@ def on_busy_profile(busy_profile, cycle):
|
|
|
253
267
|
idx = np.argmin(busy_profile[f0_idx:fn_idx])
|
|
254
268
|
clear_frequencies[cycle] = (f0_idx + idx) * audio_in.df
|
|
255
269
|
console_print(f"[on_busy] Set Tx freq to {clear_frequencies[cycle]:6.1f} for cycle {cycle}")
|
|
270
|
+
if qso.band_info['b'] is None:
|
|
271
|
+
console_print(f"[PyFT8] Band not set; please select a band.", color = 'red')
|
|
256
272
|
|
|
257
273
|
def on_control_click(btn_widg):
|
|
258
274
|
btn_def = btn_widg.user_data
|
|
@@ -283,7 +299,7 @@ def console_print(text, color = 'white'):
|
|
|
283
299
|
print(text)
|
|
284
300
|
|
|
285
301
|
def cli():
|
|
286
|
-
global audio_in, audio_out, output_device_idx, rig, gui, qso, config, clear_frequencies, pskr_upload
|
|
302
|
+
global audio_in, audio_out, output_device_idx, rig, gui, qso, config, config_folder, clear_frequencies, pskr_upload
|
|
287
303
|
import time
|
|
288
304
|
parser = argparse.ArgumentParser(prog='PyFT8rx', description = 'Command Line FT8 decoder')
|
|
289
305
|
parser.add_argument('-c', '--config_folder', help = 'Location of config folder e.g. C:/Users/drala/Documents/Projects/GitHub/G1OJS/PyFT8_cfg', default = './')
|
|
@@ -297,12 +313,12 @@ def cli():
|
|
|
297
313
|
|
|
298
314
|
output_device_idx = None
|
|
299
315
|
config_folder = f"{args.config_folder}".strip()
|
|
300
|
-
get_config(
|
|
301
|
-
logging = Logging(
|
|
316
|
+
get_config()
|
|
317
|
+
logging = Logging()
|
|
302
318
|
mc, mg = config['station']['call'], config['station']['grid']
|
|
303
319
|
if mc is not None and 'pskreporter' in config.keys():
|
|
304
320
|
if config['pskreporter']['upload'] == 'Y':
|
|
305
|
-
pskr_upload = PSKR_upload(mc, mg, software =
|
|
321
|
+
pskr_upload = PSKR_upload(mc, mg, software = f"PyFT8 v{VER}", tt = int(time.time()), console_print = console_print) if not mc is None else None
|
|
306
322
|
console_print(f"[PyFT8] Spots will upload to pskreporter")
|
|
307
323
|
qso = FT8_QSO(logging)
|
|
308
324
|
rig = Rig(config)
|
|
@@ -47,13 +47,11 @@ def unpack(bits):
|
|
|
47
47
|
return ('Free text','not','implemented')
|
|
48
48
|
else:
|
|
49
49
|
return (['DXpedition','Field Day', 'Field Day', 'Telemetry'][n3-1],'not','implemented')
|
|
50
|
-
elif i3 == 1:
|
|
50
|
+
elif i3 == 1 or i3 == 2: # 1 = Std Msg incl /R 2 = 'EU VHF' = Std Msg incl /P
|
|
51
51
|
gr, bits = get_bits(bits,16)
|
|
52
52
|
cb, bits = get_bits(bits,29)
|
|
53
53
|
ca, bits = get_bits(bits,29)
|
|
54
|
-
return (decode_call(ca), decode_call(cb), decode_grid(gr))
|
|
55
|
-
elif i3 == 2:
|
|
56
|
-
return ('EU VHF','not','implemented')
|
|
54
|
+
return (decode_call(ca, i3), decode_call(cb, i3), decode_grid(gr))
|
|
57
55
|
elif i3 == 3:
|
|
58
56
|
return ('RTTY RU','not','implemented')
|
|
59
57
|
elif i3 == 4:
|
|
@@ -61,13 +59,13 @@ def unpack(bits):
|
|
|
61
59
|
elif i3 == 5:
|
|
62
60
|
return ('EU VHF','not','implemented')
|
|
63
61
|
|
|
64
|
-
def decode_call(call_int):
|
|
62
|
+
def decode_call(call_int, i3):
|
|
65
63
|
from string import ascii_uppercase as ltrs, digits as digs
|
|
66
64
|
table_7 = {'DE':(0,0),'QRZ':(1,1),'CQ':(2,2), 'CQ nnn':(3,1002),'CQ x':(1004,1029),
|
|
67
65
|
'CQ xx':(1031,1731),'CQ xxxx':(21443,532443),'<....>':(2063592,2063592+4194303)}
|
|
68
66
|
call_fields = [ (' ' + digs + ltrs, 36*10*27**3), (digs + ltrs, 10*27**3), (digs + ' ' * 17, 27**3),
|
|
69
67
|
(' ' + ltrs, 27**2), (' ' + ltrs, 27), (' ' + ltrs, 1) ]
|
|
70
|
-
|
|
68
|
+
portable_rover = call_int & 1
|
|
71
69
|
call_int >>= 1
|
|
72
70
|
for ct, (lo, hi) in table_7.items():
|
|
73
71
|
if lo <= call_int <= hi:
|
|
@@ -78,7 +76,9 @@ def decode_call(call_int):
|
|
|
78
76
|
idx, call_int = divmod(call_int, div)
|
|
79
77
|
chars.append(alphabet[idx])
|
|
80
78
|
call = ''.join(chars).strip()
|
|
81
|
-
|
|
79
|
+
if portable_rover:
|
|
80
|
+
call = call + ('/P' if i3 == 2 else '/R')
|
|
81
|
+
return call
|
|
82
82
|
|
|
83
83
|
def decode_grid(grid_int):
|
|
84
84
|
g15 = grid_int & 0x7FFF
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyFT8
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.1
|
|
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
|
|
@@ -18,7 +18,7 @@ Requires-Dist: pyaudio
|
|
|
18
18
|
Dynamic: license-file
|
|
19
19
|
|
|
20
20
|
# PyFT8 [](https://pepy.tech/projects/pyft8)
|
|
21
|
-
# All-Python FT8 Transceiver
|
|
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.
|
|
24
24
|
|
|
@@ -30,11 +30,19 @@ If you're interested in how this works, maybe have a look at [MiniPyFT8](https:/
|
|
|
30
30
|
- Doesn't try to do everything, so launches quickly (~2 seconds on my old Dell Optiplex 790)
|
|
31
31
|
- Use with or without gui (receive and send messages via command line commands)
|
|
32
32
|
- GUI provides simultaneous views of odd and even cycles
|
|
33
|
-
- GUI shows worked-before info on CQ calls
|
|
33
|
+
- GUI shows worked-before info on CQ calls
|
|
34
34
|
- Messages overlaid on waterfall signals that produce them
|
|
35
35
|
- Automatically chooses clearest Tx frequency
|
|
36
36
|
- Modern programming language throughout
|
|
37
37
|
- Finds sound cards by keywords so follows them if windows moves them ...
|
|
38
|
+
- Logs QSOs to ADIF file and all spots to WSJTX-style ALL.txt file
|
|
39
|
+
- Uploads spots to pskreporter
|
|
40
|
+
|
|
41
|
+
To enable uploading of spots to pskreporter, make sure that your .ini file includes
|
|
42
|
+
```
|
|
43
|
+
[pskreporter]
|
|
44
|
+
upload = Y
|
|
45
|
+
```
|
|
38
46
|
|
|
39
47
|
<img width="1003" height="1020" alt="image" src="https://github.com/user-attachments/assets/bf6e3f78-531a-4c9b-ab2b-b51cc04ad980" />
|
|
40
48
|
|
|
@@ -112,6 +120,7 @@ Note - section 9 of the QEX paper states that the above two WSJT-X resources are
|
|
|
112
120
|
**FT8 decoding explorations / explanations**
|
|
113
121
|
- [VK3JPK's FT8 notes](https://github.com/vk3jpk/ft8-notes) including comprehensive [Python source code](https://github.com/vk3jpk/ft8-notes/blob/master/ft8.py)
|
|
114
122
|
- [G4JNT notes on LDPC coding process](http://www.g4jnt.com/WSJT-X_LdpcModesCodingProcess.pdf)
|
|
123
|
+
- [Kristian Glass's 2025-05-06 Understanding the FT8 binary protocol](https://notes.doismellburning.co.uk/notebook/2025-05-06-understanding-the-ft8-binary-protocol/)
|
|
115
124
|
|
|
116
125
|
**FT8 decoding in hardware**
|
|
117
126
|
- [Optimizing the (Web-888) FT8 Skimmer Experience](https://www.rx-888.com/web/design/digi.html) (see also [RX-888 project](https://www.rx-888.com/) )
|
|
@@ -122,7 +131,8 @@ Note - section 9 of the QEX paper states that the above two WSJT-X resources are
|
|
|
122
131
|
- [Post about ft8play](https://groups.io/g/FT8-Digital-Mode/topic/i_made_a_thing_ft8play/107846361)
|
|
123
132
|
|
|
124
133
|
**Browser-based decoder/encoders**
|
|
125
|
-
- [ft8js](https://e04.github.io/ft8js/example/browser/index.html) - source [github](https://github.com/e04/ft8js?tab=readme-ov-file), uses [FT8_lib](https://github.com/kgoba/ft8_lib)
|
|
134
|
+
- [ft8js by e04](https://e04.github.io/ft8js/example/browser/index.html) - source [github](https://github.com/e04/ft8js?tab=readme-ov-file), uses web-assembled version of [FT8_lib](https://github.com/kgoba/ft8_lib)
|
|
135
|
+
- [ft8ts by e04](https://github.com/e04/ft8ts), - pure TypeScript implementation
|
|
126
136
|
- [ChromeFT8 Browser Extension](https://github.com/Transwarp8/ChromeFT8), decoder adapted from [ft8js](https://e04.github.io/ft8js/example/browser/index.html)
|
|
127
137
|
|
|
128
138
|
<script data-goatcounter="https://g1ojs-github.goatcounter.com/count" async src="//gc.zgo.at/count.js"></script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# PyFT8 [](https://pepy.tech/projects/pyft8)
|
|
2
|
-
# All-Python FT8 Transceiver
|
|
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.
|
|
5
5
|
|
|
@@ -11,11 +11,19 @@ If you're interested in how this works, maybe have a look at [MiniPyFT8](https:/
|
|
|
11
11
|
- Doesn't try to do everything, so launches quickly (~2 seconds on my old Dell Optiplex 790)
|
|
12
12
|
- Use with or without gui (receive and send messages via command line commands)
|
|
13
13
|
- GUI provides simultaneous views of odd and even cycles
|
|
14
|
-
- GUI shows worked-before info on CQ calls
|
|
14
|
+
- GUI shows worked-before info on CQ calls
|
|
15
15
|
- Messages overlaid on waterfall signals that produce them
|
|
16
16
|
- Automatically chooses clearest Tx frequency
|
|
17
17
|
- Modern programming language throughout
|
|
18
18
|
- Finds sound cards by keywords so follows them if windows moves them ...
|
|
19
|
+
- Logs QSOs to ADIF file and all spots to WSJTX-style ALL.txt file
|
|
20
|
+
- Uploads spots to pskreporter
|
|
21
|
+
|
|
22
|
+
To enable uploading of spots to pskreporter, make sure that your .ini file includes
|
|
23
|
+
```
|
|
24
|
+
[pskreporter]
|
|
25
|
+
upload = Y
|
|
26
|
+
```
|
|
19
27
|
|
|
20
28
|
<img width="1003" height="1020" alt="image" src="https://github.com/user-attachments/assets/bf6e3f78-531a-4c9b-ab2b-b51cc04ad980" />
|
|
21
29
|
|
|
@@ -93,6 +101,7 @@ Note - section 9 of the QEX paper states that the above two WSJT-X resources are
|
|
|
93
101
|
**FT8 decoding explorations / explanations**
|
|
94
102
|
- [VK3JPK's FT8 notes](https://github.com/vk3jpk/ft8-notes) including comprehensive [Python source code](https://github.com/vk3jpk/ft8-notes/blob/master/ft8.py)
|
|
95
103
|
- [G4JNT notes on LDPC coding process](http://www.g4jnt.com/WSJT-X_LdpcModesCodingProcess.pdf)
|
|
104
|
+
- [Kristian Glass's 2025-05-06 Understanding the FT8 binary protocol](https://notes.doismellburning.co.uk/notebook/2025-05-06-understanding-the-ft8-binary-protocol/)
|
|
96
105
|
|
|
97
106
|
**FT8 decoding in hardware**
|
|
98
107
|
- [Optimizing the (Web-888) FT8 Skimmer Experience](https://www.rx-888.com/web/design/digi.html) (see also [RX-888 project](https://www.rx-888.com/) )
|
|
@@ -103,7 +112,8 @@ Note - section 9 of the QEX paper states that the above two WSJT-X resources are
|
|
|
103
112
|
- [Post about ft8play](https://groups.io/g/FT8-Digital-Mode/topic/i_made_a_thing_ft8play/107846361)
|
|
104
113
|
|
|
105
114
|
**Browser-based decoder/encoders**
|
|
106
|
-
- [ft8js](https://e04.github.io/ft8js/example/browser/index.html) - source [github](https://github.com/e04/ft8js?tab=readme-ov-file), uses [FT8_lib](https://github.com/kgoba/ft8_lib)
|
|
115
|
+
- [ft8js by e04](https://e04.github.io/ft8js/example/browser/index.html) - source [github](https://github.com/e04/ft8js?tab=readme-ov-file), uses web-assembled version of [FT8_lib](https://github.com/kgoba/ft8_lib)
|
|
116
|
+
- [ft8ts by e04](https://github.com/e04/ft8ts), - pure TypeScript implementation
|
|
107
117
|
- [ChromeFT8 Browser Extension](https://github.com/Transwarp8/ChromeFT8), decoder adapted from [ft8js](https://e04.github.io/ft8js/example/browser/index.html)
|
|
108
118
|
|
|
109
119
|
<script data-goatcounter="https://g1ojs-github.goatcounter.com/count" async src="//gc.zgo.at/count.js"></script>
|
|
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
|