mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
Merge pull request #619 from DJ2LS/dev-send-message
This commit is contained in:
commit
01714c7691
5 changed files with 155 additions and 0 deletions
|
@ -3,3 +3,10 @@ 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}$"
|
||||||
return re.compile(regexp).match(callsign) is not None
|
return re.compile(regexp).match(callsign) is not None
|
||||||
|
|
||||||
|
def validate_message_attachment(attachment):
|
||||||
|
for field in ['name', 'type', 'data']:
|
||||||
|
if field not in attachment:
|
||||||
|
raise ValueError(f"Attachment missing '{field}'")
|
||||||
|
if len(attachment[field]) < 1:
|
||||||
|
raise ValueError(f"Attachment has empty '{field}'")
|
||||||
|
|
26
modem/command_message_send.py
Normal file
26
modem/command_message_send.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from command import TxCommand
|
||||||
|
import api_validations
|
||||||
|
import base64
|
||||||
|
from queue import Queue
|
||||||
|
from arq_session_iss import ARQSessionISS
|
||||||
|
from message_p2p import MessageP2P
|
||||||
|
from arq_data_type_handler import ARQDataTypeHandler
|
||||||
|
|
||||||
|
class SendMessageCommand(TxCommand):
|
||||||
|
"""Command to send a P2P message using an ARQ transfer session
|
||||||
|
"""
|
||||||
|
|
||||||
|
def set_params_from_api(self, apiParams):
|
||||||
|
origin = f"{self.config['STATION']['mycall']}-{self.config['STATION']['myssid']}"
|
||||||
|
self.message = MessageP2P.from_api_params(origin, apiParams)
|
||||||
|
|
||||||
|
def transmit(self, modem):
|
||||||
|
data, data_type = self.arq_data_type_handler.prepare(self.message.to_payload, 'p2pmsg_lzma')
|
||||||
|
iss = ARQSessionISS(self.config, modem,
|
||||||
|
self.message.destination,
|
||||||
|
data,
|
||||||
|
self.state_manager,
|
||||||
|
data_type)
|
||||||
|
|
||||||
|
self.state_manager.register_arq_iss_session(iss)
|
||||||
|
iss.start()
|
71
modem/message_p2p.py
Normal file
71
modem/message_p2p.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import datetime
|
||||||
|
import api_validations
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
class MessageP2P:
|
||||||
|
def __init__(self, origin: str, destination: str, body: str, attachments: list) -> None:
|
||||||
|
self.timestamp = datetime.datetime.now().isoformat()
|
||||||
|
self.origin = origin
|
||||||
|
self.destination = destination
|
||||||
|
self.body = body
|
||||||
|
self.attachments = attachments
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_api_params(cls, origin: str, params: dict):
|
||||||
|
|
||||||
|
dxcall = params['dxcall']
|
||||||
|
if not api_validations.validate_freedata_callsign(dxcall):
|
||||||
|
dxcall = f"{dxcall}-0"
|
||||||
|
|
||||||
|
if not api_validations.validate_freedata_callsign(dxcall):
|
||||||
|
raise ValueError(f"Invalid dxcall given ({params['dxcall']})")
|
||||||
|
|
||||||
|
body = params['body']
|
||||||
|
if len(body) < 1:
|
||||||
|
raise ValueError(f"Body cannot be empty")
|
||||||
|
|
||||||
|
attachments = []
|
||||||
|
if 'attachments' in params:
|
||||||
|
for a in params['attachments']:
|
||||||
|
api_validations.validate_message_attachment(a)
|
||||||
|
attachments.append(cls.__decode_attachment__(a))
|
||||||
|
|
||||||
|
return cls(origin, dxcall, body, attachments)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_payload(cls, payload):
|
||||||
|
payload_message = json.loads(payload)
|
||||||
|
attachments = list(map(cls.__decode_attachment__, payload_message['attachments']))
|
||||||
|
return cls(payload_message['origin'], payload_message['destination'],
|
||||||
|
payload_message['body'], attachments)
|
||||||
|
|
||||||
|
def get_id(self) -> str:
|
||||||
|
return f"{self.origin}.{self.destination}.{self.timestamp}"
|
||||||
|
|
||||||
|
def __encode_attachment__(self, binary_attachment: dict):
|
||||||
|
encoded_attachment = binary_attachment.copy()
|
||||||
|
encoded_attachment['data'] = str(base64.b64encode(binary_attachment['data']), 'utf-8')
|
||||||
|
return encoded_attachment
|
||||||
|
|
||||||
|
def __decode_attachment__(encoded_attachment: dict):
|
||||||
|
decoded_attachment = encoded_attachment.copy()
|
||||||
|
decoded_attachment['data'] = base64.b64decode(encoded_attachment['data'])
|
||||||
|
return decoded_attachment
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""Make a dictionary out of the message data
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
'id': self.get_id(),
|
||||||
|
'origin': self.origin,
|
||||||
|
'destination': self.destination,
|
||||||
|
'body': self.body,
|
||||||
|
'attachments': list(map(self.__encode_attachment__, self.attachments)),
|
||||||
|
}
|
||||||
|
|
||||||
|
def to_payload(self):
|
||||||
|
"""Make a byte array ready to be sent out of the message data"""
|
||||||
|
json_string = json.dumps(self.to_dict())
|
||||||
|
return json_string
|
|
@ -16,6 +16,7 @@ import command_ping
|
||||||
import command_feq
|
import command_feq
|
||||||
import command_test
|
import command_test
|
||||||
import command_arq_raw
|
import command_arq_raw
|
||||||
|
import command_message_send
|
||||||
import event_manager
|
import event_manager
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
@ -234,6 +235,13 @@ def get_post_radio():
|
||||||
elif request.method == 'GET':
|
elif request.method == 'GET':
|
||||||
return api_response(app.state_manager.get_radio_status())
|
return api_response(app.state_manager.get_radio_status())
|
||||||
|
|
||||||
|
@app.route('/freedata/messages', methods=['POST'])
|
||||||
|
def post_freedata_message():
|
||||||
|
if enqueue_tx_command(command_message_send.SendMessageCommand, request.json):
|
||||||
|
return api_response(request.json)
|
||||||
|
else:
|
||||||
|
api_abort('Error executing command...', 500)
|
||||||
|
|
||||||
# @app.route('/modem/arq_connect', methods=['POST'])
|
# @app.route('/modem/arq_connect', methods=['POST'])
|
||||||
# @app.route('/modem/arq_disconnect', methods=['POST'])
|
# @app.route('/modem/arq_disconnect', methods=['POST'])
|
||||||
# @app.route('/modem/send_raw', methods=['POST'])
|
# @app.route('/modem/send_raw', methods=['POST'])
|
||||||
|
|
43
tests/test_message_p2p.py
Executable file
43
tests/test_message_p2p.py
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
import sys
|
||||||
|
sys.path.append('modem')
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from config import CONFIG
|
||||||
|
from message_p2p import MessageP2P
|
||||||
|
|
||||||
|
class TestDataFrameFactory(unittest.TestCase):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
config_manager = CONFIG('modem/config.ini.example')
|
||||||
|
cls.config = config_manager.read()
|
||||||
|
cls.mycall = f"{cls.config['STATION']['mycall']}-{cls.config['STATION']['myssid']}"
|
||||||
|
|
||||||
|
|
||||||
|
def testFromApiParams(self):
|
||||||
|
api_params = {
|
||||||
|
'dxcall': 'DJ2LS-3',
|
||||||
|
'body': 'Hello World!',
|
||||||
|
}
|
||||||
|
message = MessageP2P.from_api_params(self.mycall, api_params)
|
||||||
|
self.assertEqual(message.destination, api_params['dxcall'])
|
||||||
|
self.assertEqual(message.body, api_params['body'])
|
||||||
|
|
||||||
|
def testToPayloadWithAttachment(self):
|
||||||
|
attachment = {
|
||||||
|
'name': 'test.gif',
|
||||||
|
'type': 'image/gif',
|
||||||
|
'data': np.random.bytes(1024)
|
||||||
|
}
|
||||||
|
message = MessageP2P(self.mycall, 'DJ2LS-3', 'Hello World!', [attachment])
|
||||||
|
payload = message.to_payload()
|
||||||
|
|
||||||
|
received_message = MessageP2P.from_payload(payload)
|
||||||
|
self.assertEqual(message.origin, received_message.origin)
|
||||||
|
self.assertEqual(message.destination, received_message.destination)
|
||||||
|
self.assertCountEqual(message.attachments, received_message.attachments)
|
||||||
|
self.assertEqual(attachment['data'], received_message.attachments[0]['data'])
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
Loading…
Reference in a new issue