2023-11-09 12:52:03 +00:00
|
|
|
from flask import Flask, request, jsonify, make_response
|
2023-11-06 12:49:37 +00:00
|
|
|
from flask_sock import Sock
|
2023-11-08 09:54:08 +00:00
|
|
|
from flask_cors import CORS
|
2023-11-06 14:36:11 +00:00
|
|
|
import os
|
2023-11-06 20:44:36 +00:00
|
|
|
import serial_ports
|
2023-11-06 12:49:37 +00:00
|
|
|
from config import CONFIG
|
2023-11-06 20:44:36 +00:00
|
|
|
import audio
|
2023-11-08 17:49:38 +00:00
|
|
|
import queue
|
2023-11-09 09:37:45 +00:00
|
|
|
import server_commands
|
2023-11-09 21:11:53 +00:00
|
|
|
import service_manager
|
2023-11-11 19:01:15 +00:00
|
|
|
import state_manager
|
2023-11-14 14:01:01 +00:00
|
|
|
import threading
|
|
|
|
import ujson as json
|
2023-11-06 12:49:37 +00:00
|
|
|
|
|
|
|
app = Flask(__name__)
|
2023-11-08 09:54:08 +00:00
|
|
|
CORS(app)
|
|
|
|
CORS(app, resources={r"/*": {"origins": "*"}})
|
2023-11-06 12:49:37 +00:00
|
|
|
sock = Sock(app)
|
2023-11-14 14:01:01 +00:00
|
|
|
app.config['SOCK_SERVER_OPTIONS'] = {'ping_interval': 5}
|
|
|
|
print(app.config)
|
|
|
|
print(app.config['SOCK_SERVER_OPTIONS'])
|
2023-11-06 14:36:11 +00:00
|
|
|
# set config file to use
|
|
|
|
def set_config():
|
|
|
|
if 'FREEDATA_CONFIG' in os.environ:
|
|
|
|
config_file = os.environ['FREEDATA_CONFIG']
|
|
|
|
else:
|
|
|
|
config_file = 'config.ini'
|
2023-11-06 12:49:37 +00:00
|
|
|
|
2023-11-06 14:36:11 +00:00
|
|
|
if os.path.exists(config_file):
|
2023-11-14 14:01:01 +00:00
|
|
|
print(f"Using config from {config_file}")
|
2023-11-06 14:36:11 +00:00
|
|
|
else:
|
2023-11-14 14:01:01 +00:00
|
|
|
print(f"Config file '{config_file}' not found. Exiting.")
|
2023-11-06 14:36:11 +00:00
|
|
|
exit(1)
|
2023-11-06 12:49:37 +00:00
|
|
|
|
2023-11-06 14:36:11 +00:00
|
|
|
app.config_manager = CONFIG(config_file)
|
|
|
|
|
|
|
|
set_config()
|
|
|
|
|
2023-11-07 10:36:49 +00:00
|
|
|
# start modem
|
2023-11-14 14:01:01 +00:00
|
|
|
app.state_queue = queue.Queue() # queue which holds latest states
|
2023-11-09 21:11:53 +00:00
|
|
|
app.modem_events = queue.Queue() # queue which holds latest events
|
2023-11-14 18:26:11 +00:00
|
|
|
app.modem_fft = queue.Queue() # queue which holds latest fft data
|
2023-11-09 21:11:53 +00:00
|
|
|
app.modem_service = queue.Queue() # start / stop modem service
|
|
|
|
|
2023-11-11 19:01:15 +00:00
|
|
|
# init state manager
|
|
|
|
app.states = state_manager.STATES(app.state_queue)
|
|
|
|
|
2023-11-09 21:11:53 +00:00
|
|
|
# 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)
|
2023-11-09 16:14:22 +00:00
|
|
|
|
2023-11-07 10:36:49 +00:00
|
|
|
|
2023-11-06 12:49:37 +00:00
|
|
|
## REST API
|
|
|
|
@app.route('/', methods=['GET'])
|
|
|
|
def index():
|
2023-11-06 14:36:11 +00:00
|
|
|
return api_response({'name': 'FreeDATA API',
|
2023-11-06 12:49:37 +00:00
|
|
|
'description': '',
|
|
|
|
'api_version': 1,
|
|
|
|
'license': 'GPL3.0',
|
|
|
|
'documentation': 'https://wiki.freedata.app',
|
|
|
|
})
|
|
|
|
|
|
|
|
# get and set config
|
|
|
|
@app.route('/config', methods=['GET', 'POST'])
|
|
|
|
def config():
|
2023-11-11 12:37:18 +00:00
|
|
|
if request.method in ['POST']:
|
2023-11-06 19:52:33 +00:00
|
|
|
set_config = app.config_manager.write(request.json)
|
2023-11-12 21:35:10 +00:00
|
|
|
app.modem_service.put("restart")
|
2023-11-06 12:49:37 +00:00
|
|
|
if not set_config:
|
|
|
|
response = api_response(None, 'error writing config')
|
|
|
|
else:
|
|
|
|
response = api_response(set_config)
|
|
|
|
return response
|
|
|
|
elif request.method == 'GET':
|
2023-11-06 14:36:11 +00:00
|
|
|
return api_response(app.config_manager.read())
|
2023-11-06 12:49:37 +00:00
|
|
|
|
2023-11-06 20:44:36 +00:00
|
|
|
@app.route('/devices/audio', methods=['GET'])
|
|
|
|
def get_audio_devices():
|
|
|
|
dev_in, dev_out = audio.get_audio_devices()
|
|
|
|
devices = { 'in': dev_in, 'out': dev_out }
|
|
|
|
return api_response(devices)
|
|
|
|
|
|
|
|
@app.route('/devices/serial', methods=['GET'])
|
|
|
|
def get_serial_devices():
|
|
|
|
devices = serial_ports.get_ports()
|
|
|
|
return api_response(devices)
|
|
|
|
|
2023-11-17 21:35:52 +00:00
|
|
|
@app.route('/modem/state', methods=['GET'])
|
|
|
|
def get_modem_state():
|
|
|
|
return api_response(app.states.sendState())
|
|
|
|
|
2023-11-09 09:37:45 +00:00
|
|
|
@app.route('/modem/cqcqcq', methods=['POST', 'GET'])
|
|
|
|
def post_cqcqcq():
|
2023-11-11 12:37:18 +00:00
|
|
|
if request.method not in ['POST']:
|
2023-11-09 09:37:45 +00:00
|
|
|
return api_response({"info": "endpoint for triggering a CQ via POST"})
|
2023-11-14 14:01:01 +00:00
|
|
|
if app.states.is_modem_running:
|
|
|
|
server_commands.cqcqcq()
|
2023-11-11 12:37:18 +00:00
|
|
|
return api_response({"cmd": "cqcqcq"})
|
2023-11-09 09:37:45 +00:00
|
|
|
|
2023-11-09 14:48:10 +00:00
|
|
|
@app.route('/modem/beacon', methods=['POST'])
|
|
|
|
def post_beacon():
|
2023-11-11 12:37:18 +00:00
|
|
|
if request.method not in ['POST']:
|
2023-11-09 14:48:10 +00:00
|
|
|
return api_response({"info": "endpoint for controlling BEACON STATE via POST"})
|
2023-11-14 14:01:01 +00:00
|
|
|
if app.states.is_modem_running:
|
|
|
|
server_commands.beacon(request.json)
|
2023-11-11 12:37:18 +00:00
|
|
|
return api_response(request.json)
|
2023-11-09 14:48:10 +00:00
|
|
|
|
|
|
|
@app.route('/modem/ping_ping', methods=['POST'])
|
|
|
|
def post_ping():
|
2023-11-11 12:37:18 +00:00
|
|
|
if request.method not in ['POST']:
|
2023-11-09 14:48:10 +00:00
|
|
|
return api_response({"info": "endpoint for controlling PING via POST"})
|
2023-11-14 14:01:01 +00:00
|
|
|
if app.states.is_modem_running:
|
|
|
|
server_commands.ping_ping(request.json)
|
2023-11-11 12:37:18 +00:00
|
|
|
return api_response(request.json)
|
|
|
|
|
|
|
|
@app.route('/modem/send_test_frame', methods=['POST'])
|
|
|
|
def post_send_test_frame():
|
|
|
|
if request.method not in ['POST']:
|
|
|
|
return api_response({"info": "endpoint for triggering a TEST_FRAME via POST"})
|
2023-11-14 14:01:01 +00:00
|
|
|
if app.states.is_modem_running:
|
|
|
|
server_commands.modem_send_test_frame()
|
2023-11-11 12:37:18 +00:00
|
|
|
return api_response({"cmd": "test_frame"})
|
|
|
|
|
|
|
|
@app.route('/modem/fec_transmit', methods=['POST'])
|
|
|
|
def post_send_fec_frame():
|
|
|
|
if request.method not in ['POST']:
|
|
|
|
return api_response({"info": "endpoint for triggering a FEC frame via POST"})
|
2023-11-14 14:01:01 +00:00
|
|
|
if app.states.is_modem_running:
|
|
|
|
server_commands.modem_fec_transmit(request.json)
|
2023-11-11 12:37:18 +00:00
|
|
|
return api_response(request.json)
|
|
|
|
|
|
|
|
@app.route('/modem/fec_is_writing', methods=['POST'])
|
2023-11-11 19:01:15 +00:00
|
|
|
def post_send_fec_is_writing():
|
2023-11-11 12:37:18 +00:00
|
|
|
if request.method not in ['POST']:
|
|
|
|
return api_response({"info": "endpoint for triggering a IS WRITING frame via POST"})
|
2023-11-14 14:01:01 +00:00
|
|
|
if app.states.is_modem_running:
|
|
|
|
server_commands.modem_fec_is_writing(request.json)
|
2023-11-11 12:37:18 +00:00
|
|
|
return api_response(request.json)
|
2023-11-09 14:48:10 +00:00
|
|
|
|
2023-11-09 21:11:53 +00:00
|
|
|
@app.route('/modem/start', methods=['POST'])
|
|
|
|
def post_modem_start():
|
2023-11-11 12:37:18 +00:00
|
|
|
if request.method not in ['POST']:
|
2023-11-09 21:11:53 +00:00
|
|
|
return api_response({"info": "endpoint for STARTING modem via POST"})
|
2023-11-11 12:37:18 +00:00
|
|
|
print("start received...")
|
|
|
|
app.modem_service.put("start")
|
|
|
|
return api_response(request.json)
|
2023-11-09 21:11:53 +00:00
|
|
|
|
|
|
|
@app.route('/modem/stop', methods=['POST'])
|
|
|
|
def post_modem_stop():
|
2023-11-11 12:37:18 +00:00
|
|
|
if request.method not in ['POST']:
|
2023-11-09 21:11:53 +00:00
|
|
|
return api_response({"info": "endpoint for STOPPING modem via POST"})
|
2023-11-11 12:37:18 +00:00
|
|
|
print("stop received...")
|
|
|
|
|
|
|
|
app.modem_service.put("stop")
|
|
|
|
return api_response(request.json)
|
|
|
|
|
2023-11-13 17:50:46 +00:00
|
|
|
@app.route('/version', methods=['GET'])
|
|
|
|
def get_modem_version():
|
2023-11-17 22:05:41 +00:00
|
|
|
return api_response({"version": 0})
|
2023-11-13 17:50:46 +00:00
|
|
|
|
2023-11-11 12:37:18 +00:00
|
|
|
|
|
|
|
# @app.route('/modem/arq_connect', methods=['POST'])
|
|
|
|
# @app.route('/modem/arq_disconnect', methods=['POST'])
|
|
|
|
# @app.route('/modem/send_raw', methods=['POST'])
|
|
|
|
# @app.route('/modem/stop_transmission', methods=['POST'])
|
2023-11-14 14:01:01 +00:00
|
|
|
# @app.route('/modem/listen', methods=['POST']) # not needed if we are restarting modem on changing settings
|
|
|
|
# @app.route('/modem/record_audio', methods=['POST'])
|
|
|
|
# @app.route('/modem/responde_to_call', methods=['POST']) # not needed if we are restarting modem on changing settings
|
|
|
|
# @app.route('/modem/responde_to_cq', methods=['POST']) # not needed if we are restarting modem on changing settings
|
|
|
|
# @app.route('/modem/audio_levels', methods=['POST']) # tx and rx # not needed if we are restarting modem on changing settings
|
|
|
|
# @app.route('/modem/mesh_ping', methods=['POST'])
|
|
|
|
# @app.route('/mesh/routing_table', methods=['GET'])
|
|
|
|
# @app.route('/modem/get_rx_buffer', methods=['GET'])
|
|
|
|
# @app.route('/modem/del_rx_buffer', methods=['POST'])
|
|
|
|
# @app.route('/rig/status', methods=['GET'])
|
|
|
|
# @app.route('/rig/mode', methods=['POST'])
|
|
|
|
# @app.route('/rig/frequency', methods=['POST'])
|
|
|
|
# @app.route('/rig/test_hamlib', methods=['POST'])
|
2023-11-08 18:21:36 +00:00
|
|
|
|
|
|
|
|
2023-11-09 18:35:38 +00:00
|
|
|
|
2023-11-14 14:01:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
def transmit_sock_data_worker(client_list, event_queue):
|
|
|
|
while True:
|
|
|
|
event = event_queue.get()
|
|
|
|
clients = client_list.copy()
|
|
|
|
for client in clients:
|
2023-11-09 18:35:38 +00:00
|
|
|
try:
|
2023-11-14 14:01:01 +00:00
|
|
|
client.send(event)
|
2023-11-09 18:35:38 +00:00
|
|
|
except Exception:
|
|
|
|
client_list.remove(client)
|
2023-11-14 14:01:01 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def sock_watchdog(sock, client_list, event_queue):
|
|
|
|
event_queue.put(json.dumps({"freedata-message": "hello-client"}))
|
2023-11-17 21:35:52 +00:00
|
|
|
|
2023-11-14 14:01:01 +00:00
|
|
|
client_list.add(sock)
|
|
|
|
while True:
|
|
|
|
try:
|
|
|
|
sock.receive(timeout=1)
|
|
|
|
except Exception as e:
|
2023-11-14 18:26:11 +00:00
|
|
|
print(f"client connection lost: {e}")
|
|
|
|
try:
|
|
|
|
client_list.remove(sock)
|
|
|
|
except Exception as err:
|
|
|
|
print(f"error removing client from list: {e} | {err}")
|
2023-11-14 14:01:01 +00:00
|
|
|
break
|
|
|
|
return
|
2023-11-09 18:35:38 +00:00
|
|
|
|
2023-11-06 12:49:37 +00:00
|
|
|
# Event websocket
|
|
|
|
@sock.route('/events')
|
2023-11-09 16:14:22 +00:00
|
|
|
def sock_events(sock):
|
2023-11-14 14:01:01 +00:00
|
|
|
sock_watchdog(sock, events_client_list, app.modem_events)
|
2023-11-09 16:14:22 +00:00
|
|
|
|
|
|
|
@sock.route('/fft')
|
|
|
|
def sock_fft(sock):
|
2023-11-14 14:01:01 +00:00
|
|
|
sock_watchdog(sock, fft_client_list, app.modem_fft)
|
2023-11-11 12:37:18 +00:00
|
|
|
|
2023-11-11 19:46:49 +00:00
|
|
|
@sock.route('/states')
|
|
|
|
def sock_states(sock):
|
2023-11-14 14:01:01 +00:00
|
|
|
sock_watchdog(sock, states_client_list, app.state_queue)
|
2023-11-17 21:35:52 +00:00
|
|
|
|
2023-11-11 12:37:18 +00:00
|
|
|
|
2023-11-14 14:01:01 +00:00
|
|
|
# websocket multi client support for using with queued information.
|
|
|
|
# our client set which contains all connected websocket clients
|
|
|
|
events_client_list = set()
|
|
|
|
fft_client_list = set()
|
|
|
|
states_client_list = set()
|
2023-11-11 12:37:18 +00:00
|
|
|
|
2023-11-14 14:01:01 +00:00
|
|
|
# start a worker thread for every socket endpoint
|
|
|
|
events_thread = threading.Thread(target=transmit_sock_data_worker, daemon=True, args=(events_client_list, app.modem_events))
|
|
|
|
events_thread.start()
|
2023-11-11 12:37:18 +00:00
|
|
|
|
2023-11-14 14:01:01 +00:00
|
|
|
states_thread = threading.Thread(target=transmit_sock_data_worker, daemon=True, args=(states_client_list, app.state_queue))
|
|
|
|
states_thread.start()
|
2023-11-11 12:37:18 +00:00
|
|
|
|
2023-11-14 14:01:01 +00:00
|
|
|
fft_thread = threading.Thread(target=transmit_sock_data_worker, daemon=True, args=(fft_client_list, app.modem_fft))
|
|
|
|
fft_thread.start()
|