2023-12-05 14:40:04 +00:00
|
|
|
import sys
|
2024-01-15 15:04:11 +00:00
|
|
|
import time
|
2023-12-05 14:40:04 +00:00
|
|
|
sys.path.append('modem')
|
|
|
|
|
|
|
|
import unittest
|
2023-12-17 00:43:23 +00:00
|
|
|
import unittest.mock
|
2023-12-05 14:40:04 +00:00
|
|
|
from config import CONFIG
|
|
|
|
import helpers
|
|
|
|
import queue
|
|
|
|
import threading
|
|
|
|
import base64
|
|
|
|
from command_arq_raw import ARQRawCommand
|
|
|
|
from state_manager import StateManager
|
|
|
|
from frame_dispatcher import DISPATCHER
|
2023-12-06 10:32:59 +00:00
|
|
|
import random
|
|
|
|
import structlog
|
2023-12-17 11:31:44 +00:00
|
|
|
import numpy as np
|
2023-12-19 14:01:08 +00:00
|
|
|
from event_manager import EventManager
|
2024-01-28 20:29:56 +00:00
|
|
|
from state_manager import StateManager
|
2023-12-20 11:26:28 +00:00
|
|
|
from data_frame_factory import DataFrameFactory
|
2023-12-26 20:52:06 +00:00
|
|
|
import codec2
|
2024-01-15 15:04:11 +00:00
|
|
|
import arq_session_irs
|
2023-12-15 23:51:57 +00:00
|
|
|
class TestModem:
|
2024-01-28 20:29:56 +00:00
|
|
|
def __init__(self, event_q, state_q):
|
2023-12-15 23:51:57 +00:00
|
|
|
self.data_queue_received = queue.Queue()
|
2023-12-17 00:43:23 +00:00
|
|
|
self.demodulator = unittest.mock.Mock()
|
2023-12-19 22:01:18 +00:00
|
|
|
self.event_manager = EventManager([event_q])
|
2023-12-26 20:52:06 +00:00
|
|
|
self.logger = structlog.get_logger('Modem')
|
2024-01-28 20:29:56 +00:00
|
|
|
self.states = StateManager(state_q)
|
2023-12-26 20:52:06 +00:00
|
|
|
|
|
|
|
def getFrameTransmissionTime(self, mode):
|
|
|
|
samples = 0
|
|
|
|
c2instance = codec2.open_instance(mode.value)
|
|
|
|
samples += codec2.api.freedv_get_n_tx_preamble_modem_samples(c2instance)
|
|
|
|
samples += codec2.api.freedv_get_n_tx_modem_samples(c2instance)
|
|
|
|
samples += codec2.api.freedv_get_n_tx_postamble_modem_samples(c2instance)
|
|
|
|
time = samples / 8000
|
|
|
|
return time
|
2023-12-15 23:51:57 +00:00
|
|
|
|
|
|
|
def transmit(self, mode, repeats: int, repeat_delay: int, frames: bytearray) -> bool:
|
2023-12-26 20:52:06 +00:00
|
|
|
|
|
|
|
# Simulate transmission time
|
|
|
|
tx_time = self.getFrameTransmissionTime(mode) + 0.1 # PTT
|
|
|
|
self.logger.info(f"TX {tx_time} seconds...")
|
|
|
|
threading.Event().wait(tx_time)
|
|
|
|
|
2023-12-20 11:26:28 +00:00
|
|
|
transmission = {
|
|
|
|
'mode': mode,
|
|
|
|
'bytes': frames,
|
|
|
|
}
|
|
|
|
self.data_queue_received.put(transmission)
|
2023-12-15 23:51:57 +00:00
|
|
|
|
2023-12-05 14:40:04 +00:00
|
|
|
class TestARQSession(unittest.TestCase):
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def setUpClass(cls):
|
|
|
|
config_manager = CONFIG('modem/config.ini.example')
|
|
|
|
cls.config = config_manager.read()
|
2023-12-06 10:32:59 +00:00
|
|
|
cls.logger = structlog.get_logger("TESTS")
|
2023-12-20 11:26:28 +00:00
|
|
|
cls.frame_factory = DataFrameFactory(cls.config)
|
2023-12-06 10:32:59 +00:00
|
|
|
|
2023-12-05 14:40:04 +00:00
|
|
|
# ISS
|
|
|
|
cls.iss_state_manager = StateManager(queue.Queue())
|
2024-01-04 14:46:58 +00:00
|
|
|
cls.iss_event_manager = EventManager([queue.Queue()])
|
2023-12-05 14:40:04 +00:00
|
|
|
cls.iss_event_queue = queue.Queue()
|
2024-01-28 20:29:56 +00:00
|
|
|
cls.iss_state_queue = queue.Queue()
|
|
|
|
cls.iss_modem = TestModem(cls.iss_event_queue, cls.iss_state_queue)
|
2023-12-05 14:40:04 +00:00
|
|
|
cls.iss_frame_dispatcher = DISPATCHER(cls.config,
|
2024-01-04 14:46:58 +00:00
|
|
|
cls.iss_event_manager,
|
2023-12-05 14:40:04 +00:00
|
|
|
cls.iss_state_manager,
|
2023-12-15 23:51:57 +00:00
|
|
|
cls.iss_modem)
|
2023-12-05 14:40:04 +00:00
|
|
|
|
|
|
|
# IRS
|
|
|
|
cls.irs_state_manager = StateManager(queue.Queue())
|
2024-01-04 14:46:58 +00:00
|
|
|
cls.irs_event_manager = EventManager([queue.Queue()])
|
2023-12-05 14:40:04 +00:00
|
|
|
cls.irs_event_queue = queue.Queue()
|
2024-01-28 20:29:56 +00:00
|
|
|
cls.irs_state_queue = queue.Queue()
|
|
|
|
cls.irs_modem = TestModem(cls.irs_event_queue, cls.irs_state_queue)
|
2023-12-05 14:40:04 +00:00
|
|
|
cls.irs_frame_dispatcher = DISPATCHER(cls.config,
|
2024-01-04 14:46:58 +00:00
|
|
|
cls.irs_event_manager,
|
2023-12-05 14:40:04 +00:00
|
|
|
cls.irs_state_manager,
|
2023-12-15 23:51:57 +00:00
|
|
|
cls.irs_modem)
|
2023-12-06 10:32:59 +00:00
|
|
|
|
|
|
|
# Frame loss probability in %
|
2023-12-19 16:19:12 +00:00
|
|
|
cls.loss_probability = 30
|
2023-12-05 14:40:04 +00:00
|
|
|
|
2023-12-19 22:01:18 +00:00
|
|
|
cls.channels_running = True
|
|
|
|
|
|
|
|
def channelWorker(self, modem_transmit_queue: queue.Queue, frame_dispatcher: DISPATCHER):
|
|
|
|
while self.channels_running:
|
|
|
|
# Transfer data between both parties
|
|
|
|
try:
|
2023-12-20 11:26:28 +00:00
|
|
|
transmission = modem_transmit_queue.get(timeout=1)
|
2023-12-19 22:01:18 +00:00
|
|
|
if random.randint(0, 100) < self.loss_probability:
|
|
|
|
self.logger.info(f"[{threading.current_thread().name}] Frame lost...")
|
|
|
|
continue
|
2023-12-20 11:26:28 +00:00
|
|
|
|
|
|
|
frame_bytes = transmission['bytes']
|
2023-12-19 22:01:18 +00:00
|
|
|
frame_dispatcher.new_process_data(frame_bytes, None, len(frame_bytes), 0, 0)
|
|
|
|
except queue.Empty:
|
2023-12-06 10:47:47 +00:00
|
|
|
continue
|
2023-12-19 22:01:18 +00:00
|
|
|
self.logger.info(f"[{threading.current_thread().name}] Channel closed.")
|
|
|
|
|
|
|
|
def waitForSession(self, q, outbound = False):
|
|
|
|
key = 'arq-transfer-outbound' if outbound else 'arq-transfer-inbound'
|
|
|
|
while True:
|
|
|
|
ev = q.get()
|
2023-12-29 18:25:59 +00:00
|
|
|
if key in ev and ('success' in ev[key] or 'ABORTED' in ev[key]):
|
2023-12-19 22:01:18 +00:00
|
|
|
self.logger.info(f"[{threading.current_thread().name}] {key} session ended.")
|
|
|
|
break
|
2023-12-29 18:25:59 +00:00
|
|
|
|
2023-12-05 14:40:04 +00:00
|
|
|
def establishChannels(self):
|
2023-12-19 22:01:18 +00:00
|
|
|
self.channels_running = True
|
2023-12-05 14:40:04 +00:00
|
|
|
self.iss_to_irs_channel = threading.Thread(target=self.channelWorker,
|
2023-12-15 23:51:57 +00:00
|
|
|
args=[self.iss_modem.data_queue_received,
|
2023-12-05 14:40:04 +00:00
|
|
|
self.irs_frame_dispatcher],
|
2023-12-05 18:01:48 +00:00
|
|
|
name = "ISS to IRS channel")
|
2023-12-05 14:40:04 +00:00
|
|
|
self.iss_to_irs_channel.start()
|
|
|
|
|
|
|
|
self.irs_to_iss_channel = threading.Thread(target=self.channelWorker,
|
2023-12-15 23:51:57 +00:00
|
|
|
args=[self.irs_modem.data_queue_received,
|
2023-12-05 14:40:04 +00:00
|
|
|
self.iss_frame_dispatcher],
|
|
|
|
name = "IRS to ISS channel")
|
|
|
|
self.irs_to_iss_channel.start()
|
|
|
|
|
2023-12-19 22:01:18 +00:00
|
|
|
def waitAndCloseChannels(self):
|
|
|
|
self.waitForSession(self.iss_event_queue, True)
|
|
|
|
self.waitForSession(self.irs_event_queue, False)
|
|
|
|
self.channels_running = False
|
|
|
|
|
|
|
|
def testARQSessionSmallPayload(self):
|
2023-12-08 09:25:26 +00:00
|
|
|
# set Packet Error Rate (PER) / frame loss probability
|
2024-01-21 19:34:01 +00:00
|
|
|
self.loss_probability = 0
|
2023-12-08 09:25:26 +00:00
|
|
|
|
2023-12-05 14:40:04 +00:00
|
|
|
self.establishChannels()
|
|
|
|
params = {
|
2024-01-14 22:33:06 +00:00
|
|
|
'dxcall': "XX1XXX-1",
|
2023-12-05 14:40:04 +00:00
|
|
|
'data': base64.b64encode(bytes("Hello world!", encoding="utf-8")),
|
2024-01-21 19:34:01 +00:00
|
|
|
'type': "raw_lzma"
|
2023-12-05 14:40:04 +00:00
|
|
|
}
|
|
|
|
cmd = ARQRawCommand(self.config, self.iss_state_manager, self.iss_event_queue, params)
|
2023-12-15 23:51:57 +00:00
|
|
|
cmd.run(self.iss_event_queue, self.iss_modem)
|
2023-12-19 22:01:18 +00:00
|
|
|
self.waitAndCloseChannels()
|
2024-01-15 15:04:11 +00:00
|
|
|
del cmd
|
2023-12-17 11:31:44 +00:00
|
|
|
|
2023-12-26 20:52:06 +00:00
|
|
|
def DisabledtestARQSessionLargePayload(self):
|
2023-12-17 11:31:44 +00:00
|
|
|
# set Packet Error Rate (PER) / frame loss probability
|
2023-12-26 20:52:06 +00:00
|
|
|
self.loss_probability = 0
|
2023-12-17 11:31:44 +00:00
|
|
|
|
|
|
|
self.establishChannels()
|
|
|
|
params = {
|
2024-01-14 22:33:06 +00:00
|
|
|
'dxcall': "XX1XXX-1",
|
2023-12-17 11:31:44 +00:00
|
|
|
'data': base64.b64encode(np.random.bytes(1000)),
|
2024-01-21 19:34:01 +00:00
|
|
|
'type': "raw_lzma"
|
2023-12-17 11:31:44 +00:00
|
|
|
}
|
|
|
|
cmd = ARQRawCommand(self.config, self.iss_state_manager, self.iss_event_queue, params)
|
|
|
|
cmd.run(self.iss_event_queue, self.iss_modem)
|
|
|
|
|
2023-12-19 22:01:18 +00:00
|
|
|
self.waitAndCloseChannels()
|
2024-01-15 15:04:11 +00:00
|
|
|
del cmd
|
2023-12-17 11:31:44 +00:00
|
|
|
|
2023-12-28 21:47:17 +00:00
|
|
|
def testARQSessionAbortTransmissionISS(self):
|
|
|
|
# set Packet Error Rate (PER) / frame loss probability
|
|
|
|
self.loss_probability = 0
|
|
|
|
|
|
|
|
self.establishChannels()
|
|
|
|
params = {
|
2024-01-14 22:33:06 +00:00
|
|
|
'dxcall': "XX1XXX-1",
|
2023-12-28 21:47:17 +00:00
|
|
|
'data': base64.b64encode(np.random.bytes(100)),
|
|
|
|
}
|
|
|
|
cmd = ARQRawCommand(self.config, self.iss_state_manager, self.iss_event_queue, params)
|
|
|
|
cmd.run(self.iss_event_queue, self.iss_modem)
|
|
|
|
|
|
|
|
threading.Event().wait(np.random.randint(1,10))
|
|
|
|
for id in self.iss_state_manager.arq_iss_sessions:
|
|
|
|
self.iss_state_manager.arq_iss_sessions[id].abort_transmission()
|
|
|
|
|
|
|
|
self.waitAndCloseChannels()
|
2024-01-15 15:04:11 +00:00
|
|
|
del cmd
|
2023-12-28 21:47:17 +00:00
|
|
|
|
|
|
|
def testARQSessionAbortTransmissionIRS(self):
|
|
|
|
# set Packet Error Rate (PER) / frame loss probability
|
|
|
|
self.loss_probability = 0
|
|
|
|
|
|
|
|
self.establishChannels()
|
|
|
|
params = {
|
2024-01-14 22:33:06 +00:00
|
|
|
'dxcall': "XX1XXX-1",
|
2023-12-28 21:47:17 +00:00
|
|
|
'data': base64.b64encode(np.random.bytes(100)),
|
|
|
|
}
|
|
|
|
cmd = ARQRawCommand(self.config, self.iss_state_manager, self.iss_event_queue, params)
|
|
|
|
cmd.run(self.iss_event_queue, self.iss_modem)
|
|
|
|
|
|
|
|
threading.Event().wait(np.random.randint(1,10))
|
|
|
|
for id in self.irs_state_manager.arq_irs_sessions:
|
|
|
|
self.irs_state_manager.arq_irs_sessions[id].abort_transmission()
|
|
|
|
|
|
|
|
self.waitAndCloseChannels()
|
2024-01-15 15:04:11 +00:00
|
|
|
del cmd
|
|
|
|
|
|
|
|
def testSessionCleanupISS(self):
|
|
|
|
|
|
|
|
params = {
|
|
|
|
'dxcall': "XX1XXX-1",
|
|
|
|
'data': base64.b64encode(np.random.bytes(100)),
|
|
|
|
}
|
|
|
|
cmd = ARQRawCommand(self.config, self.iss_state_manager, self.iss_event_queue, params)
|
|
|
|
cmd.run(self.iss_event_queue, self.iss_modem)
|
|
|
|
for session_id in self.iss_state_manager.arq_iss_sessions:
|
|
|
|
session = self.iss_state_manager.arq_iss_sessions[session_id]
|
|
|
|
ISS_States = session.state_enum
|
|
|
|
session.state = ISS_States.FAILED
|
|
|
|
session.session_ended = time.time() - 1000
|
|
|
|
if session.is_session_outdated():
|
|
|
|
self.logger.info(f"session [{session_id}] outdated - deleting it")
|
|
|
|
self.iss_state_manager.remove_arq_iss_session(session_id)
|
|
|
|
break
|
|
|
|
del cmd
|
|
|
|
|
|
|
|
def testSessionCleanupIRS(self):
|
|
|
|
session = arq_session_irs.ARQSessionIRS(self.config,
|
|
|
|
self.irs_modem,
|
|
|
|
'AA1AAA-1',
|
|
|
|
random.randint(0, 255))
|
|
|
|
self.irs_state_manager.register_arq_irs_session(session)
|
|
|
|
for session_id in self.irs_state_manager.arq_irs_sessions:
|
|
|
|
session = self.irs_state_manager.arq_irs_sessions[session_id]
|
|
|
|
irs_States = session.state_enum
|
|
|
|
session.state = irs_States.FAILED
|
|
|
|
session.session_ended = time.time() - 1000
|
|
|
|
if session.is_session_outdated():
|
|
|
|
self.logger.info(f"session [{session_id}] outdated - deleting it")
|
|
|
|
self.irs_state_manager.remove_arq_irs_session(session_id)
|
|
|
|
break
|
2023-12-28 21:47:17 +00:00
|
|
|
|
2023-12-05 14:40:04 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
unittest.main()
|