PyFT8 2.7.0__tar.gz → 2.7.2__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.0 → pyft8-2.7.2}/PKG-INFO +9 -9
  2. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/mqtt.py +27 -26
  3. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/pyft8.py +4 -3
  4. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8.egg-info/PKG-INFO +9 -9
  5. {pyft8-2.7.0 → pyft8-2.7.2}/README.md +8 -8
  6. {pyft8-2.7.0 → pyft8-2.7.2}/pyproject.toml +1 -1
  7. {pyft8-2.7.0 → pyft8-2.7.2}/LICENSE +0 -0
  8. {pyft8-2.7.0 → pyft8-2.7.2}/MANIFEST.in +0 -0
  9. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/__init__.py +0 -0
  10. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/callhashes.py +0 -0
  11. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/gui.py +0 -0
  12. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/hamlib.py +0 -0
  13. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/maidenhead.py +0 -0
  14. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/pskr_upload.py +0 -0
  15. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/receiver.py +0 -0
  16. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/rigctrl.py +0 -0
  17. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/time_utils.py +0 -0
  18. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/transmitter.py +0 -0
  19. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8.egg-info/SOURCES.txt +0 -0
  20. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8.egg-info/dependency_links.txt +0 -0
  21. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8.egg-info/entry_points.txt +0 -0
  22. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8.egg-info/requires.txt +0 -0
  23. {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8.egg-info/top_level.txt +0 -0
  24. {pyft8-2.7.0 → pyft8-2.7.2}/setup.cfg +0 -0
  25. {pyft8-2.7.0 → pyft8-2.7.2}/tests/dev/CQ AAAA.py +0 -0
  26. {pyft8-2.7.0 → pyft8-2.7.2}/tests/dev/osd.py +0 -0
  27. {pyft8-2.7.0 → pyft8-2.7.2}/tests/dev/test_generate_wav.py +0 -0
  28. {pyft8-2.7.0 → pyft8-2.7.2}/tests/dev/test_loopback_performance.py +0 -0
  29. {pyft8-2.7.0 → pyft8-2.7.2}/tests/dev/view_worked_before.py +0 -0
  30. {pyft8-2.7.0 → pyft8-2.7.2}/tests/plot_baseline.py +0 -0
  31. {pyft8-2.7.0 → pyft8-2.7.2}/tests/spare.py +0 -0
  32. {pyft8-2.7.0 → pyft8-2.7.2}/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.0
3
+ Version: 2.7.2
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
@@ -28,22 +28,24 @@ If you're interested in how this works, maybe have a look at [MiniPyFT8](https:/
28
28
 
29
29
  ## Features
30
30
  - Rx and Tx of standard messages with optional /P and /R, and nonstandard calls plus hashed calls
31
- - Doesn't try to do everything, so launches quickly (~2 seconds on my old Dell Optiplex 790)
31
+ - Launches quickly (~2 seconds on my old Dell Optiplex 790)
32
32
  - Use with or without gui (receive and send messages via command line commands)
33
33
  - Automatically chooses clearest Tx frequency
34
34
  - Modern programming language throughout
35
35
  - Finds sound cards by keywords so follows them if windows moves them ...
36
36
  - Logs QSOs to ADIF file and all spots to WSJTX-style ALL.txt file
37
37
  - Uploads spots to pskreporter
38
- - Direct CAT control for some rigs drops connection when not used, allowing sharing of rig's serial port
38
+ - Direct CAT control for some rigs, designed to drop connection when not used, allowing sharing of rig's serial port
39
39
  - Or control rigs via Hamlib
40
40
 
41
41
  The Gui shows:
42
42
  - Simultaneous views of odd and even cycles
43
43
  - Messages overlaid on waterfall signals that produce them
44
- - Worked-before info and fine grid locators / distance and bearing in the message boxes
45
- - Band activity on band select buttons
44
+ - Worked-before info and fine grid locators / distance and bearing in the message boxes
45
+ - List of stations hearing your transmissions on the selected band
46
+ - Band activity in your level 4 square live updated next to band select buttons
46
47
  - Number of remote stations hearing your Tx, number of remote Txs that you're hearing, plus the same info for the 'best' station in your level 4 square
48
+ - Data used for the above is cached to disk so is not lost when restarting the program
47
49
 
48
50
  To enable uploading of spots to pskreporter, make sure that your .ini file includes
49
51
  ```
@@ -51,8 +53,7 @@ To enable uploading of spots to pskreporter, make sure that your .ini file inclu
51
53
  upload = Y
52
54
  ```
53
55
 
54
- <img width="980" height="807" alt="screenshot" src="https://github.com/user-attachments/assets/ac393a05-277a-4d98-bd74-78bcb0ae8b03" />
55
-
56
+ ![screenshot](screenshot.png)
56
57
 
57
58
  ## Motivation
58
59
  This started out as me thinking "How hard can it be, really?" after some frustration with Windows moving sound devices around and wanting to get a minimal decoder running that I can fully control.
@@ -88,8 +89,7 @@ Alternatively, you can run PyFT8 without rig control; if there is no rig found,
88
89
 
89
90
  The image below shows the number of decodes from PyFT8, WSJT-x V2.7.0 running in NORM mode, and FT8_lib, using the same 10 minutes of busy 20m audio that is used to test ft8_lib.
90
91
 
91
- <img width="640" height="480" alt="performance snapshot" src="https://github.com/G1OJS/PyFT8/blob/main/performance%20snapshot.png" />
92
-
92
+ ![performance snapshot](performance%20snapshot.png)
93
93
 
94
94
  ## Limitations
95
95
  PyFT8 doesn't decode / encode *all* message types. The table below shows which are handled.
@@ -13,6 +13,7 @@ class DiskDict:
13
13
  self.file = file
14
14
  self.data = {}
15
15
  self.load()
16
+ self.lock = threading.Lock()
16
17
  threading.Thread(target = self._autosave, daemon = True).start()
17
18
 
18
19
  def _autosave(self, autosave_period = 15):
@@ -26,7 +27,7 @@ class DiskDict:
26
27
  self.data = pickle.load(f)
27
28
 
28
29
  def save(self):
29
- if self.data:
30
+ with self.lock:
30
31
  with open(f"{self.file}","wb") as f:
31
32
  pickle.dump(self.data, f)
32
33
 
@@ -36,11 +37,12 @@ class PSKR_MQTT_listener:
36
37
  self.hearing_me = DiskDict(f"{config_folder}/hearing_me.pkl")
37
38
  self.home_square = home_square
38
39
  self.callsign_cache = DiskDict(f"{config_folder}/callsign_cache.pkl")
39
- self.band_TxRx_homecall_report_times = DiskDict(f"{config_folder}/report_times.pkl")
40
- self.band_TxRx_homecall_countremotes = {}
40
+ self.band_TxRx_homecall_report_times = DiskDict(f"{config_folder}/report_times_271.pkl")
41
+ self.band_TxRx_homecall_couniTxRxemotes = {}
41
42
  self.home_activity = {}
42
43
  self.home_most_remotes = {}
43
44
  self.lock = threading.Lock()
45
+
44
46
  mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
45
47
  mqttc.on_connect = self.on_connect
46
48
  mqttc.on_message = self.on_message
@@ -52,7 +54,7 @@ class PSKR_MQTT_listener:
52
54
  threading.Thread(target = self.count_activity, daemon = True).start()
53
55
 
54
56
  def on_connect(self, client, userdata, flags, reason_code, properties):
55
- #pskr/filter/v2/{band}/{mode}/{sendercall}/{receivercall}/{senderlocator}/{receiverlocator}/{sendercountry}/{receivercountry}
57
+ #pskr/filter/v2/{band}/{mode}/{sendercall}/{receivercall}/{senderlocator}/{receiverlocator}/{sendercouniTxRxy}/{receivercouniTxRxy}
56
58
  print(f"[MQTT] Requesting mqtt feed for {self.home_square}")
57
59
  client.subscribe(f"pskr/filter/v2/+/FT8/+/+/{self.home_square}/#")
58
60
  client.subscribe(f"pskr/filter/v2/+/FT8/+/+/+/{self.home_square}/#")
@@ -63,50 +65,49 @@ class PSKR_MQTT_listener:
63
65
  except:
64
66
  return
65
67
  sc, rc = (d['sc'], d['sl']), (d['rc'], d['rl'])
66
- for i, c in enumerate([sc, rc]):
68
+ for iTxRx, c in enumerate([sc, rc]):
67
69
  call, loc = c
68
- if call not in self.callsign_cache.data:
69
- self.callsign_cache.data[call] = loc
70
+ self.callsign_cache.data[call] = loc
70
71
  if self.home_square in loc:
71
- key = f"{d['b']}_{['Tx','Rx'][i]}_{call}"
72
- if not key in self.band_TxRx_homecall_report_times.data:
73
- with self.lock:
74
- self.band_TxRx_homecall_report_times.data[key] = []
72
+ key = (d['b'], iTxRx, call)
73
+ self.band_TxRx_homecall_report_times.data.setdefault(key, [])
75
74
  self.band_TxRx_homecall_report_times.data[key].append(time.time())
76
75
  if d['sc'] == self.my_call:
77
- if d['b'] not in self.hearing_me.data:
78
- self.hearing_me.data[d['b']] = {}
79
- if d['rc'] not in self.hearing_me.data[d['b']]:
80
- self.hearing_me.data[d['b']][d['rc']] = {'t':time.time(), 'rp':d['rp'], 'c':d['rc']}
76
+ self.hearing_me.data.setdefault(d['b'], {})[d['rc']] = {'t': time.time(),'rp': d['rp'],'c': d['rc']}
81
77
 
82
78
  def count_activity(self):
83
79
  import numpy as np
84
80
  while True:
85
81
  time.sleep(5)
86
82
  self.home_activity = {}
87
- self.band_TxRx_homecall_countremotes = {}
83
+ self.band_TxRx_homecall_couniTxRxemotes = {}
88
84
  self.home_most_remotes = {}
89
85
  with self.lock:
90
- for band_TxRx_homecall in self.band_TxRx_homecall_report_times.data:
91
- b = band_TxRx_homecall.split("_")[0]
86
+ # clear counters for each band
87
+ for b in self.home_activity:
92
88
  self.home_activity[b] = [0, 0]
89
+ for b in self.home_most_remotes:
90
+ self.home_most_remotes[b] = [('',0), ('',0)]
93
91
 
92
+ # keep only the remote spots that happened in the SPOTLIFE window
94
93
  for band_TxRx_homecall in self.band_TxRx_homecall_report_times.data:
95
94
  band_TxRx_homecall_report_times = self.band_TxRx_homecall_report_times.data[band_TxRx_homecall]
96
95
  band_TxRx_homecall_report_times = [t for t in band_TxRx_homecall_report_times if (time.time() - t) < SPOTLIFE]
97
96
  self.band_TxRx_homecall_report_times.data[band_TxRx_homecall] = band_TxRx_homecall_report_times
98
97
 
98
+ # count number of local Tx and Rx, and identify the local Tx and Rx with most remote spots
99
99
  for band_TxRx_homecall in self.band_TxRx_homecall_report_times.data:
100
100
  band_TxRx_homecall_report_times = self.band_TxRx_homecall_report_times.data[band_TxRx_homecall]
101
101
  if len(band_TxRx_homecall_report_times):
102
- b, tr, c = band_TxRx_homecall.split("_")
103
- self.home_activity[b][['Tx','Rx'].index(tr)] +=1
102
+ b, iTxRx, c = band_TxRx_homecall
103
+ self.home_activity.setdefault(b, [0, 0])
104
+ self.home_activity[b][iTxRx] +=1
105
+ self.home_most_remotes.setdefault(b, [('',0), ('',0)])
104
106
  nremotes = len(band_TxRx_homecall_report_times)
105
- if not b in self.home_most_remotes:
106
- self.home_most_remotes[b] = [('',0), ('',0)]
107
- if nremotes>self.home_most_remotes[b][['Tx','Rx'].index(tr)][1]:
108
- self.home_most_remotes[b][['Tx','Rx'].index(tr)] = (c, nremotes)
107
+ if nremotes>self.home_most_remotes[b][iTxRx][1]:
108
+ self.home_most_remotes[b][iTxRx] = (c, nremotes)
109
109
 
110
+ # prune the hearing me list to <SPOTLIFE
110
111
  for b in self.hearing_me.data:
111
112
  newdict = {}
112
113
  for c in self.hearing_me.data[b]:
@@ -115,8 +116,8 @@ class PSKR_MQTT_listener:
115
116
  self.hearing_me.data[b] = newdict
116
117
 
117
118
  def get_spot_counts(self, band, call):
118
- tx_reports = self.band_TxRx_homecall_report_times.data.get(f"{band}_Tx_{call}", [])
119
- rx_reports = self.band_TxRx_homecall_report_times.data.get(f"{band}_Rx_{call}", [])
119
+ tx_reports = self.band_TxRx_homecall_report_times.data.get((band, 0, call), [])
120
+ rx_reports = self.band_TxRx_homecall_report_times.data.get((band, 1, call), [])
120
121
  n_spotting = len(tx_reports) if tx_reports else 0
121
122
  n_spotted = len(rx_reports) if rx_reports else 0
122
123
  return n_spotted, n_spotting
@@ -14,7 +14,7 @@ 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.0'
17
+ VER = '2.7.2'
18
18
 
19
19
  MAX_TX_START_SECONDS = 2.5
20
20
  rig, gui, qso, adif_logging, pskr_info, pskr_upload = None, None, None, None, None, None
@@ -308,8 +308,9 @@ def on_gui_sidebars_refresh(gui):
308
308
 
309
309
  #refresh hearing me
310
310
  hearing_me_text = []
311
- if b is not None and b in pskr_info.hearing_me.data:
312
- for h in pskr_info.hearing_me.data[b].values():
311
+ if b is not None and b in pskr_info.hearing_me.data:
312
+ hm = pskr_info.hearing_me.data[b].values()
313
+ for h in hm:
313
314
  geo_text = geo_text = get_geo_text(h['c'])
314
315
  hearing_me_text.append(f"{h['c']:<7} {int(h['rp']):+03d} {geo_text:<12}")
315
316
  gui.hm.list_print(['Hearing me:'] + hearing_me_text)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyFT8
3
- Version: 2.7.0
3
+ Version: 2.7.2
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
@@ -28,22 +28,24 @@ If you're interested in how this works, maybe have a look at [MiniPyFT8](https:/
28
28
 
29
29
  ## Features
30
30
  - Rx and Tx of standard messages with optional /P and /R, and nonstandard calls plus hashed calls
31
- - Doesn't try to do everything, so launches quickly (~2 seconds on my old Dell Optiplex 790)
31
+ - Launches quickly (~2 seconds on my old Dell Optiplex 790)
32
32
  - Use with or without gui (receive and send messages via command line commands)
33
33
  - Automatically chooses clearest Tx frequency
34
34
  - Modern programming language throughout
35
35
  - Finds sound cards by keywords so follows them if windows moves them ...
36
36
  - Logs QSOs to ADIF file and all spots to WSJTX-style ALL.txt file
37
37
  - Uploads spots to pskreporter
38
- - Direct CAT control for some rigs drops connection when not used, allowing sharing of rig's serial port
38
+ - Direct CAT control for some rigs, designed to drop connection when not used, allowing sharing of rig's serial port
39
39
  - Or control rigs via Hamlib
40
40
 
41
41
  The Gui shows:
42
42
  - Simultaneous views of odd and even cycles
43
43
  - Messages overlaid on waterfall signals that produce them
44
- - Worked-before info and fine grid locators / distance and bearing in the message boxes
45
- - Band activity on band select buttons
44
+ - Worked-before info and fine grid locators / distance and bearing in the message boxes
45
+ - List of stations hearing your transmissions on the selected band
46
+ - Band activity in your level 4 square live updated next to band select buttons
46
47
  - Number of remote stations hearing your Tx, number of remote Txs that you're hearing, plus the same info for the 'best' station in your level 4 square
48
+ - Data used for the above is cached to disk so is not lost when restarting the program
47
49
 
48
50
  To enable uploading of spots to pskreporter, make sure that your .ini file includes
49
51
  ```
@@ -51,8 +53,7 @@ To enable uploading of spots to pskreporter, make sure that your .ini file inclu
51
53
  upload = Y
52
54
  ```
53
55
 
54
- <img width="980" height="807" alt="screenshot" src="https://github.com/user-attachments/assets/ac393a05-277a-4d98-bd74-78bcb0ae8b03" />
55
-
56
+ ![screenshot](screenshot.png)
56
57
 
57
58
  ## Motivation
58
59
  This started out as me thinking "How hard can it be, really?" after some frustration with Windows moving sound devices around and wanting to get a minimal decoder running that I can fully control.
@@ -88,8 +89,7 @@ Alternatively, you can run PyFT8 without rig control; if there is no rig found,
88
89
 
89
90
  The image below shows the number of decodes from PyFT8, WSJT-x V2.7.0 running in NORM mode, and FT8_lib, using the same 10 minutes of busy 20m audio that is used to test ft8_lib.
90
91
 
91
- <img width="640" height="480" alt="performance snapshot" src="https://github.com/G1OJS/PyFT8/blob/main/performance%20snapshot.png" />
92
-
92
+ ![performance snapshot](performance%20snapshot.png)
93
93
 
94
94
  ## Limitations
95
95
  PyFT8 doesn't decode / encode *all* message types. The table below shows which are handled.
@@ -9,22 +9,24 @@ If you're interested in how this works, maybe have a look at [MiniPyFT8](https:/
9
9
 
10
10
  ## Features
11
11
  - Rx and Tx of standard messages with optional /P and /R, and nonstandard calls plus hashed calls
12
- - Doesn't try to do everything, so launches quickly (~2 seconds on my old Dell Optiplex 790)
12
+ - Launches quickly (~2 seconds on my old Dell Optiplex 790)
13
13
  - Use with or without gui (receive and send messages via command line commands)
14
14
  - Automatically chooses clearest Tx frequency
15
15
  - Modern programming language throughout
16
16
  - Finds sound cards by keywords so follows them if windows moves them ...
17
17
  - Logs QSOs to ADIF file and all spots to WSJTX-style ALL.txt file
18
18
  - Uploads spots to pskreporter
19
- - Direct CAT control for some rigs drops connection when not used, allowing sharing of rig's serial port
19
+ - Direct CAT control for some rigs, designed to drop connection when not used, allowing sharing of rig's serial port
20
20
  - Or control rigs via Hamlib
21
21
 
22
22
  The Gui shows:
23
23
  - Simultaneous views of odd and even cycles
24
24
  - Messages overlaid on waterfall signals that produce them
25
- - Worked-before info and fine grid locators / distance and bearing in the message boxes
26
- - Band activity on band select buttons
25
+ - Worked-before info and fine grid locators / distance and bearing in the message boxes
26
+ - List of stations hearing your transmissions on the selected band
27
+ - Band activity in your level 4 square live updated next to band select buttons
27
28
  - Number of remote stations hearing your Tx, number of remote Txs that you're hearing, plus the same info for the 'best' station in your level 4 square
29
+ - Data used for the above is cached to disk so is not lost when restarting the program
28
30
 
29
31
  To enable uploading of spots to pskreporter, make sure that your .ini file includes
30
32
  ```
@@ -32,8 +34,7 @@ To enable uploading of spots to pskreporter, make sure that your .ini file inclu
32
34
  upload = Y
33
35
  ```
34
36
 
35
- <img width="980" height="807" alt="screenshot" src="https://github.com/user-attachments/assets/ac393a05-277a-4d98-bd74-78bcb0ae8b03" />
36
-
37
+ ![screenshot](screenshot.png)
37
38
 
38
39
  ## Motivation
39
40
  This started out as me thinking "How hard can it be, really?" after some frustration with Windows moving sound devices around and wanting to get a minimal decoder running that I can fully control.
@@ -69,8 +70,7 @@ Alternatively, you can run PyFT8 without rig control; if there is no rig found,
69
70
 
70
71
  The image below shows the number of decodes from PyFT8, WSJT-x V2.7.0 running in NORM mode, and FT8_lib, using the same 10 minutes of busy 20m audio that is used to test ft8_lib.
71
72
 
72
- <img width="640" height="480" alt="performance snapshot" src="https://github.com/G1OJS/PyFT8/blob/main/performance%20snapshot.png" />
73
-
73
+ ![performance snapshot](performance%20snapshot.png)
74
74
 
75
75
  ## Limitations
76
76
  PyFT8 doesn't decode / encode *all* message types. The table below shows which are handled.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "PyFT8"
3
- version = "2.7.0"
3
+ version = "2.7.2"
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