PyFT8 3.6.3__tar.gz → 3.6.4__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 (42) hide show
  1. {pyft8-3.6.3 → pyft8-3.6.4}/PKG-INFO +1 -1
  2. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8/gui.py +51 -61
  3. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8/message_broker.py +5 -8
  4. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8/pyft8.py +4 -4
  5. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8/qso_manager.py +1 -1
  6. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8/receiver.py +1 -0
  7. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8.egg-info/PKG-INFO +1 -1
  8. {pyft8-3.6.3 → pyft8-3.6.4}/pyproject.toml +1 -1
  9. {pyft8-3.6.3 → pyft8-3.6.4}/LICENSE +0 -0
  10. {pyft8-3.6.3 → pyft8-3.6.4}/MANIFEST.in +0 -0
  11. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8/__init__.py +0 -0
  12. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8/databases.py +0 -0
  13. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8/osd.py +0 -0
  14. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8/pskreporter.py +0 -0
  15. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8/rigctrl.py +0 -0
  16. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8/time_utils.py +0 -0
  17. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8/transmitter.py +0 -0
  18. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8.egg-info/SOURCES.txt +0 -0
  19. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8.egg-info/dependency_links.txt +0 -0
  20. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8.egg-info/entry_points.txt +0 -0
  21. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8.egg-info/requires.txt +0 -0
  22. {pyft8-3.6.3 → pyft8-3.6.4}/PyFT8.egg-info/top_level.txt +0 -0
  23. {pyft8-3.6.3 → pyft8-3.6.4}/README.md +0 -0
  24. {pyft8-3.6.3 → pyft8-3.6.4}/setup.cfg +0 -0
  25. {pyft8-3.6.3 → pyft8-3.6.4}/tests/misc/CQ AAAA.py +0 -0
  26. {pyft8-3.6.3 → pyft8-3.6.4}/tests/misc/animation.py +0 -0
  27. {pyft8-3.6.3 → pyft8-3.6.4}/tests/misc/bits_loopback.py +0 -0
  28. {pyft8-3.6.3 → pyft8-3.6.4}/tests/misc/bits_loopback_short.py +0 -0
  29. {pyft8-3.6.3 → pyft8-3.6.4}/tests/misc/osd.py +0 -0
  30. {pyft8-3.6.3 → pyft8-3.6.4}/tests/misc/symbols_loopback.py +0 -0
  31. {pyft8-3.6.3 → pyft8-3.6.4}/tests/misc/test_generate_wav.py +0 -0
  32. {pyft8-3.6.3 → pyft8-3.6.4}/tests/misc/test_loopback_performance.py +0 -0
  33. {pyft8-3.6.3 → pyft8-3.6.4}/tests/misc/text.py +0 -0
  34. {pyft8-3.6.3 → pyft8-3.6.4}/tests/misc/view_worked_before.py +0 -0
  35. {pyft8-3.6.3 → pyft8-3.6.4}/tests/pipeline/Fine tuning compare IOS_PC.py +0 -0
  36. {pyft8-3.6.3 → pyft8-3.6.4}/tests/pipeline/Targetted tests.py +0 -0
  37. {pyft8-3.6.3 → pyft8-3.6.4}/tests/pipeline/test_01.wav +0 -0
  38. {pyft8-3.6.3 → pyft8-3.6.4}/tests/play_wavs.py +0 -0
  39. {pyft8-3.6.3 → pyft8-3.6.4}/tests/plot_decode_times.py +0 -0
  40. {pyft8-3.6.3 → pyft8-3.6.4}/tests/spare.py +0 -0
  41. {pyft8-3.6.3 → pyft8-3.6.4}/tests/spare2.py +0 -0
  42. {pyft8-3.6.3 → pyft8-3.6.4}/tests/test_batch_and_live.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyFT8
3
- Version: 3.6.3
3
+ Version: 3.6.4
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
@@ -26,9 +26,8 @@ def add_my_axes(fig, pos):
26
26
  MESSAGE_TYPES = {'generic':{'bg':'blue', 'fg':'white', 'alpha':0.5}, 'CQ':{'bg':'green', 'fg':'white', 'alpha':0.8},
27
27
  'from_me': {'bg':'yellow', 'fg':'black', 'alpha':0.95}, 'to_me':{'bg':'red', 'fg':'white', 'alpha':0.9}}
28
28
  class Msg_box:
29
- def __init__(self, fig, ax, x, y, w, h, message, onclick):
29
+ def __init__(self, fig, ax, x, y, w, h, message):
30
30
  from matplotlib.patches import Rectangle
31
- self.onclick = onclick
32
31
  self.fig, self.ax = fig, ax
33
32
  message_type_params = MESSAGE_TYPES[message['message_type']]
34
33
  rect = Rectangle((x, y), w, h, edgecolor='lime', lw=2)
@@ -36,7 +35,6 @@ class Msg_box:
36
35
  self.patch.set_visible(False)
37
36
  text_inst = self.ax.text(x, y+1, message['display_text'], fontsize='small', fontweight = 'bold' )
38
37
  text_inst.set_visible(False)
39
- self.cid = fig.canvas.mpl_connect('button_press_event', self._onclick)
40
38
  text_inst.set_color(message_type_params['fg'])
41
39
  self.patch.set_facecolor(message_type_params['bg'])
42
40
  self.patch.set_alpha(message_type_params['alpha'])
@@ -46,6 +44,10 @@ class Msg_box:
46
44
  for a in self.artists:
47
45
  a.set_visible(True)
48
46
 
47
+ def contains(self, x, y):
48
+ if self.patch.get_visible():
49
+ return self.patch.get_extents().contains(x, y)
50
+
49
51
  def draw(self):
50
52
  for a in self.artists:
51
53
  self.ax.draw_artist(a)
@@ -61,11 +63,6 @@ class Msg_box:
61
63
  self.artists.remove(a)
62
64
  self = None
63
65
 
64
- def _onclick(self, event):
65
- b, _ = self.patch.contains(event)
66
- if(b):
67
- self.onclick({'action': 'MESSAGE_CLICK', 'message':self.message})
68
-
69
66
  class Panel:
70
67
  def __init__(self, fig, pos):
71
68
  self.fig = fig
@@ -93,16 +90,16 @@ class Panel:
93
90
  self.ax.draw_artist(a)
94
91
 
95
92
  class ButtonBox:
96
- def __init__(self, fig, box, btn_pc = 30, onclick = None, clickargs=None, btn_text = ' ', info_text = ' '):
97
- self.clickargs = clickargs
93
+ def __init__(self, fig, box, btn_pc = 30, btn_text = ' ', info_text = ' '):
94
+ self.id = btn_text
98
95
  btnbox, infobox = box.copy(), box.copy()
99
96
  btnbox[2] = box[2] * btn_pc /100
100
97
  infobox[2] = box[2] * (100-btn_pc) /100
101
98
  infobox[0] = box[0] + box[2] * (btn_pc /100)
102
99
  self.btn_axs = add_my_axes(fig, btnbox)
103
100
  self.btn_axs.set_facecolor(BUTTONCOLOR)
101
+ self.bbox = self.btn_axs.bbox
104
102
  self.btn_widg = Button(self.btn_axs, btn_text, color = BUTTONCOLOR, hovercolor = HOVERCOLOR)
105
- self.btn_widg.on_clicked(lambda x: onclick(clickargs))
106
103
  self.info_axs = add_my_axes(fig, infobox)
107
104
  self.info_art = self.info_axs.text(0.03, 0.5, info_text, color = INFO_TEXT_COLOR, verticalalignment = 'center', clip_on = True)
108
105
  self.state_is_active = None
@@ -120,7 +117,7 @@ class ButtonBox:
120
117
  self.info_art.set_text(info_text)
121
118
  if color is not None:
122
119
  self.info_art.set_color(color)
123
-
120
+
124
121
  class Gui:
125
122
  def __init__(self, message_broker, rig_control, console_print, configured_bands, hearing_me_since_mins = 5):
126
123
  self.hearing_me_since_mins = hearing_me_since_mins
@@ -151,44 +148,33 @@ class Gui:
151
148
  self.msg_boxes = []
152
149
  self.button_boxes = []
153
150
 
154
- bh, bs = 0.02, 0.002
155
- bb = ButtonBox(self.fig, [L['pmargin'], self.wf_top - (len(self.button_boxes)+1) * bh + bs, L['sidebar_width'], bh-bs], btn_pc = 100,
156
- btn_text = "CQ", onclick = self._on_click_local, clickargs = {'action':'CQ'})
157
- self.button_boxes.append(bb)
158
- bb = ButtonBox(self.fig, [L['pmargin'], self.wf_top - (len(self.button_boxes)+1) * bh + bs, L['sidebar_width'], bh-bs], btn_pc = 100,
159
- btn_text = "Repeat last", onclick = self._on_click_local, clickargs = {'action':'RPT_LAST'})
160
- self.button_boxes.append(bb)
161
- bb = ButtonBox(self.fig, [L['pmargin'], self.wf_top - (len(self.button_boxes)+1) * bh + bs, L['sidebar_width'], bh-bs], btn_pc = 100,
162
- btn_text = "Tx off", onclick = self._on_click_local, clickargs = {'action':'TX_OFF'})
163
- self.button_boxes.append(bb)
164
- for band_info in self.configured_bands:
165
- band, fMHz = band_info['band'], band_info['fMHz']
166
- bb = ButtonBox(self.fig, [L['pmargin'], self.wf_top - (len(self.button_boxes)+1) * bh + bs, L['sidebar_width'], bh-bs], btn_pc = 30,
167
- btn_text = band, onclick = self._on_click_local, clickargs = {'action':'SET_BAND', 'band':band, 'fMHz':fMHz})
168
- self.button_boxes.append(bb)
151
+ bh, bsep = 0.02, 0.002
152
+ bx, bw = L['pmargin'], L['sidebar_width']
153
+ bxs = self.button_boxes
154
+ self.button_boxes.append(ButtonBox(self.fig, [bx, self.wf_top - (len(bxs)+1)*bh + bsep, bw, bh-bsep], btn_text = "CQ", btn_pc = 100) )
155
+ self.button_boxes.append(ButtonBox(self.fig, [bx, self.wf_top - (len(bxs)+1)*bh + bsep, bw, bh-bsep], btn_text = "Repeat last", btn_pc = 100) )
156
+ self.button_boxes.append(ButtonBox(self.fig, [bx, self.wf_top - (len(bxs)+1)*bh + bsep, bw, bh-bsep], btn_text = "Tx off", btn_pc = 100) )
157
+ for band in self.configured_bands:
158
+ self.button_boxes.append(ButtonBox(self.fig, [bx, self.wf_top - (len(self.button_boxes)+1) * bh + bsep, bw, bh-bsep], btn_pc = 30, btn_text = band))
169
159
 
170
- hm_top = self.wf_top - (len(self.button_boxes)+2) * bh + bs - L['vsep1']
160
+ hm_top = self.wf_top - (len(self.button_boxes)+2) * bh + bsep - L['vsep1']
171
161
 
172
162
  self.console = Panel(self.fig, [self.wf_left, self.wf_top+L['vsep1'], 1-self.wf_left-L['pmargin'], L['banner_height']])
173
163
  self.console_rows_text = None
174
164
  self.hearing_panel = Panel(self.fig, [L['pmargin'], L['pmargin'], L['sidebar_width'], hm_top])
175
165
 
166
+ self.cid = self.fig.canvas.mpl_connect('button_press_event', self._oncanvasclick)
167
+
176
168
  def main_loop(self):
177
169
  last_ptr = 0
178
170
  self.plt.show(block = False)
179
- t0 = time_utils.time()
180
171
  while True:
181
- t = time_utils.time()
182
- #print(f"{t-t0:6.3f}")
183
- t0=t
184
- time_utils.sleep(0.05)
185
172
  self.image.set_data(self.waterfall_data['data'])
186
173
  if self.needs_redraw:
187
174
  self.needs_redraw = False
188
- plt.pause(0.1)
175
+ plt.pause(0.01)
189
176
  else:
190
177
  self.ax_wf.draw_artist(self.image)
191
- self.fig.canvas.update()
192
178
  for mb in self.msg_boxes:
193
179
  mb.draw()
194
180
  self.fig.canvas.update()
@@ -202,13 +188,13 @@ class Gui:
202
188
  self._refresh_band_buttons()
203
189
  self._refresh_home_panel()
204
190
  self._clear_msg_boxes(curr_cycle)
205
- self.needs_redraw = True
206
191
 
207
192
  def display_message(self, message):
208
193
  x = int(message['t0'] / self.waterfall_data['dt'] + message['their_tx_cycle'] * self.waterfall_data['pixels_per_cycle'])
209
194
  y = int(message['fHz'] / self.waterfall_data['df'])
210
- mb = Msg_box(self.fig, self.ax_wf, x, y, self.waterfall_data['sig_w'], self.waterfall_data['sig_h'], message, onclick = self._on_click_local)
195
+ mb = Msg_box(self.fig, self.ax_wf, x, y, self.waterfall_data['sig_w'], self.waterfall_data['sig_h'], message)
211
196
  self.msg_boxes.append(mb)
197
+
212
198
  def get_band_info(self):
213
199
  return self.band_info
214
200
 
@@ -229,24 +215,30 @@ class Gui:
229
215
  color = row_text['color'])
230
216
  self.needs_redraw = True
231
217
 
232
- def _on_click_local(self, clickargs):
233
- btn_action = clickargs['action']
234
- if(btn_action == 'SET_BAND'):
235
- current_band, freqMHz = clickargs['band'], clickargs['fMHz']
236
- self.band_info = {'current_band':current_band, 'fMHz':freqMHz, 'time_set':time_utils.time()}
218
+ def _set_band(self, band):
219
+ if band in self.configured_bands:
220
+ self.band_info = {'current_band':band, 'fMHz':self.configured_bands[band], 'time_set':time_utils.time()}
237
221
  self.console_print(f"[PyFT8] Set band: {self.band_info['current_band']} {self.band_info['fMHz']}")
238
- self._refresh_hearing()
239
- for cyc in range(2):
240
- self._clear_msg_boxes(cyc)
241
- self._refresh_band_buttons()
242
- self._refresh_home_panel()
243
- if clickargs['action'] == "MESSAGE_CLICK":
244
- m = clickargs['message']
245
- m_string = f"{m['hail']} {m['their_call']} {m['grid_rpt']}"
246
- self.console_print(f"[GUI] Clicked on message '{m_string}'")
247
- self.needs_redraw = True
248
- self.qso_manager.on_click(clickargs)
249
222
 
223
+ def _oncanvasclick(self, clickargs):
224
+ #print(f"Click at {time_utils.cycle_time():6.2f}")
225
+
226
+ if clickargs.inaxes is self.ax_wf:
227
+ for mb in self.msg_boxes:
228
+ if mb.contains(clickargs.x, clickargs.y):
229
+ self.console_print(f"[GUI] Clicked on message '{mb.message['msg_text']}'")
230
+ self.qso_manager.on_click({'action':"MESSAGE_CLICK", 'message':mb.message})
231
+ return
232
+ for bb in self.button_boxes:
233
+ if bb.bbox.contains(clickargs.x, clickargs.y):
234
+ if bb.id[:-1].isnumeric():
235
+ self.needs_redraw = True
236
+ self._set_band(bb.id)
237
+ self.qso_manager.on_click({'action':"SET_BAND", 'fMHz':self.configured_bands[bb.id]})
238
+ self.after_search(curr_cycle = None)
239
+ else:
240
+ action = ["CQ", "RPT_LAST", "TX_OFF"][['CQ', 'Repeat last', 'Tx off'].index(bb.id)]
241
+ self.qso_manager.on_click({'action':action})
250
242
 
251
243
  def _hide_msg_boxes(self, curr_cycle):
252
244
  to_hide = [mb for mb in self.msg_boxes if mb.cycle == curr_cycle]
@@ -254,8 +246,8 @@ class Gui:
254
246
  mb.hide()
255
247
  self.needs_redraw = True
256
248
 
257
- def _clear_msg_boxes(self, curr_cycle):
258
- to_remove = [mb for mb in self.msg_boxes if mb.cycle == curr_cycle]
249
+ def _clear_msg_boxes(self, curr_cycle = None):
250
+ to_remove = [mb for mb in self.msg_boxes if mb.cycle == curr_cycle or curr_cycle is None]
259
251
  self.msg_boxes = [mb for mb in self.msg_boxes if mb.cycle != curr_cycle]
260
252
  for mb in to_remove:
261
253
  mb.remove()
@@ -282,12 +274,10 @@ class Gui:
282
274
  current_band = self.band_info['current_band']
283
275
  grd = self.myGrid[:4]
284
276
  for bb in self.button_boxes:
285
- button_band = bb.clickargs.get('band',None)
286
- if button_band is not None:
287
- bb.set_state(button_band == current_band)
288
- if button_band in self.history.home_activity:
289
- cnts = self.history.home_activity[button_band]
290
- bb.set_info_text(f"{cnts[0]}Tx, {cnts[1]}Rx")
277
+ bb.set_state(bb.id == current_band)
278
+ if bb.id in self.history.home_activity:
279
+ cnts = self.history.home_activity[bb.id]
280
+ bb.set_info_text(f"{cnts[0]}Tx, {cnts[1]}Rx")
291
281
  self.needs_redraw = True
292
282
 
293
283
  def _refresh_hearing(self):
@@ -22,28 +22,25 @@ class Broker():
22
22
  def process_message(self, message_dict):
23
23
  hail, their_call, grid_rpt = message_dict['hail'], message_dict['their_call'], message_dict['grid_rpt']
24
24
  cyclestart_string, their_snr = message_dict['cyclestart_string'], message_dict['their_snr']
25
- m = message_dict
26
- screen_format = f"{cyclestart_string} {their_snr} {m['dt']:4.1f} {m['fHz']:4.0f} ~ {hail} {their_call} {grid_rpt}"
27
- print(f"{screen_format:50s} decoded@ {m['decode_completed']:5.1f}s, dec = {m['decode_status']}")
28
-
29
25
  mtype_val = 0 + 1*(their_call == self.myCall) + 2*(hail == self.myCall) + 3*(their_call != self.myCall and hail.startswith('CQ'))
30
26
  mtype = ['generic', 'from_me', 'to_me', 'CQ'][mtype_val]
31
27
  message_dict.update( {'message_type':mtype, 'display_text':f"{hail} {their_call} {grid_rpt}",
32
28
  'priority':(mtype == 'to_me' or mtype == 'CQ')} )
33
-
34
29
  if self.history:
35
30
  current_band = self.gui.get_band_info()['current_band']
36
31
  hearing_me, wb_text, geo_text = self.history.get_message_extra_info(their_call, current_band)
37
32
  display_text = f"{message_dict['hail']} {message_dict['their_call']} {message_dict['grid_rpt']} {hearing_me} {wb_text} {geo_text}"
38
33
  message_dict.update( {'hearing_me':hearing_me, 'wb_text':wb_text, 'geo_text':geo_text, 'display_text':display_text } )
34
+
39
35
  if message_dict['priority']:
40
36
  if self.gui:
41
37
  self.gui.display_message(message_dict)
42
-
43
- self.message_queue_non_time_critical.put(message_dict)
44
-
45
38
  if self.on_decode:
46
39
  self.on_decode(message_dict)
40
+ m = message_dict
41
+ screen_format = f"{cyclestart_string} {their_snr} {m['dt']:4.1f} {m['fHz']:4.0f} ~ {hail} {their_call} {grid_rpt}"
42
+ print(f"{screen_format:50s} decoded@ {m['decode_completed']:5.1f}s, dec = {m['decode_status']}")
43
+ self.message_queue_non_time_critical.put(message_dict)
47
44
 
48
45
  def _process_message_ntc(self):
49
46
  while True:
@@ -11,7 +11,7 @@ from PyFT8.databases import ADIF
11
11
  from PyFT8.qso_manager import QSO_manager
12
12
  from PyFT8.message_broker import Broker
13
13
 
14
- VER = '3.6.3'
14
+ VER = '3.6.4'
15
15
  PSKR_REFRESH_MINS = 20
16
16
  message_broker = None
17
17
 
@@ -129,9 +129,9 @@ def cli():
129
129
  message_broker.soundcard_out = soundcard_out
130
130
  message_broker.adif_logging = ADIF(f"{config_folder}/PyFT8.adi")
131
131
  message_broker.history = History(config_folder, myCall, myGrid, config['gui']['loc'])
132
- configured_bands = []
133
- for b,f in config['bands'].items():
134
- configured_bands.append({'band':b, 'fMHz':f})
132
+ configured_bands = {}
133
+ for b, f in config['bands'].items():
134
+ configured_bands[b] = f
135
135
  message_broker.gui = Gui(message_broker, rig_control, console_print, configured_bands)
136
136
  qso_manager = QSO_manager(message_broker, rig_control, console_print)
137
137
  message_broker.gui.register_qso_manager(qso_manager)
@@ -11,7 +11,7 @@ class QSO_manager:
11
11
  self.find_clear_freq = message_broker.rx.find_clear_freq
12
12
  self.adif_logging = message_broker.adif_logging
13
13
  self.in_qso_with = False
14
- self.tx_payload = None
14
+ self.tx_payload, self.last_tx_payload = None, None
15
15
  self.transmitting = False
16
16
  self.tx_cycle = 0
17
17
  self.console_print = console_print
@@ -589,6 +589,7 @@ class Receiver():
589
589
  duplicate_filter.add(key)
590
590
  hail, their_call, grid_rpt = c.msg_tuple
591
591
  message_dict = {'cyclestart_string':c.cyclestart['string'], 'hail':hail, 'their_call':their_call, 'grid_rpt':grid_rpt,
592
+ 'msg_text':f"{hail} {their_call} {grid_rpt}",
592
593
  'their_snr':f"{c.snr:+03d}", 'their_tx_cycle':c.origin['odd_even'],
593
594
  't0':c.origin['t0'], 'dt':c.origin['t0'] - 0.5, 'fHz':c.origin['f0'],
594
595
  'decode_status':c.decode_status, 'decode_completed':time_utils.cycle_time()}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyFT8
3
- Version: 3.6.3
3
+ Version: 3.6.4
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 = "3.6.3"
3
+ version = "3.6.4"
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
File without changes
File without changes
File without changes
File without changes