281 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			281 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/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())
 |