PyFT8 2.7.6__tar.gz → 2.7.7__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 (32) hide show
  1. {pyft8-2.7.6 → pyft8-2.7.7}/PKG-INFO +1 -1
  2. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8/gui.py +4 -1
  3. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8/mqtt.py +24 -29
  4. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8/pyft8.py +38 -24
  5. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8.egg-info/PKG-INFO +1 -1
  6. {pyft8-2.7.6 → pyft8-2.7.7}/pyproject.toml +1 -1
  7. {pyft8-2.7.6 → pyft8-2.7.7}/LICENSE +0 -0
  8. {pyft8-2.7.6 → pyft8-2.7.7}/MANIFEST.in +0 -0
  9. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8/__init__.py +0 -0
  10. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8/callhashes.py +0 -0
  11. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8/hamlib.py +0 -0
  12. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8/maidenhead.py +0 -0
  13. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8/pskr_upload.py +0 -0
  14. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8/receiver.py +0 -0
  15. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8/rigctrl.py +0 -0
  16. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8/time_utils.py +0 -0
  17. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8/transmitter.py +0 -0
  18. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8.egg-info/SOURCES.txt +0 -0
  19. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8.egg-info/dependency_links.txt +0 -0
  20. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8.egg-info/entry_points.txt +0 -0
  21. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8.egg-info/requires.txt +0 -0
  22. {pyft8-2.7.6 → pyft8-2.7.7}/PyFT8.egg-info/top_level.txt +0 -0
  23. {pyft8-2.7.6 → pyft8-2.7.7}/README.md +0 -0
  24. {pyft8-2.7.6 → pyft8-2.7.7}/setup.cfg +0 -0
  25. {pyft8-2.7.6 → pyft8-2.7.7}/tests/dev/CQ AAAA.py +0 -0
  26. {pyft8-2.7.6 → pyft8-2.7.7}/tests/dev/osd.py +0 -0
  27. {pyft8-2.7.6 → pyft8-2.7.7}/tests/dev/test_generate_wav.py +0 -0
  28. {pyft8-2.7.6 → pyft8-2.7.7}/tests/dev/test_loopback_performance.py +0 -0
  29. {pyft8-2.7.6 → pyft8-2.7.7}/tests/dev/view_worked_before.py +0 -0
  30. {pyft8-2.7.6 → pyft8-2.7.7}/tests/plot_baseline.py +0 -0
  31. {pyft8-2.7.6 → pyft8-2.7.7}/tests/spare.py +0 -0
  32. {pyft8-2.7.6 → pyft8-2.7.7}/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.7.6
3
+ Version: 2.7.7
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
@@ -145,6 +145,7 @@ class Gui:
145
145
  self.msg_boxes = {}
146
146
  self.decode_queue = queue.Queue()
147
147
  self.make_layout(config)
148
+ self.display_cycle = 0
148
149
  self.ani = FuncAnimation(self.fig, self._animate, interval = 40, frames=(100000), blit=True)
149
150
 
150
151
  def set_bandstats_title(self, txt):
@@ -195,7 +196,8 @@ class Gui:
195
196
 
196
197
 
197
198
  def refresh_sidebars(self):
198
- self.on_gui_sidebars_refresh(self)
199
+ self.display_cycle = (self.display_cycle + 1) %2
200
+ self.on_gui_sidebars_refresh(self, self.display_cycle)
199
201
 
200
202
  def add_message_box(self, message):
201
203
  self.decode_queue.put(message)
@@ -217,6 +219,7 @@ class Gui:
217
219
  self._display_message_box(self.decode_queue.get())
218
220
  if (frame % 10 == 0):
219
221
  self._tidy_msg_boxes()
222
+ if (frame % 30 == 0):
220
223
  self.refresh_sidebars()
221
224
  return [self.image, *self.ax_wf.patches, *self.ax_wf.texts, *self.band_stats.lineartists, *self.console.lineartists, *self.hm.lineartists,
222
225
  *[bb.label for bb in self.button_boxes], *[bb.label2 for bb in self.button_boxes]]
@@ -35,8 +35,8 @@ class DiskDict:
35
35
  os.replace(tmp_file, self.file)
36
36
 
37
37
  class PSKR_MQTT_listener:
38
- def __init__(self, config_folder, my_call, home_square, spotlife):
39
- self.spotlife = spotlife
38
+ def __init__(self, config_folder, my_call, home_square, pskr_refresh_mins):
39
+ self.pskr_refresh_mins = pskr_refresh_mins
40
40
  self.my_call = my_call
41
41
  self.hearing_me = DiskDict(f"{config_folder}/hearing_me.pkl")
42
42
  self.heard_by_me = DiskDict(f"{config_folder}/heard_by_me.pkl")
@@ -45,7 +45,6 @@ class PSKR_MQTT_listener:
45
45
  self.home_square = home_square
46
46
  self.callsign_cache = DiskDict(f"{config_folder}/callsign_cache.pkl")
47
47
  self.band_TxRx_homecall_report_times = DiskDict(f"{config_folder}/report_times.pkl")
48
- self.band_TxRx_homecall_couniTxRxemotes = {}
49
48
  self.home_activity = {}
50
49
  self.home_most_remotes = {}
51
50
  self.lock = threading.Lock()
@@ -66,30 +65,37 @@ class PSKR_MQTT_listener:
66
65
  client.subscribe(f"pskr/filter/v2/+/FT8/+/+/{self.home_square}/#")
67
66
  client.subscribe(f"pskr/filter/v2/+/FT8/+/+/+/{self.home_square}/#")
68
67
 
68
+ def store_best_location(self, call, loc):
69
+ existing_loc = self.callsign_cache.data.get(call, '')
70
+ if len(loc) > len(existing_loc):
71
+ self.callsign_cache.data[call] = loc
72
+
73
+ def add_homespots_record(self, key, t):
74
+ self.band_TxRx_homecall_report_times.data.setdefault(key, [])
75
+ self.band_TxRx_homecall_report_times.data[key].append(t)
76
+
77
+ def add_myspots_record(self, data, band, call, t, rp):
78
+ data.setdefault(band, {})
79
+ data[band][call] = {'t': t,'rp':rp,'c':call}
80
+
69
81
  def on_message(self, client, userdata, msg):
70
82
  try:
71
83
  d = literal_eval(msg.payload.decode())
72
84
  except:
73
85
  return
74
- self.add_spot(d)
75
-
76
- def add_spot(self, d):
86
+ tnow = time.time()
77
87
  sc, rc = (d['sc'], d['sl']), (d['rc'], d['rl'])
78
- for iTxRx, c in enumerate([sc, rc]):
79
- call, loc = c
80
- self.callsign_cache.data[call] = loc
81
- tnow = time.time()
88
+ for iTxRx, call_loc in enumerate([sc, rc]):
89
+ call, loc = call_loc
90
+ self.store_best_location(call, loc)
82
91
  if self.home_square in loc:
83
- key = (d['b'], iTxRx, call)
84
- self.band_TxRx_homecall_report_times.data.setdefault(key, [])
85
- self.band_TxRx_homecall_report_times.data[key].append(tnow)
92
+ self.add_homespots_record((d['b'], iTxRx, call), tnow)
86
93
  if d['sc'] == self.my_call:
87
- self.hearing_me.data.setdefault(d['b'], {})
88
94
  if d['rc'] not in self.hearing_me.data[d['b']]:
89
95
  self.hearing_me_new.append(d['rc'])
90
- self.hearing_me.data[d['b']][d['rc']] = {'t': tnow,'rp': d['rp'],'c': d['rc']}
96
+ self.add_myspots_record(self.hearing_me.data, d['b'], d['rc'], tnow, d['rp'])
91
97
  if d['rc'] == self.my_call:
92
- self.heard_by_me.data.setdefault(d['b'], {})
98
+ self.add_myspots_record(self.heard_by_me.data, d['b'], d['sc'], tnow, d['rp'])
93
99
  if d['sc'] not in self.heard_by_me.data[d['b']]:
94
100
  self.heard_by_me_new.append(d['sc'])
95
101
  self.heard_by_me.data[d['b']][d['sc']] = {'t': tnow,'rp': d['rp'],'c': d['sc']}
@@ -99,7 +105,6 @@ class PSKR_MQTT_listener:
99
105
  while True:
100
106
  time.sleep(5)
101
107
  self.home_activity = {}
102
- self.band_TxRx_homecall_couniTxRxemotes = {}
103
108
  self.home_most_remotes = {}
104
109
  with self.lock:
105
110
  # clear counters for each band
@@ -108,10 +113,10 @@ class PSKR_MQTT_listener:
108
113
  for b in self.home_most_remotes:
109
114
  self.home_most_remotes[b] = [('',0), ('',0)]
110
115
 
111
- # keep only the remote spots that happened in the self.spotlife window
116
+ # keep only the remote spots that happened in the self.pskr_refresh_mins window
112
117
  for band_TxRx_homecall in self.band_TxRx_homecall_report_times.data:
113
118
  band_TxRx_homecall_report_times = self.band_TxRx_homecall_report_times.data[band_TxRx_homecall]
114
- band_TxRx_homecall_report_times = [t for t in band_TxRx_homecall_report_times if (time.time() - t) < self.spotlife]
119
+ band_TxRx_homecall_report_times = [t for t in band_TxRx_homecall_report_times if (time.time() - t) < 60*self.pskr_refresh_mins]
115
120
  self.band_TxRx_homecall_report_times.data[band_TxRx_homecall] = band_TxRx_homecall_report_times
116
121
 
117
122
  # count number of local Tx and Rx, and identify the local Tx and Rx with most remote spots
@@ -133,13 +138,3 @@ class PSKR_MQTT_listener:
133
138
  n_spotted = len(rx_reports) if rx_reports else 0
134
139
  return n_spotted, n_spotting
135
140
 
136
- if __name__ == '__main__':
137
- pskr = PSKR_MQTT_listener("IO90")
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
@@ -14,10 +14,11 @@ from PyFT8.hamlib import Rig_hamlib
14
14
  from PyFT8.mqtt import PSKR_MQTT_listener
15
15
  import PyFT8.maidenhead as maidenhead
16
16
 
17
- VER = '2.7.6'
17
+ VER = '2.7.7'
18
18
 
19
19
  MAX_TX_START_SECONDS = 2.5
20
- SPOTLIFE = 5*60
20
+ HEARING_PANEL_LIFE_MINS = 5
21
+ PSKR_REFRESH_MINS = 20
21
22
  rig, gui, qso, adif_logging, pskr_info, pskr_upload = None, None, None, None, None, None
22
23
  busy_profile, hearing_me = None, None
23
24
 
@@ -253,18 +254,25 @@ def write_all_txt_row(message):
253
254
 
254
255
  #============= Callbacks for Receiver ==========================================================
255
256
  def on_rx_decode(c):
257
+ if (c.decode_completed - qso.band_info['time_set']) < 5: # prevent bad QRG -> heard_by_me and pskreporter upload data
258
+ return
256
259
  message = Message(c)
257
260
  if gui:
258
261
  gui.add_message_box(message)
259
- if qso.band_info['b'] is not None and pskr_upload is not None:
260
- _, dx_call, dx_grid = c.msg_tuple
261
- if dx_call != 'not' and dx_call != config['station']['call']:
262
- pskr_upload.add_report(dx_call, int(1000000*float(qso.band_info['fMHz'])) + c.fHz, c.snr, 'FT8', 1, int(time.time()))
263
- loc = pskr_info.callsign_cache.data.get(dx_call, dx_grid)
264
- # can't use the next line unless I keep more info than just time in the spots data and deduplicate
265
- # pskr_info.add_spot({'sc':dx_call, 'sl':loc, 'rc':config['station']['call'], 'rl':config['station']['grid'], 'b':qso.band_info['b'], 'rp': c.snr})
266
262
  print(message.wsjtx_screen_format())
267
263
  write_all_txt_row(message)
264
+ if qso.band_info['b'] is not None and pskr_upload is not None:
265
+ call_a, call_b, grid_rpt = c.msg_tuple
266
+ if call_b == 'not':
267
+ return
268
+ call_b_grid = grid_rpt if isGrid(grid_rpt) else ''
269
+ if call_b != config['station']['call']:
270
+ pskr_upload.add_report(call_b, int(1000000*float(qso.band_info['fMHz'])) + c.fHz, c.snr, 'FT8', 1, int(time.time()))
271
+ pskr_info.store_best_location(call_b, call_b_grid)
272
+ pskr_info.add_myspots_record(pskr_info.heard_by_me.data, qso.band_info['b'], call_b, int(time.time()), c.snr)
273
+ if call_b == config['station']['call'] and (isReport(grid_rpt) or isRReport(grid_rpt)):
274
+ rpt = grid_rpt.replace("R","")
275
+ pskr_info.add_myspots_record(pskr_info.hearing_me.data, qso.band_info['b'], call_a, int(time.time()), rpt)
268
276
 
269
277
  def on_rx_busy_profile(busy_profile_new, cycle):
270
278
  global busy_profile, clearest_frequency
@@ -280,7 +288,7 @@ def on_rx_busy_profile(busy_profile_new, cycle):
280
288
  console_print(f"[on_busy] Clear Tx frequency found at {clearest_frequency:6.1f}")
281
289
 
282
290
  #============= Callbacks for GUI ==========================================================
283
- def on_gui_sidebars_refresh(gui):
291
+ def on_gui_sidebars_refresh(gui, display_cycle):
284
292
  if qso.band_info['b'] is None:
285
293
  console_print(f"[PyFT8] Band not set; please select a band.", color = 'red')
286
294
  if pskr_info is None:
@@ -310,18 +318,24 @@ def on_gui_sidebars_refresh(gui):
310
318
  gui.band_stats.scroll_print(f"{n_spotted:<7} {rx_lead[1]:<7}", color = '#b6f0c6')
311
319
 
312
320
  #refresh hearing me / heard by me panel
313
- cycle = global_time_utils.curr_cycle_from_time()
314
- data = pskr_info.hearing_me.data if cycle == 1 else pskr_info.heard_by_me.data
315
- new_calls = pskr_info.hearing_me_new if cycle == 1 else pskr_info.heard_by_me_new
316
- txts, cols = [f"Hearing me <{SPOTLIFE/60:.0f} mins" if cycle==1 else f"Heard by me <{SPOTLIFE/60:.0f} mins"], ['white']
321
+ data = pskr_info.hearing_me.data if display_cycle == 1 else pskr_info.heard_by_me.data
322
+ timewindow_str = f"<{HEARING_PANEL_LIFE_MINS:.0f} mins"
323
+ title_txt = f"Hearing me {timewindow_str}" if display_cycle==1 else f"Heard by me {timewindow_str}"
324
+ display_rows = [(title_txt, 2e40, 'white')]
325
+ tnow = time.time()
317
326
  if b is not None and b in data:
318
- hm = [h for h in data[b].values() if (time.time() - h['t']) < SPOTLIFE]
319
- for h in hm:
320
- geo_text = geo_text = get_geo_text(h['c'])
321
- txts.append(f"{h['c']:<7} {int(h['rp']):+03d} {geo_text:<12}")
322
- col = 'white' if h['c'] in new_calls else 'lime'
323
- cols.append(col)
324
- gui.hm.list_print(txts, cols)
327
+ band_rpts = data[b]
328
+ calls_now = [call for call in band_rpts if (tnow - band_rpts[call]['t']) < 60*HEARING_PANEL_LIFE_MINS]
329
+ subtitle_txt = f"{len(calls_now)}/{len(band_rpts)} now/ever"
330
+ display_rows.append((subtitle_txt, 1e40, 'white'))
331
+ new_calls = pskr_info.hearing_me_new if display_cycle == 1 else pskr_info.heard_by_me_new
332
+ for remote_call in calls_now:
333
+ rpt = band_rpts[remote_call]
334
+ call, snr, geo_text, timestamp = rpt['c'], int(rpt['rp']), get_geo_text(remote_call), rpt['t']
335
+ color = 'white' if remote_call in new_calls else 'lime'
336
+ display_rows.append((f"{remote_call:<7} {snr:+03d} {geo_text:<12}", timestamp, color))
337
+ display_rows.sort(key = lambda row: row[1], reverse = True)
338
+ gui.hm.list_print([row[0] for row in display_rows], [row[2] for row in display_rows])
325
339
 
326
340
  def on_gui_control_click(btn_def):
327
341
  btn_action = btn_def['action']
@@ -336,7 +350,7 @@ def on_gui_control_click(btn_def):
336
350
  qso.tx_cycle = None
337
351
  if(btn_action == 'SET_BAND'):
338
352
  band, freqMHz = btn_def['band'], btn_def['freq']
339
- qso.band_info = {'b':band, 'fMHz':freqMHz}
353
+ qso.band_info = {'b':band, 'fMHz':freqMHz, 'time_set':time.time()}
340
354
  rig.set_freq_Hz(int(1000000*float(qso.band_info['fMHz'])))
341
355
  console_print(f"[PyFT8] Set band: {qso.band_info['b']} {qso.band_info['fMHz']}")
342
356
  gui.band_stats.clear()
@@ -373,7 +387,7 @@ def cli():
373
387
  if mc is not None and 'pskreporter' in config.keys():
374
388
  if config['pskreporter']['upload'] == 'Y':
375
389
  pskr_upload = PSKR_upload(mc, mg, software = f"PyFT8 v{VER}", console_print = console_print) if not mc is None else None
376
- pskr_info = PSKR_MQTT_listener(config_folder, mc, mg[:4], SPOTLIFE)
390
+ pskr_info = PSKR_MQTT_listener(config_folder, mc, mg[:4], PSKR_REFRESH_MINS)
377
391
  qso = FT8_QSO()
378
392
  if config.has_section('hamlib_rig'):
379
393
  console_print("Connecting to rig via Hamlib")
@@ -405,7 +419,7 @@ def cli():
405
419
  rx = Receiver(audio_in, [200, 3100], on_rx_decode, on_rx_busy_profile)
406
420
  audio_in.start_streamed_audio(input_device_idx)
407
421
  if gui is not None:
408
- gui.set_bandstats_title(f"Spots to/from {config['station']['grid'][:4]} <{SPOTLIFE/60:.0f} mins")
422
+ gui.set_bandstats_title(f"Pskreporter Spots\nto/from {config['station']['grid'][:4]} <{PSKR_REFRESH_MINS:.0f} mins")
409
423
  gui.plt.show()
410
424
  else:
411
425
  wait_for_keyboard()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyFT8
3
- Version: 2.7.6
3
+ Version: 2.7.7
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "PyFT8"
3
- version = "2.7.6"
3
+ version = "2.7.7"
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes