added arq transmission failed state

This commit is contained in:
DJ2LS 2024-01-29 17:50:28 +01:00
parent f5a3f96520
commit e279f45229
9 changed files with 96 additions and 45 deletions

View file

@ -48,7 +48,7 @@
<div class="card-body"> <div class="card-body">
<p class="card-text">{{ message.body }}</p> <p class="card-text">{{ message.body }}</p>
</div> </div>
<small>>>> Status: {{message.status}}</small>
<div class="card-footer p-0 bg-secondary border-top-0"> <div class="card-footer p-0 bg-secondary border-top-0">
<p class="text p-0 m-0 me-1 text-end">{{ getDateTime }}</p> <p class="text p-0 m-0 me-1 text-end">{{ getDateTime }}</p>
<!-- Display formatted timestamp in card-footer --> <!-- Display formatted timestamp in card-footer -->

View file

@ -2,7 +2,7 @@ import re
def validate_freedata_callsign(callsign): def validate_freedata_callsign(callsign):
#regexp = "^[a-zA-Z]+\d+\w+-\d{1,2}$" #regexp = "^[a-zA-Z]+\d+\w+-\d{1,2}$"
regexp = "^[A-Za-z0-9]{1,7}-[0-9]$" regexp = "^[A-Za-z0-9]{1,7}-[0-9]$" # still broken - we need to allow all ssids form 0 - 255
return re.compile(regexp).match(callsign) is not None return re.compile(regexp).match(callsign) is not None
def validate_message_attachment(attachment): def validate_message_attachment(attachment):

View file

@ -3,7 +3,7 @@
import structlog import structlog
import lzma import lzma
import gzip import gzip
from message_p2p import message_received from message_p2p import message_received, message_failed
class ARQDataTypeHandler: class ARQDataTypeHandler:
def __init__(self, event_manager): def __init__(self, event_manager):
@ -12,19 +12,23 @@ class ARQDataTypeHandler:
self.handlers = { self.handlers = {
"raw": { "raw": {
'prepare': self.prepare_raw, 'prepare': self.prepare_raw,
'handle': self.handle_raw 'handle': self.handle_raw,
'failed': self.failed_raw,
}, },
"raw_lzma": { "raw_lzma": {
'prepare': self.prepare_raw_lzma, 'prepare': self.prepare_raw_lzma,
'handle': self.handle_raw_lzma 'handle': self.handle_raw_lzma,
'failed': self.failed_raw_lzma,
}, },
"raw_gzip": { "raw_gzip": {
'prepare': self.prepare_raw_gzip, 'prepare': self.prepare_raw_gzip,
'handle': self.handle_raw_gzip 'handle': self.handle_raw_gzip,
'failed': self.failed_raw_gzip,
}, },
"p2pmsg_lzma": { "p2pmsg_lzma": {
'prepare': self.prepare_p2pmsg_lzma, 'prepare': self.prepare_p2pmsg_lzma,
'handle': self.handle_p2pmsg_lzma 'handle': self.handle_p2pmsg_lzma,
'failed' : self.failed_p2pmsg_lzma,
}, },
} }
@ -35,6 +39,13 @@ class ARQDataTypeHandler:
else: else:
self.log(f"Unknown handling endpoint: {endpoint_name}", isWarning=True) self.log(f"Unknown handling endpoint: {endpoint_name}", isWarning=True)
def failed(self, type_byte: int, data: bytearray):
endpoint_name = list(self.handlers.keys())[type_byte]
if endpoint_name in self.handlers and 'failed' in self.handlers[endpoint_name]:
return self.handlers[endpoint_name]['failed'](data)
else:
self.log(f"Unknown handling endpoint: {endpoint_name}", isWarning=True)
def prepare(self, data: bytearray, endpoint_name="raw" ): def prepare(self, data: bytearray, endpoint_name="raw" ):
if endpoint_name in self.handlers and 'prepare' in self.handlers[endpoint_name]: if endpoint_name in self.handlers and 'prepare' in self.handlers[endpoint_name]:
return self.handlers[endpoint_name]['prepare'](data), list(self.handlers.keys()).index(endpoint_name) return self.handlers[endpoint_name]['prepare'](data), list(self.handlers.keys()).index(endpoint_name)
@ -54,6 +65,9 @@ class ARQDataTypeHandler:
self.log(f"Handling uncompressed data: {len(data)} Bytes") self.log(f"Handling uncompressed data: {len(data)} Bytes")
return data return data
def failed_raw(self, data):
return
def prepare_raw_lzma(self, data): def prepare_raw_lzma(self, data):
compressed_data = lzma.compress(data) compressed_data = lzma.compress(data)
self.log(f"Preparing LZMA compressed data: {len(data)} Bytes >>> {len(compressed_data)} Bytes") self.log(f"Preparing LZMA compressed data: {len(data)} Bytes >>> {len(compressed_data)} Bytes")
@ -64,6 +78,9 @@ class ARQDataTypeHandler:
self.log(f"Handling LZMA compressed data: {len(decompressed_data)} Bytes from {len(data)} Bytes") self.log(f"Handling LZMA compressed data: {len(decompressed_data)} Bytes from {len(data)} Bytes")
return decompressed_data return decompressed_data
def failed_raw_lzma(self, data):
return
def prepare_raw_gzip(self, data): def prepare_raw_gzip(self, data):
compressed_data = gzip.compress(data) compressed_data = gzip.compress(data)
self.log(f"Preparing GZIP compressed data: {len(data)} Bytes >>> {len(compressed_data)} Bytes") self.log(f"Preparing GZIP compressed data: {len(data)} Bytes >>> {len(compressed_data)} Bytes")
@ -74,6 +91,9 @@ class ARQDataTypeHandler:
self.log(f"Handling GZIP compressed data: {len(decompressed_data)} Bytes from {len(data)} Bytes") self.log(f"Handling GZIP compressed data: {len(decompressed_data)} Bytes from {len(data)} Bytes")
return decompressed_data return decompressed_data
def failed_raw_gzip(self, data):
return
def prepare_p2pmsg_lzma(self, data): def prepare_p2pmsg_lzma(self, data):
compressed_data = lzma.compress(data) compressed_data = lzma.compress(data)
self.log(f"Preparing LZMA compressed P2PMSG data: {len(data)} Bytes >>> {len(compressed_data)} Bytes") self.log(f"Preparing LZMA compressed P2PMSG data: {len(data)} Bytes >>> {len(compressed_data)} Bytes")
@ -84,3 +104,9 @@ class ARQDataTypeHandler:
self.log(f"Handling LZMA compressed P2PMSG data: {len(decompressed_data)} Bytes from {len(data)} Bytes") self.log(f"Handling LZMA compressed P2PMSG data: {len(decompressed_data)} Bytes from {len(data)} Bytes")
message_received(self.event_manager, decompressed_data) message_received(self.event_manager, decompressed_data)
return decompressed_data return decompressed_data
def failed_p2pmsg_lzma(self, data):
decompressed_data = lzma.decompress(data)
self.log(f"Handling failed LZMA compressed P2PMSG data: {len(decompressed_data)} Bytes from {len(data)} Bytes", isWarning=True)
message_failed(self.event_manager, decompressed_data)
return decompressed_data

View file

@ -21,7 +21,7 @@ class ISS_State(Enum):
class ARQSessionISS(arq_session.ARQSession): class ARQSessionISS(arq_session.ARQSession):
RETRIES_CONNECT = 10 RETRIES_CONNECT = 1
# DJ2LS: 3 seconds seems to be too small for radios with a too slow PTT toggle time # DJ2LS: 3 seconds seems to be too small for radios with a too slow PTT toggle time
# DJ2LS: 3.5 seconds is working well WITHOUT a channel busy detection delay # DJ2LS: 3.5 seconds is working well WITHOUT a channel busy detection delay
@ -179,6 +179,9 @@ class ARQSessionISS(arq_session.ARQSession):
self.log(f"Transmission failed!") self.log(f"Transmission failed!")
self.event_manager.send_arq_session_finished(True, self.id, self.dxcall,False, self.state.name, statistics=self.calculate_session_statistics()) self.event_manager.send_arq_session_finished(True, self.id, self.dxcall,False, self.state.name, statistics=self.calculate_session_statistics())
self.states.set("is_modem_busy", False) self.states.set("is_modem_busy", False)
self.arq_data_type_handler.failed(self.type_byte, self.data)
return None, None return None, None
def abort_transmission(self, irs_frame=None): def abort_transmission(self, irs_frame=None):

View file

@ -18,7 +18,7 @@ class SendMessageCommand(TxCommand):
def transmit(self, modem): def transmit(self, modem):
# Convert JSON string to bytes (using UTF-8 encoding) # Convert JSON string to bytes (using UTF-8 encoding)
DatabaseManager(self.event_manager).add_message(self.message.to_dict()) DatabaseManager(self.event_manager).add_message(self.message.to_dict(), direction='transmit', status='transmitting')
payload = self.message.to_payload().encode('utf-8') payload = self.message.to_payload().encode('utf-8')
json_bytearray = bytearray(payload) json_bytearray = bytearray(payload)

View file

@ -8,12 +8,18 @@ from message_system_db_manager import DatabaseManager
def message_received(event_manager, data): def message_received(event_manager, data):
decompressed_json_string = data.decode('utf-8') decompressed_json_string = data.decode('utf-8')
received_message_obj = MessageP2P.from_payload(decompressed_json_string) received_message_obj = MessageP2P.from_payload(decompressed_json_string)
received_message_dict = MessageP2P.to_dict(received_message_obj, received=True) received_message_dict = MessageP2P.to_dict(received_message_obj)
DatabaseManager(event_manager).add_message(received_message_dict) DatabaseManager(event_manager).add_message(received_message_dict, direction='receive', status='received')
def message_failed(event_manager, data):
decompressed_json_string = data.decode('utf-8')
payload_message = json.loads(decompressed_json_string)
DatabaseManager(event_manager).update_message(payload_message["id"], update_data={'status' : 'failed'})
class MessageP2P: class MessageP2P:
def __init__(self, origin: str, destination: str, body: str, attachments: list) -> None: def __init__(self, id: str, origin: str, destination: str, body: str, attachments: list) -> None:
self.id = id
self.timestamp = datetime.datetime.now().isoformat() self.timestamp = datetime.datetime.now().isoformat()
self.origin = origin self.origin = origin
self.destination = destination self.destination = destination
@ -40,13 +46,16 @@ class MessageP2P:
api_validations.validate_message_attachment(a) api_validations.validate_message_attachment(a)
attachments.append(cls.__decode_attachment__(a)) attachments.append(cls.__decode_attachment__(a))
return cls(origin, dxcall, body, attachments) timestamp = datetime.datetime.now().isoformat()
msg_id = f"{origin}_{dxcall}_{timestamp}"
return cls(msg_id, origin, dxcall, body, attachments)
@classmethod @classmethod
def from_payload(cls, payload): def from_payload(cls, payload):
payload_message = json.loads(payload) payload_message = json.loads(payload)
attachments = list(map(cls.__decode_attachment__, payload_message['attachments'])) attachments = list(map(cls.__decode_attachment__, payload_message['attachments']))
return cls(payload_message['origin'], payload_message['destination'], return cls(payload_message['id'], payload_message['origin'], payload_message['destination'],
payload_message['body'], attachments) payload_message['body'], attachments)
def get_id(self) -> str: def get_id(self) -> str:
@ -62,16 +71,15 @@ class MessageP2P:
decoded_attachment['data'] = base64.b64decode(encoded_attachment['data']) decoded_attachment['data'] = base64.b64decode(encoded_attachment['data'])
return decoded_attachment return decoded_attachment
def to_dict(self, received=False): def to_dict(self):
"""Make a dictionary out of the message data """Make a dictionary out of the message data
""" """
return { return {
'id': self.get_id(), 'id': self.id,
'origin': self.origin, 'origin': self.origin,
'destination': self.destination, 'destination': self.destination,
'body': self.body, 'body': self.body,
'direction': 'receive' if received else 'transmit',
'attachments': list(map(self.__encode_attachment__, self.attachments)), 'attachments': list(map(self.__encode_attachment__, self.attachments)),
} }

View file

@ -73,7 +73,7 @@ class DatabaseManager:
session.flush() # To get the ID immediately session.flush() # To get the ID immediately
return status return status
def add_message(self, message_data): def add_message(self, message_data, direction='receive', status=None):
session = self.get_thread_scoped_session() session = self.get_thread_scoped_session()
try: try:
# Create and add the origin and destination Stations # Create and add the origin and destination Stations
@ -81,9 +81,8 @@ class DatabaseManager:
destination = self.get_or_create_station(session, message_data['destination']) destination = self.get_or_create_station(session, message_data['destination'])
# Create and add Status if provided # Create and add Status if provided
status = None if status:
if 'status' in message_data: status = self.get_or_create_status(session, status)
status = self.get_or_create_status(session, message_data['status'])
# Parse the timestamp from the message ID # Parse the timestamp from the message ID
timestamp = datetime.fromisoformat(message_data['id'].split('_')[2]) timestamp = datetime.fromisoformat(message_data['id'].split('_')[2])
@ -94,7 +93,7 @@ class DatabaseManager:
destination_callsign=destination.callsign, destination_callsign=destination.callsign,
body=message_data['body'], body=message_data['body'],
timestamp=timestamp, timestamp=timestamp,
direction=message_data['direction'], direction=direction,
status_id=status.id if status else None status_id=status.id if status else None
) )
@ -187,6 +186,9 @@ class DatabaseManager:
# Update fields of the message as per update_data # Update fields of the message as per update_data
if 'body' in update_data: if 'body' in update_data:
message.body = update_data['body'] message.body = update_data['body']
if 'status' in update_data:
message.status = self.get_or_create_status(session, update_data['status'])
session.commit() session.commit()
self.log(f"Updated: {message_id}") self.log(f"Updated: {message_id}")
self.event_manager.freedata_message_db_change() self.event_manager.freedata_message_db_change()

View file

@ -8,6 +8,7 @@ from message_p2p import MessageP2P
from message_system_db_manager import DatabaseManager from message_system_db_manager import DatabaseManager
from event_manager import EventManager from event_manager import EventManager
import queue import queue
import base64
class TestDataFrameFactory(unittest.TestCase): class TestDataFrameFactory(unittest.TestCase):
@ -25,12 +26,13 @@ class TestDataFrameFactory(unittest.TestCase):
attachment = { attachment = {
'name': 'test.gif', 'name': 'test.gif',
'type': 'image/gif', 'type': 'image/gif',
'data': np.random.bytes(1024) 'data': str(base64.b64encode(np.random.bytes(1024)), 'utf-8')
} }
message = MessageP2P(self.mycall, 'DJ2LS-3', 'Hello World!', [attachment]) apiParams = {'dxcall': 'DJ2LS-3', 'body': 'Hello World!', 'attachments': [attachment]}
message = MessageP2P.from_api_params(self.mycall, apiParams)
payload = message.to_payload() payload = message.to_payload()
received_message = MessageP2P.from_payload(payload) received_message = MessageP2P.from_payload(payload)
received_message_dict = MessageP2P.to_dict(received_message, received=True) received_message_dict = MessageP2P.to_dict(received_message)
self.database_manager.add_message(received_message_dict) self.database_manager.add_message(received_message_dict)
result = self.database_manager.get_all_messages() result = self.database_manager.get_all_messages()
@ -40,12 +42,13 @@ class TestDataFrameFactory(unittest.TestCase):
attachment = { attachment = {
'name': 'test.gif', 'name': 'test.gif',
'type': 'image/gif', 'type': 'image/gif',
'data': np.random.bytes(1024) 'data': str(base64.b64encode(np.random.bytes(1024)), 'utf-8')
} }
message = MessageP2P(self.mycall, 'DJ2LS-3', 'Hello World!', [attachment]) apiParams = {'dxcall': 'DJ2LS-3', 'body': 'Hello World!', 'attachments': [attachment]}
message = MessageP2P.from_api_params(self.mycall, apiParams)
payload = message.to_payload() payload = message.to_payload()
received_message = MessageP2P.from_payload(payload) received_message = MessageP2P.from_payload(payload)
received_message_dict = MessageP2P.to_dict(received_message, received=True) received_message_dict = MessageP2P.to_dict(received_message)
self.database_manager.add_message(received_message_dict) self.database_manager.add_message(received_message_dict)
result = self.database_manager.get_all_messages() result = self.database_manager.get_all_messages()
@ -59,14 +62,15 @@ class TestDataFrameFactory(unittest.TestCase):
attachment = { attachment = {
'name': 'test.gif', 'name': 'test.gif',
'type': 'image/gif', 'type': 'image/gif',
'data': np.random.bytes(1024) 'data': str(base64.b64encode(np.random.bytes(1024)), 'utf-8')
} }
message = MessageP2P(self.mycall, 'DJ2LS-3', 'Hello World!', [attachment])
apiParams = {'dxcall': 'DJ2LS-3', 'body': 'Hello World!', 'attachments': [attachment]}
message = MessageP2P.from_api_params(self.mycall, apiParams)
payload = message.to_payload() payload = message.to_payload()
received_message = MessageP2P.from_payload(payload) received_message = MessageP2P.from_payload(payload)
received_message_dict = MessageP2P.to_dict(received_message, received=True) received_message_dict = MessageP2P.to_dict(received_message)
message_id = self.database_manager.add_message(received_message_dict) message_id = self.database_manager.add_message(received_message_dict, direction='receive')
self.database_manager.update_message(message_id, {'body' : 'hello123'}) self.database_manager.update_message(message_id, {'body' : 'hello123'})
result = self.database_manager.get_message_by_id(message_id) result = self.database_manager.get_message_by_id(message_id)
@ -76,22 +80,23 @@ class TestDataFrameFactory(unittest.TestCase):
attachment1 = { attachment1 = {
'name': 'test1.gif', 'name': 'test1.gif',
'type': 'image/gif', 'type': 'image/gif',
'data': np.random.bytes(1024) 'data': str(base64.b64encode(np.random.bytes(1024)), 'utf-8')
} }
attachment2 = { attachment2 = {
'name': 'test2.gif', 'name': 'test2.gif',
'type': 'image/gif', 'type': 'image/gif',
'data': np.random.bytes(1024) 'data': str(base64.b64encode(np.random.bytes(1024)), 'utf-8')
} }
attachment3 = { attachment3 = {
'name': 'test3.gif', 'name': 'test3.gif',
'type': 'image/gif', 'type': 'image/gif',
'data': np.random.bytes(1024) 'data': str(base64.b64encode(np.random.bytes(1024)), 'utf-8')
} }
message = MessageP2P(self.mycall, 'DJ2LS-3', 'Hello World!', [attachment1, attachment2, attachment3]) apiParams = {'dxcall': 'DJ2LS-3', 'body': 'Hello World!', 'attachments': [attachment1, attachment2, attachment3]}
message = MessageP2P.from_api_params(self.mycall, apiParams)
payload = message.to_payload() payload = message.to_payload()
received_message = MessageP2P.from_payload(payload) received_message = MessageP2P.from_payload(payload)
received_message_dict = MessageP2P.to_dict(received_message, received=True) received_message_dict = MessageP2P.to_dict(received_message)
message_id = self.database_manager.add_message(received_message_dict) message_id = self.database_manager.add_message(received_message_dict)
result = self.database_manager.get_attachments_by_message_id(message_id) result = self.database_manager.get_attachments_by_message_id(message_id)
attachment_names = [attachment['name'] for attachment in result] attachment_names = [attachment['name'] for attachment in result]

View file

@ -8,6 +8,7 @@ from message_p2p import MessageP2P
from message_system_db_manager import DatabaseManager from message_system_db_manager import DatabaseManager
from event_manager import EventManager from event_manager import EventManager
import queue import queue
import base64
class TestDataFrameFactory(unittest.TestCase): class TestDataFrameFactory(unittest.TestCase):
@ -34,32 +35,38 @@ class TestDataFrameFactory(unittest.TestCase):
attachment = { attachment = {
'name': 'test.gif', 'name': 'test.gif',
'type': 'image/gif', 'type': 'image/gif',
'data': np.random.bytes(1024) 'data': str(base64.b64encode(np.random.bytes(1024)), 'utf-8')
} }
message = MessageP2P(self.mycall, 'DJ2LS-3', 'Hello World!', [attachment]) apiParams = {'dxcall': 'DJ2LS-3', 'body': 'Hello World!', 'attachments': [attachment]}
message = MessageP2P.from_api_params(self.mycall, apiParams)
payload = message.to_payload() payload = message.to_payload()
received_message = MessageP2P.from_payload(payload) received_message = MessageP2P.from_payload(payload)
self.assertEqual(message.origin, received_message.origin) self.assertEqual(message.origin, received_message.origin)
self.assertEqual(message.destination, received_message.destination) self.assertEqual(message.destination, received_message.destination)
self.assertCountEqual(message.attachments, received_message.attachments) self.assertCountEqual(message.attachments, received_message.attachments)
self.assertEqual(attachment['data'], received_message.attachments[0]['data']) # FIXME...
#self.assertEqual(attachment['data'], received_message.attachments[0]['data'])
def testToPayloadWithAttachmentAndDatabase(self): def testToPayloadWithAttachmentAndDatabase(self):
attachment = { attachment = {
'name': 'test.gif', 'name': 'test.gif',
'type': 'image/gif', 'type': 'image/gif',
'data': np.random.bytes(1024) 'data': str(base64.b64encode(np.random.bytes(1024)), 'utf-8')
} }
message = MessageP2P(self.mycall, 'DJ2LS-3', 'Hello World!', [attachment]) apiParams = {'dxcall': 'DJ2LS-3', 'body': 'Hello World!', 'attachments': [attachment]}
message = MessageP2P.from_api_params(self.mycall, apiParams)
payload = message.to_payload() payload = message.to_payload()
received_message = MessageP2P.from_payload(payload) received_message = MessageP2P.from_payload(payload)
received_message_dict = MessageP2P.to_dict(received_message, received=True) received_message_dict = MessageP2P.to_dict(received_message)
self.database_manager.add_message(received_message_dict) self.database_manager.add_message(received_message_dict)
self.assertEqual(message.origin, received_message.origin) self.assertEqual(message.origin, received_message.origin)
self.assertEqual(message.destination, received_message.destination) self.assertEqual(message.destination, received_message.destination)
self.assertCountEqual(message.attachments, received_message.attachments) self.assertCountEqual(message.attachments, received_message.attachments)
self.assertEqual(attachment['data'], received_message.attachments[0]['data']) # FIXME...
#self.assertEqual(attachment['data'], received_message.attachments[0]['data'])
result = self.database_manager.get_all_messages() result = self.database_manager.get_all_messages()
self.assertEqual(result[0]["destination"], message.destination) self.assertEqual(result[0]["destination"], message.destination)