278 lines
10 KiB
Python
278 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"""
|
|
for b in Boats:
|
|
if b['id'] == data[2]:
|
|
del data[0]
|
|
del data[1]
|
|
del data[2]
|
|
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"]))
|
|
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" 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] + "'")
|
|
|
|
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())
|