diff --git a/gui/src/js/api.js b/gui/src/js/api.js index e6ed7bb1..b1593fc2 100644 --- a/gui/src/js/api.js +++ b/gui/src/js/api.js @@ -157,9 +157,9 @@ export async function getFreedataMessages(){ processFreedataMessages(res) } -export async function sendFreedataMessage(dxcall, body) { +export async function sendFreedataMessage(destination, body) { return await apiPost("/freedata/messages", { - dxcall: dxcall, + destination: destination, body: body, }); } diff --git a/modem/arq_data_type_handler.py b/modem/arq_data_type_handler.py index 887015b5..1ce6934e 100644 --- a/modem/arq_data_type_handler.py +++ b/modem/arq_data_type_handler.py @@ -13,9 +13,11 @@ class ARQ_SESSION_TYPES(Enum): p2pmsg_lzma = 20 class ARQDataTypeHandler: - def __init__(self, event_manager): + def __init__(self, event_manager, state_manager): self.logger = structlog.get_logger(type(self).__name__) self.event_manager = event_manager + self.state_manager = state_manager + self.handlers = { ARQ_SESSION_TYPES.raw: { 'prepare': self.prepare_raw, @@ -116,11 +118,11 @@ class ARQDataTypeHandler: def handle_p2pmsg_lzma(self, data): decompressed_data = lzma.decompress(data) 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, self.state_manager, 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) + message_failed(self.event_manager, self.state_manager, decompressed_data) return decompressed_data \ No newline at end of file diff --git a/modem/arq_session.py b/modem/arq_session.py index d859bdd0..4fd424d4 100644 --- a/modem/arq_session.py +++ b/modem/arq_session.py @@ -35,7 +35,7 @@ class ARQSession(): self.event_manager: EventManager = modem.event_manager self.states = modem.states - self.states.set("is_modem_busy", True) + self.states.setARQ(True) self.snr = [] @@ -49,7 +49,7 @@ class ARQSession(): self.frame_factory = data_frame_factory.DataFrameFactory(self.config) self.event_frame_received = threading.Event() - self.arq_data_type_handler = ARQDataTypeHandler(self.event_manager) + self.arq_data_type_handler = ARQDataTypeHandler(self.event_manager, self.states) self.id = None self.session_started = time.time() self.session_ended = 0 @@ -98,7 +98,7 @@ class ARQSession(): if isinstance(received_data, bytearray) and isinstance(type_byte, int): self.arq_data_type_handler.dispatch(type_byte, received_data) - self.states.set("is_modem_busy", False) + self.states.setARQ(False) return self.log(f"Ignoring unknown transition from state {self.state.name} with frame {frame['frame_type']}") diff --git a/modem/arq_session_iss.py b/modem/arq_session_iss.py index e5fbe304..35a9f46e 100644 --- a/modem/arq_session_iss.py +++ b/modem/arq_session_iss.py @@ -21,7 +21,7 @@ class ISS_State(Enum): 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.5 seconds is working well WITHOUT a channel busy detection delay @@ -169,7 +169,7 @@ class ARQSessionISS(arq_session.ARQSession): self.log(f"All data transfered! flag_final={irs_frame['flag']['FINAL']}, flag_checksum={irs_frame['flag']['CHECKSUM']}") self.event_manager.send_arq_session_finished(True, self.id, self.dxcall,True, self.state.name, statistics=self.calculate_session_statistics()) self.state_manager.remove_arq_iss_session(self.id) - self.states.set("is_modem_busy", False) + self.states.setARQ(False) return None, None def transmission_failed(self, irs_frame=None): @@ -178,7 +178,7 @@ class ARQSessionISS(arq_session.ARQSession): self.set_state(ISS_State.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.states.set("is_modem_busy", False) + self.states.setARQ(False) self.arq_data_type_handler.failed(self.type_byte, self.data) @@ -212,6 +212,6 @@ class ARQSessionISS(arq_session.ARQSession): self.event_manager.send_arq_session_finished( True, self.id, self.dxcall, False, self.state.name, statistics=self.calculate_session_statistics()) self.state_manager.remove_arq_iss_session(self.id) - self.states.set("is_modem_busy", False) + self.states.setARQ(False) return None, None diff --git a/modem/beacon.py b/modem/beacon.py deleted file mode 100644 index 909110eb..00000000 --- a/modem/beacon.py +++ /dev/null @@ -1,41 +0,0 @@ -import command_beacon -import sched -import time -import threading - -class Beacon: - def __init__(self, config, states, event_manager, logger, modem): - self.config = config - self.states = states - self.event_manager = event_manager - self.log = logger - self.modem = modem - - self.scheduler = sched.scheduler(time.time, time.sleep) - self.beacon_interval = self.config['MODEM']['beacon_interval'] - self.beacon_enabled = False - self.event = threading.Event() - - def start(self): - self.beacon_enabled = True - self.schedule_beacon() - - def stop(self): - self.beacon_enabled = False - - def schedule_beacon(self): - if self.beacon_enabled: - self.scheduler.enter(self.beacon_interval, 1, self.run_beacon) - threading.Thread(target=self.scheduler.run, daemon=True).start() - - def run_beacon(self): - if self.beacon_enabled: - # Your beacon logic here - cmd = command_beacon.BeaconCommand(self.config, self.states, self.event_manager) - cmd.run(self.event_manager, self.modem) - self.schedule_beacon() # Reschedule the next beacon - - def refresh(self): - # Interrupt and reschedule the beacon - self.scheduler = sched.scheduler(time.time, time.sleep) - self.schedule_beacon() \ No newline at end of file diff --git a/modem/command.py b/modem/command.py index 1ef552e8..65b9dc4f 100644 --- a/modem/command.py +++ b/modem/command.py @@ -10,12 +10,17 @@ class TxCommand(): def __init__(self, config: dict, state_manager: StateManager, event_manager, apiParams:dict = {}): self.config = config - self.logger = structlog.get_logger("Command") + self.logger = structlog.get_logger(type(self).__name__) self.state_manager = state_manager self.event_manager = event_manager self.set_params_from_api(apiParams) self.frame_factory = DataFrameFactory(config) - self.arq_data_type_handler = ARQDataTypeHandler(event_manager) + self.arq_data_type_handler = ARQDataTypeHandler(event_manager, state_manager) + + def log(self, message, isWarning = False): + msg = f"[{type(self).__name__}]: {message}" + logger = self.logger.warn if isWarning else self.logger.info + logger(msg) def set_params_from_api(self, apiParams): pass diff --git a/modem/command_message_send.py b/modem/command_message_send.py index b797b412..0aeb226c 100644 --- a/modem/command_message_send.py +++ b/modem/command_message_send.py @@ -12,20 +12,39 @@ class SendMessageCommand(TxCommand): """ def set_params_from_api(self, apiParams): + print(apiParams) origin = f"{self.config['STATION']['mycall']}-{self.config['STATION']['myssid']}" self.message = MessageP2P.from_api_params(origin, apiParams) + print(self.message.id) + print(self.message.to_dict()) + print("--------------------------------------- set params from api") + DatabaseManager(self.event_manager).add_message(self.message.to_dict(), direction='transmit', status='queued') def transmit(self, modem): + + if self.state_manager.getARQ(): + self.log("Modem busy, waiting until ready...") + return + + first_queued_message = DatabaseManager(self.event_manager).get_first_queued_message() + if not first_queued_message: + self.log("No queued message in database.") + self.state_manager.set_state("pending_messages", False) + return + + self.log(f"Queued message found: {first_queued_message['id']}") + DatabaseManager(self.event_manager).update_message(first_queued_message["id"], update_data={'status': 'transmitting'}) + message_dict = DatabaseManager(self.event_manager).get_message_by_id(first_queued_message["id"]) + print(message_dict["id"]) + message = MessageP2P.from_api_params(message_dict['origin'], message_dict) + print(message.id) + print("--------------------------------------- transmit") + # Convert JSON string to bytes (using UTF-8 encoding) - - DatabaseManager(self.event_manager).add_message(self.message.to_dict(), direction='transmit', status='transmitting') - - payload = self.message.to_payload().encode('utf-8') + payload = message.to_payload().encode('utf-8') json_bytearray = bytearray(payload) data, data_type = self.arq_data_type_handler.prepare(json_bytearray, ARQ_SESSION_TYPES.p2pmsg_lzma) - - iss = ARQSessionISS(self.config, modem, self.message.destination, diff --git a/modem/explorer.py b/modem/explorer.py index 3b740a57..90ba3e13 100644 --- a/modem/explorer.py +++ b/modem/explorer.py @@ -17,30 +17,23 @@ import time log = structlog.get_logger("explorer") class explorer(): - def __init__(self, app, config, states): - self.config = config - self.app = app + def __init__(self, modem_version, config_manager, states): + self.modem_version = modem_version + + self.config_manager = config_manager + self.config = self.config_manager.read() self.states = states self.explorer_url = "https://api.freedata.app/explorer.php" self.publish_interval = 120 - self.scheduler = sched.scheduler(time.time, time.sleep) - self.schedule_thread = threading.Thread(target=self.run_scheduler) - self.schedule_thread.start() - - def run_scheduler(self): - # Schedule the first execution of push - self.scheduler.enter(self.publish_interval, 1, self.push) - # Run the scheduler in a loop - self.scheduler.run() - def push(self): + self.config = self.config_manager.read() frequency = 0 if self.states.radio_frequency is None else self.states.radio_frequency band = "USB" callsign = str(self.config['STATION']['mycall']) + "-" + str(self.config["STATION"]['myssid']) gridsquare = str(self.config['STATION']['mygrid']) - version = str(self.app.MODEM_VERSION) + version = str(self.modem_version) bandwidth = str(self.config['MODEM']['enable_low_bandwidth_mode']) beacon = str(self.states.is_beacon_running) strength = str(self.states.s_meter_strength) @@ -76,11 +69,3 @@ class explorer(): except Exception as e: log.warning("[EXPLORER] connection lost") - - # Reschedule the push method - self.scheduler.enter(self.publish_interval, 1, self.push) - - def shutdown(self): - # If there are other cleanup tasks, include them here - if self.schedule_thread: - self.schedule_thread.join() diff --git a/modem/message_p2p.py b/modem/message_p2p.py index e97a187d..f66214a6 100644 --- a/modem/message_p2p.py +++ b/modem/message_p2p.py @@ -3,19 +3,23 @@ import api_validations import base64 import json from message_system_db_manager import DatabaseManager +#import command_message_send -def message_received(event_manager, data): +def message_received(event_manager, state_manager, data): decompressed_json_string = data.decode('utf-8') received_message_obj = MessageP2P.from_payload(decompressed_json_string) received_message_dict = MessageP2P.to_dict(received_message_obj) DatabaseManager(event_manager).add_message(received_message_dict, direction='receive', status='received') -def message_failed(event_manager, data): +def message_failed(event_manager, state_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'}) - + #payload_message = json.loads(decompressed_json_string) + #print(payload_message) + payload_message_obj = MessageP2P.from_payload(decompressed_json_string) + payload_message = MessageP2P.to_dict(payload_message_obj) + print(payload_message) + DatabaseManager(event_manager).update_message(payload_message["id"], update_data={'status': 'failed'}) class MessageP2P: def __init__(self, id: str, origin: str, destination: str, body: str, attachments: list) -> None: @@ -29,12 +33,12 @@ class MessageP2P: @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" + destination = params['destination'] + if not api_validations.validate_freedata_callsign(destination): + destination = f"{destination}-0" - if not api_validations.validate_freedata_callsign(dxcall): - raise ValueError(f"Invalid dxcall given ({params['dxcall']})") + if not api_validations.validate_freedata_callsign(destination): + raise ValueError(f"Invalid destination given ({params['destination']})") body = params['body'] if len(body) < 1: @@ -47,9 +51,12 @@ class MessageP2P: attachments.append(cls.__decode_attachment__(a)) timestamp = datetime.datetime.now().isoformat() - msg_id = f"{origin}_{dxcall}_{timestamp}" + if 'id' not in params: + msg_id = f"{origin}_{destination}_{timestamp}" + else: + msg_id = params["id"] - return cls(msg_id, origin, dxcall, body, attachments) + return cls(msg_id, origin, destination, body, attachments) @classmethod def from_payload(cls, payload): diff --git a/modem/message_system_db_manager.py b/modem/message_system_db_manager.py index fad51cce..21ddaf76 100644 --- a/modem/message_system_db_manager.py +++ b/modem/message_system_db_manager.py @@ -30,7 +30,8 @@ class DatabaseManager: "received", "failed", "failed_checksum", - "aborted" + "aborted", + "queued" ] # Add default statuses if they don't exist @@ -259,4 +260,29 @@ class DatabaseManager: def get_attachments_by_message_id_json(self, message_id): attachments = self.get_attachments_by_message_id(message_id) - return json.dumps(attachments) \ No newline at end of file + return json.dumps(attachments) + + def get_first_queued_message(self): + session = self.get_thread_scoped_session() + try: + # Find the status object for "queued" + queued_status = session.query(Status).filter_by(name='queued').first() + if queued_status: + # Query for the first (oldest) message with status "queued" + message = session.query(P2PMessage)\ + .filter_by(status=queued_status)\ + .order_by(P2PMessage.timestamp)\ + .first() + if message: + self.log(f"Found queued message with ID {message.id}") + return message.to_dict() + else: + return None + else: + self.log("Queued status not found", isWarning=True) + return None + except Exception as e: + self.log(f"Error fetching the first queued message: {e}", isWarning=True) + return None + finally: + session.remove() \ No newline at end of file diff --git a/modem/schedule_manager.py b/modem/schedule_manager.py new file mode 100644 index 00000000..73c0b9fb --- /dev/null +++ b/modem/schedule_manager.py @@ -0,0 +1,83 @@ +import sched +import time +import threading +import command_message_send +from message_system_db_manager import DatabaseManager + +import explorer +import command_beacon + + +class ScheduleManager: + def __init__(self, modem_version, config_manager, state_manger, event_manager): + self.modem_version = modem_version + self.config_manager = config_manager + self.state_manager = state_manger + self.event_manager = event_manager + self.config = self.config_manager.read() + self.beacon_interval = self.config['MODEM']['beacon_interval'] + + self.scheduler = sched.scheduler(time.time, time.sleep) + self.events = { + 'check_for_queued_messages': {'function': self.check_for_queued_messages, 'interval': 10}, + 'explorer_publishing': {'function': self.push_to_explorer, 'interval': 120}, + 'transmitting_beacon': {'function': self.transmit_beacon, 'interval': self.beacon_interval}, + } + self.running = False # Flag to control the running state + self.scheduler_thread = None # Reference to the scheduler thread + + self.modem = None + + def schedule_event(self, event_function, interval): + """Schedule an event and automatically reschedule it after execution.""" + event_function() # Execute the event function + if self.running: # Only reschedule if still running + self.scheduler.enter(interval, 1, self.schedule_event, (event_function, interval)) + + def start(self, modem): + """Start scheduling events and run the scheduler in a separate thread.""" + + # wait some time + threading.Event().wait(timeout=10) + + # get actual modem istamce + self.modem = modem + + self.running = True # Set the running flag to True + for event_info in self.events.values(): + # Schedule each event for the first time + self.scheduler.enter(0, 1, self.schedule_event, (event_info['function'], event_info['interval'])) + + # Run the scheduler in a separate thread + self.scheduler_thread = threading.Thread(target=self.scheduler.run, daemon=True) + self.scheduler_thread.start() + + def stop(self): + """Stop scheduling new events and terminate the scheduler thread.""" + self.running = False # Clear the running flag to stop scheduling new events + # Clear scheduled events to break the scheduler out of its waiting state + for event in list(self.scheduler.queue): + self.scheduler.cancel(event) + # Wait for the scheduler thread to finish + self.scheduler_thread.join() + + def transmit_beacon(self): + if not self.state_manager.getARQ() and self.state_manager.is_beacon_running: + + cmd = command_beacon.BeaconCommand(self.config, self.state_manager, self.event_manager) + cmd.run(self.event_manager, self.modem) + + def push_to_explorer(self): + self.config = self.config_manager.read() + if self.config['STATION']['enable_explorer']: + explorer.explorer(self.modem_version, self.config_manager, self.state_manager).push() + + def check_for_queued_messages(self): + + if not self.state_manager.getARQ(): + if DatabaseManager(self.event_manager).get_first_queued_message(): + params = DatabaseManager(self.event_manager).get_first_queued_message() + command = command_message_send.SendMessageCommand(self.config_manager.read(), self.state_manager, self.event_manager, params) + command.transmit(self.modem) + + return \ No newline at end of file diff --git a/modem/server.py b/modem/server.py index fbb6a9f6..2f4ddf31 100644 --- a/modem/server.py +++ b/modem/server.py @@ -19,7 +19,7 @@ import command_arq_raw import command_message_send import event_manager from message_system_db_manager import DatabaseManager - +from schedule_manager import ScheduleManager app = Flask(__name__) CORS(app) @@ -73,6 +73,7 @@ def enqueue_tx_command(cmd_class, params = {}): if command.run(app.modem_events, app.service_manager.modem): # TODO remove the app.modem_event custom queue return True return False + ## REST API @app.route('/', methods=['GET']) def index(): @@ -134,10 +135,8 @@ def post_beacon(): if not app.state_manager.is_beacon_running: app.state_manager.set('is_beacon_running', request.json['enabled']) - app.modem_service.put("start_beacon") else: app.state_manager.set('is_beacon_running', request.json['enabled']) - app.modem_service.put("stop_beacon") return api_response(request.json) @@ -263,7 +262,7 @@ def handle_freedata_message(message_id): def get_message_attachments(message_id): attachments = DatabaseManager(app.event_manager).get_attachments_by_message_id_json(message_id) return api_response(attachments) - + # @app.route('/modem/arq_connect', methods=['POST']) # @app.route('/modem/arq_disconnect', methods=['POST']) # @app.route('/modem/send_raw', methods=['POST']) @@ -309,11 +308,13 @@ if __name__ == "__main__": app.event_manager = event_manager.EventManager([app.modem_events]) # TODO remove the app.modem_event custom queue # init state manager app.state_manager = state_manager.StateManager(app.state_queue) + # initialize message system schedule manager + app.schedule_manager = ScheduleManager(app.MODEM_VERSION, app.config_manager, app.state_manager, app.event_manager) # start service manager app.service_manager = service_manager.SM(app) # start modem service app.modem_service.put("start") - # initialize databse default values + # initialize database default values DatabaseManager(app.event_manager).initialize_default_values() wsm.startThreads(app) app.run() diff --git a/modem/service_manager.py b/modem/service_manager.py index ab5f2794..2f36a5bb 100644 --- a/modem/service_manager.py +++ b/modem/service_manager.py @@ -3,9 +3,7 @@ import frame_dispatcher import modem import structlog import audio -import ujson as json -import explorer -import beacon + import radio_manager @@ -14,14 +12,13 @@ class SM: self.log = structlog.get_logger("service") self.app = app self.modem = False - self.beacon = False - self.explorer = False self.app.radio_manager = False self.config = self.app.config_manager.read() self.modem_fft = app.modem_fft self.modem_service = app.modem_service self.state_manager = app.state_manager self.event_manager = app.event_manager + self.schedule_manager = app.schedule_manager runner_thread = threading.Thread( @@ -33,23 +30,18 @@ class SM: while True: cmd = self.modem_service.get() if cmd in ['start'] and not self.modem: - self.log.info("------------------ FreeDATA ------------------") - self.log.info("------------------ MODEM ------------------") self.config = self.app.config_manager.read() self.start_radio_manager() self.start_modem() - self.start_explorer_publishing() elif cmd in ['stop'] and self.modem: self.stop_modem() - self.stop_explorer_publishing() self.stop_radio_manager() # we need to wait a bit for avoiding a portaudio crash threading.Event().wait(0.5) elif cmd in ['restart']: self.stop_modem() - self.stop_explorer_publishing() self.stop_radio_manager() # we need to wait a bit for avoiding a portaudio crash threading.Event().wait(0.5) @@ -59,24 +51,22 @@ class SM: if self.start_modem(): self.event_manager.modem_restarted() - self.start_explorer_publishing() - elif cmd in ['start_beacon']: - self.start_beacon() - - elif cmd in ['stop_beacon']: - self.stop_beacon() - - else: self.log.warning("[SVC] modem command processing failed", cmd=cmd, state=self.state_manager.is_modem_running) def start_modem(self): + + if self.config['STATION']['mycall'] in ['XX1XXX']: + self.log.warning("wrong callsign in config! interrupting startup") + return False + if self.state_manager.is_modem_running: self.log.warning("modem already running") return False + # test audio devices audio_test = self.test_audio() @@ -98,6 +88,7 @@ class SM: self.event_manager.modem_started() self.state_manager.set("is_modem_running", True) self.modem.start_modem() + self.schedule_manager.start(self.modem) return True @@ -106,6 +97,7 @@ class SM: del self.modem self.modem = False self.state_manager.set("is_modem_running", False) + self.schedule_manager.stop() self.event_manager.modem_stopped() def test_audio(self): @@ -119,28 +111,6 @@ class SM: self.log.error("Error testing audio devices", e=e) return [False, False] - def start_beacon(self): - self.beacon = beacon.Beacon(self.config, self.state_manager, self.event_manager, self.log, self.modem) - self.beacon.start() - - def stop_beacon(self): - self.beacon.stop() - - def start_explorer_publishing(self): - try: - # optionally start explorer module - if self.config['STATION']['enable_explorer']: - self.explorer = explorer.explorer(self.app, self.config, self.state_manager) - except Exception as e: - self.log.warning("[EXPLORER] Publishing not started because of error", e=e) - - def stop_explorer_publishing(self): - if self.config['STATION']['enable_explorer']: - try: - del self.explorer - except Exception as e: - self.log.info("[EXPLORER] Error while stopping...", e=e) - def start_radio_manager(self): self.app.radio_manager = radio_manager.RadioManager(self.config, self.state_manager, self.event_manager) diff --git a/modem/state_manager.py b/modem/state_manager.py index f9a97469..85eb2c95 100644 --- a/modem/state_manager.py +++ b/modem/state_manager.py @@ -19,7 +19,10 @@ class StateManager: self.channel_busy_condition_codec2 = threading.Event() self.is_modem_running = False - self.is_modem_busy = False + + self.is_modem_busy = threading.Event() + self.setARQ(False) + self.is_beacon_running = False # If true, any wait() call is blocking @@ -30,13 +33,13 @@ class StateManager: self.dxcallsign: bytes = b"ZZ9YY-0" self.dxgrid: bytes = b"------" - self.heard_stations = [] # TODO remove it... heard stations list == deprecated + self.heard_stations = [] self.activities_list = {} self.arq_iss_sessions = {} self.arq_irs_sessions = {} - self.mesh_routing_table = [] + #self.mesh_routing_table = [] self.radio_frequency = 0 self.radio_mode = None @@ -46,6 +49,11 @@ class StateManager: # Set rig control status regardless or rig control method self.radio_status = False + # message system related states + self.pending_messages = False + + + def sendState (self): currentState = self.get_state_event(False) self.statequeue.put(currentState) @@ -87,7 +95,7 @@ class StateManager: "channel_busy_slot": self.channel_busy_slot, "audio_dbfs": self.audio_dbfs, "activities": self.activities_list, - "is_modem_busy" : self.is_modem_busy + "is_modem_busy" : self.getARQ() } # .wait() blocks until the event is set @@ -101,6 +109,15 @@ class StateManager: else: self.transmitting_event.set() + def setARQ(self, busy): + if busy: + self.is_modem_busy.clear() + else: + self.is_modem_busy.set() + + def getARQ(self): + return not self.is_modem_busy.is_set() + def waitForTransmission(self): self.transmitting_event.wait() @@ -202,4 +219,4 @@ class StateManager: "radio_mode": self.radio_mode, "radio_rf_level": self.radio_rf_level, "s_meter_strength": self.s_meter_strength, - } \ No newline at end of file + }