diff --git a/gui/preload-main.js b/gui/preload-main.js index d2bc7d75..a19fefc9 100644 --- a/gui/preload-main.js +++ b/gui/preload-main.js @@ -267,7 +267,7 @@ window.addEventListener('DOMContentLoaded', () => { // START TRANSMISSION document.getElementById("startTransmission").addEventListener("click", () => { - + document.getElementById("mySidebar").style.width = "0px"; var fileList = document.getElementById("dataModalFile").files; var reader = new FileReader(); @@ -635,7 +635,120 @@ ipcRenderer.on('action-update-tnc-state', (event, arg) => { tbl.appendChild(row); } + + + // DISPLAY INFO TOASTS + if (typeof(arg.info) == 'undefined') { + var infoLength = 0 + } else { + var infoLength = arg.info.length + } + for (i = 0; i < infoLength; i++) { + + // SENDING CQ TOAST + if (arg.info[i] == "CQ;SENDING"){ + var toastCQsending = document.getElementById('toastCQsending') + var toast = bootstrap.Toast.getOrCreateInstance(toastCQsending) // Returns a Bootstrap toast instance + toast.show() + } + + // RECEIVING CQ TOAST + if (arg.info[i] == "CQ;RECEIVING"){ + var toastCQreceiving = document.getElementById('toastCQreceiving') + var toast = bootstrap.Toast.getOrCreateInstance(toastCQreceiving) // Returns a Bootstrap toast instance + toast.show() + } + + // SENDING PING TOAST + if (arg.info[i] == "PING;SENDING"){ + var toastPINGsending = document.getElementById('toastPINGsending') + var toast = bootstrap.Toast.getOrCreateInstance(toastPINGsending) // Returns a Bootstrap toast instance + toast.show() + } + // RECEIVING PING TOAST + if (arg.info[i] == "PING;RECEIVING"){ + var toastPINGreceiving = document.getElementById('toastPINGreceiving') + var toast = bootstrap.Toast.getOrCreateInstance(toastPINGreceiving) // Returns a Bootstrap toast instance + toast.show() + } + // RECEIVING PING ACK TOAST + if (arg.info[i] == "PING;RECEIVEDACK"){ + var toastPINGreceivedACK = document.getElementById('toastPINGreceivedACK') + var toast = bootstrap.Toast.getOrCreateInstance(toastPINGreceivedACK) // Returns a Bootstrap toast instance + toast.show() + } + // DATACHANNEL OPENING TOAST + if (arg.info[i] == "DATACHANNEL;OPENING"){ + var toastDATACHANNELopening = document.getElementById('toastDATACHANNELopening') + var toast = bootstrap.Toast.getOrCreateInstance(toastDATACHANNELopening) // Returns a Bootstrap toast instance + toast.show() + } + + // DATACHANNEL OPEN TOAST + if (arg.info[i] == "DATACHANNEL;OPEN"){ + var toastDATACHANNELopen = document.getElementById('toastDATACHANNELopen') + var toast = bootstrap.Toast.getOrCreateInstance(toastDATACHANNELopen) // Returns a Bootstrap toast instance + toast.show() + } + // DATACHANNEL RECEIVEDOPENER TOAST + if (arg.info[i] == "DATACHANNEL;RECEIVEDOPENER"){ + var toastDATACHANNELreceivedopener = document.getElementById('toastDATACHANNELreceivedopener') + var toast = bootstrap.Toast.getOrCreateInstance(toastDATACHANNELreceivedopener) // Returns a Bootstrap toast instance + toast.show() + } + // DATACHANNEL FAILED TOAST + if (arg.info[i] == "DATACHANNEL;FAILED"){ + var toastDATACHANNELfailed = document.getElementById('toastDATACHANNELfailed') + var toast = bootstrap.Toast.getOrCreateInstance(toastDATACHANNELfailed) // Returns a Bootstrap toast instance + toast.show() + } + // ARQ RECEIVING TOAST + if (arg.info[i] == "ARQ;RECEIVING"){ + var toastARQreceiving = document.getElementById('toastARQreceiving') + var toast = bootstrap.Toast.getOrCreateInstance(toastARQreceiving) // Returns a Bootstrap toast instance + toast.show() + } + // ARQ RECEIVING SUCCESS TOAST + if (arg.info[i] == "ARQ;RECEIVING;SUCCESS"){ + var toastARQreceivingsuccess = document.getElementById('toastARQreceivingsuccess') + var toast = bootstrap.Toast.getOrCreateInstance(toastARQreceivingsuccess) // Returns a Bootstrap toast instance + toast.show() + } + // ARQ RECEIVING FAILED TOAST + if (arg.info[i] == "ARQ;RECEIVING;FAILED"){ + var toastARQreceivingfailed = document.getElementById('toastARQreceivingfailed') + var toast = bootstrap.Toast.getOrCreateInstance(toastARQreceivingfailed) // Returns a Bootstrap toast instance + toast.show() + } + // ARQ TRANSMITTING TOAST + if (arg.info[i] == "ARQ;TRANSMITTING"){ + var toastARQtransmitting = document.getElementById('toastARQtransmitting') + var toast = bootstrap.Toast.getOrCreateInstance(toastARQtransmitting) // Returns a Bootstrap toast instance + toast.show() + } + // ARQ TRANSMITTING SUCCESS TOAST + if (arg.info[i] == "ARQ;TRANSMITTING;SUCCESS"){ + var toastARQtransmittingsuccess = document.getElementById('toastARQtransmittingsuccess') + var toast = bootstrap.Toast.getOrCreateInstance(toastARQtransmittingsuccess) // Returns a Bootstrap toast instance + toast.show() + } + // ARQ TRANSMITTING FAILED TOAST + if (arg.info[i] == "ARQ;TRANSMITTING;FAILED"){ + var toastARQtransmittingfailed = document.getElementById('toastARQtransmittingfailed') + var toast = bootstrap.Toast.getOrCreateInstance(toastARQtransmittingfailed) // Returns a Bootstrap toast instance + toast.show() + } + + + + + + + + + + } }); ipcRenderer.on('action-update-daemon-state', (event, arg) => { @@ -651,48 +764,52 @@ ipcRenderer.on('action-update-daemon-state', (event, arg) => { document.getElementById("progressbar_cpu_value").innerHTML = arg.cpu_usage + "%" // UPDATE AUDIO INPUT + if (arg.tnc_running_state == "stopped") { + if (document.getElementById("audio_input_selectbox").length != arg.input_devices.length) { + document.getElementById("audio_input_selectbox").innerHTML = "" + for (i = 0; i < arg.input_devices.length; i++) { + var option = document.createElement("option"); + option.text = arg.input_devices[i]['NAME']; + option.value = arg.input_devices[i]['ID']; - if (document.getElementById("audio_input_selectbox").length != arg.input_devices.length) { - document.getElementById("audio_input_selectbox").innerHTML = "" - for (i = 0; i < arg.input_devices.length; i++) { - var option = document.createElement("option"); - option.text = arg.input_devices[i]['NAME']; - option.value = arg.input_devices[i]['ID']; - - document.getElementById("audio_input_selectbox").add(option); + document.getElementById("audio_input_selectbox").add(option); + } } } // UPDATE AUDIO OUTPUT - - if (document.getElementById("audio_output_selectbox").length != arg.output_devices.length) { - document.getElementById("audio_output_selectbox").innerHTML = "" - for (i = 0; i < arg.output_devices.length; i++) { - var option = document.createElement("option"); - option.text = arg.output_devices[i]['NAME']; - option.value = arg.output_devices[i]['ID']; - document.getElementById("audio_output_selectbox").add(option); + if (arg.tnc_running_state == "stopped") { + if (document.getElementById("audio_output_selectbox").length != arg.output_devices.length) { + document.getElementById("audio_output_selectbox").innerHTML = "" + for (i = 0; i < arg.output_devices.length; i++) { + var option = document.createElement("option"); + option.text = arg.output_devices[i]['NAME']; + option.value = arg.output_devices[i]['ID']; + document.getElementById("audio_output_selectbox").add(option); + } } } // UPDATE SERIAL DEVICES - - if (document.getElementById("hamlib_deviceport").length != arg.serial_devices.length) { - document.getElementById("hamlib_deviceport").innerHTML = "" - for (i = 0; i < arg.serial_devices.length; i++) { - var option = document.createElement("option"); - option.text = arg.serial_devices[i]['DESCRIPTION']; - option.value = arg.serial_devices[i]['PORT']; - document.getElementById("hamlib_deviceport").add(option); + if (arg.tnc_running_state == "stopped") { + if (document.getElementById("hamlib_deviceport").length != arg.serial_devices.length) { + document.getElementById("hamlib_deviceport").innerHTML = "" + for (i = 0; i < arg.serial_devices.length; i++) { + var option = document.createElement("option"); + option.text = arg.serial_devices[i]['DESCRIPTION']; + option.value = arg.serial_devices[i]['PORT']; + document.getElementById("hamlib_deviceport").add(option); + } } } - - if (document.getElementById("hamlib_ptt_port").length != arg.serial_devices.length) { - document.getElementById("hamlib_ptt_port").innerHTML = "" - for (i = 0; i < arg.serial_devices.length; i++) { - var option = document.createElement("option"); - option.text = arg.serial_devices[i]['DESCRIPTION']; - option.value = arg.serial_devices[i]['PORT']; - document.getElementById("hamlib_ptt_port").add(option); + if (arg.tnc_running_state == "stopped") { + if (document.getElementById("hamlib_ptt_port").length != arg.serial_devices.length) { + document.getElementById("hamlib_ptt_port").innerHTML = "" + for (i = 0; i < arg.serial_devices.length; i++) { + var option = document.createElement("option"); + option.text = arg.serial_devices[i]['DESCRIPTION']; + option.value = arg.serial_devices[i]['PORT']; + document.getElementById("hamlib_ptt_port").add(option); + } } } diff --git a/gui/sock.js b/gui/sock.js index aec33ba3..f5d44d71 100644 --- a/gui/sock.js +++ b/gui/sock.js @@ -136,6 +136,7 @@ client.on('data', function(data) { rms_level: (data['AUDIO_RMS'] / 1000) * 100, fft: data['FFT'], scatter: data['SCATTER'], + info: data['INFO'], rx_buffer_length: data['RX_BUFFER_LENGTH'], tx_n_max_retries: data['TX_N_MAX_RETRIES'], arq_tx_n_frames_per_burst: data['ARQ_TX_N_FRAMES_PER_BURST'], diff --git a/gui/src/index.html b/gui/src/index.html index d7c66d86..28c96353 100644 --- a/gui/src/index.html +++ b/gui/src/index.html @@ -2,841 +2,979 @@ - - - - - - - - - - - - - FreeDATA - - - - + + + + + + + + + + + FreeDATA + + + + + + +
+ + + +
+
+ + - - - - -
- --> - - -
- - - -
-
-
-
-
- Step 1: AUDIO SETTINGS -
-
-
- RX - -
-
- TX - -
-
-
-

RX AUDIO LEVEL

-
-
- +
+ +
+
+
+
+
+ Step 1: AUDIO SETTINGS +
+
+
+ RX +
-
-
-
-
- Step 2: RADIO SETTINGS -
-
-
- RIG - - Speed - -
-
- Port - -
-
-
- PTT - - Port - -
-
- +
+ TX +
-
-
-
-
Step 3: TNC STATUS
-
- -
-
-
- TNC -
-
-
- - --- - -
-
-
-
-
- CPU -
-
-
-
-

-
-
-
-
-
- RAM -
-
-
-
-

-
-
-
-
-
- TOE -
-
-

-
-
-
-
- +
+
+

RX AUDIO LEVEL

-
-
-
-
-
-
-
-
-
MY STATION
-
-
-
-
- - -
-
-
-
- - -
-
-
- -
-
-
-
-
-
PING & CQ
-
-
-
-
- Ping - - - ACK - 0000 km - 0 dB -
-
-
-
- -
-
-
- -
-
-
-
-
-
-
-
-
- - - - -
-
-
- - -
-
-
-
-
-
HEARD STATIONS
-
- - - - - - - - - - - - - - - -
TimeDXCallDXGridDistanceTypeSNR
- -
-
-
-
-
-
- - -
-
- - - - - - - - - - - - - - - +
+
+
+
+
+ + + + +
+
+
+ + +
+
+
+
+
+
HEARD STATIONS
+
+ + + + + + + + + + + + + + + +
TimeDXCallDXGridDistanceTypeSNR
+ +
+
+
+
+
+
+ + +
+ + + + + + + + + + + + + + diff --git a/gui/src/styles.css b/gui/src/styles.css index 493b8ef9..a7bd4268 100644 --- a/gui/src/styles.css +++ b/gui/src/styles.css @@ -26,7 +26,7 @@ height: 100%; /* 100% Full-height */ width: 0; /* 0 width - change this with JavaScript */ position: fixed; /* Stay in place */ - z-index: 1000; /* Stay on top */ + z-index: 100; /* Stay on top */ top: 0; /*left: 1220px;*/ right: 0; @@ -34,4 +34,4 @@ overflow-x: hidden; /* Disable horizontal scroll */ padding-top: 60px; /* Place content 60px from the top */ transition: 0.5s; /* 0.5 second transition effect to slide in the sidebar */ -} \ No newline at end of file +} diff --git a/tnc/data_handler.py b/tnc/data_handler.py index 79fc3b72..753a3473 100644 --- a/tnc/data_handler.py +++ b/tnc/data_handler.py @@ -86,6 +86,7 @@ def arq_data_received(data_in, bytes_per_frame): arq_percent_burst = int((RX_N_FRAME_OF_BURST / RX_N_FRAMES_PER_BURST) * 100) #arq_percent_frame = int(((RX_N_FRAME_OF_DATA_FRAME) / RX_N_FRAMES_PER_DATA_FRAME) * 100) calculate_transfer_rate_rx(RX_N_FRAMES_PER_DATA_FRAME, RX_N_FRAME_OF_DATA_FRAME, RX_START_OF_TRANSMISSION, RX_PAYLOAD_PER_ARQ_FRAME) + static.INFO.append("ARQ;RECEIVING") logging.log(24, "ARQ | RX | " + str(DATA_CHANNEL_MODE) + " | F:[" + str(RX_N_FRAME_OF_BURST) + "/" + str(RX_N_FRAMES_PER_BURST) + "] [" + str(arq_percent_burst).zfill(3) + "%] T:[" + str(RX_N_FRAME_OF_DATA_FRAME) + "/" + str(RX_N_FRAMES_PER_DATA_FRAME) + "] [" + str(int(static.ARQ_TRANSMISSION_PERCENT)).zfill(3) + "%] [SNR:" + str(static.SNR) + "]") @@ -233,6 +234,7 @@ def arq_data_received(data_in, bytes_per_frame): # IF THE FRAME PAYLOAD CRC IS EQUAL TO THE FRAME CRC WHICH IS KNOWN FROM THE HEADER --> SUCCESS if frame_payload_crc == data_frame_crc: + static.INFO.append("ARQ;RECEIVING;SUCCESS") logging.log(25, "ARQ | RX | DATA FRAME SUCESSFULLY RECEIVED! :-) ") calculate_transfer_rate_rx(RX_N_FRAMES_PER_DATA_FRAME, RX_N_FRAME_OF_DATA_FRAME, RX_START_OF_TRANSMISSION, RX_PAYLOAD_PER_ARQ_FRAME) # append received frame to RX_BUFFER @@ -272,6 +274,7 @@ def arq_data_received(data_in, bytes_per_frame): print("ARQ_FRAME_EOF_RECEIVED " + str(RX_FRAME_EOF_RECEIVED)) print(static.RX_FRAME_BUFFER) calculate_transfer_rate_rx(RX_N_FRAMES_PER_DATA_FRAME, RX_N_FRAME_OF_DATA_FRAME, RX_START_OF_TRANSMISSION, RX_PAYLOAD_PER_ARQ_FRAME) + static.INFO.append("ARQ;RECEIVING;FAILED") logging.error("ARQ | RX | DATA FRAME NOT SUCESSFULLY RECEIVED!") @@ -341,7 +344,7 @@ def arq_transmit(data_out, mode, n_frames_per_burst): # --------------------------------------------- LETS CREATE A BUFFER BY SPLITTING THE FILES INTO PEACES TX_BUFFER = [data_out[i:i + TX_PAYLOAD_PER_ARQ_FRAME] for i in range(0, len(data_out), TX_PAYLOAD_PER_ARQ_FRAME)] TX_BUFFER_SIZE = len(TX_BUFFER) - + static.INFO.append("ARQ;TRANSMITTING") logging.info("ARQ | TX | M:" + str(DATA_CHANNEL_MODE) + " | DATA FRAME --- BYTES: " + str(len(data_out)) + " ARQ FRAMES: " + str(TX_BUFFER_SIZE)) # ----------------------- THIS IS THE MAIN LOOP----------------------------------------------------------------- @@ -527,12 +530,14 @@ def arq_transmit(data_out, mode, n_frames_per_burst): # ----------- if no ACK received and out of retries.....stop frame sending if not BURST_ACK_RECEIVED and not DATA_FRAME_ACK_RECEIVED: logging.error("ARQ | TX | NO ACK RECEIVED | DATA SHOULD BE RESEND!") + static.INFO.append("ARQ;TRANSMITTING;FAILED") break # -------------------------BREAK TX BUFFER LOOP IF ALL PACKETS HAVE BEEN SENT AND WE GOT A FRAME ACK elif TX_N_SENT_FRAMES == TX_BUFFER_SIZE and DATA_FRAME_ACK_RECEIVED: print(TX_N_SENT_FRAMES) calculate_transfer_rate_tx(TX_N_SENT_FRAMES, TX_PAYLOAD_PER_ARQ_FRAME, TX_START_OF_TRANSMISSION, TX_BUFFER_SIZE) + static.INFO.append("ARQ;TRANSMITTING;SUCCESS") logging.log(25, "ARQ | RX | FRAME ACK! - DATA TRANSMITTED! [" + str(static.ARQ_BITS_PER_SECOND) + " bit/s | " + str(static.ARQ_BYTES_PER_MINUTE) + " B/min]") break @@ -635,7 +640,7 @@ async def arq_open_data_channel(mode): global DATA_CHANNEL_READY_FOR_DATA global DATA_CHANNEL_LAST_RECEIVED - DATA_CHANNEL_MAX_RETRIES = 3 # N attempts for connecting to another station + DATA_CHANNEL_MAX_RETRIES = 5 # N attempts for connecting to another station DATA_CHANNEL_MODE = int(mode) DATA_CHANNEL_LAST_RECEIVED = int(time.time()) @@ -650,6 +655,8 @@ async def arq_open_data_channel(mode): while not DATA_CHANNEL_READY_FOR_DATA: time.sleep(0.01) for attempt in range(1,DATA_CHANNEL_MAX_RETRIES+1): + static.INFO.append("DATACHANNEL;OPENING") + logging.info("DATA [" + str(static.MYCALLSIGN, 'utf-8') + "]>> <<[" + str(static.DXCALLSIGN, 'utf-8') + "] A:[" + str(attempt) + "/" + str(DATA_CHANNEL_MAX_RETRIES) + "]") while not modem.transmit_signalling(connection_frame, 1): time.sleep(0.01) @@ -665,6 +672,7 @@ async def arq_open_data_channel(mode): print("attempt:" + str(attempt) + "/" + str(DATA_CHANNEL_MAX_RETRIES)) if not DATA_CHANNEL_READY_FOR_DATA and attempt == DATA_CHANNEL_MAX_RETRIES: + static.INFO.append("DATACHANNEL;FAILED") logging.info("DATA [" + str(static.MYCALLSIGN, 'utf-8') + "]>>X<<[" + str(static.DXCALLSIGN, 'utf-8') + "]") static.TNC_STATE = 'IDLE' static.ARQ_STATE = 'IDLE' @@ -676,6 +684,7 @@ def arq_received_data_channel_opener(data_in): #global DATA_CHANNEL_MODE global DATA_CHANNEL_LAST_RECEIVED + static.INFO.append("DATACHANNEL;RECEIVEDOPENER") static.DXCALLSIGN_CRC8 = bytes(data_in[2:3]).rstrip(b'\x00') static.DXCALLSIGN = bytes(data_in[3:9]).rstrip(b'\x00') helpers.add_to_heard_stations(static.DXCALLSIGN,static.DXGRID, 'DATA-CHANNEL', static.SNR) @@ -710,6 +719,7 @@ def arq_received_channel_is_open(data_in): global DATA_CHANNEL_READY_FOR_DATA global DATA_CHANNEL_MODE + static.INFO.append("DATACHANNEL;OPEN") static.DXCALLSIGN_CRC8 = bytes(data_in[2:3]).rstrip(b'\x00') static.DXCALLSIGN = bytes(data_in[3:9]).rstrip(b'\x00') helpers.add_to_heard_stations(static.DXCALLSIGN,static.DXGRID, 'DATA-CHANNEL', static.SNR) @@ -745,6 +755,9 @@ def arq_received_channel_is_open(data_in): def transmit_ping(callsign): static.DXCALLSIGN = bytes(callsign, 'utf-8').rstrip(b'\x00') static.DXCALLSIGN_CRC8 = helpers.get_crc_8(static.DXCALLSIGN) + + static.INFO.append("PING;SENDING") + logging.info("PING [" + str(static.MYCALLSIGN, 'utf-8') + "] >>> [" + str(static.DXCALLSIGN, 'utf-8') + "] [SNR:" + str(static.SNR) + "]") ping_frame = bytearray(14) @@ -763,6 +776,8 @@ def received_ping(data_in): static.DXCALLSIGN_CRC8 = bytes(data_in[2:3]).rstrip(b'\x00') static.DXCALLSIGN = bytes(data_in[3:9]).rstrip(b'\x00') helpers.add_to_heard_stations(static.DXCALLSIGN,static.DXGRID, 'PING', static.SNR) + + static.INFO.append("PING;RECEIVING") logging.info("PING [" + str(static.MYCALLSIGN, 'utf-8') + "] <<< [" + str(static.DXCALLSIGN, 'utf-8') + "] [SNR:" + str(static.SNR) + "]") ping_frame = bytearray(14) @@ -783,6 +798,7 @@ def received_ping_ack(data_in): helpers.add_to_heard_stations(static.DXCALLSIGN,static.DXGRID, 'PING-ACK', static.SNR) + static.INFO.append("PING;RECEIVEDACK") logging.info("PING [" + str(static.MYCALLSIGN, 'utf-8') + "] >|< [" + str(static.DXCALLSIGN, 'utf-8') + "]["+ str(static.DXGRID, 'utf-8') +"] [SNR:" + str(static.SNR) + "]") static.TNC_STATE = 'IDLE' @@ -792,7 +808,8 @@ def received_ping_ack(data_in): def transmit_cq(): logging.info("CQ CQ CQ") - + static.INFO.append("CQ;SENDING") + cq_frame = bytearray(14) cq_frame[:1] = bytes([200]) cq_frame[1:2] = b'\x01' @@ -809,7 +826,7 @@ def received_cq(data_in): # here we add the received station to the heard stations buffer dxcallsign = bytes(data_in[2:8]).rstrip(b'\x00') dxgrid = bytes(data_in[8:14]).rstrip(b'\x00') - + static.INFO.append("CQ;RECEIVING") logging.info("CQ RCVD [" + str(dxcallsign, 'utf-8') + "]["+ str(dxgrid, 'utf-8') +"] [SNR" + str(static.SNR) + "]") helpers.add_to_heard_stations(dxcallsign,dxgrid, 'CQ CQ CQ', static.SNR) diff --git a/tnc/sock.py b/tnc/sock.py index f26de385..c01e3041 100644 --- a/tnc/sock.py +++ b/tnc/sock.py @@ -198,13 +198,16 @@ class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler): "ARQ_BYTES_PER_MINUTE_BURST": str(static.ARQ_BYTES_PER_MINUTE_BURST), "ARQ_TRANSMISSION_PERCENT": str(static.ARQ_TRANSMISSION_PERCENT), "TOTAL_BYTES": str(static.TOTAL_BYTES), - + "INFO" : static.INFO, "STATIONS": [], "EOF": "EOF", } # we want to transmit scatter data only once to reduce network traffic static.SCATTER = [] + + # we want to display INFO messages only once + static.INFO = [] for i in range(0, len(static.HEARD_STATIONS)): output["STATIONS"].append({"DXCALLSIGN": str(static.HEARD_STATIONS[i][0], 'utf-8'), "DXGRID": str(static.HEARD_STATIONS[i][1], 'utf-8'),"TIMESTAMP": static.HEARD_STATIONS[i][2], "DATATYPE": static.HEARD_STATIONS[i][3], "SNR": static.HEARD_STATIONS[i][4]}) diff --git a/tnc/static.py b/tnc/static.py index 1bbc498d..02609094 100644 --- a/tnc/static.py +++ b/tnc/static.py @@ -83,3 +83,6 @@ RX_FRAME_BUFFER = [] # ------- HEARD STATIOS BUFFER HEARD_STATIONS = [] + +# ------- INFO MESSAGE BUFFER +INFO = []