#!/usr/bin/env python import asyncio import websockets from websockets.server import serve from datetime import datetime import json ADMIN_ID = "1234" BOAT_DATA_INTERVAL_MAX = 0.1 # seconds BOAT_STATE_INCTRL = 2 BOAT_STATE_AVAILABLE = 1 BOAT_STATE_LOCKED = 0 BOAT_STATE_TERMINATED = -1 BOAT_STATE_DELETE = -2 Boats = [] Clients = [] def client_disconnect(client): if client['state'] != 'kicked': client['state'] = 'del' if client['boat'] is not None: client['boat']['state'] = BOAT_STATE_AVAILABLE client['boat'] = None for i, cli in enumerate(Clients): if cli['state'] == 'del': del Clients[i] def boat_disconnect(boat): boat['state'] = BOAT_STATE_DELETE for i, cli in enumerate(Boats): if cli['state'] == BOAT_STATE_DELETE: del Boats[i] async def sendToClient(client, msg): try: await client['ws'].send(msg) except websockets.exceptions.ConnectionClosed: client_disconnect(client) async def sendToBoat(boat, cmd): boat['log'].append({"t": datetime.timestamp(datetime.now()), "type": "tx", "msg": cmd}) await boat['ws'].send(cmd) async def echo_boats(client): """echo list of all not locked boats to client""" data = "boats:" if client['state'] != "kicked": for boat in Boats: if boat['state'] != BOAT_STATE_LOCKED and boat['state'] != BOAT_STATE_TERMINATED: data += str(boat['id']) + ";" + str(boat['name']) + ";" if boat["state"] == BOAT_STATE_AVAILABLE: data += 'available:' elif boat["state"] == BOAT_STATE_INCTRL: data += 'inctrl:' await sendToClient(client, data) async def echo_locked_boats(client): """echo list of all locked boats to client""" if client['id'] == ADMIN_ID: data = "lockedBoats:" for boat in Boats: if boat['state'] == BOAT_STATE_LOCKED: data += str(boat['id']) + ";" + str(boat['name']) + ";locked:" await sendToClient(client, data) async def echo_clients(client): """echo list of all clients to client""" if client['id'] == ADMIN_ID: data = "clients:" for clie in Clients: clientId = clie['id'] if (clie['id'] == ADMIN_ID): clientId = "admin" boatId = "----" if (clie['boat'] is not None): boatId = clie['boat']['id'] data += clientId + ";" + boatId + ";" + str(clie['state']) + ":" await sendToClient(client, data) async def take_controll(client, boat): """let a client take controll a boat""" if client['boat'] is not None: client['boat']["state"] = BOAT_STATE_AVAILABLE client["boat"] = None for b in Boats: if b['id'] == boat and b['state'] != BOAT_STATE_TERMINATED: if b['state'] == BOAT_STATE_AVAILABLE: b["state"] = BOAT_STATE_INCTRL print("take controll: " + str(client["id"]) + " -> " + b["name"]) client["boat"] = b await sendToClient(client, "OK") return else: break await sendToClient(client, "FAIL") async def free_boat(boat): """make boat available for next client""" for client in Clients: if client["boat"] is not None and client["boat"]['id'] == boat: client["boat"] = None await sendToClient(client, "FAIL") break for b in Boats: if b['id'] == boat and b['state'] != BOAT_STATE_TERMINATED: b["state"] = BOAT_STATE_AVAILABLE break async def lock_boat(boat): """lock a boat so client can't take controll over it""" for client in Clients: if client["boat"] is not None and client["boat"]['id'] == boat: client["boat"] = None await sendToClient(client, "FAIL") break for b in Boats: if b['id'] == boat and b['state'] != BOAT_STATE_TERMINATED: b["state"] = BOAT_STATE_LOCKED break async def getlog(client, data): """send boat log to client""" for b in Boats: if b['id'] == data[2]: await sendToClient(client, "log;" + b['id'] + ";" + json.dumps(b['log'])) return async def sendcmd(data): """send command to boat""" if len(data) > 3: for b in Boats: if b['id'] == data[2]: del data[2] del data[1] del data[0] print(data) data = ";".join(data) await sendToBoat(b, data + "\n") return async def kick_client(clientId): """kick a client""" for client in Clients: if client['id'] == client and client['state'] != "terminated": if client['boat'] is not None: client['boat']['state'] = BOAT_STATE_AVAILABLE client['boat'] = None client['state'] = "kicked" await client['ws'].send("FAIL") return async def on_message(message, client): """handel for client messages""" data = message.replace('\n', '').split(';') if data[0] != client["id"]: print("invalid id: " + str(data[0]) + " != " + str(client["id"])) elif len(data) >= 2: if data[1] == "boats": await echo_boats(client) elif data[1] == "ctrl": await take_controll(client, data[2]) elif data[1] == "end": await take_controll(client, "noBoat") elif data[1] == "free" and client["id"] == ADMIN_ID: await free_boat(data[2]) elif data[1] == "lock" and client["id"] == ADMIN_ID: await lock_boat(data[2]) elif data[1] == "locked" and client["id"] == ADMIN_ID: await echo_locked_boats(client) elif data[1] == "kick" and client["id"] == ADMIN_ID: await kick_client(data[2]) elif data[1] == "clients": await echo_clients(client) elif data[1] == "d": if client['boat'] is None: print("WARN: controll cmd (" + client['id'] + ") to None: " + data[2]) await client['ws'].send("FAIL") elif client['boat']['lastMsg'] + BOAT_DATA_INTERVAL_MAX < datetime.timestamp(datetime.now()): # print("INFO: controll cmd (" + client['id'] + ") to " + client['boat']['name'] + ": " + data[2]) await sendToBoat(client['boat'], "d:" + data[2] + "\n") client['boat']['lastMsg'] = datetime.timestamp(datetime.now()) elif data[1] == "getlog" and client['id'] == ADMIN_ID: await getlog(client, data) elif data[1] == "sendcmd" and client['id'] == ADMIN_ID: await sendcmd(data) elif client['id'] == ADMIN_ID: print("WARN: invalid command (admin): '" + data[1] + "'") else: print("WARN: invalid command (" + client['id'] + "): '" + data[1] + "'") else: print("WARN: to little arguments") async def new_client(clientId, ws): """handler for every new client connection""" client = { "id": clientId, "boat": None, "ws": ws, "state": "active" } if client['id'] == ADMIN_ID: print("INFO: new client connected: admin") else: print("INFO: new client connected: " + client['id']) for clie in Clients: if clie['id'] == client['id']: if client['state'] == "kicked": client['state'] = "kicked" clie['state'] = 'terminated' Clients.append(client) return client async def new_boat(boatId, name, ws): """handler for every new boat connection""" boat = { "id": boatId, "name": name, "ws": ws, "state": BOAT_STATE_AVAILABLE, "lastMsg": 0, "log": [] } print("INFO: new boat connected: " + boatId) for i, bo in enumerate(Boats): if bo['id'] == boat['id']: bo['state'] = BOAT_STATE_TERMINATED # await bo['ws'].close() # del Boats[i] Boats.append(boat) return boat async def run(ws, path): """hadeler for every new websocket connection""" client = None try: print("INFO: new connection") async for msg in ws: for message in msg.split("\n"): if len(message) == 0: continue message = message.split(';') if len(message) == 3 and message[1] == "4675": client = await new_client(message[0], ws) if client is not None: try: async for msg in ws: for message in msg.split("\n"): if len(message) == 0: continue if client['state'] == 'active': await on_message(message, client) else: break except websockets.exceptions.ConnectionClosed: print("INFO: client " + client['id'] + ": execption") print("INFO: client " + client['id'] + ": disconnected") client_disconnect(client) break elif (len(message) == 3 and message[1] == "3440"): client = await new_boat(message[0], message[2], ws) if client is not None: try: async for msg in ws: for message in msg.split("\n"): if len(message) == 0: continue print("boat" + client['id'] + " says '" + message + "'") client['log'].append({ "t": datetime.timestamp(datetime.now()), "type": "rx", "msg": message }) except websockets.exceptions.ConnectionClosed: print("INFO: boat " + client['id'] + ": execption") print("INFO: boat " + client['id'] + ": disconnected") boat_disconnect(client) break except websockets.exceptions.ConnectionClosed: print("WARN: connection disconected") async def main(): """main""" async with serve(run, "0.0.0.0", 8080): await asyncio.Future() asyncio.run(main())