mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
428 lines
No EOL
15 KiB
Python
428 lines
No EOL
15 KiB
Python
import time
|
|
from modem_frametypes import FRAME_TYPE as FR_TYPE
|
|
from codec2 import FREEDV_MODE
|
|
from queues import MODEM_TRANSMIT_QUEUE
|
|
import threading
|
|
import helpers
|
|
import codec2
|
|
import modem
|
|
from random import randrange
|
|
import uuid
|
|
import structlog
|
|
import ujson as json
|
|
|
|
TESTMODE = False
|
|
|
|
class BROADCAST:
|
|
|
|
def __init__(self, config, event_queue, states):
|
|
self.log = structlog.get_logger("DHBC")
|
|
|
|
self.states = states
|
|
self.event_queue = event_queue
|
|
self.config = config
|
|
|
|
self.beacon_interval = 0
|
|
self.beacon_interval_timer = 0
|
|
self.beacon_paused = False
|
|
self.beacon_thread = threading.Thread(
|
|
target=self.run_beacon, name="watchdog", daemon=True
|
|
)
|
|
self.beacon_thread.start()
|
|
|
|
|
|
# length of signalling frame
|
|
self.length_sig0_frame = 14
|
|
self.modem_frequency_offset = 0
|
|
|
|
# load config
|
|
self.mycallsign = config['STATION']['mycall']
|
|
self.myssid = config['STATION']['myssid']
|
|
self.mycallsign += "-" + str(self.myssid)
|
|
encoded_call = helpers.callsign_to_bytes(self.mycallsign)
|
|
self.mycallsign = helpers.bytes_to_callsign(encoded_call)
|
|
self.mygrid = config['STATION']['mygrid']
|
|
self.enable_fsk = config['MODEM']['enable_fsk']
|
|
self.respond_to_cq = config['MODEM']['respond_to_cq']
|
|
self.respond_to_call = True
|
|
|
|
self.duration_datac13 = 2.0
|
|
self.duration_sig1_frame = self.duration_datac13
|
|
|
|
|
|
def send_test_frame(self) -> None:
|
|
"""Send an empty test frame"""
|
|
test_frame = bytearray(126)
|
|
test_frame[:1] = bytes([FR_TYPE.TEST_FRAME.value])
|
|
self.enqueue_frame_for_tx(
|
|
frame_to_tx=[test_frame], c2_mode=FREEDV_MODE.datac13.value
|
|
)
|
|
|
|
def send_fec(self, mode, wakeup, payload, mycallsign):
|
|
"""Send an empty test frame"""
|
|
|
|
mode_int = codec2.freedv_get_mode_value_by_name(mode)
|
|
payload_per_frame = modem.get_bytes_per_frame(mode_int) - 2
|
|
fec_payload_length = payload_per_frame - 1
|
|
|
|
# check callsign
|
|
if mycallsign in [None]:
|
|
mycallsign = self.mycallsign
|
|
mycallsign = helpers.callsign_to_bytes(mycallsign)
|
|
mycallsign = helpers.bytes_to_callsign(mycallsign)
|
|
|
|
if wakeup:
|
|
mode_int_wakeup = codec2.freedv_get_mode_value_by_name("sig0")
|
|
payload_per_wakeup_frame = modem.get_bytes_per_frame(mode_int_wakeup) - 2
|
|
fec_wakeup_frame = bytearray(payload_per_wakeup_frame)
|
|
fec_wakeup_frame[:1] = bytes([FR_TYPE.FEC_WAKEUP.value])
|
|
fec_wakeup_frame[1:7] = helpers.callsign_to_bytes(mycallsign)
|
|
fec_wakeup_frame[7:8] = bytes([mode_int])
|
|
fec_wakeup_frame[8:9] = bytes([1]) # n payload bursts
|
|
print(mode_int_wakeup)
|
|
|
|
self.enqueue_frame_for_tx(
|
|
frame_to_tx=[fec_wakeup_frame], c2_mode=codec2.FREEDV_MODE["sig1"].value
|
|
)
|
|
time.sleep(1)
|
|
fec_frame = bytearray(payload_per_frame)
|
|
fec_frame[:1] = bytes([FR_TYPE.FEC.value])
|
|
fec_frame[1:payload_per_frame] = bytes(payload[:fec_payload_length])
|
|
self.enqueue_frame_for_tx(
|
|
frame_to_tx=[fec_frame], c2_mode=codec2.FREEDV_MODE[mode].value
|
|
)
|
|
|
|
def send_fec_is_writing(self, mycallsign) -> None:
|
|
"""Send an fec is writing frame"""
|
|
|
|
fec_frame = bytearray(14)
|
|
fec_frame[:1] = bytes([FR_TYPE.IS_WRITING.value])
|
|
fec_frame[1:7] = helpers.callsign_to_bytes(mycallsign)
|
|
|
|
# send burst only if channel not busy - but without waiting
|
|
# otherwise burst will be dropped
|
|
if not self.states.channel_busy and not self.states.is_transmitting:
|
|
self.enqueue_frame_for_tx(
|
|
frame_to_tx=[fec_frame], c2_mode=codec2.FREEDV_MODE["sig0"].value
|
|
)
|
|
else:
|
|
return False
|
|
|
|
|
|
def enqueue_frame_for_tx(
|
|
self,
|
|
frame_to_tx, # : list[bytearray], # this causes a crash on python 3.7
|
|
c2_mode=FREEDV_MODE.sig0.value,
|
|
copies=1,
|
|
repeat_delay=0,
|
|
) -> None:
|
|
"""
|
|
Send (transmit) supplied frame to Modem
|
|
|
|
:param frame_to_tx: Frame data to send
|
|
:type frame_to_tx: list of bytearrays
|
|
:param c2_mode: Codec2 mode to use, defaults to datac13
|
|
:type c2_mode: int, optional
|
|
:param copies: Number of frame copies to send, defaults to 1
|
|
:type copies: int, optional
|
|
:param repeat_delay: Delay time before sending repeat frame, defaults to 0
|
|
:type repeat_delay: int, optional
|
|
"""
|
|
frame_type = FR_TYPE(int.from_bytes(frame_to_tx[0][:1], byteorder="big")).name
|
|
self.log.debug("[Modem] enqueue_frame_for_tx", c2_mode=FREEDV_MODE(c2_mode).name, data=frame_to_tx,
|
|
type=frame_type)
|
|
|
|
MODEM_TRANSMIT_QUEUE.put([c2_mode, copies, repeat_delay, frame_to_tx])
|
|
|
|
# Wait while transmitting
|
|
while self.states.is_transmitting:
|
|
threading.Event().wait(0.01)
|
|
|
|
def transmit_cq(self) -> None:
|
|
"""
|
|
Transmit a CQ
|
|
Args:
|
|
self
|
|
|
|
Returns:
|
|
Nothing
|
|
"""
|
|
self.log.info("[Modem] CQ CQ CQ")
|
|
|
|
event_data = json.dumps({
|
|
"freedata": "modem-message",
|
|
"cq": "transmitting",
|
|
"mycallsign": str(self.mycallsign, "UTF-8"),
|
|
"dxcallsign": "None"
|
|
})
|
|
# finally push data to our network queue
|
|
self.event_queue.put(event_data)
|
|
|
|
|
|
cq_frame = bytearray(self.length_sig0_frame)
|
|
cq_frame[:1] = bytes([FR_TYPE.CQ.value])
|
|
cq_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign)
|
|
cq_frame[7:11] = helpers.encode_grid(self.mygrid)
|
|
|
|
self.log.debug("[Modem] CQ Frame:", data=[cq_frame])
|
|
|
|
if self.enable_fsk:
|
|
self.log.info("[Modem] ENABLE FSK", state=self.enable_fsk)
|
|
self.enqueue_frame_for_tx([cq_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value)
|
|
else:
|
|
self.enqueue_frame_for_tx([cq_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0)
|
|
|
|
def received_cq(self, data_in: bytes, snr) -> None:
|
|
"""
|
|
Called when we receive a CQ frame
|
|
Args:
|
|
data_in:bytes:
|
|
|
|
Returns:
|
|
Nothing
|
|
"""
|
|
# here we add the received station to the heard stations buffer
|
|
dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7]))
|
|
self.log.debug("[Modem] received_cq:", dxcallsign=dxcallsign)
|
|
self.dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8")
|
|
|
|
|
|
event_data = json.dumps({
|
|
"freedata": "modem-message",
|
|
"cq": "received",
|
|
"mycallsign": str(self.mycallsign, "UTF-8"),
|
|
"dxcallsign": str(dxcallsign, "UTF-8"),
|
|
"dxgrid":str(self.dxgrid, "UTF-8"),
|
|
})
|
|
# finally push data to our network queue
|
|
self.event_queue.put(event_data)
|
|
|
|
self.log.info(
|
|
"[Modem] CQ RCVD ["
|
|
+ str(dxcallsign, "UTF-8")
|
|
+ "]["
|
|
+ str(self.dxgrid, "UTF-8")
|
|
+ "] ",
|
|
snr=snr,
|
|
)
|
|
helpers.add_to_heard_stations(
|
|
dxcallsign,
|
|
self.dxgrid,
|
|
"CQ CQ CQ",
|
|
snr,
|
|
self.modem_frequency_offset,
|
|
self.states.radio_frequency,
|
|
self.states.heard_stations
|
|
)
|
|
|
|
if self.respond_to_cq and self.respond_to_call:
|
|
self.transmit_qrv(dxcallsign, snr)
|
|
|
|
def transmit_qrv(self, dxcallsign: bytes, snr) -> None:
|
|
"""
|
|
Called when we send a QRV frame
|
|
Args:
|
|
self,
|
|
dxcallsign
|
|
|
|
"""
|
|
# Sleep a random amount of time before responding to make it more likely to be
|
|
# heard when many stations respond. Each DATAC0 frame is 0.44 sec (440ms) in
|
|
# duration, plus overhead. Set the wait interval to be random between 0 and
|
|
# self.duration_sig1_frame * 4 == 4 slots
|
|
# in self.duration_sig1_frame increments.
|
|
# FIXME This causes problems when running ctests - we need to figure out why
|
|
if not TESTMODE:
|
|
self.log.info("[Modem] Waiting for QRV slot...")
|
|
helpers.wait(randrange(0, int(self.duration_sig1_frame * 4), self.duration_sig1_frame * 10 // 10.0))
|
|
|
|
|
|
event_data = json.dumps({
|
|
"freedata": "modem-message",
|
|
"qrv": "transmitting",
|
|
"dxcallsign": str(dxcallsign, "UTF-8"),
|
|
})
|
|
# finally push data to our network queue
|
|
self.event_queue.put(event_data)
|
|
|
|
self.log.info("[Modem] Sending QRV!")
|
|
print(self.mycallsign)
|
|
qrv_frame = bytearray(self.length_sig0_frame)
|
|
qrv_frame[:1] = bytes([FR_TYPE.QRV.value])
|
|
qrv_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign)
|
|
qrv_frame[7:11] = helpers.encode_grid(self.mygrid)
|
|
qrv_frame[11:12] = helpers.snr_to_bytes(snr)
|
|
|
|
if self.enable_fsk:
|
|
self.log.info("[Modem] ENABLE FSK", state=self.enable_fsk)
|
|
self.enqueue_frame_for_tx([qrv_frame], c2_mode=FREEDV_MODE.fsk_ldpc_0.value)
|
|
else:
|
|
self.enqueue_frame_for_tx([qrv_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1, repeat_delay=0)
|
|
|
|
def received_qrv(self, data_in: bytes, snr) -> None:
|
|
"""
|
|
Called when we receive a QRV frame
|
|
Args:
|
|
data_in:bytes:
|
|
|
|
"""
|
|
# here we add the received station to the heard stations buffer
|
|
dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7]))
|
|
self.dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8")
|
|
dxsnr = helpers.snr_from_bytes(data_in[11:12])
|
|
|
|
combined_snr = f"{snr}/{dxsnr}"
|
|
|
|
event_data = json.dumps({
|
|
"freedata": "modem-message",
|
|
"qrv": "received",
|
|
"dxcallsign": str(dxcallsign, "UTF-8"),
|
|
"dxgrid":str(self.dxgrid, "UTF-8"),
|
|
"snr":str(snr),
|
|
"dxsnr": str(dxsnr)
|
|
})
|
|
# finally push data to our network queue
|
|
self.event_queue.put(event_data)
|
|
|
|
self.log.info(
|
|
"[Modem] QRV RCVD ["
|
|
+ str(dxcallsign, "UTF-8")
|
|
+ "]["
|
|
+ str(self.dxgrid, "UTF-8")
|
|
+ "] ",
|
|
snr=snr,
|
|
dxsnr=dxsnr
|
|
)
|
|
helpers.add_to_heard_stations(
|
|
dxcallsign,
|
|
self.dxgrid,
|
|
"QRV",
|
|
combined_snr,
|
|
self.modem_frequency_offset,
|
|
self.states.radio_frequency,
|
|
self.states.heard_stations
|
|
)
|
|
|
|
|
|
|
|
def received_is_writing(self, data_in: bytes, snr) -> None:
|
|
"""
|
|
Called when we receive a IS WRITING frame
|
|
Args:
|
|
data_in:bytes:
|
|
|
|
"""
|
|
# here we add the received station to the heard stations buffer
|
|
dxcallsign = helpers.bytes_to_callsign(bytes(data_in[1:7]))
|
|
|
|
self.send_data_to_socket_queue(
|
|
freedata="modem-message",
|
|
fec="is_writing",
|
|
dxcallsign=str(dxcallsign, "UTF-8")
|
|
)
|
|
|
|
self.log.info(
|
|
"[Modem] IS_WRITING RCVD ["
|
|
+ str(dxcallsign, "UTF-8")
|
|
+ "] ",
|
|
)
|
|
|
|
|
|
# ----------- BROADCASTS
|
|
def run_beacon(self) -> None:
|
|
"""
|
|
Controlling function for running a beacon
|
|
Args:
|
|
|
|
self: arq class
|
|
|
|
Returns:
|
|
|
|
"""
|
|
try:
|
|
while True:
|
|
threading.Event().wait(0.5)
|
|
while self.states.is_beacon_running:
|
|
if (
|
|
not self.states.is_arq_session
|
|
and not self.arq_file_transfer
|
|
and not self.beacon_paused
|
|
#and not self.states.channel_busy
|
|
and not self.states.is_modem_busy
|
|
and not self.states.is_arq_state
|
|
):
|
|
self.send_data_to_socket_queue(
|
|
freedata="modem-message",
|
|
beacon="transmitting",
|
|
dxcallsign="None",
|
|
interval=self.beacon_interval,
|
|
)
|
|
self.log.info(
|
|
"[Modem] Sending beacon!", interval=self.beacon_interval
|
|
)
|
|
|
|
beacon_frame = bytearray(self.length_sig0_frame)
|
|
beacon_frame[:1] = bytes([FR_TYPE.BEACON.value])
|
|
beacon_frame[1:7] = helpers.callsign_to_bytes(self.mycallsign)
|
|
beacon_frame[7:11] = helpers.encode_grid(self.mygrid)
|
|
|
|
if self.enable_fsk:
|
|
self.log.info("[Modem] ENABLE FSK", state=self.enable_fsk)
|
|
self.enqueue_frame_for_tx(
|
|
[beacon_frame],
|
|
c2_mode=FREEDV_MODE.fsk_ldpc_0.value,
|
|
)
|
|
else:
|
|
self.enqueue_frame_for_tx([beacon_frame], c2_mode=FREEDV_MODE.sig0.value, copies=1,
|
|
repeat_delay=0)
|
|
if self.enable_morse_identifier:
|
|
MODEM_TRANSMIT_QUEUE.put(["morse", 1, 0, self.mycallsign])
|
|
|
|
self.beacon_interval_timer = time.time() + self.beacon_interval
|
|
while (
|
|
time.time() < self.beacon_interval_timer
|
|
and self.states.is_beacon_running
|
|
and not self.beacon_paused
|
|
):
|
|
threading.Event().wait(0.01)
|
|
|
|
except Exception as err:
|
|
self.log.debug("[Modem] run_beacon: ", exception=err)
|
|
|
|
def received_beacon(self, data_in: bytes, snr) -> None:
|
|
"""
|
|
Called if we received a beacon
|
|
Args:
|
|
data_in:bytes:
|
|
|
|
"""
|
|
# here we add the received station to the heard stations buffer
|
|
beacon_callsign = helpers.bytes_to_callsign(bytes(data_in[1:7]))
|
|
self.dxgrid = bytes(helpers.decode_grid(data_in[7:11]), "UTF-8")
|
|
self.send_data_to_socket_queue(
|
|
freedata="modem-message",
|
|
beacon="received",
|
|
uuid=str(uuid.uuid4()),
|
|
timestamp=int(time.time()),
|
|
dxcallsign=str(beacon_callsign, "UTF-8"),
|
|
dxgrid=str(self.dxgrid, "UTF-8"),
|
|
snr=str(snr),
|
|
)
|
|
|
|
self.log.info(
|
|
"[Modem] BEACON RCVD ["
|
|
+ str(beacon_callsign, "UTF-8")
|
|
+ "]["
|
|
+ str(self.dxgrid, "UTF-8")
|
|
+ "] ",
|
|
snr=snr,
|
|
)
|
|
helpers.add_to_heard_stations(
|
|
beacon_callsign,
|
|
self.dxgrid,
|
|
"BEACON",
|
|
snr,
|
|
self.modem_frequency_offset,
|
|
self.states.radio_frequency,
|
|
self.states.heard_stations
|
|
) |