import json import os import asyncio import websockets from common.runtime import load_root_env from typing import List from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException load_root_env(__file__) threshold_to_alarm = int(os.getenv('threshold_to_alarm')) time_to_jam = int(os.getenv('time_to_jam')) time_to_fresh = int(os.getenv('time_to_fresh')) lochost = os.getenv('lochost') locport = int(os.getenv('locport')) jamhost = os.getenv('jamhost') jamport = os.getenv('jamport') # TODO Добавить обработку кнопки и водопад. class FreqConfig: """ Конфиг частот, отображаемых на планшете. Атрибуты: freq_config: Словарь с частотами и преднастройкой (вкл/выкл). """ def __init__(self): self.freq_config = { '433': False, '500': False, '700': True, '868': True, '915': True, '1200': True, '1500': False, '2400': True, '5200': True, '5800': True } def get(self): return self.freq_config def get_status(self, freq): """ Проверка активности выбранной частоты. :param freq: Частота для проверки. :return: Вкл/выкл. """ return self.freq_config[freq] def set_active(self, freq: str, status: bool): """ Переключение частоты в состояние вкл/выкл. :param freq: Частота для отключения/включения. :param status: Положение, в которое переключается частота (True - вкл, False - выкл). :return: None. """ self.freq_config[freq] = status app = FastAPI() websocket_connections: List[WebSocket] = [] jam_server_connect = None alarm = False freqconfig = FreqConfig() def check_active_tablets() -> None: """ Проверка активных соединений с каким-либо планшетом. """ if not websocket_connections: raise HTTPException(status_code=400, detail="No active WebSocket connections = No tablets in sight.") async def send_to_tablets(package) -> None: """ Рассылка данных по планшетам. :param package: Собранный пакет данных для отправки. """ global alarm if not websocket_connections: print('Нет подключенных планшетов/клиентов.') else: for websocket in websocket_connections: try: print('Пытаемся отправить данные клиенту ', websocket) await websocket.send_json(package) print(f'Пакет {package} успешно отправлен.') except Exception as e: print(f"Не смогли отправить данные клиенту по вебсокету: {e}") async def check_alarm(amplitude: int): if amplitude > threshold_to_alarm: return await send_jam_server_alarm() else: return False # async def freq_active(freq: str): # """ # Запуск скрина с частотой после ее активации в частотном конфиге. # :param freq: Частота для запуска скрина. # """ # # # TODO добавить чек частоты в скринах, а то вдруг мы запускаем скрин, а он уже запущен. # print(f'АКТИВИРУЕМ ЧАСТОТУ {freq}') # command = f"screen -dmS {freq} sh -c \"export PYTHONPATH=/home/orangepi && bash -c \'python3 " \ # f"/home/orangepi/DroneScanner/src/main_5800.py\' && exec bash\"" # screen = subprocess.run(command, shell=True, capture_output=True, text=True) # # # Проверяем результат выполнения # if screen.returncode == 0: # print("Команда успешно выполнена") # else: # print("Ошибка при выполнении команды") # print("STDERR:", screen.stderr) # # # async def freq_deactive(freq: str): # """ # Килл скрин с частотой после ее деактивации в частотном конфиге. # :param freq: Частота для скрин килл. # """ # # print(f'ОТКЛЮЧАЕМ ЧАСТОТУ {freq}') @app.post('/send-waterfall/') async def send_waterfall(data: dict) -> None: """ Прием водопада и отсылка на планшеты, если необходимо. :param data: Водопад в виде пакета data = {...} """ print('bubble tea!') async def set_freq_config(data: dict): """ Переключение состояний частот пришедших с одного планшета и рассылка этой информации на остальные. Запуск/отключение необходимых скринов. :param data: Словарь вида {частота: состояние} :return: None. """ for freq, activ in data.items(): freqconfig.set_active(freq, activ) print(f'Частота {freq} перешла в состояние {activ}: {freqconfig.get_status(freq)}') msg = {'type': 'freq_config', 'data': {freq: activ}} await send_to_tablets(msg) # if activ: # await freq_active(freq) # # else: # await freq_deactive(freq) @app.post("/process_data") async def send_data(data: dict): """ Прием данных со скриптов детекции в формате data = {"freq": freq, "amplitude": amplitude } где freq - строка, amplitude - int, их обработка и рассылка по планшетам. :param data: Словарь. """ global alarm global jam_server_connect # check_active_tablets() print('На сервер пришли данные: ', data) if alarm and jam_server_connect: print('Подавитель активен.') return {'message': 'Подавитель активен.'} elif alarm and jam_server_connect is None: alarm = False if not freqconfig.get_status(data['freq']): print('Частота выключена.') return {'message': 'Частота выключена.'} if await check_alarm(data['amplitude']): print('Затриггерились') msg = {'type': 'freq', 'data': data } await send_to_tablets(msg) @app.websocket("/ws") async def websocket_endpoint(websocket: WebSocket) -> None: """ Прием данных (freq config) по вебсокету от клиента (планшетов) и их обработка. При подключении к серверу - отсылка на клиента текущее состояние частотного конфига. :param websocket: :return: """ await websocket.accept() websocket_connections.append(websocket) msg = {'type': 'freq_config', 'data': freqconfig.get() } await websocket.send_json(msg) try: while True: try: data_from_client = await websocket.receive_json() # Ожидание получения данных от клиента if 'freq' in data_from_client: data = data_from_client['freq'] print('Приняли с планшета: ', data) await set_freq_config(data) except WebSocketDisconnect: print("Client disconnected") websocket_connections.remove(websocket) break except Exception as e: print(f"Error receiving data: {e}") except WebSocketDisconnect: websocket_connections.remove(websocket) # print(e) # print(f"Client disconnected: {e.code} - {e.reason}") ################################################################################## # Подключение к серверу глушилок, как клиент по вебсокету и обработка информации. ################################################################################## async def jammer_active(): """ Активируем подавитель. """ global alarm print('АКТИВИРУЕМ ПОДАВИТЕЛЬ!!!!') alarm = True async def jammer_deactive(): """ Отключаем подавитель. """ global alarm print('ОТКЛЮАЕМ ПОДАВИТЕЛЬ!!!!') alarm = False async def send_jam_server_alarm(): """ Отправить пакет с триггером на сервер подавителей. :return: True, если соединение с сервером активно и данные отправлены успешно. False - иначе. """ global jam_server_connect msg = {'type': 'freq_alarm', 'data': True} if jam_server_connect: await jam_server_connect.send(json.dumps(msg)) return True else: return False async def jam_server(): """ Подключиться к серверу подавителей по вебсокету как клиент. Получение и обработка пакетов - активация/деактивация подавителя. """ uri = f'ws://{jamhost}:{jamport}/ws' global jam_server_connect while True: try: jam_server_connect = await websockets.connect(uri) while True: data_from_jam_server = await jam_server_connect.recv() data_from_jam_server = json.loads(data_from_jam_server) print('Принял с сервера глушилок: ', data_from_jam_server) if data_from_jam_server['type'] == 'run': alarm_status = (data_from_jam_server['data'])['state'] print(alarm_status) if alarm_status: await jammer_active() else: await jammer_deactive() except Exception as e: jam_server_connect = None if alarm: await jammer_deactive() @app.on_event("startup") async def startup_event(): """ Запуск подключения к серверу подавления и получения от него сообщений. """ asyncio.create_task(jam_server()) ################################################################################## # Запуск приложения. ################################################################################## if __name__ == "__main__": import uvicorn uvicorn.run(app, host=lochost, port=locport)