You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
135 lines
3.7 KiB
135 lines
3.7 KiB
import queue |
|
import threading |
|
import subprocess |
|
|
|
import frames |
|
import crypto |
|
|
|
|
|
def wrap_frame(message, callsign): |
|
return f"Syncronizing... Synchronizing... Synchronizing...{message}https://kj7rrv.com/leonet DE {callsign}\n\n" |
|
|
|
|
|
class Transmitter: |
|
def __init__(self, callsign): |
|
self.callsign = callsign |
|
self.queue = queue.Queue() |
|
self.transmit = self.queue.put |
|
threading.Thread(target=self.tx_loop).start() |
|
|
|
def tx_loop(self): |
|
while True: |
|
send(wrap_frame(self.queue.get(), callsign)) |
|
|
|
|
|
def send(data): |
|
print(data) |
|
subprocess.run( |
|
["minimodem", "--tx", "300"], |
|
input=data, |
|
encoding="ascii", |
|
) |
|
|
|
|
|
class InvalidFrameError(BaseException): |
|
pass |
|
|
|
|
|
class FrameDecodeError(InvalidFrameError): |
|
pass |
|
|
|
|
|
class FrameAuthenticationError(InvalidFrameError): |
|
pass |
|
|
|
|
|
def decode(frame_bytes, passwords): |
|
try: |
|
frame = frame_bytes.decode("ascii") |
|
|
|
main, checksum, auth_code = frame.split(" ")[1:-1] |
|
|
|
if checksum != crypto.find_checksum(main): |
|
raise FrameDecodeError() |
|
|
|
frame_type, recipient, originator, payload = main.split("#") |
|
|
|
if passwords is not None: |
|
password = passwords.get( |
|
originator if directions[frame_type] == "OR" else recipient, "" |
|
) |
|
|
|
if auth_code != crypto.find_auth_code(password, main): |
|
raise FrameAuthenticationError |
|
|
|
if frame_type == "MSG": |
|
body, message_id = payload.split(";") |
|
timestamp, safe_message = body.split("&") |
|
message = make_message_unsafe(safe_message) |
|
return frame_objects.MsgFrame( |
|
recipient, |
|
originator, |
|
message_id, |
|
timestamp, |
|
message, |
|
) |
|
elif frame_type == "QRY": |
|
return frame_objects.QryFrame(recipient, originator, payload) |
|
elif frame_type == "ACK": |
|
return frame_objects.AckFrame(recipient, originator, payload) |
|
elif frame_type == "Nak": |
|
return frame_objects.NakFrame(recipient, originator, payload) |
|
else: |
|
raise FrameDecodeError() |
|
except BaseException: |
|
raise FrameDecodeError() |
|
|
|
|
|
class Receiver: |
|
def __init__(self, identifier): |
|
self.modem = subprocess.Popen( |
|
["minimodem", "--quiet", "--rx", "300"], stdout=subprocess.PIPE |
|
) |
|
self.running = True |
|
self.queue = queue.Queue() |
|
self.receive = self.queue.get |
|
self.identifier = identifier |
|
|
|
threading.Thread(target=self.rx_loop).start() |
|
|
|
def rx_loop(self): |
|
rx_buffer = b"" |
|
|
|
while True: |
|
if not self.running: |
|
break |
|
|
|
byte = self.modem.stdout.read(1) |
|
if byte == b"\n": |
|
if rx_buffer.startswith(b"LEONET") and rx_buffer.endswith( |
|
b"TENOEL" |
|
): |
|
try: |
|
frame = decode(rx_buffer, None) # passwords dict |
|
if ( |
|
self.identifier is None |
|
or frame.frame_recipient == self.identifier |
|
): |
|
self.queue.put(frame) |
|
except InvalidFrameError as e: |
|
pass |
|
rx_buffer = b"" |
|
else: |
|
rx_buffer += byte |
|
|
|
|
|
class Transceiver: |
|
def __init__(self, identifier, callsign): |
|
self.transmitter = Transmitter(callsign) |
|
self.receiver = Receiver(identifier) |
|
|
|
self.tx_queue = self.transmitter.queue |
|
self.rx_queue = self.receiver.queue |
|
|
|
self.transmit = self.transmitter.transmit |
|
self.receive = self.receiver.receive
|
|
|