PyFT8 2.10.0__tar.gz → 2.12.0__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.10.0 → pyft8-2.12.0}/PKG-INFO +3 -1
- {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/databases.py +22 -19
- {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/pskreporter.py +1 -1
- {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/pyft8.py +91 -63
- {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8.egg-info/PKG-INFO +3 -1
- {pyft8-2.10.0 → pyft8-2.12.0}/README.md +2 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/pyproject.toml +1 -1
- {pyft8-2.10.0 → pyft8-2.12.0}/LICENSE +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/MANIFEST.in +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/__init__.py +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/gui.py +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/receiver.py +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/rigctrl.py +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/time_utils.py +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/transmitter.py +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8.egg-info/SOURCES.txt +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8.egg-info/dependency_links.txt +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8.egg-info/entry_points.txt +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8.egg-info/requires.txt +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8.egg-info/top_level.txt +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/setup.cfg +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/tests/dev/CQ AAAA.py +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/tests/dev/osd.py +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/tests/dev/test_generate_wav.py +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/tests/dev/test_loopback_performance.py +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/tests/dev/view_worked_before.py +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/tests/plot_baseline.py +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/tests/spare.py +0 -0
- {pyft8-2.10.0 → pyft8-2.12.0}/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
|
+
Version: 2.12.0
|
|
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
|
|
@@ -20,6 +20,8 @@ Dynamic: license-file
|
|
|
20
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
|
+
## Note - my recent additions to the GUI have caused issues with the non-GUI based command line processing. I'm working on fixing this. V2.11.0 addresses this somewhat but I need to complete the revision and check the notes below for the command line options ##
|
|
24
|
+
|
|
23
25
|
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
26
|
|
|
25
27
|
PyFT8 is somewhat experimental, with a focus on demonstrating FT8 written in Python, but can be used as a standalone replacement for WSJT-x and other software.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from PyFT8.pskreporter import PSKR_MQTT_listener
|
|
2
|
-
import threading, time, os, pickle
|
|
2
|
+
import threading, time, os, pickle, json
|
|
3
3
|
|
|
4
4
|
call_hashes = {}
|
|
5
5
|
def add_call_hashes(call):
|
|
@@ -47,14 +47,15 @@ def grids_to_dist_brg(sq1, sq2, units):
|
|
|
47
47
|
return (r, degrees(b) % 360)
|
|
48
48
|
|
|
49
49
|
class DiskDict:
|
|
50
|
-
def __init__(self, file):
|
|
50
|
+
def __init__(self, file, autosave_t0):
|
|
51
51
|
self.lock = threading.Lock()
|
|
52
52
|
self.file = file
|
|
53
53
|
self.data = {}
|
|
54
54
|
self.load()
|
|
55
|
-
threading.Thread(target = self._autosave, daemon = True).start()
|
|
55
|
+
threading.Thread(target = self._autosave, args=(autosave_t0,), daemon = True).start()
|
|
56
56
|
|
|
57
|
-
def _autosave(self, autosave_period = 15):
|
|
57
|
+
def _autosave(self, autosave_t0, autosave_period = 15):
|
|
58
|
+
time.sleep(autosave_t0)
|
|
58
59
|
while True:
|
|
59
60
|
time.sleep(autosave_period)
|
|
60
61
|
self.save()
|
|
@@ -63,31 +64,32 @@ class DiskDict:
|
|
|
63
64
|
with self.lock:
|
|
64
65
|
if(os.path.exists(self.file)):
|
|
65
66
|
with open(f"{self.file}","rb") as f:
|
|
66
|
-
self.data =
|
|
67
|
+
self.data = json.load(f)
|
|
67
68
|
|
|
68
69
|
def save(self):
|
|
69
70
|
with self.lock:
|
|
70
71
|
tmp_file = f"{self.file}.tmp"
|
|
71
|
-
with open(tmp_file, "
|
|
72
|
-
|
|
72
|
+
with open(tmp_file, "w") as f:
|
|
73
|
+
json.dump(self.data, f)
|
|
73
74
|
f.flush()
|
|
74
75
|
os.fsync(f.fileno())
|
|
75
76
|
os.replace(tmp_file, self.file)
|
|
76
77
|
|
|
77
78
|
class History:
|
|
78
79
|
def __init__(self, config_folder, my_call, home_square, pskr_refresh_mins, parse_all_file):
|
|
80
|
+
self.t0 = time.time()
|
|
79
81
|
self.pskr_refresh_mins = pskr_refresh_mins
|
|
80
82
|
self.config_folder = config_folder
|
|
81
83
|
self.my_call = my_call
|
|
82
84
|
self.home_square = home_square
|
|
83
85
|
self.home_square_lev4 = home_square[:4]
|
|
84
86
|
self.dist_brg_cache = {}
|
|
85
|
-
self.hearing_me = DiskDict(f"{self.config_folder}/hearing_me.
|
|
86
|
-
self.heard_by_me = DiskDict(f"{self.config_folder}/heard_by_me.
|
|
87
|
+
self.hearing_me = DiskDict(f"{self.config_folder}/hearing_me.json", 3) # all-time record of hearing me
|
|
88
|
+
self.heard_by_me = DiskDict(f"{self.config_folder}/heard_by_me.json", 5) # all-time record of heard by me
|
|
87
89
|
self.hearing_me_new = {}
|
|
88
90
|
self.heard_by_me_new = {}
|
|
89
|
-
self.call_to_grid = DiskDict(f"{self.config_folder}/call_to_grid.
|
|
90
|
-
self.band_TxRx_homecall_report_times = DiskDict(f"{self.config_folder}/report_times.
|
|
91
|
+
self.call_to_grid = DiskDict(f"{self.config_folder}/call_to_grid.json", 7) # all time cache call -> fine locator
|
|
92
|
+
self.band_TxRx_homecall_report_times = DiskDict(f"{self.config_folder}/report_times.json", 9) # last 20 mins data -> per band tx/rx & current band detail
|
|
91
93
|
self.home_activity = {}
|
|
92
94
|
self.home_most_remotes = {}
|
|
93
95
|
self.lock = threading.Lock()
|
|
@@ -160,7 +162,7 @@ class History:
|
|
|
160
162
|
call, grid = call_grid
|
|
161
163
|
self.store_best_grid(call, grid)
|
|
162
164
|
if self.home_square_lev4 in grid:
|
|
163
|
-
self.add_homespots_record(
|
|
165
|
+
self.add_homespots_record(f"{d['b']}_{iTxRx}_{call}", tnow)
|
|
164
166
|
if d['sc'] == self.my_call:
|
|
165
167
|
self.add_myspots_record(self.hearing_me.data, self.hearing_me_new, d['b'], d['rc'], tnow, d['rp'])
|
|
166
168
|
if d['rc'] == self.my_call:
|
|
@@ -174,7 +176,7 @@ class History:
|
|
|
174
176
|
|
|
175
177
|
def add_homespots_record(self, key, t):
|
|
176
178
|
self.band_TxRx_homecall_report_times.data.setdefault(key, [])
|
|
177
|
-
self.band_TxRx_homecall_report_times.data[key].append(int(t))
|
|
179
|
+
self.band_TxRx_homecall_report_times.data[key].append(int(t-self.t0))
|
|
178
180
|
|
|
179
181
|
def add_myspots_record(self, historic_data, new_alert_data, band, call, t, rp):
|
|
180
182
|
self._update_new_alert(band, call, historic_data, new_alert_data)
|
|
@@ -218,22 +220,23 @@ class History:
|
|
|
218
220
|
for b in self.home_activity:
|
|
219
221
|
self.home_activity[b] = [0, 0]
|
|
220
222
|
for b in self.home_most_remotes:
|
|
221
|
-
self.home_most_remotes[b] = [('',0), ('',0)]
|
|
223
|
+
self.home_most_remotes[b] = [('Nobody',0), ('Nobody',0)]
|
|
222
224
|
|
|
223
225
|
# keep only the remote spots that happened in the self.pskr_refresh_mins window
|
|
224
226
|
for band_TxRx_homecall in self.band_TxRx_homecall_report_times.data:
|
|
225
227
|
band_TxRx_homecall_report_times = self.band_TxRx_homecall_report_times.data[band_TxRx_homecall]
|
|
226
|
-
band_TxRx_homecall_report_times = [t for t in band_TxRx_homecall_report_times if (time.time() - t) < 60*self.pskr_refresh_mins]
|
|
228
|
+
band_TxRx_homecall_report_times = [t for t in band_TxRx_homecall_report_times if (time.time() - self.t0 - t) < 60*self.pskr_refresh_mins]
|
|
227
229
|
self.band_TxRx_homecall_report_times.data[band_TxRx_homecall] = band_TxRx_homecall_report_times
|
|
228
230
|
|
|
229
231
|
# count number of local Tx and Rx, and identify the local Tx and Rx with most remote spots
|
|
230
232
|
for band_TxRx_homecall in self.band_TxRx_homecall_report_times.data:
|
|
231
233
|
band_TxRx_homecall_report_times = self.band_TxRx_homecall_report_times.data[band_TxRx_homecall]
|
|
232
234
|
if len(band_TxRx_homecall_report_times):
|
|
233
|
-
b, iTxRx, c = band_TxRx_homecall
|
|
235
|
+
b, iTxRx, c = band_TxRx_homecall.split('_')
|
|
236
|
+
iTxRx = int(iTxRx)
|
|
234
237
|
self.home_activity.setdefault(b, [0, 0])
|
|
235
238
|
self.home_activity[b][iTxRx] +=1
|
|
236
|
-
self.home_most_remotes.setdefault(b, [('',0), ('',0)])
|
|
239
|
+
self.home_most_remotes.setdefault(b, [('Nobody',0), ('Nobody',0)])
|
|
237
240
|
nremotes = len(band_TxRx_homecall_report_times)
|
|
238
241
|
current_winner = self.home_most_remotes[b][iTxRx]
|
|
239
242
|
if nremotes > current_winner[1]:
|
|
@@ -241,8 +244,8 @@ class History:
|
|
|
241
244
|
self.home_most_remotes[b][iTxRx] = (c, nremotes)
|
|
242
245
|
|
|
243
246
|
def get_spot_counts(self, band, call):
|
|
244
|
-
tx_reports = self.band_TxRx_homecall_report_times.data.get(
|
|
245
|
-
rx_reports = self.band_TxRx_homecall_report_times.data.get(
|
|
247
|
+
tx_reports = self.band_TxRx_homecall_report_times.data.get(f"{band}_0_{call}", [])
|
|
248
|
+
rx_reports = self.band_TxRx_homecall_report_times.data.get(f"{band}_1_{call}", [])
|
|
246
249
|
n_spotting = len(tx_reports) if tx_reports else 0
|
|
247
250
|
n_spotted = len(rx_reports) if rx_reports else 0
|
|
248
251
|
return n_spotted, n_spotting
|
|
@@ -51,7 +51,7 @@ class PSKR_upload:
|
|
|
51
51
|
self.rx_block = self._block(b"\x99\x92", rx)
|
|
52
52
|
self.console_print = console_print
|
|
53
53
|
self.lock = threading.Lock()
|
|
54
|
-
print(f"[PSKR_upload] Spots will upload to pskreporter")
|
|
54
|
+
print(f"[PSKR_upload] Spots will upload to pskreporter if Rx band is known")
|
|
55
55
|
threading.Thread(target = self._check_for_send, daemon = True).start()
|
|
56
56
|
|
|
57
57
|
def _enc_str(self, s):
|
|
@@ -12,31 +12,41 @@ from PyFT8.time_utils import global_time_utils
|
|
|
12
12
|
from PyFT8.rigctrl import Rig_CAT, Rig_hamlib
|
|
13
13
|
from PyFT8.databases import History, ADIF
|
|
14
14
|
|
|
15
|
-
VER = '2.
|
|
15
|
+
VER = '2.12.0'
|
|
16
16
|
|
|
17
17
|
MAX_TX_START_SECONDS = 2.5
|
|
18
18
|
HEARING_PANEL_LIFE_MINS = 5
|
|
19
19
|
PSKR_REFRESH_MINS = 20
|
|
20
|
-
rig, gui, qso, adif_logging, history, pskr_upload = None, None, None, None, None, None
|
|
20
|
+
config_folder, rig, gui, qso, adif_logging, history, pskr_upload, output_device_idx = None, None, None, None, None, None, None, None
|
|
21
21
|
busy_profile, hearing_me = None, None
|
|
22
22
|
|
|
23
23
|
def get_config():
|
|
24
|
-
import configparser
|
|
24
|
+
import configparser, sys
|
|
25
25
|
global config
|
|
26
26
|
config = configparser.ConfigParser()
|
|
27
27
|
ini_file = f"{config_folder}/PyFT8.ini"
|
|
28
28
|
if not os.path.exists(ini_file):
|
|
29
|
-
|
|
29
|
+
resp = input(f"No config file found at {ini_file}\nWould you like to create one (Y/N)? ")
|
|
30
|
+
if resp.upper() !="Y":
|
|
31
|
+
print("Exiting program")
|
|
32
|
+
sys.exit()
|
|
33
|
+
station_callsign = input(f"Please enter your callsign: ")
|
|
34
|
+
station_grid = ''
|
|
35
|
+
while len(station_grid) < 4:
|
|
36
|
+
station_grid = input(f"Please enter your Maidenhead locator (at least 4 characters, you can edit this later): ")
|
|
37
|
+
config['station'] = {'call':station_callsign, 'grid':station_grid}
|
|
30
38
|
config['bands'] = {'20m':14.074}
|
|
39
|
+
config['gui'] = {'loc':'km_deg', 'wb':'Y'}
|
|
31
40
|
config['hamlib_rig'] = {'rigctld':'C:/WSJT/wsjtx/bin/rigctld-wsjtx', 'port': 'COM4', 'baud_rate':9600, 'model':3070}
|
|
32
41
|
config['rig'] = {'port': 'COM4', 'baud_rate':9600,
|
|
33
42
|
'set_freq_command':'FEFE88E0.05.0000000000.FD', 'set_freq_value':'5|5|vfBcdLU|1|0',
|
|
34
43
|
'ptt_on_command':'FEFE88E0.1C00.01.FD', 'ptt_off_command':'FEFE88E0.1C00.00.FD'}
|
|
35
|
-
config['pskreporter'] = {'upload':'
|
|
44
|
+
config['pskreporter'] = {'upload':'Y'}
|
|
36
45
|
with open(ini_file, 'w') as f:
|
|
37
46
|
config.write(f)
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
print(f"Wrote default config to {ini_file}. Please open and edit to add bands, frequencies and preferences and re-launch the program.")
|
|
48
|
+
sys.exit()
|
|
49
|
+
print(f"Reading config from {ini_file}")
|
|
40
50
|
config.read(ini_file)
|
|
41
51
|
|
|
42
52
|
class Message:
|
|
@@ -51,11 +61,13 @@ class Message:
|
|
|
51
61
|
self.is_from_me = c.msg_tuple[1] == mycall
|
|
52
62
|
self.is_to_me = c.msg_tuple[0] == mycall
|
|
53
63
|
self.is_cq = c.msg_tuple[0].startswith('CQ')
|
|
54
|
-
self.geo_text = history.get_geo_text(c.msg_tuple[1], config['gui']['loc'])
|
|
64
|
+
self.geo_text = history.get_geo_text(c.msg_tuple[1], config['gui']['loc']) if history else ''
|
|
55
65
|
tnow = time.time()
|
|
56
|
-
wb_time = adif_logging.cache.get(c.msg_tuple[1],'')
|
|
66
|
+
wb_time = adif_logging.cache.get(c.msg_tuple[1],'') if adif_logging else 0
|
|
57
67
|
wb_text = f"wb: {global_time_utils.format_duration(tnow - float(wb_time))}" if wb_time else ''
|
|
58
|
-
hearing_me = '
|
|
68
|
+
hearing_me = ''
|
|
69
|
+
if history:
|
|
70
|
+
hearing_me = '# ' if history.is_hearing_me(band, c.msg_tuple[1], tnow - 60*HEARING_PANEL_LIFE_MINS) else ' '
|
|
59
71
|
self.gui_text = f"{c.msg} {hearing_me}{wb_text} {self.geo_text}"
|
|
60
72
|
|
|
61
73
|
def wsjtx_screen_format(self):
|
|
@@ -193,7 +205,8 @@ def on_rx_decode(c):
|
|
|
193
205
|
gui.add_message_box(message)
|
|
194
206
|
print(message.wsjtx_screen_format())
|
|
195
207
|
fMHz = float(qso.band_info['fMHz']) if qso.band_info['fMHz'] is not None else 0
|
|
196
|
-
history
|
|
208
|
+
if history:
|
|
209
|
+
history.write_all_txt_row(c.cyclestart['string'], fMHz, 'Rx', 'FT8', c.snr, c.dt, c.fHz, c.msg)
|
|
197
210
|
if qso.band_info['b'] is not None and pskr_upload is not None:
|
|
198
211
|
call_a, call_b, grid_rpt = c.msg_tuple
|
|
199
212
|
if call_b == 'not':
|
|
@@ -201,9 +214,10 @@ def on_rx_decode(c):
|
|
|
201
214
|
call_b_grid = grid_rpt if isGrid(grid_rpt) else ''
|
|
202
215
|
if call_b != config['station']['call']:
|
|
203
216
|
pskr_upload.add_report(call_b, int(1000000*float(qso.band_info['fMHz'])) + c.fHz, c.snr, 'FT8', 1, int(time.time()))
|
|
204
|
-
history
|
|
205
|
-
|
|
206
|
-
|
|
217
|
+
if history:
|
|
218
|
+
history.store_best_grid(call_b, call_b_grid)
|
|
219
|
+
history.add_myspots_record(history.heard_by_me.data, history.heard_by_me_new, qso.band_info['b'], call_b, int(time.time()), c.snr)
|
|
220
|
+
if history and call_b == config['station']['call'] and (isReport(grid_rpt) or isRReport(grid_rpt)):
|
|
207
221
|
rpt = grid_rpt.replace("R","")
|
|
208
222
|
history.add_myspots_record(history.hearing_me.data, history.hearing_me_new, qso.band_info['b'], call_a, int(time.time()), rpt)
|
|
209
223
|
|
|
@@ -301,7 +315,7 @@ def console_print(text, color = 'white'):
|
|
|
301
315
|
|
|
302
316
|
def cli():
|
|
303
317
|
global audio_in, audio_out, output_device_idx, rig, gui, qso, config, config_folder, clearest_frequency, adif_logging, pskr_upload, history
|
|
304
|
-
import time
|
|
318
|
+
import time, sys
|
|
305
319
|
parser = argparse.ArgumentParser(prog='PyFT8rx', description = 'Command Line FT8 decoder')
|
|
306
320
|
parser.add_argument('-c', '--config_folder', help = 'Location of config folder e.g. C:/Users/drala/Documents/Projects/GitHub/G1OJS/PyFT8_cfg', default = './')
|
|
307
321
|
parser.add_argument('-i', '--inputcard_keywords', help = 'Comma-separated keywords to identify the input sound device')
|
|
@@ -309,68 +323,82 @@ def cli():
|
|
|
309
323
|
parser.add_argument('-o','--outputcard_keywords', help = 'Comma-separated keywords to identify the output sound device')
|
|
310
324
|
parser.add_argument('-n','--no_gui', action='store_true', help = "Don't create a gui")
|
|
311
325
|
parser.add_argument('-m','--transmit_message', nargs='?', help = 'Transmit a message')
|
|
312
|
-
parser.add_argument('-w','--wave_output_file', nargs='?', help = 'Wave output file name'
|
|
326
|
+
parser.add_argument('-w','--wave_output_file', nargs='?', help = 'Wave output file name')
|
|
313
327
|
parser.add_argument('-a', '--parse_all_file', action='store_true', help = 'parse and save .../config_folder/ALL.txt to heard me / heard by me data')
|
|
314
328
|
args = parser.parse_args()
|
|
315
329
|
|
|
316
|
-
|
|
330
|
+
if len(sys.argv)==1:
|
|
331
|
+
parser.print_help(sys.stderr)
|
|
332
|
+
sys.exit(1)
|
|
333
|
+
|
|
334
|
+
if args.transmit_message or args.outputcard_keywords:
|
|
335
|
+
audio_out = AudioOut()
|
|
336
|
+
clearest_frequency = 760
|
|
337
|
+
|
|
338
|
+
if args.transmit_message and args.wave_output_file:
|
|
339
|
+
make_wav(args.transmit_message, f"{args.wave_output_file}")
|
|
340
|
+
sys.exit(1)
|
|
341
|
+
|
|
317
342
|
config_folder = f"{args.config_folder}".strip()
|
|
318
343
|
get_config()
|
|
319
|
-
adif_logging = ADIF(f"{config_folder}/PyFT8.adi")
|
|
320
344
|
mc, mg = config['station']['call'], config['station']['grid']
|
|
345
|
+
qso = FT8_QSO()
|
|
346
|
+
|
|
347
|
+
if args.parse_all_file:
|
|
348
|
+
history = History(config_folder, mc, mg, PSKR_REFRESH_MINS, True)
|
|
349
|
+
sys.exit(1)
|
|
350
|
+
|
|
351
|
+
if config.has_section('hamlib_rig'):
|
|
352
|
+
console_print("Connecting to rig via Hamlib")
|
|
353
|
+
rig = Rig_hamlib(config)
|
|
354
|
+
else:
|
|
355
|
+
console_print("Connecting to rig via CAT")
|
|
356
|
+
rig = Rig_CAT(config)
|
|
357
|
+
|
|
358
|
+
if args.outputcard_keywords:
|
|
359
|
+
outputcard_keywords = args.outputcard_keywords.replace(' ','').split(',')
|
|
360
|
+
output_device_idx = audio_out.find_device(outputcard_keywords)
|
|
361
|
+
|
|
362
|
+
if args.transmit_message and rig and args.outputcard_keywords:
|
|
363
|
+
qso.set_tx_message(args.transmit_message)
|
|
364
|
+
sys.exit(1)
|
|
365
|
+
|
|
366
|
+
if not args.inputcard_keywords:
|
|
367
|
+
print("No input device specified")
|
|
368
|
+
sys.exit(1)
|
|
369
|
+
else:
|
|
370
|
+
audio_in = AudioIn(3100)
|
|
371
|
+
input_device_idx = audio_in.find_device(args.inputcard_keywords.replace(' ','').split(','))
|
|
372
|
+
if not input_device_idx:
|
|
373
|
+
console_print("No input device")
|
|
374
|
+
sys.exit(1)
|
|
375
|
+
rx = Receiver(audio_in, [200, 3100], on_rx_decode, on_rx_busy_profile)
|
|
376
|
+
audio_in.start_streamed_audio(input_device_idx)
|
|
377
|
+
|
|
378
|
+
if not args.no_gui:
|
|
379
|
+
gui = Gui(audio_in.dBgrid_main, 4, 2, config, on_gui_sidebars_refresh, on_gui_msg_click, on_gui_control_click)
|
|
380
|
+
history = History(config_folder, mc, mg, PSKR_REFRESH_MINS, args.parse_all_file)
|
|
381
|
+
adif_logging = ADIF(f"{config_folder}/PyFT8.adi")
|
|
382
|
+
history.load_from_wb(adif_logging.cache)
|
|
383
|
+
|
|
321
384
|
if mc is not None and 'pskreporter' in config.keys():
|
|
322
385
|
if config['pskreporter']['upload'] == 'Y':
|
|
323
386
|
pskr_upload = PSKR_upload(mc, mg, software = f"PyFT8 v{VER}", console_print = console_print) if not mc is None else None
|
|
324
|
-
history = History(config_folder, mc, mg, PSKR_REFRESH_MINS, args.parse_all_file)
|
|
325
387
|
|
|
326
|
-
if
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
console_print("Connecting to rig via Hamlib")
|
|
332
|
-
rig = Rig_hamlib(config)
|
|
333
|
-
else:
|
|
334
|
-
console_print("Connecting to rig via CAT")
|
|
335
|
-
rig = Rig_CAT(config)
|
|
336
|
-
|
|
337
|
-
if args.transmit_message or args.outputcard_keywords:
|
|
338
|
-
audio_out = AudioOut()
|
|
339
|
-
clearest_frequency = 760
|
|
340
|
-
|
|
341
|
-
if args.outputcard_keywords:
|
|
342
|
-
outputcard_keywords = args.outputcard_keywords.replace(' ','').split(',')
|
|
343
|
-
output_device_idx = audio_out.find_device(outputcard_keywords)
|
|
344
|
-
|
|
345
|
-
if args.transmit_message:
|
|
346
|
-
if args.outputcard_keywords:
|
|
347
|
-
qso.set_tx_message(args.transmit_message)
|
|
348
|
-
else:
|
|
349
|
-
make_wav(args.transmit_message, f"{args.config_folder}/{args.wave_output_file}")
|
|
350
|
-
else:
|
|
351
|
-
audio_in = AudioIn(3100)
|
|
352
|
-
input_device_idx = audio_in.find_device(args.inputcard_keywords.replace(' ','').split(','))
|
|
353
|
-
if not input_device_idx:
|
|
354
|
-
console_print("No input device")
|
|
355
|
-
else:
|
|
356
|
-
gui = None if args.no_gui else Gui(audio_in.dBgrid_main, 4, 2, config, on_gui_sidebars_refresh, on_gui_msg_click, on_gui_control_click)
|
|
357
|
-
rx = Receiver(audio_in, [200, 3100], on_rx_decode, on_rx_busy_profile)
|
|
358
|
-
audio_in.start_streamed_audio(input_device_idx)
|
|
359
|
-
if gui is not None:
|
|
360
|
-
gui.set_bandstats_title(f"Pskreporter Spots\nto/from {config['station']['grid'][:4]} <{PSKR_REFRESH_MINS:.0f} mins")
|
|
361
|
-
gui.plt.show()
|
|
362
|
-
else:
|
|
363
|
-
wait_for_keyboard()
|
|
388
|
+
if gui is None:
|
|
389
|
+
wait_for_keyboard()
|
|
390
|
+
else:
|
|
391
|
+
gui.set_bandstats_title(f"Pskreporter Spots\nto/from {config['station']['grid'][:4]} <{PSKR_REFRESH_MINS:.0f} mins")
|
|
392
|
+
gui.plt.show()
|
|
364
393
|
|
|
365
394
|
|
|
366
395
|
#================== TEST CODE ============================================================
|
|
367
396
|
if __name__ == "__main__":
|
|
368
397
|
import mock
|
|
369
|
-
#with mock.patch('sys.argv', ['pyft8', '-c C:/Users/drala/Documents/Projects/GitHub/G1OJS/PyFT8_cfg', '-a']):
|
|
370
398
|
with mock.patch('sys.argv', ['pyft8', '-i Mic, CODEC', '-o Speak, CODEC', '-c C:/Users/drala/Documents/Projects/GitHub/G1OJS/PyFT8_cfg']):
|
|
371
|
-
#with mock.patch('sys.argv', ['pyft8', '-
|
|
372
|
-
#with mock.patch('sys.argv', ['pyft8', '-i Mic, CODEC', '-
|
|
373
|
-
#with mock.patch('sys.argv', ['pyft8', '-
|
|
374
|
-
#with mock.patch('sys.argv', ['pyft8', '-m', "CQ G1OJS IO90",
|
|
375
|
-
#with mock.patch('sys.argv', ['pyft8', '-m', "CQ G1OJS IO90"]):
|
|
399
|
+
#with mock.patch('sys.argv', ['pyft8', '-c C:/Users/drala/Documents/Projects/GitHub/G1OJS/PyFT8_cfg', '-a']):
|
|
400
|
+
#with mock.patch('sys.argv', ['pyft8', '-i Mic, CODEC', '-c C:/Users/drala/Documents/Projects/GitHub/G1OJS/PyFT8_cfg']):
|
|
401
|
+
#with mock.patch('sys.argv', ['pyft8', '-i Mic, CODEC', '-n', '-c C:/Users/drala/Documents/Projects/GitHub/G1OJS/PyFT8_cfg']):
|
|
402
|
+
#with mock.patch('sys.argv', ['pyft8', '-m', "CQ G1OJS IO90", "-w", "PyFT8.wav"]):
|
|
403
|
+
#with mock.patch('sys.argv', ['pyft8', '-m', "CQ G1OJS IO90", '-o', "Speak, CODEC", '-c C:/Users/drala/Documents/Projects/GitHub/G1OJS/PyFT8_cfg']):
|
|
376
404
|
cli()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyFT8
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.12.0
|
|
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
|
|
@@ -20,6 +20,8 @@ Dynamic: license-file
|
|
|
20
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
|
+
## Note - my recent additions to the GUI have caused issues with the non-GUI based command line processing. I'm working on fixing this. V2.11.0 addresses this somewhat but I need to complete the revision and check the notes below for the command line options ##
|
|
24
|
+
|
|
23
25
|
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
26
|
|
|
25
27
|
PyFT8 is somewhat experimental, with a focus on demonstrating FT8 written in Python, but can be used as a standalone replacement for WSJT-x and other software.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
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
|
+
## Note - my recent additions to the GUI have caused issues with the non-GUI based command line processing. I'm working on fixing this. V2.11.0 addresses this somewhat but I need to complete the revision and check the notes below for the command line options ##
|
|
5
|
+
|
|
4
6
|
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
7
|
|
|
6
8
|
PyFT8 is somewhat experimental, with a focus on demonstrating FT8 written in Python, but can be used as a standalone replacement for WSJT-x and other software.
|
|
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
|
|
File without changes
|