FreeDATA/modem/arq_session.py

184 lines
6.6 KiB
Python
Raw Normal View History

2024-02-24 20:49:53 +00:00
import datetime
2024-03-24 19:07:18 +00:00
import threading
2023-12-06 10:59:35 +00:00
import codec2
2023-12-05 17:50:39 +00:00
import data_frame_factory
2023-12-05 18:12:21 +00:00
import structlog
2023-12-19 14:01:08 +00:00
from event_manager import EventManager
2023-12-14 16:29:04 +00:00
from modem_frametypes import FRAME_TYPE
import time
from arq_data_type_handler import ARQDataTypeHandler
2024-01-20 20:47:21 +00:00
2023-12-05 17:50:39 +00:00
2024-03-24 19:07:18 +00:00
class ARQSession:
2023-12-05 17:50:39 +00:00
2023-12-14 21:53:32 +00:00
SPEED_LEVEL_DICT = {
0: {
'mode': codec2.FREEDV_MODE.datac4,
'min_snr': -10,
'duration_per_frame': 5.17,
'bandwidth': 250,
2023-12-14 21:53:32 +00:00
},
1: {
'mode': codec2.FREEDV_MODE.datac3,
'min_snr': 0,
'duration_per_frame': 3.19,
'bandwidth': 563,
2023-12-14 21:53:32 +00:00
},
2: {
'mode': codec2.FREEDV_MODE.datac1,
'min_snr': 3,
'duration_per_frame': 4.18,
'bandwidth': 1700,
2023-12-14 21:53:32 +00:00
},
}
def __init__(self, config: dict, modem, dxcall: str):
2023-12-05 18:12:21 +00:00
self.logger = structlog.get_logger(type(self).__name__)
2023-12-05 17:50:39 +00:00
self.config = config
2023-12-19 14:01:08 +00:00
self.event_manager: EventManager = modem.event_manager
self.states = modem.states
2024-02-02 18:37:02 +00:00
self.states.setARQ(True)
2023-12-19 14:01:08 +00:00
2023-12-14 16:29:04 +00:00
self.snr = []
2023-12-05 17:50:39 +00:00
self.dxcall = dxcall
2023-12-14 16:29:04 +00:00
self.dx_snr = []
2023-12-05 17:50:39 +00:00
self.modem = modem
2023-12-05 17:50:39 +00:00
self.speed_level = 0
self.previous_speed_level = 0
2023-12-14 16:29:04 +00:00
self.frames_per_burst = 1
2023-12-05 17:50:39 +00:00
self.frame_factory = data_frame_factory.DataFrameFactory(self.config)
2023-12-14 16:29:04 +00:00
self.event_frame_received = threading.Event()
2023-12-05 17:50:39 +00:00
2024-02-02 18:37:02 +00:00
self.arq_data_type_handler = ARQDataTypeHandler(self.event_manager, self.states)
2023-12-05 17:50:39 +00:00
self.id = None
self.session_started = time.time()
self.session_ended = 0
self.session_max_age = 500
2023-12-05 17:50:39 +00:00
2024-02-24 20:49:53 +00:00
# histogram lists for storing statistics
self.snr_histogram = []
self.bpm_histogram = []
self.time_histogram = []
2024-03-24 19:07:18 +00:00
def log(self, message, isWarning=False):
2024-01-05 15:25:14 +00:00
msg = f"[{type(self).__name__}][id={self.id}][state={self.state}]: {message}"
2023-12-11 18:02:50 +00:00
logger = self.logger.warn if isWarning else self.logger.info
logger(msg)
2023-12-10 08:59:02 +00:00
2023-12-05 17:50:39 +00:00
def get_mode_by_speed_level(self, speed_level):
2023-12-14 21:53:32 +00:00
return self.SPEED_LEVEL_DICT[speed_level]["mode"]
2023-12-05 17:50:39 +00:00
2023-12-15 13:58:53 +00:00
def transmit_frame(self, frame: bytearray, mode='auto'):
2023-12-11 18:02:50 +00:00
self.log("Transmitting frame")
2023-12-15 13:58:53 +00:00
if mode in ['auto']:
2023-12-15 15:22:38 +00:00
mode = self.get_mode_by_speed_level(self.speed_level)
2023-12-15 13:41:11 +00:00
self.modem.transmit(mode, 1, 1, frame)
2023-12-05 18:01:48 +00:00
2023-12-13 13:33:09 +00:00
def set_state(self, state):
2023-12-19 14:01:08 +00:00
if self.state == state:
self.log(f"{type(self).__name__} state {self.state.name} unchanged.")
else:
self.log(f"{type(self).__name__} state change from {self.state.name} to {state.name}")
2023-12-05 18:12:21 +00:00
self.state = state
2023-12-06 10:59:35 +00:00
2023-12-14 16:29:04 +00:00
def get_data_payload_size(self):
return self.frame_factory.get_available_data_payload_for_mode(
FRAME_TYPE.ARQ_BURST_FRAME,
2023-12-14 21:53:32 +00:00
self.SPEED_LEVEL_DICT[self.speed_level]["mode"]
2023-12-14 16:29:04 +00:00
)
2023-12-12 21:33:17 +00:00
def set_details(self, snr, frequency_offset):
2024-02-24 20:49:53 +00:00
self.snr = snr
2023-12-12 21:33:17 +00:00
self.frequency_offset = frequency_offset
2023-12-14 16:29:04 +00:00
def on_frame_received(self, frame):
self.event_frame_received.set()
self.log(f"Received {frame['frame_type']}")
2023-12-14 16:29:04 +00:00
frame_type = frame['frame_type_int']
2024-03-24 19:07:18 +00:00
if self.state in self.STATE_TRANSITION and frame_type in self.STATE_TRANSITION[self.state]:
action_name = self.STATE_TRANSITION[self.state][frame_type]
received_data, type_byte = getattr(self, action_name)(frame)
if isinstance(received_data, bytearray) and isinstance(type_byte, int):
self.arq_data_type_handler.dispatch(type_byte, received_data, self.update_histograms(len(received_data), len(received_data)))
return
2023-12-14 16:29:04 +00:00
2024-01-20 20:47:21 +00:00
self.log(f"Ignoring unknown transition from state {self.state.name} with frame {frame['frame_type']}")
2023-12-21 14:05:22 +00:00
def is_session_outdated(self):
session_alivetime = time.time() - self.session_max_age
2024-03-24 19:07:18 +00:00
return self.session_ended < session_alivetime and self.state.name in [
'FAILED',
'ENDED',
'ABORTED',
]
def calculate_session_duration(self):
2024-02-24 20:49:53 +00:00
if self.session_ended == 0:
return time.time() - self.session_started
return self.session_ended - self.session_started
2024-02-27 21:39:45 +00:00
def calculate_session_statistics(self, confirmed_bytes, total_bytes):
duration = self.calculate_session_duration()
2024-03-24 19:07:18 +00:00
# total_bytes = self.total_length
# self.total_length
duration_in_minutes = duration / 60 # Convert duration from seconds to minutes
# Calculate bytes per minute
if duration_in_minutes > 0:
2024-02-27 21:39:45 +00:00
bytes_per_minute = int(confirmed_bytes / duration_in_minutes)
else:
bytes_per_minute = 0
2024-02-24 20:49:53 +00:00
# Convert histograms lists to dictionaries
2024-03-24 19:07:18 +00:00
time_histogram_dict = dict(enumerate(self.time_histogram))
snr_histogram_dict = dict(enumerate(self.snr_histogram))
bpm_histogram_dict = dict(enumerate(self.bpm_histogram))
2024-02-24 20:49:53 +00:00
return {
2024-02-24 20:49:53 +00:00
'total_bytes': total_bytes,
'duration': duration,
'bytes_per_minute': bytes_per_minute,
'time_histogram': time_histogram_dict,
'snr_histogram': snr_histogram_dict,
'bpm_histogram': bpm_histogram_dict,
}
2024-02-27 21:39:45 +00:00
def update_histograms(self, confirmed_bytes, total_bytes):
stats = self.calculate_session_statistics(confirmed_bytes, total_bytes)
2024-02-24 20:49:53 +00:00
self.snr_histogram.append(self.snr)
self.bpm_histogram.append(stats['bytes_per_minute'])
self.time_histogram.append(datetime.datetime.now().isoformat())
2024-03-15 12:28:21 +00:00
# Limit the size of each histogram to the last 20 entries
self.snr_histogram = self.snr_histogram[-20:]
self.bpm_histogram = self.bpm_histogram[-20:]
self.time_histogram = self.time_histogram[-20:]
2024-02-27 21:39:45 +00:00
return stats
2024-02-22 14:05:54 +00:00
def get_appropriate_speed_level(self, snr, maximum_bandwidth=None):
if maximum_bandwidth is None:
maximum_bandwidth = self.config['MODEM']['maximum_bandwidth']
# Adjust maximum_bandwidth based on special conditions or invalid configurations
if maximum_bandwidth == 0:
# Use the maximum available bandwidth from the speed level dictionary
maximum_bandwidth = max(details['bandwidth'] for details in self.SPEED_LEVEL_DICT.values())
# Initialize appropriate_speed_level to the lowest level that meets the minimum criteria
2024-02-22 14:05:54 +00:00
appropriate_speed_level = min(self.SPEED_LEVEL_DICT.keys())
2024-02-22 14:05:54 +00:00
for level, details in self.SPEED_LEVEL_DICT.items():
if snr >= details['min_snr'] and details['bandwidth'] <= maximum_bandwidth and level > appropriate_speed_level:
appropriate_speed_level = level
2024-03-24 19:07:18 +00:00
return appropriate_speed_level