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.
Files changed (29) hide show
  1. {pyft8-2.10.0 → pyft8-2.12.0}/PKG-INFO +3 -1
  2. {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/databases.py +22 -19
  3. {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/pskreporter.py +1 -1
  4. {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/pyft8.py +91 -63
  5. {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8.egg-info/PKG-INFO +3 -1
  6. {pyft8-2.10.0 → pyft8-2.12.0}/README.md +2 -0
  7. {pyft8-2.10.0 → pyft8-2.12.0}/pyproject.toml +1 -1
  8. {pyft8-2.10.0 → pyft8-2.12.0}/LICENSE +0 -0
  9. {pyft8-2.10.0 → pyft8-2.12.0}/MANIFEST.in +0 -0
  10. {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/__init__.py +0 -0
  11. {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/gui.py +0 -0
  12. {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/receiver.py +0 -0
  13. {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/rigctrl.py +0 -0
  14. {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/time_utils.py +0 -0
  15. {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8/transmitter.py +0 -0
  16. {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8.egg-info/SOURCES.txt +0 -0
  17. {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8.egg-info/dependency_links.txt +0 -0
  18. {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8.egg-info/entry_points.txt +0 -0
  19. {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8.egg-info/requires.txt +0 -0
  20. {pyft8-2.10.0 → pyft8-2.12.0}/PyFT8.egg-info/top_level.txt +0 -0
  21. {pyft8-2.10.0 → pyft8-2.12.0}/setup.cfg +0 -0
  22. {pyft8-2.10.0 → pyft8-2.12.0}/tests/dev/CQ AAAA.py +0 -0
  23. {pyft8-2.10.0 → pyft8-2.12.0}/tests/dev/osd.py +0 -0
  24. {pyft8-2.10.0 → pyft8-2.12.0}/tests/dev/test_generate_wav.py +0 -0
  25. {pyft8-2.10.0 → pyft8-2.12.0}/tests/dev/test_loopback_performance.py +0 -0
  26. {pyft8-2.10.0 → pyft8-2.12.0}/tests/dev/view_worked_before.py +0 -0
  27. {pyft8-2.10.0 → pyft8-2.12.0}/tests/plot_baseline.py +0 -0
  28. {pyft8-2.10.0 → pyft8-2.12.0}/tests/spare.py +0 -0
  29. {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.10.0
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 [![PyPI Downloads](https://static.pepy.tech/personalized-badge/pyft8?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/pyft8) [![PyPI Downloads](https://static.pepy.tech/personalized-badge/pyft8?period=weekly&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads/wk)](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 = pickle.load(f)
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, "wb") as f:
72
- pickle.dump(self.data, f)
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.pkl") # all-time record of hearing me
86
- self.heard_by_me = DiskDict(f"{self.config_folder}/heard_by_me.pkl") # all-time record of 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.pkl") # all time cache call -> fine locator
90
- self.band_TxRx_homecall_report_times = DiskDict(f"{self.config_folder}/report_times.pkl") # last 20 mins data -> per band tx/rx & current band detail
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((d['b'], iTxRx, call), tnow)
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((band, 0, call), [])
245
- rx_reports = self.band_TxRx_homecall_report_times.data.get((band, 1, call), [])
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.10.0'
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
- config['station'] = {'call':'station_callsign', 'grid':'station_grid'}
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':'N'}
44
+ config['pskreporter'] = {'upload':'Y'}
36
45
  with open(ini_file, 'w') as f:
37
46
  config.write(f)
38
- console_print(f"Wrote default config to {ini_file}")
39
- console_print(f"Reading config from {ini_file}")
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 = '# ' if history.is_hearing_me(band, c.msg_tuple[1], tnow - 60*HEARING_PANEL_LIFE_MINS) else ' '
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.write_all_txt_row(c.cyclestart['string'], fMHz, 'Rx', 'FT8', c.snr, c.dt, c.fHz, c.msg)
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.store_best_grid(call_b, call_b_grid)
205
- 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)
206
- if call_b == config['station']['call'] and (isReport(grid_rpt) or isRReport(grid_rpt)):
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', default = 'PyFT8.wav')
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
- output_device_idx = None
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 not args.parse_all_file:
327
-
328
- history.load_from_wb(adif_logging.cache)
329
- qso = FT8_QSO()
330
- if config.has_section('hamlib_rig'):
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', '-i Mic, CODEC']):
372
- #with mock.patch('sys.argv', ['pyft8', '-i Mic, CODEC', '-n']):
373
- #with mock.patch('sys.argv', ['pyft8', '-m', "CQ G1OJS IO90", '-cC:/Users/drala/Documents/Projects/GitHub/G1OJS/PyFT8_cfg']):
374
- #with mock.patch('sys.argv', ['pyft8', '-m', "CQ G1OJS IO90", '-o', "Speak, CODEC"]):
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.10.0
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 [![PyPI Downloads](https://static.pepy.tech/personalized-badge/pyft8?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/pyft8) [![PyPI Downloads](https://static.pepy.tech/personalized-badge/pyft8?period=weekly&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads/wk)](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 [![PyPI Downloads](https://static.pepy.tech/personalized-badge/pyft8?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/pyft8) [![PyPI Downloads](https://static.pepy.tech/personalized-badge/pyft8?period=weekly&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads/wk)](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.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "PyFT8"
3
- version = "2.10.0"
3
+ version = "2.12.0"
4
4
  license = "GPL-3.0-or-later"
5
5
 
6
6
  authors = [
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