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.
- {pyft8-2.7.0 → pyft8-2.7.2}/PKG-INFO +9 -9
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/mqtt.py +27 -26
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/pyft8.py +4 -3
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8.egg-info/PKG-INFO +9 -9
- {pyft8-2.7.0 → pyft8-2.7.2}/README.md +8 -8
- {pyft8-2.7.0 → pyft8-2.7.2}/pyproject.toml +1 -1
- {pyft8-2.7.0 → pyft8-2.7.2}/LICENSE +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/MANIFEST.in +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/__init__.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/callhashes.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/gui.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/hamlib.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/maidenhead.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/pskr_upload.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/receiver.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/rigctrl.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/time_utils.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8/transmitter.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8.egg-info/SOURCES.txt +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8.egg-info/dependency_links.txt +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8.egg-info/entry_points.txt +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8.egg-info/requires.txt +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/PyFT8.egg-info/top_level.txt +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/setup.cfg +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/tests/dev/CQ AAAA.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/tests/dev/osd.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/tests/dev/test_generate_wav.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/tests/dev/test_loopback_performance.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/tests/dev/view_worked_before.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/tests/plot_baseline.py +0 -0
- {pyft8-2.7.0 → pyft8-2.7.2}/tests/spare.py +0 -0
- {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.
|
|
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
|
-
-
|
|
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
|
|
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
|
-
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
+

|
|
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
|
-
|
|
92
|
-
|
|
92
|
+

|
|
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
|
-
|
|
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}/
|
|
40
|
-
self.
|
|
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}/{
|
|
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
|
|
68
|
+
for iTxRx, c in enumerate([sc, rc]):
|
|
67
69
|
call, loc = c
|
|
68
|
-
|
|
69
|
-
self.callsign_cache.data[call] = loc
|
|
70
|
+
self.callsign_cache.data[call] = loc
|
|
70
71
|
if self.home_square in loc:
|
|
71
|
-
key =
|
|
72
|
-
|
|
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
|
-
|
|
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.
|
|
83
|
+
self.band_TxRx_homecall_couniTxRxemotes = {}
|
|
88
84
|
self.home_most_remotes = {}
|
|
89
85
|
with self.lock:
|
|
90
|
-
for
|
|
91
|
-
|
|
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,
|
|
103
|
-
self.home_activity
|
|
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
|
|
106
|
-
self.home_most_remotes[b] =
|
|
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(
|
|
119
|
-
rx_reports = self.band_TxRx_homecall_report_times.data.get(
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
-
|
|
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
|
|
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
|
-
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
+

|
|
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
|
-
|
|
92
|
-
|
|
92
|
+

|
|
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
|
-
-
|
|
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
|
|
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
|
-
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
+

|
|
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
|
-
|
|
73
|
-
|
|
73
|
+

|
|
74
74
|
|
|
75
75
|
## Limitations
|
|
76
76
|
PyFT8 doesn't decode / encode *all* message types. The table below shows which are handled.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|