215 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			215 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/env python
 | |
| 
 | |
| import asyncio
 | |
| 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
 | |
| 
 | |
| Boats = []
 | |
| Clients = []
 | |
| 
 | |
| async def sendToBoat(boat, cmd):
 | |
|     client['boat']['log'].append({"t": datetime.timestamp(datetime.now()), "type": "tx", "msg": cmd})
 | |
|     await client['boat']['ws'].send(cmd)
 | |
| 
 | |
| async def echo_boats(client):
 | |
|     """echo list of all not locked boats to client"""
 | |
|     data = "boats:"
 | |
|     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 client['ws'].send(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 client['ws'].send(data + '\n')
 | |
| 
 | |
| async def echo_clients(client):
 | |
|     """echo list of all clients to client"""
 | |
|     if client['id'] == ADMIN_ID:
 | |
|         data = "clients:"
 | |
|         for clie in Clients:
 | |
|             data += str(clie['id']) + ";" + str(clie['boat']) + ";" + str(clie['state']) + ":"
 | |
|         await client['ws'].send(data + '\n')
 | |
| 
 | |
| 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 client['ws'].send("OK")
 | |
|                 return
 | |
|             else:
 | |
|                 break
 | |
|     await client['ws'].send("FAIL")
 | |
| 
 | |
| async def free_boat(boat):
 | |
|     """make boat available for next client"""
 | |
|     for client in Clients:
 | |
|         if client["boat"]['id'] == boat:
 | |
|             client["boat"] = None
 | |
|             break
 | |
|     for b in Boats:
 | |
|         if b['id'] == boat and b['state'] != BOAT_STATE_TERMINATED:
 | |
|             if boat['state'] == BOAT_STATE_INCTRL:
 | |
|                 boat["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"]['id'] == boat:
 | |
|             client["boat"] = None
 | |
|             break
 | |
|     for b in Boats:
 | |
|         if b['id'] == boat and b['state'] != BOAT_STATE_TERMINATED:
 | |
|             boat["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 client['ws'].send("log;" + b['id'] + ";" + json.dumps(b['log']))
 | |
|             return
 | |
| 
 | |
| async def sendcmd(data):
 | |
|     """send command to boat"""
 | |
|     for b in Boats:
 | |
|         if b['id'] == data[2]:
 | |
|             del data[0]
 | |
|             del data[1]
 | |
|             data = ";".join(data)
 | |
|             await sendToBoat(b, data)
 | |
|             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"]))
 | |
|     else:
 | |
|         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":
 | |
|             await echo_locked_boats(client)
 | |
|         elif data[1] == "kick":
 | |
|             print("kick")
 | |
|         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])
 | |
|             elif client['boat']['lastMsg'] + BOAT_DATA_INTERVAL_MAX < datetime.timestamp(datetime.now()):
 | |
|                 # print("INFO: controll cmd (" + client['id'] + ") to " + client['boat']['name'] + ": " + data[2])
 | |
|                 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)
 | |
|         else:
 | |
|             print("WARN: invalid command (" + client['id'] + "): '" + data[1] + "'")
 | |
| 
 | |
| async def new_client(clientId, ws):
 | |
|     """handler for every new client connection"""
 | |
|     client = { "id": clientId, "boat": None, "ws": ws, "state": "active" }
 | |
|     print("new client connected: " + client['id'])
 | |
|     for clie in Clients:
 | |
|         if clie['id'] == client['id']:
 | |
|             clie['state'] = 'terminated'
 | |
|     Clients.append(client)
 | |
|     if client['id'] == ADMIN_ID:
 | |
|         await echo_boats(client)
 | |
|         await echo_locked_boats(client)
 | |
|         await echo_clients(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("new boat connected: " + boatId)
 | |
|     for bo in Boats:
 | |
|         if bo['id'] == boat['id']:
 | |
|             bo['state'] = BOAT_STATE_TERMINATED
 | |
|             await bo['ws'].close()
 | |
|     Boats.append(boat)
 | |
|     return boat
 | |
| 
 | |
| async def run(ws, path):
 | |
|     """hadeler for every new websocket connection"""
 | |
|     print("new websocket connection: " + path)
 | |
|     client = None
 | |
|     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:
 | |
|                     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
 | |
|                 break
 | |
|             elif (len(message) == 3 and message[1] == "3440"):
 | |
|                 client = await new_boat(message[0], message[2], ws)
 | |
|                 if client is not None:
 | |
|                     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
 | |
|                             })
 | |
|                 break
 | |
| 
 | |
| async def main():
 | |
|     """main"""
 | |
|     async with serve(run, "0.0.0.0", 8080):
 | |
|         await asyncio.Future()
 | |
| 
 | |
| asyncio.run(main())
 |