PyFT8 3.0.0__tar.gz → 3.1.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.
- {pyft8-3.0.0 → pyft8-3.1.0}/PKG-INFO +4 -3
- {pyft8-3.0.0 → pyft8-3.1.0}/PyFT8/databases.py +15 -10
- {pyft8-3.0.0 → pyft8-3.1.0}/PyFT8/pskreporter.py +2 -1
- {pyft8-3.0.0 → pyft8-3.1.0}/PyFT8/pyft8.py +9 -4
- {pyft8-3.0.0 → pyft8-3.1.0}/PyFT8/receiver.py +3 -3
- pyft8-3.1.0/PyFT8/rigctrl.py +103 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/PyFT8/transmitter.py +68 -36
- {pyft8-3.0.0 → pyft8-3.1.0}/PyFT8.egg-info/PKG-INFO +4 -3
- {pyft8-3.0.0 → pyft8-3.1.0}/README.md +3 -2
- {pyft8-3.0.0 → pyft8-3.1.0}/pyproject.toml +1 -1
- pyft8-3.0.0/PyFT8/rigctrl.py +0 -43
- {pyft8-3.0.0 → pyft8-3.1.0}/LICENSE +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/MANIFEST.in +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/PyFT8/__init__.py +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/PyFT8/gui.py +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/PyFT8/time_utils.py +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/PyFT8.egg-info/SOURCES.txt +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/PyFT8.egg-info/dependency_links.txt +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/PyFT8.egg-info/entry_points.txt +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/PyFT8.egg-info/requires.txt +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/PyFT8.egg-info/top_level.txt +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/setup.cfg +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/tests/dev/CQ AAAA.py +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/tests/dev/osd.py +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/tests/dev/test_generate_wav.py +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/tests/dev/test_loopback_performance.py +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/tests/dev/view_worked_before.py +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/tests/plot_baseline.py +0 -0
- {pyft8-3.0.0 → pyft8-3.1.0}/tests/spare.py +0 -0
- {pyft8-3.0.0 → pyft8-3.1.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: 3.
|
|
3
|
+
Version: 3.1.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
|
|
@@ -27,7 +27,8 @@ PyFT8 is somewhat experimental, with a focus on demonstrating FT8 written in Pyt
|
|
|
27
27
|
If you're interested in how this works, maybe have a look at [MiniPyFT8](https://github.com/G1OJS/MiniPyFT8) which puts all of the receive code in a single 300 line Python file.
|
|
28
28
|
|
|
29
29
|
## Features
|
|
30
|
-
- Rx and Tx of standard messages with optional /P and /R
|
|
30
|
+
- Rx and Tx of standard messages with optional /P and /R
|
|
31
|
+
- RX of nonstandard calls plus hashed calls
|
|
31
32
|
- Launches quickly (~2 seconds on my old Dell Optiplex 790)
|
|
32
33
|
- Use with or without gui (receive and send messages via command line commands)
|
|
33
34
|
- Automatically chooses clearest Tx frequency
|
|
@@ -127,7 +128,7 @@ PyFT8 doesn't decode / encode *all* message types. The table below shows which a
|
|
|
127
128
|
|1|Std Msg |Y| Y |Standard <=6 char callsigns, can include /R |
|
|
128
129
|
|2|EU VHF |Y|Y| Standard <=6 char callsigns, can include /P |
|
|
129
130
|
|3|RTTY RU | | | |
|
|
130
|
-
|4|NonStd Call |Y|
|
|
131
|
+
|4|NonStd Call |Y|N| <=11 char callsigns + hashed call|
|
|
131
132
|
|5|EU VHF | | | |
|
|
132
133
|
|
|
133
134
|
## Acknowledgements
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from PyFT8.pskreporter import PSKR_MQTT_listener
|
|
2
2
|
import threading, time, os, pickle, json
|
|
3
3
|
|
|
4
|
-
call_hashes = {}
|
|
4
|
+
call_hashes, hashes_for_calls = {}, {}
|
|
5
5
|
|
|
6
6
|
def add_call_hashes(call):
|
|
7
7
|
global call_hashes
|
|
@@ -17,9 +17,9 @@ def add_call_hashes(call):
|
|
|
17
17
|
x = x * 47055833459
|
|
18
18
|
x = x & ((1 << 64) - 1)
|
|
19
19
|
x = x >> (64 - m)
|
|
20
|
-
hashes.append(x)
|
|
20
|
+
hashes.append((x, m))
|
|
21
21
|
call_hashes[(x, m)] = call
|
|
22
|
-
|
|
22
|
+
hashes_for_calls[call] = hashes
|
|
23
23
|
|
|
24
24
|
def grid_to_latlong(grid, centre = True):
|
|
25
25
|
lat, lon = -90, -180
|
|
@@ -71,12 +71,17 @@ class DiskDict:
|
|
|
71
71
|
pass
|
|
72
72
|
|
|
73
73
|
def save(self):
|
|
74
|
-
|
|
75
|
-
with
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
if self.data != {}:
|
|
75
|
+
with self.lock:
|
|
76
|
+
with open(f"{self.file}_tmp", "w") as f:
|
|
77
|
+
try:
|
|
78
|
+
json.dump(self.data, f)
|
|
79
|
+
except:
|
|
80
|
+
return
|
|
81
|
+
try:
|
|
82
|
+
os.replace(f"{self.file}_tmp", self.file)
|
|
83
|
+
except:
|
|
84
|
+
return
|
|
80
85
|
|
|
81
86
|
class History:
|
|
82
87
|
def __init__(self, config_folder, my_call, home_square, pskr_refresh_mins, parse_all_file):
|
|
@@ -185,7 +190,7 @@ class History:
|
|
|
185
190
|
self._update_new_alert(band, call, historic_data, new_alert_data)
|
|
186
191
|
historic_data.setdefault(band, {})
|
|
187
192
|
if call in historic_data[band]:
|
|
188
|
-
if t < historic_data[band][call]['t']:
|
|
193
|
+
if int(t) < int(historic_data[band][call]['t']):
|
|
189
194
|
return
|
|
190
195
|
historic_data[band][call] = {'t': int(t),'rp':int(rp)}
|
|
191
196
|
|
|
@@ -9,7 +9,8 @@ class PSKR_MQTT_listener:
|
|
|
9
9
|
def __init__(self, home_square, on_spot):
|
|
10
10
|
self.home_square = home_square
|
|
11
11
|
self.on_spot = on_spot
|
|
12
|
-
|
|
12
|
+
client_id = "PyFT8_" + ''.join(random.choice('0123456789ABCDEF') for i in range(16))
|
|
13
|
+
mqttc = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id)
|
|
13
14
|
mqttc.on_connect = self.on_connect
|
|
14
15
|
mqttc.on_message = self.on_message
|
|
15
16
|
try:
|
|
@@ -9,10 +9,10 @@ from PyFT8.pskreporter import PSKR_upload
|
|
|
9
9
|
from PyFT8.gui import Gui
|
|
10
10
|
from PyFT8.transmitter import AudioOut
|
|
11
11
|
from PyFT8.time_utils import global_time_utils
|
|
12
|
-
from PyFT8.rigctrl import Rig_hamlib
|
|
12
|
+
from PyFT8.rigctrl import Rig_hamlib, Rig_CAT
|
|
13
13
|
from PyFT8.databases import History, ADIF
|
|
14
14
|
|
|
15
|
-
VER = '3.
|
|
15
|
+
VER = '3.1.0'
|
|
16
16
|
|
|
17
17
|
MAX_TX_START_SECONDS = 2.5
|
|
18
18
|
HEARING_PANEL_LIFE_MINS = 20
|
|
@@ -315,8 +315,7 @@ def cli():
|
|
|
315
315
|
import time, sys
|
|
316
316
|
parser = argparse.ArgumentParser(prog='PyFT8rx', description = 'Command Line FT8 decoder')
|
|
317
317
|
parser.add_argument('-c', '--config_folder', help = 'Location of config folder e.g. C:/Users/drala/Documents/Projects/GitHub/G1OJS/PyFT8_cfg', default = './')
|
|
318
|
-
parser.add_argument('-i', '--inputcard_keywords', help = 'Comma-separated keywords to identify the input sound device')
|
|
319
|
-
#parser.add_argument('-v','--verbose', action='store_true', help = 'Verbose: include debugging output')
|
|
318
|
+
parser.add_argument('-i', '--inputcard_keywords', help = 'Comma-separated keywords to identify the input sound device')
|
|
320
319
|
parser.add_argument('-o','--outputcard_keywords', help = 'Comma-separated keywords to identify the output sound device')
|
|
321
320
|
parser.add_argument('-n','--no_gui', action='store_true', help = "Don't create a gui")
|
|
322
321
|
parser.add_argument('-m','--transmit_message', nargs='?', help = 'Transmit a message')
|
|
@@ -348,6 +347,12 @@ def cli():
|
|
|
348
347
|
if config.has_section('hamlib_rig'):
|
|
349
348
|
console_print("Connecting to rig via Hamlib")
|
|
350
349
|
rig = Rig_hamlib(config)
|
|
350
|
+
else:
|
|
351
|
+
console_print("Connecting to rig via CAT")
|
|
352
|
+
rig = Rig_CAT(config)
|
|
353
|
+
|
|
354
|
+
if config.has_section('launch'):
|
|
355
|
+
os.system(config['launch']['app'])
|
|
351
356
|
|
|
352
357
|
if args.outputcard_keywords:
|
|
353
358
|
outputcard_keywords = args.outputcard_keywords.replace(' ','').split(',')
|
|
@@ -45,10 +45,10 @@ def unpack(bits):
|
|
|
45
45
|
i3, bits74 = get_bitfields(bits,[3])
|
|
46
46
|
if i3 == 0:
|
|
47
47
|
n3, bits71 = get_bitfields(bits74,[3])
|
|
48
|
-
if n3
|
|
49
|
-
return ('Free text','not','implemented')
|
|
48
|
+
if n3 <= 4:
|
|
49
|
+
return (['Free text', 'DXpedition','Field Day', 'Field Day', 'Telemetry'][n3],'not','implemented')
|
|
50
50
|
else:
|
|
51
|
-
return (
|
|
51
|
+
return ('Unknown mode','not','implemented')
|
|
52
52
|
elif i3 == 1 or i3 == 2: # 1 = Std Msg incl /R 2 = 'EU VHF' = Std Msg incl /P
|
|
53
53
|
return unpack_std(bits74, i3)
|
|
54
54
|
elif i3 == 3:
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import serial, time, socket, subprocess, threading, psutil
|
|
2
|
+
|
|
3
|
+
class Rig_CAT:
|
|
4
|
+
def __init__(self, config, verbose = False):
|
|
5
|
+
self.serial_port = False
|
|
6
|
+
self.port = config['rig']['port']
|
|
7
|
+
self.baud_rate = config['rig']['baud_rate']
|
|
8
|
+
self.ptt_on_cmd = self.parse_configstr(config['rig']['ptt_on_command']) if config['rig']['ptt_on_command']else None
|
|
9
|
+
self.ptt_off_cmd = self.parse_configstr(config['rig']['ptt_off_command']) if config['rig']['ptt_off_command'] else None
|
|
10
|
+
self.set_freq_cmd = self.parse_configstr(config['rig']['set_freq_command']) if config['rig']['set_freq_command'] else None
|
|
11
|
+
self.set_freq_value = config['rig']['set_freq_value'] if config['rig']['set_freq_value'] else None
|
|
12
|
+
self.verbose = verbose
|
|
13
|
+
|
|
14
|
+
def parse_configstr(self, configstr):
|
|
15
|
+
if "." in configstr:
|
|
16
|
+
hexstr = configstr.replace(".", "")
|
|
17
|
+
return bytearray.fromhex(hexstr)
|
|
18
|
+
else:
|
|
19
|
+
return bytearray(configstr.encode())
|
|
20
|
+
|
|
21
|
+
def vprint(self, text):
|
|
22
|
+
if self.verbose:
|
|
23
|
+
print(text)
|
|
24
|
+
|
|
25
|
+
def _sendCAT(self, msg):
|
|
26
|
+
try:
|
|
27
|
+
self.serial_port = serial.Serial(port = self.port, baudrate = self.baud_rate, timeout = 0.1)
|
|
28
|
+
except Exception as e:
|
|
29
|
+
print(f"[CAT] couldn't open {self.port}: {e}")
|
|
30
|
+
if (self.serial_port):
|
|
31
|
+
self.serial_port.reset_input_buffer()
|
|
32
|
+
self.vprint(f"[CAT] send {msg.hex(' ')}")
|
|
33
|
+
try:
|
|
34
|
+
self.serial_port.write(msg)
|
|
35
|
+
time.sleep(0.05)
|
|
36
|
+
self.serial_port.close()
|
|
37
|
+
except Exception as e:
|
|
38
|
+
print(f"[CAT] couldn't send CAT command {msg} on {self.port}: {e}")
|
|
39
|
+
|
|
40
|
+
def set_freq_Hz(self, freqHz):
|
|
41
|
+
if self.set_freq_cmd and self.set_freq_value:
|
|
42
|
+
self.vprint(f"[CAT] SET frequency to {freqHz} Hz")
|
|
43
|
+
start, length, fmt, mult, offset = self.set_freq_value.split("|")
|
|
44
|
+
start, length, mult, offset = int(start), int(length), int(mult), int(offset)
|
|
45
|
+
fVal = freqHz * mult + offset
|
|
46
|
+
nDigits = length if fmt == "text" else 2*length
|
|
47
|
+
s = f"{fVal:0{nDigits}d}"
|
|
48
|
+
if fmt=='text':
|
|
49
|
+
fBytes = s.encode()
|
|
50
|
+
else:
|
|
51
|
+
pairs = [(int(s[i]) << 4) | int(s[i+1]) for i in range(0, len(s), 2)]
|
|
52
|
+
if fmt == "vfBcdLU":
|
|
53
|
+
fBytes = bytes(pairs[::-1])
|
|
54
|
+
else:
|
|
55
|
+
fBytes = bytes(pairs)
|
|
56
|
+
cmd = bytearray(self.set_freq_cmd)
|
|
57
|
+
cmd[start:start+length] = fBytes
|
|
58
|
+
if fmt.startswith("vfBcd"): # CI-V
|
|
59
|
+
cmd = b'\x00' + cmd
|
|
60
|
+
self._sendCAT(cmd)
|
|
61
|
+
|
|
62
|
+
def ptt_on(self):
|
|
63
|
+
if self.ptt_on_cmd:
|
|
64
|
+
self.vprint(f"[CAT] PTT On")
|
|
65
|
+
self._sendCAT(self.ptt_on_cmd)
|
|
66
|
+
|
|
67
|
+
def ptt_off(self):
|
|
68
|
+
if self.ptt_off_cmd:
|
|
69
|
+
self.vprint(f"[CAT] PTT Off")
|
|
70
|
+
self._sendCAT(self.ptt_off_cmd)
|
|
71
|
+
|
|
72
|
+
class Rig_hamlib:
|
|
73
|
+
def __init__(self, config):
|
|
74
|
+
com = config['hamlib_rig']['port']
|
|
75
|
+
s = config['hamlib_rig']['baud_rate']
|
|
76
|
+
rigctld = config['hamlib_rig']['rigctld']
|
|
77
|
+
rig = config['hamlib_rig']['model']
|
|
78
|
+
host, port ="localhost", 4532
|
|
79
|
+
if not any(['rigctld' in i.name() for i in psutil.process_iter()]):
|
|
80
|
+
cmd = f"{rigctld} -m {rig} -r {com} -s {s}"
|
|
81
|
+
threading.Thread(target = subprocess.run, args = (cmd,)).start()
|
|
82
|
+
time.sleep(0.5)
|
|
83
|
+
self.sock = socket.create_connection((host, port))
|
|
84
|
+
self.set_mode("PKTUSB")
|
|
85
|
+
|
|
86
|
+
def cmd(self, command):
|
|
87
|
+
if self.sock:
|
|
88
|
+
self.sock.sendall((command + "\n").encode())
|
|
89
|
+
return self.sock.recv(1024).decode()
|
|
90
|
+
|
|
91
|
+
def set_mode(self, mode):
|
|
92
|
+
self.cmd(f"M {mode} 0")
|
|
93
|
+
|
|
94
|
+
def set_freq_Hz(self, hz):
|
|
95
|
+
self.cmd(f"F {hz}")
|
|
96
|
+
|
|
97
|
+
def ptt_on(self):
|
|
98
|
+
self.cmd(f"T 1")
|
|
99
|
+
|
|
100
|
+
def ptt_off(self):
|
|
101
|
+
self.cmd(f"T 0")
|
|
102
|
+
|
|
103
|
+
|
|
@@ -2,7 +2,7 @@ import numpy as np
|
|
|
2
2
|
import wave
|
|
3
3
|
import pyaudio
|
|
4
4
|
import time
|
|
5
|
-
from PyFT8.databases import add_call_hashes
|
|
5
|
+
from PyFT8.databases import hashes_for_calls, add_call_hashes
|
|
6
6
|
|
|
7
7
|
#==================== AUDIO OUT ================================================================
|
|
8
8
|
|
|
@@ -56,35 +56,42 @@ class AudioOut:
|
|
|
56
56
|
stream.stop_stream()
|
|
57
57
|
stream.close()
|
|
58
58
|
|
|
59
|
-
#==================== PACK
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
#==================== PACK ================================================================
|
|
60
|
+
|
|
61
|
+
def ifindex(arr, val, default = None):
|
|
62
|
+
return arr.index(val) if val in arr else default
|
|
62
63
|
|
|
63
64
|
def pack_message(c1, c2, gr):
|
|
64
65
|
symbols, bits77 = _pack_message(c1, c2, gr)
|
|
65
66
|
return symbols
|
|
66
67
|
|
|
67
68
|
def _pack_message(c1, c2, gr):
|
|
68
|
-
|
|
69
|
-
c28b, p1b = pack_ft8_c28(c2)
|
|
69
|
+
c29a, c29b = pack_ft8_c29(c1), pack_ft8_c29(c2)
|
|
70
70
|
g15, ir = pack_ft8_g15(gr)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
if c29a and c29b:
|
|
72
|
+
c28a, p1a = c29a
|
|
73
|
+
c28b, p1b = c29b
|
|
74
|
+
i3 = 2 if c1.endswith('/P') or c2.endswith('/P') else 1
|
|
75
|
+
n3 = 0
|
|
76
|
+
bits77 = (c28a<<28+1+1+1+15+3) | (p1a<<28+1+1+15+3) | (c28b<<1+1+15+3) | (p1b <<1+15+3) | (ir<<15+3) | (g15<< 3) | (i3)
|
|
77
|
+
symbols = encode_bits77(bits77)
|
|
78
|
+
if c29b and not c29a:
|
|
79
|
+
add_call_hashes(c1)
|
|
80
|
+
c28a, p1a = 2063592 + hashes_for_calls[c1][2][0], 0
|
|
81
|
+
c28b, p1b = c29b
|
|
82
|
+
i3 = 2 if c2.endswith('/P') else 1
|
|
83
|
+
n3 = 0
|
|
74
84
|
bits77 = (c28a<<28+1+1+1+15+3) | (p1a<<28+1+1+15+3) | (c28b<<1+1+15+3) | (p1b <<1+15+3) | (ir<<15+3) | (g15<< 3) | (i3)
|
|
75
85
|
symbols = encode_bits77(bits77)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
rrr, cq_ = 0, 0
|
|
84
|
-
if gr in ('RRR', 'RR73', '73'):
|
|
85
|
-
rrr = ('', 'RRR', 'RR73', '73').index(gr)
|
|
86
|
-
bits77 = (hashes[1]<<58+1+2+1+3) | (c58 <<1+2+1+3) | (swp<<2+1+3) | (rrr<<1+3) | (cq_<<3) | (i3)
|
|
86
|
+
if c29a and not c29b:
|
|
87
|
+
add_call_hashes(c2)
|
|
88
|
+
c28b, p1b = 2063592 + hashes_for_calls[c2][2][0], 0
|
|
89
|
+
c28a, p1a = c29a
|
|
90
|
+
i3 = 2 if c1.endswith('/P') else 1
|
|
91
|
+
n3 = 0
|
|
92
|
+
bits77 = (c28a<<28+1+1+1+15+3) | (p1a<<28+1+1+15+3) | (c28b<<1+1+15+3) | (p1b <<1+15+3) | (ir<<15+3) | (g15<< 3) | (i3)
|
|
87
93
|
symbols = encode_bits77(bits77)
|
|
94
|
+
|
|
88
95
|
return symbols, bits77
|
|
89
96
|
|
|
90
97
|
|
|
@@ -96,24 +103,23 @@ def pack_ft8_c58(call):
|
|
|
96
103
|
n58 = n58*38 + chars.index(call[i])
|
|
97
104
|
return n58
|
|
98
105
|
|
|
99
|
-
def
|
|
100
|
-
if '/' in call
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
else:
|
|
106
|
+
def pack_ft8_c29(call):
|
|
107
|
+
if '/' not in call or call.endswith("P") or call.endswith("R"):
|
|
108
|
+
t = ifindex(['DE','QRZ','CQ'], call)
|
|
109
|
+
if t is not None:
|
|
110
|
+
return t, 0
|
|
105
111
|
p1 = 1 if call[-2:] in ('/P', '/R') else 0
|
|
106
112
|
call = call.replace('/P','').replace('/R','')
|
|
107
|
-
if len(call)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
if len(call) <= 6:
|
|
114
|
+
prepend_space = '' if call[2].isdigit() else ' '
|
|
115
|
+
call = (prepend_space + call + ' ')[:6]
|
|
116
|
+
a = ' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
117
|
+
b = ' ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
118
|
+
x = [ifindex(a, call[0]), ifindex(a[1:], call[1]), ifindex(a[1:], call[2]), ifindex(b, call[3]), ifindex(b, call[4]), ifindex(b, call[5]), 1 ]
|
|
119
|
+
if not None in x:
|
|
120
|
+
y = [36*10*27*27*27, 10*27*27*27, 27*27*27, 27*27, 27, 1, 2_063_592 + 4_194_304]
|
|
121
|
+
c28 = int(np.dot(x,y))
|
|
122
|
+
return c28, p1
|
|
117
123
|
|
|
118
124
|
def pack_ft8_g15(txt):
|
|
119
125
|
ir = 0
|
|
@@ -138,6 +144,11 @@ def pack_ft8_g15(txt):
|
|
|
138
144
|
v = v * 10 + int(txt[3])
|
|
139
145
|
return int(v), ir
|
|
140
146
|
|
|
147
|
+
#============ ENCODE ========================================================================================================================
|
|
148
|
+
|
|
149
|
+
generator_matrix_rows = ["8329ce11bf31eaf509f27fc", "761c264e25c259335493132", "dc265902fb277c6410a1bdc", "1b3f417858cd2dd33ec7f62", "09fda4fee04195fd034783a", "077cccc11b8873ed5c3d48a", "29b62afe3ca036f4fe1a9da", "6054faf5f35d96d3b0c8c3e", "e20798e4310eed27884ae90", "775c9c08e80e26ddae56318", "b0b811028c2bf997213487c", "18a0c9231fc60adf5c5ea32", "76471e8302a0721e01b12b8", "ffbccb80ca8341fafb47b2e", "66a72a158f9325a2bf67170", "c4243689fe85b1c51363a18", "0dff739414d1a1b34b1c270", "15b48830636c8b99894972e", "29a89c0d3de81d665489b0e", "4f126f37fa51cbe61bd6b94", "99c47239d0d97d3c84e0940", "1919b75119765621bb4f1e8", "09db12d731faee0b86df6b8", "488fc33df43fbdeea4eafb4", "827423ee40b675f756eb5fe", "abe197c484cb74757144a9a", "2b500e4bc0ec5a6d2bdbdd0", "c474aa53d70218761669360", "8eba1a13db3390bd6718cec", "753844673a27782cc42012e", "06ff83a145c37035a5c1268", "3b37417858cc2dd33ec3f62", "9a4a5a28ee17ca9c324842c", "bc29f465309c977e89610a4", "2663ae6ddf8b5ce2bb29488", "46f231efe457034c1814418", "3fb2ce85abe9b0c72e06fbe", "de87481f282c153971a0a2e", "fcd7ccf23c69fa99bba1412", "f0261447e9490ca8e474cec", "4410115818196f95cdd7012", "088fc31df4bfbde2a4eafb4", "b8fef1b6307729fb0a078c0", "5afea7acccb77bbc9d99a90", "49a7016ac653f65ecdc9076", "1944d085be4e7da8d6cc7d0", "251f62adc4032f0ee714002", "56471f8702a0721e00b12b8", "2b8e4923f2dd51e2d537fa0", "6b550a40a66f4755de95c26", "a18ad28d4e27fe92a4f6c84", "10c2e586388cb82a3d80758", "ef34a41817ee02133db2eb0", "7e9c0c54325a9c15836e000", "3693e572d1fde4cdf079e86", "bfb2cec5abe1b0c72e07fbe", "7ee18230c583cccc57d4b08", "a066cb2fedafc9f52664126", "bb23725abc47cc5f4cc4cd2", "ded9dba3bee40c59b5609b4", "d9a7016ac653e6decdc9036", "9ad46aed5f707f280ab5fc4", "e5921c77822587316d7d3c2", "4f14da8242a8b86dca73352", "8b8b507ad467d4441df770e", "22831c9cf1169467ad04b68", "213b838fe2ae54c38ee7180", "5d926b6dd71f085181a4e12", "66ab79d4b29ee6e69509e56", "958148682d748a38dd68baa", "b8ce020cf069c32a723ab14", "f4331d6d461607e95752746", "6da23ba424b9596133cf9c8", "a636bcbc7b30c5fbeae67fe", "5cb0d86a07df654a9089a20", "f11f106848780fc9ecdd80a", "1fbb5364fb8d2c9d730d5ba", "fcb86bc70a50c9d02a5d034", "a534433029eac15f322e34c", "c989d9c7c3d3b8c55d75130", "7bb38b2f0186d46643ae962", "2644ebadeb44b9467d1f42c", "608cc857594bfbb55d69600"]
|
|
150
|
+
kGEN = np.array([int(row,16)>>1 for row in generator_matrix_rows])
|
|
151
|
+
|
|
141
152
|
def ldpc_encode(msg_crc: int) -> int:
|
|
142
153
|
msg_crc = int(msg_crc)
|
|
143
154
|
parity_bits = 0
|
|
@@ -182,6 +193,8 @@ def append_crc(bits77_int):
|
|
|
182
193
|
#==================== TESTS ================================================================
|
|
183
194
|
|
|
184
195
|
if __name__ == "__main__":
|
|
196
|
+
|
|
197
|
+
print("Test standard calls")
|
|
185
198
|
OK = True
|
|
186
199
|
msgs = [("G1OJS/P", "G1OJS/P", "IO90"),("WM3PEN","EA6VQ","+08"),("E67A/P","EA6VQ","R-08"),
|
|
187
200
|
("CQ","CT7ARQ/P","JO03"), ("EC5A","9A5E","RR73"), ("EC5A/P","9A5E","73"), ("EC5A/MM","9A5E","73"),
|
|
@@ -195,5 +208,24 @@ if __name__ == "__main__":
|
|
|
195
208
|
#print(''.join([str(s) for s in symbols]))
|
|
196
209
|
print("\nPASSED" if OK else "\nFAILED")
|
|
197
210
|
|
|
211
|
+
print("\nTest non-standard calls")
|
|
212
|
+
OK = True
|
|
213
|
+
msgs = [("SX200M","G1OJS","IO90"),
|
|
214
|
+
("G1OJS","SX200M","CB21"),
|
|
215
|
+
("CQ","SX200M","CB21")]
|
|
216
|
+
expected_symbols = ["3140652203407700750145313654745000003140652276403052651370607066332604363140652",
|
|
217
|
+
"",
|
|
218
|
+
"3140652564261623472565070174400214333140652601351750040163007617513443213140652"]
|
|
219
|
+
for i, msg_tx in enumerate(msgs):
|
|
220
|
+
symbols, bits77 = _pack_message(*msg_tx)
|
|
221
|
+
from PyFT8.receiver import unpack
|
|
222
|
+
msg_rx = unpack(bits77)
|
|
223
|
+
print(f"\n{msg_tx}\n{msg_rx}")
|
|
224
|
+
OK = OK and (msg_tx == msg_rx) or 'implemented' in msg_rx
|
|
225
|
+
print(f"exp: {expected_symbols[i]}")
|
|
226
|
+
print(f"gen: {''.join([str(s) for s in symbols])}")
|
|
227
|
+
print("\nPASSED" if OK else "\nFAILED")
|
|
228
|
+
|
|
229
|
+
print("\nUnpack 'CQ DX G1OJS IO90'")
|
|
198
230
|
print(unpack(int('00000000000000000100011011110000010010000000000111000001100011111000010010001',2)))
|
|
199
231
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyFT8
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1.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
|
|
@@ -27,7 +27,8 @@ PyFT8 is somewhat experimental, with a focus on demonstrating FT8 written in Pyt
|
|
|
27
27
|
If you're interested in how this works, maybe have a look at [MiniPyFT8](https://github.com/G1OJS/MiniPyFT8) which puts all of the receive code in a single 300 line Python file.
|
|
28
28
|
|
|
29
29
|
## Features
|
|
30
|
-
- Rx and Tx of standard messages with optional /P and /R
|
|
30
|
+
- Rx and Tx of standard messages with optional /P and /R
|
|
31
|
+
- RX of nonstandard calls plus hashed calls
|
|
31
32
|
- Launches quickly (~2 seconds on my old Dell Optiplex 790)
|
|
32
33
|
- Use with or without gui (receive and send messages via command line commands)
|
|
33
34
|
- Automatically chooses clearest Tx frequency
|
|
@@ -127,7 +128,7 @@ PyFT8 doesn't decode / encode *all* message types. The table below shows which a
|
|
|
127
128
|
|1|Std Msg |Y| Y |Standard <=6 char callsigns, can include /R |
|
|
128
129
|
|2|EU VHF |Y|Y| Standard <=6 char callsigns, can include /P |
|
|
129
130
|
|3|RTTY RU | | | |
|
|
130
|
-
|4|NonStd Call |Y|
|
|
131
|
+
|4|NonStd Call |Y|N| <=11 char callsigns + hashed call|
|
|
131
132
|
|5|EU VHF | | | |
|
|
132
133
|
|
|
133
134
|
## Acknowledgements
|
|
@@ -8,7 +8,8 @@ PyFT8 is somewhat experimental, with a focus on demonstrating FT8 written in Pyt
|
|
|
8
8
|
If you're interested in how this works, maybe have a look at [MiniPyFT8](https://github.com/G1OJS/MiniPyFT8) which puts all of the receive code in a single 300 line Python file.
|
|
9
9
|
|
|
10
10
|
## Features
|
|
11
|
-
- Rx and Tx of standard messages with optional /P and /R
|
|
11
|
+
- Rx and Tx of standard messages with optional /P and /R
|
|
12
|
+
- RX of nonstandard calls plus hashed calls
|
|
12
13
|
- Launches quickly (~2 seconds on my old Dell Optiplex 790)
|
|
13
14
|
- Use with or without gui (receive and send messages via command line commands)
|
|
14
15
|
- Automatically chooses clearest Tx frequency
|
|
@@ -108,7 +109,7 @@ PyFT8 doesn't decode / encode *all* message types. The table below shows which a
|
|
|
108
109
|
|1|Std Msg |Y| Y |Standard <=6 char callsigns, can include /R |
|
|
109
110
|
|2|EU VHF |Y|Y| Standard <=6 char callsigns, can include /P |
|
|
110
111
|
|3|RTTY RU | | | |
|
|
111
|
-
|4|NonStd Call |Y|
|
|
112
|
+
|4|NonStd Call |Y|N| <=11 char callsigns + hashed call|
|
|
112
113
|
|5|EU VHF | | | |
|
|
113
114
|
|
|
114
115
|
## Acknowledgements
|
pyft8-3.0.0/PyFT8/rigctrl.py
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import time, socket, subprocess, threading
|
|
2
|
-
|
|
3
|
-
class Rig_hamlib:
|
|
4
|
-
def __init__(self, config):
|
|
5
|
-
com = config['hamlib_rig']['port']
|
|
6
|
-
s = config['hamlib_rig']['baud_rate']
|
|
7
|
-
rigctld = config['hamlib_rig']['rigctld']
|
|
8
|
-
rig = config['hamlib_rig']['model']
|
|
9
|
-
host, port ="localhost", 4532
|
|
10
|
-
cmd = f"{rigctld} -m {rig} -r {com} -s {s}"
|
|
11
|
-
self.sock = None
|
|
12
|
-
if not self.create_socket(host, port):
|
|
13
|
-
threading.Thread(target = subprocess.run, args = (cmd,)).start()
|
|
14
|
-
time.sleep(0.5)
|
|
15
|
-
if not self.create_socket(host, port):
|
|
16
|
-
print(f"Couldn't create socket for hamlib on {host}:{port}")
|
|
17
|
-
self.set_mode("PKTUSB")
|
|
18
|
-
|
|
19
|
-
def create_socket(self, host, port):
|
|
20
|
-
try:
|
|
21
|
-
self.sock = socket.create_connection((host, port))
|
|
22
|
-
except:
|
|
23
|
-
return False
|
|
24
|
-
return True
|
|
25
|
-
|
|
26
|
-
def cmd(self, command):
|
|
27
|
-
if self.sock:
|
|
28
|
-
self.sock.sendall((command + "\n").encode())
|
|
29
|
-
return self.sock.recv(1024).decode()
|
|
30
|
-
|
|
31
|
-
def set_mode(self, mode):
|
|
32
|
-
self.cmd(f"M {mode} 0")
|
|
33
|
-
|
|
34
|
-
def set_freq_Hz(self, hz):
|
|
35
|
-
self.cmd(f"F {hz}")
|
|
36
|
-
|
|
37
|
-
def ptt_on(self):
|
|
38
|
-
self.cmd(f"T 1")
|
|
39
|
-
|
|
40
|
-
def ptt_off(self):
|
|
41
|
-
self.cmd(f"T 0")
|
|
42
|
-
|
|
43
|
-
|
|
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
|