diff --git a/gui/src/components/settings.vue b/gui/src/components/settings.vue index e41935e6..2a70715f 100644 --- a/gui/src/components/settings.vue +++ b/gui/src/components/settings.vue @@ -16,12 +16,7 @@ import settings_exp from "./settings_exp.vue"; aria-labelledby="list-settings-list" >
-
-
- Please restart the modem - after changing settings -
-
+
diff --git a/gui/src/js/settingsHandler.ts b/gui/src/js/settingsHandler.ts index 8eb81211..4e54310e 100644 --- a/gui/src/js/settingsHandler.ts +++ b/gui/src/js/settingsHandler.ts @@ -170,8 +170,7 @@ export function getModemConfigAsJSON() { MESH: { enable_protocol: settings.enable_mesh_features, }, - Modem: { - enable_explorer: settings.enable_explorer, + MODEM: { enable_fft: settings.enable_fft, tuning_range_fmax: settings.tuning_range_fmax, tuning_range_fmin: settings.tuning_range_fmin, @@ -180,7 +179,6 @@ export function getModemConfigAsJSON() { respond_to_cq: settings.respond_to_cq, rx_buffer_size: settings.rx_buffer_size, enable_scatter: "False", - enable_stats: settings.explorer_stats, tx_delay: settings.tx_delay, }, NETWORK: { @@ -195,6 +193,8 @@ export function getModemConfigAsJSON() { mycall: settings.mycall + "-" + settings.myssid, mygrid: settings.mygrid, ssid_list: [], + enable_explorer: settings.enable_explorer, + enable_stats: settings.explorer_stats, }, TCI: { tci_ip: settings.tci_ip, diff --git a/modem/broadcast.py b/modem/broadcast.py index 688e7372..1a9f41ab 100644 --- a/modem/broadcast.py +++ b/modem/broadcast.py @@ -4,8 +4,6 @@ import helpers import time import modem import base64 -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem -import sock import ujson as json class broadcastHandler: @@ -13,7 +11,9 @@ class broadcastHandler: log = structlog.get_logger("BROADCAST") - def __init__(self, event_queue) -> None: + def __init__(self, config, event_queue) -> None: + self.mycallsign = config['STATION']['mycall'] + self.fec_wakeup_callsign = bytes() self.longest_duration = 6 self.wakeup_received = False @@ -83,9 +83,7 @@ class broadcastHandler: # and make sure we are not overwrite them if they exist try: if "mycallsign" not in jsondata: - jsondata["mycallsign"] = str(Station.mycallsign, "UTF-8") - if "dxcallsign" not in jsondata: - jsondata["dxcallsign"] = str(Station.dxcallsign, "UTF-8") + jsondata["mycallsign"] = str(self.mycallsign, "UTF-8") except Exception as e: self.log.debug("[Modem] error adding callsigns to network message", e=e) @@ -94,7 +92,6 @@ class broadcastHandler: self.log.debug("[Modem] send_data_to_socket_queue:", jsondata=json_data_out) # finally push data to our network queue - sock.SOCKET_QUEUE.put(json_data_out) self.event_queue.put(json_data_out) def watchdog(self): diff --git a/modem/config.ini.example b/modem/config.ini.example index a599dd79..23fdeb38 100644 --- a/modem/config.ini.example +++ b/modem/config.ini.example @@ -2,13 +2,16 @@ modemport = 3000 [STATION] -mycall = DJ2LS-9 -mygrid = JN49em +mycall = DJ2LS-5 +mygrid = JN49en ssid_list = [] +enable_explorer = False +enable_stats = False + [AUDIO] -input_device = c07e -output_device = c07e +input_device = 4f55 +output_device = 4f55 rx_audio_level = 0 tx_audio_level = 0 enable_auto_tune = False @@ -22,14 +25,11 @@ radioport = None [TCI] tci_ip = 127.0.0.1 tci_port = 50001 -ip = 127.0.0.1 -port = 50001 [MESH] enable_protocol = False -[Modem] -enable_explorer = False +[MODEM] enable_fft = True tuning_range_fmax = 50 tuning_range_fmin = -50 @@ -38,6 +38,5 @@ enable_low_bandwidth_mode = False respond_to_cq = True rx_buffer_size = 16 enable_scatter = False -enable_stats = False tx_delay = 0 diff --git a/modem/data_handler.py b/modem/data_handler.py index ca28f0cb..3aab174d 100644 --- a/modem/data_handler.py +++ b/modem/data_handler.py @@ -23,7 +23,7 @@ import codec2 import helpers import modem import numpy as np -import sock +import deprecated_sock from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem import structlog import stats @@ -41,7 +41,7 @@ class DATA: log = structlog.get_logger("DATA") - def __init__(self, config, event_queue) -> None: + def __init__(self, config, event_queue, states) -> None: self.stats = stats.stats() self.event_queue = event_queue @@ -111,7 +111,7 @@ class DATA: self.rx_n_frames_per_burst = 0 self.max_n_frames_per_burst = 1 - self.broadcast = broadcast.broadcastHandler(self.event_queue) + self.broadcast = broadcast.broadcastHandler(config, self.event_queue) # Flag to indicate if we received a low bandwidth mode channel opener self.received_LOW_BANDWIDTH_MODE = False @@ -327,9 +327,9 @@ class DATA: # [2] STATE bool if data[2]: self.beacon_interval = data[1] - Beacon.beacon_state = True + self.states.set("is_beacon_running", True) else: - Beacon.beacon_state = False + self.states.set("is_beacon_running", False) elif data[0] == "ARQ_RAW": # [1] DATA_OUT bytes @@ -2772,7 +2772,7 @@ class DATA: try: while True: threading.Event().wait(0.5) - while Beacon.beacon_state: + while self.states.is_beacon_runining: if ( not ARQ.arq_session and not self.arq_file_transfer @@ -2811,7 +2811,7 @@ class DATA: self.beacon_interval_timer = time.time() + self.beacon_interval while ( time.time() < self.beacon_interval_timer - and Beacon.beacon_state + and self.states.is_beacon_runining and not Beacon.beacon_pause ): threading.Event().wait(0.01) diff --git a/modem/deprecated_daemon.py b/modem/deprecated_daemon.py index b2a55c66..6ef6ebb5 100755 --- a/modem/deprecated_daemon.py +++ b/modem/deprecated_daemon.py @@ -25,7 +25,7 @@ import audio import crcengine import log_handler import serial.tools.list_ports -import sock +import deprecated_sock from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem import structlog @@ -544,7 +544,7 @@ class DAEMON: elif sys.platform in ["win32", "win64"]: command.append("python") - command.append("main.py") + command.append("deprecated_main.py") command += options proc = subprocess.Popen(command) atexit.register(proc.kill) @@ -606,7 +606,7 @@ if __name__ == "__main__": mainlog.info("[DMN] Starting TCP/IP socket", port=DAEMON.port) # https://stackoverflow.com/a/16641793 socketserver.TCPServer.allow_reuse_address = True - cmdserver = sock.ThreadedTCPServer( + cmdserver = deprecated_sock.ThreadedTCPServer( (Modem.host, DAEMON.port), sock.ThreadedTCPRequestHandler ) server_thread = threading.Thread(target=cmdserver.serve_forever) diff --git a/modem/main.py b/modem/deprecated_main.py similarity index 99% rename from modem/main.py rename to modem/deprecated_main.py index fcde98f3..dc22de71 100755 --- a/modem/main.py +++ b/modem/deprecated_main.py @@ -47,7 +47,7 @@ def signal_handler(sig, frame): """ print("Closing Modem...") - sock.CLOSE_SIGNAL = True + deprecated_sock.CLOSE_SIGNAL = True sys.exit(0) @@ -144,7 +144,7 @@ try: # check if we have an int or str as device name # we need to wait until we got all parameters from argparse first before we can load the other modules - import sock + import deprecated_sock try: AudioParam.audio_input_device = int(conf.get('AUDIO', 'rx', '0')) @@ -187,7 +187,7 @@ if my_ssid not in Station.ssid_list: Station.ssid_list.append(my_ssid) # we need to wait until we got all parameters from argparse first before we can load the other modules -import sock +import deprecated_sock # config logging try: diff --git a/modem/sock.py b/modem/deprecated_sock.py similarity index 100% rename from modem/sock.py rename to modem/deprecated_sock.py diff --git a/modem/event_manager.py b/modem/event_manager.py index 3a82a991..5a0c740e 100644 --- a/modem/event_manager.py +++ b/modem/event_manager.py @@ -13,3 +13,8 @@ class EventManager: jsondata = {"ptt": str(on)} data_out = json.dumps(jsondata) self.broadcast(data_out) + + def send_scatter_change(self, data): + jsondata = {"scatter": str(data)} + data_out = json.dumps(jsondata) + self.broadcast(data_out) \ No newline at end of file diff --git a/modem/explorer.py b/modem/explorer.py index e6a65bcc..50cb1a35 100644 --- a/modem/explorer.py +++ b/modem/explorer.py @@ -9,18 +9,16 @@ Created on 05.11.23 import requests import threading -import time import ujson as json import structlog -import static -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem - +from global_instances import HamlibParam, Modem log = structlog.get_logger("explorer") - class explorer(): - def __init__(self): + def __init__(self, config, states): + self.config = config + self.states = states self.explorer_url = "https://api.freedata.app/explorer.php" self.publish_interval = 120 @@ -36,11 +34,11 @@ class explorer(): frequency = 0 if HamlibParam.hamlib_frequency is None else HamlibParam.hamlib_frequency band = "USB" - callsign = str(Station.mycallsign, "utf-8") - gridsquare = str(Station.mygrid, "utf-8") + callsign = str(self.config['STATION']['mycall'], "utf-8") + gridsquare = str(self.config['STATION']['mygrid'], "utf-8") version = str(Modem.version) - bandwidth = str(Modem.low_bandwidth_mode) - beacon = str(Beacon.beacon_state) + bandwidth = str(self.config['Modem']['low_bandwidth_mode']) + beacon = str(self.states.is_beacon_running) strength = str(HamlibParam.hamlib_strength) log.info("[EXPLORER] publish", frequency=frequency, band=band, callsign=callsign, gridsquare=gridsquare, version=version, bandwidth=bandwidth) diff --git a/modem/helpers.py b/modem/helpers.py index a2ca5026..7f3f0363 100644 --- a/modem/helpers.py +++ b/modem/helpers.py @@ -7,12 +7,10 @@ Created on Fri Dec 25 21:25:14 2020 import time from datetime import datetime,timezone import crcengine -import static -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, Statistics, TCIParam, Modem, MeshParam +from global_instances import Station, Modem import structlog import numpy as np import threading -import mesh import hashlib import hmac import os diff --git a/modem/mesh.py b/modem/mesh.py index 87dee234..94a7e1a2 100644 --- a/modem/mesh.py +++ b/modem/mesh.py @@ -37,7 +37,7 @@ SNR: negative --> * 2 # pylint: disable=import-outside-toplevel, attribute-defined-outside-init from static import FRAME_TYPE -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, MeshParam, Station, Statistics, TCIParam, Modem +from global_instances import ARQ,ModemParam, MeshParam, Station, Modem from codec2 import FREEDV_MODE import numpy as np diff --git a/modem/modem.py b/modem/modem.py index 2908d081..73e0580a 100644 --- a/modem/modem.py +++ b/modem/modem.py @@ -19,9 +19,8 @@ from collections import deque import codec2 import itertools import numpy as np -import sock import sounddevice as sd -from global_instances import AudioParam, HamlibParam, ModemParam, TCIParam, Modem +from global_instances import AudioParam, HamlibParam, ModemParam, Modem from static import FRAME_TYPE import structlog import tci @@ -83,6 +82,11 @@ class RF: self.tuning_range_fmin = config['Modem']['tuning_range_fmin'] self.tuning_range_fmax = config['Modem']['tuning_range_fmax'] + self.tci_ip = config['TCI']['tci_ip'] + self.tci_port = config['TCI']['tci_port'] + + + self.channel_busy_delay = 0 @@ -117,9 +121,7 @@ class RF: # Init FIFO queue to store modulation out in self.modoutqueue = deque() - self.event_manager = event_manager.EventManager([ - event_queue, - sock.SOCKET_QUEUE]) + self.event_manager = event_manager.EventManager([event_queue]) self.fft_queue = fft_queue @@ -222,7 +224,7 @@ class RF: def init_tci(self): # placeholder area for processing audio via TCI # https://github.com/maksimus1210/TCI - self.log.warning("[MDM] [TCI] Not yet fully implemented", ip=TCIParam.ip, port=TCIParam.port) + self.log.warning("[MDM] [TCI] Not yet fully implemented", ip=self.tci_ip, port=self.tci_port) # we are trying this by simulating an audio stream Object like with mkfifo class Object: @@ -374,54 +376,57 @@ class RF: """ # self.log.debug("[MDM] callback") - x = np.frombuffer(data_in48k, dtype=np.int16) - x = self.resampler.resample48_to_8(x) - x = set_audio_volume(x, self.rx_audio_level) + try: + x = np.frombuffer(data_in48k, dtype=np.int16) + x = self.resampler.resample48_to_8(x) + x = set_audio_volume(x, self.rx_audio_level) - # audio recording for debugging purposes - if AudioParam.audio_record: - AudioParam.audio_record_file.writeframes(x) + # audio recording for debugging purposes + if AudioParam.audio_record: + AudioParam.audio_record_file.writeframes(x) - # Avoid decoding when transmitting to reduce CPU - # TODO Overriding this for testing purposes - # if not Modem.transmitting: - length_x = len(x) - # Avoid buffer overflow by filling only if buffer for - # selected datachannel mode is not full - for audiobuffer, receive, index in [ - (self.sig0_datac13_buffer, RECEIVE_SIG0, 0), - (self.sig1_datac13_buffer, RECEIVE_SIG1, 1), - (self.dat0_datac1_buffer, RECEIVE_DATAC1, 2), - (self.dat0_datac3_buffer, RECEIVE_DATAC3, 3), - (self.dat0_datac4_buffer, RECEIVE_DATAC4, 4), - (self.fsk_ldpc_buffer_0, Modem.enable_fsk, 5), - (self.fsk_ldpc_buffer_1, Modem.enable_fsk, 6), - ]: - if (audiobuffer.nbuffer + length_x) > audiobuffer.size: - AudioParam.buffer_overflow_counter[index] += 1 - elif receive: - audiobuffer.push(x) - # end of "not Modem.transmitting" if block + # Avoid decoding when transmitting to reduce CPU + # TODO Overriding this for testing purposes + # if not Modem.transmitting: + length_x = len(x) + # Avoid buffer overflow by filling only if buffer for + # selected datachannel mode is not full + for audiobuffer, receive, index in [ + (self.sig0_datac13_buffer, RECEIVE_SIG0, 0), + (self.sig1_datac13_buffer, RECEIVE_SIG1, 1), + (self.dat0_datac1_buffer, RECEIVE_DATAC1, 2), + (self.dat0_datac3_buffer, RECEIVE_DATAC3, 3), + (self.dat0_datac4_buffer, RECEIVE_DATAC4, 4), + (self.fsk_ldpc_buffer_0, Modem.enable_fsk, 5), + (self.fsk_ldpc_buffer_1, Modem.enable_fsk, 6), + ]: + if (audiobuffer.nbuffer + length_x) > audiobuffer.size: + AudioParam.buffer_overflow_counter[index] += 1 + elif receive: + audiobuffer.push(x) + # end of "not Modem.transmitting" if block - if not self.modoutqueue or self.mod_out_locked: - data_out48k = np.zeros(frames, dtype=np.int16) - if self.enable_fft: - self.calculate_fft(x) - else: - if not HamlibParam.ptt_state: - # TODO Moved to this place for testing - # Maybe we can avoid moments of silence before transmitting - HamlibParam.ptt_state = self.radio.set_ptt(True) - self.event_manager.send_ptt_change(True) + if not self.modoutqueue or self.mod_out_locked: + data_out48k = np.zeros(frames, dtype=np.int16) + if self.enable_fft: + self.calculate_fft(x) + else: + if not HamlibParam.ptt_state: + # TODO Moved to this place for testing + # Maybe we can avoid moments of silence before transmitting + HamlibParam.ptt_state = self.radio.set_ptt(True) + self.event_manager.send_ptt_change(True) - data_out48k = self.modoutqueue.popleft() - if self.enable_fft: - self.calculate_fft(data_out48k) + data_out48k = self.modoutqueue.popleft() + if self.enable_fft: + self.calculate_fft(data_out48k) + except Exception as e: + self.log.warning(f"[MDM] audio callback not ready yet: {e}") try: outdata[:] = data_out48k[:frames] except IndexError as err: - self.log.debug(f"[MDM] callback: IndexError: {err}") + self.log.debug(f"[MDM] callback writing error: IndexError: {err}") # return (data_out48k, audio.pyaudio.paContinue) @@ -993,8 +998,8 @@ class RF: # set tuning range codec2.api.freedv_set_tuning_range( c2instance, - ctypes.c_float(ModemParam.tuning_range_fmin), - ctypes.c_float(ModemParam.tuning_range_fmax), + ctypes.c_float(self.tuning_range_fmin), + ctypes.c_float(self.tuning_range_fmax), ) # get bytes per frame @@ -1176,7 +1181,6 @@ class RF: def get_scatter(self, freedv: ctypes.c_void_p) -> None: """ Ask codec2 for data about the received signal and calculate the scatter plot. - Side effect: sets ModemParam.scatter :param freedv: codec2 instance to query :type freedv: ctypes.c_void_p @@ -1209,10 +1213,11 @@ class RF: # Send all the data if we have too-few samples, otherwise send a sampling if 150 > len(scatterdata) > 0: - ModemParam.scatter = scatterdata + self.event_manager.send_scatter_change(scatterdata) + else: # only take every tenth data point - ModemParam.scatter = scatterdata[::10] + self.event_manager.send_scatter_change(scatterdata[::10]) def calculate_snr(self, freedv: ctypes.c_void_p) -> float: """ diff --git a/modem/queues.py b/modem/queues.py index befba860..9ba94baf 100644 --- a/modem/queues.py +++ b/modem/queues.py @@ -2,8 +2,7 @@ Hold queues used by more than one module to eliminate cyclic imports. """ import queue -import static -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam, Modem +from global_instances import ARQ DATA_QUEUE_TRANSMIT = queue.Queue() DATA_QUEUE_RECEIVED = queue.Queue() diff --git a/modem/rigctld.py b/modem/rigctld.py index ced1b7fb..f31c90eb 100644 --- a/modem/rigctld.py +++ b/modem/rigctld.py @@ -10,7 +10,7 @@ import time import structlog import threading import static -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam +from global_instances import HamlibParam class radio: diff --git a/modem/server.py b/modem/server.py index e724bd14..389c0f3d 100644 --- a/modem/server.py +++ b/modem/server.py @@ -9,6 +9,8 @@ import queue import server_commands import service_manager import state_manager +import explorer + app = Flask(__name__) CORS(app) @@ -41,16 +43,12 @@ app.modem_service = queue.Queue() # start / stop modem service # init state manager app.states = state_manager.STATES(app.state_queue) -print(app.states.testvalue) -app.states.set("testvalue", "holla") - # start service manager service_manager.SM(app) # start modem service app.modem_service.put("start") - # returns a standard API response def api_response(data): return make_response(jsonify(data), 200) diff --git a/modem/service_manager.py b/modem/service_manager.py index 975ba5ab..e773c92f 100644 --- a/modem/service_manager.py +++ b/modem/service_manager.py @@ -4,6 +4,7 @@ import modem import structlog import audio import ujson as json +import explorer class SM: @@ -24,6 +25,11 @@ class SM: ) runner_thread.start() + # optionally start explorer module + if self.config['STATION']['enable_explorer']: + explorer.explorer(self.config, self.states) + + def runner(self): while True: cmd = self.modem_service.get() @@ -52,7 +58,7 @@ class SM: if False not in audio_test and None not in audio_test and not self.states.is_modem_running: self.log.info("starting modem....") self.modem = modem.RF(self.config, self.modem_events, self.modem_fft, self.modem_service, self.states) - self.data_handler = data_handler.DATA(self.config, self.modem_events) + self.data_handler = data_handler.DATA(self.config, self.modem_events, self.states) self.states.set("is_modem_running", True) return True elif self.states.is_modem_running: diff --git a/modem/state_manager.py b/modem/state_manager.py index 174b48eb..d18bd91a 100644 --- a/modem/state_manager.py +++ b/modem/state_manager.py @@ -11,6 +11,8 @@ class STATES: self.is_codec2_traffic = False self.is_modem_running = False + self.is_beacon_running = False + def set(self, key, value): setattr(self, key, value) self.statequeue.put(self.getAsJSON()) @@ -21,4 +23,6 @@ class STATES: "channel_busy": self.channel_busy, "is_codec2_traffic": self.is_codec2_traffic, "is_modem_running": self.is_modem_running, + "is_beacon_running": self.is_beacon_running, + }) \ No newline at end of file diff --git a/modem/static.py b/modem/static.py index 373d86dc..fd65a4ab 100644 --- a/modem/static.py +++ b/modem/static.py @@ -8,7 +8,6 @@ Here we are saving application wide variables and stats, which have to be access """ from dataclasses import dataclass, field -from typing import List import subprocess from enum import Enum import threading diff --git a/modem/stats.py b/modem/stats.py index d295b9fa..18983ac3 100644 --- a/modem/stats.py +++ b/modem/stats.py @@ -8,11 +8,9 @@ Created on 05.11.23 # pylint: disable=import-outside-toplevel, attribute-defined-outside-init import requests -import threading -import time import ujson as json import structlog -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam, Modem +from global_instances import ARQ, HamlibParam, Station, Modem log = structlog.get_logger("stats") diff --git a/modem/tci.py b/modem/tci.py index 3d353f52..262c8d29 100644 --- a/modem/tci.py +++ b/modem/tci.py @@ -4,10 +4,8 @@ import structlog import threading import websocket -import numpy as np import time from queues import AUDIO_TRANSMIT_QUEUE, AUDIO_RECEIVED_QUEUE -from global_instances import ARQ, AudioParam, Beacon, Channel, Daemon, HamlibParam, ModemParam, Station, TCIParam, Modem class TCICtrl: def __init__(self, hostname='127.0.0.1', port=50001): diff --git a/tools/cleanup.sh b/tools/cleanup.sh index 3343de02..e1ebfd0a 100644 --- a/tools/cleanup.sh +++ b/tools/cleanup.sh @@ -1,3 +1,3 @@ -#autopep8 --in-place --select W291,W293,W391,E231 --ignore E501 ../modem.py ../data_handler.py ../main.py ../sock.py ../static.py ../helpers.py +#autopep8 --in-place --select W291,W293,W391,E231 --ignore E501 ../modem.py ../data_handler.py ../deprecated_main.py ../deprecated_sock.py ../static.py ../helpers.py autopep8 --in-place --ignore E501 ../modem.py ../data_handler.py ../main.py ../sock.py ../static.py ../helpers.py