From ebff67b43535e8c031cda9025a6e6ffda8230c9c Mon Sep 17 00:00:00 2001 From: Nicolas Rabault Date: Tue, 9 Jan 2024 13:43:48 +0100 Subject: [PATCH] Add brokers command line tools --- pyluos/tools/robus_broker.py | 138 +++++++++++++++++++++++++++++++++++ pyluos/tools/ws_broker.py | 85 +++++++++++++++++++++ setup.py | 7 +- 3 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 pyluos/tools/robus_broker.py create mode 100644 pyluos/tools/ws_broker.py diff --git a/pyluos/tools/robus_broker.py b/pyluos/tools/robus_broker.py new file mode 100644 index 0000000..89cc637 --- /dev/null +++ b/pyluos/tools/robus_broker.py @@ -0,0 +1,138 @@ +from simple_websocket_server import WebSocketServer, WebSocket +import json +import argparse + + +class RobusEmulator(WebSocket): + prev_node = None + ptpa = False # Linked to prev + ptpb = False # Linked to next + ptpa_poke = False + ptpb_poke = False + next_node = None + msg_index = 0 + + def handle(self): + if isinstance(self.data, str): + # This is a PTP command + print("\nI receive : " + str(self.data) + + " from " + str(self.address)) + + # PTP emulation: + # Because PTP have been designed for real time response, the Robus algorythm is not really friendly to PTP management over WebSocket. + # This broker have to drive data in a specific way allowing to make it work. + # Robus HAL will send messages only during PTP reset state and read line. + # Read_line mean Poke. When we have this we can set the line depending on the presence of another node and save this poke state on the line + # The next reset received will need to be send to the other node. + # + # if (ptp line read (PTP up) : + # if (a node is connected) : + # send state 1 to the other node + # send state 1 back + # pass this ptp to poking + # else : + # send state 0 back + # if (PTP down and ptp is poking) : + # send state to the other node + + # PTPA + if self.data[3] == 'A': + # We get a PTPA data + + if (self.data[4] == '1'): + if (self.prev_node != None): + print("\t\tPTPB1 val sent to the node", + str(self.prev_node.address)) + self.prev_node.send_message("PTPB1") + print("\t\tPTPA1 val sent back to the node", + str(self.address)) + self.send_message("PTPA1") + self.prev_node.ptpb_poke = True + self.ptpa_poke = True + else: + print("\t\tPTPA0 val sent back to the node", + str(self.address)) + self.send_message("PTPA0") + + if (self.data[4] == '0' and self.ptpa_poke == True and self.prev_node != None): + print("\t\tPTPB0 val sent to the node", + str(self.prev_node.address)) + self.prev_node.send_message("PTPB0") + self.prev_node.ptpb_poke = False + self.ptpa_poke = False + + # PTPB + if self.data[3] == 'B': + # We get a PTPB data + + if (self.data[4] == '1'): + if (self.next_node != None): + print("\t\tPTPA1 val sent to the node", + str(self.next_node.address)) + self.next_node.send_message("PTPA1") + print("\t\tPTPB1 val sent back to the node", + str(self.address)) + self.send_message("PTPB1") + self.next_node.ptpa_poke = True + self.ptpb_poke = True + else: + print("\t\tPTPB0 val sent back to the node", + str(self.address)) + self.send_message("PTPB0") + + if (self.data[4] == '0' and self.ptpb_poke == True and self.next_node != None): + print("\t\tPTPA0 val sent to the node", + str(self.next_node.address)) + self.next_node.send_message("PTPA0") + self.next_node.ptpa_poke = False + self.ptpb_poke = False + + else: + # This is a broadcast message + print(str(self.msg_index)+" Data received from " + str(self.address)) + self.msg_index += 1 + for client in clients: + if client != self: + client.send_message(self.data) + + def connected(self): + print(self.address, 'connected') + clients.append(self) + # Save links to other nodes + if len(clients) >= 2: + self.prev_node = clients[-2] + self.prev_node.next_node = clients[-1] + print("connect PTPB of " + str(self.prev_node.address) + + " with PTPA of " + str(self.address)) + + def handle_close(self): + print(self.address, 'closed') + # Save links to other nodes + if self.next_node != None: + self.next_node.prev_node = self.prev_node + if self.prev_node != None: + self.prev_node.next_node = self.next_node + clients.remove(self) + + +## Parse arguments ## +parser = argparse.ArgumentParser(description='Robus WebSocket emulator broker\n', + formatter_class=argparse.RawTextHelpFormatter) +# General arguments +parser.add_argument("-p", "--port", metavar="PORT", action="store", + help="The port used by the websocket.\n" + "By default port = 8000.\n", + default=8000) +parser.add_argument("--ip", metavar="IP", action="store", + help="The ip used by the websocket.\n" + "By default ip = '127.0.0.1'.\n", + default='127.0.0.1') + +args = parser.parse_args() +clients = [] + +server = WebSocketServer(args.ip, args.port, RobusEmulator) +print("WebSocket Robus emulation is deprecaated since Luos_engine 3.1.0, please consider using a WebSocket network instead.\n") +print("WebSocket Robus emulation opened on " + + str(args.ip) + ":" + str(args.port)) +server.serve_forever() diff --git a/pyluos/tools/ws_broker.py b/pyluos/tools/ws_broker.py new file mode 100644 index 0000000..730e402 --- /dev/null +++ b/pyluos/tools/ws_broker.py @@ -0,0 +1,85 @@ +from simple_websocket_server import WebSocketServer, WebSocket +import json +import argparse + +PING = 0 +END = 1 +OK = 2 +NOK = 3 + +class WsBroker(WebSocket): + pinged = False + next_node = None + global pinger + + def handle(self): + if len(self.data) == 1: + if len(pinger) == 0: + # Data should be a ping + if self.data[0] != PING: + print("Error: received data is not a ping, received data is " + str(self.data[0])) + else: + pinger.append(self) + find_someone = False + # This is a ping command, find the next unpinged node and ping it + for client in clients: + if client != self and client.pinged == False: + # We have someone to ping + find_someone = True + client.pinged = True + client.send_message([PING]) + # ack the ping to the sender + self.send_message([OK]) + break + if find_someone == False: + # We have no one to ping, this branch is finished, we can send a NOK to this ping and reset the pinged state of all nodes + self.send_message([NOK]) + pinger.remove(pinger[0]) + for client in clients: + client.pinged = False + else: + # Data should be an end + if self.data[0] != END: + print("Error: received data is not an end, received data is " + str(self.data[0]) + " from " + str(self.address)) + else: + # send the end to the pinger + pinger[0].send_message([END]) + #remove the pinger + pinger.remove(pinger[0]) + else: + # This is a broadcast message + #print(str(len(self.data)) + str(" Data received from " + str(self.address))) + for client in clients: + if client != self: + client.send_message(self.data) + + def connected(self): + print(self.address, 'connected\n') + clients.append(self) + + def handle_close(self): + print(self.address, 'closed') + clients.remove(self) + + +## Parse arguments ## +parser = argparse.ArgumentParser(description='Luos_engine WebSocket network broker\n', + formatter_class=argparse.RawTextHelpFormatter) +# General arguments +parser.add_argument("-p", "--port", metavar="PORT", action="store", + help="The port used by the websocket.\n" + "By default port = 8000.\n", + default=8000) +parser.add_argument("--ip", metavar="IP", action="store", + help="The ip used by the websocket.\n" + "By default ip = '127.0.0.1'.\n", + default='127.0.0.1') + +args = parser.parse_args() +clients = [] +pinger = [] + +server = WebSocketServer(args.ip, args.port, WsBroker) +print("Luos_engine WebSocket network broker opened on " + + str(args.ip) + ":" + str(args.port)) +server.serve_forever() diff --git a/setup.py b/setup.py index fc2bfae..ed80d06 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,8 @@ 'anytree', 'crc8', 'ipython', - 'requests' + 'requests', + 'simple_websocket_server==0.4.2' ], extras_require={ 'tests': ['pytest', 'flake8'], @@ -44,7 +45,9 @@ 'pyluos-usb2ws = pyluos.tools.usb2ws:main', 'pyluos-bootloader = pyluos.tools.bootloader:main', 'pyluos-shell = pyluos.tools.shell:main', - 'pyluos-discover = pyluos.tools.discover:main' + 'pyluos-discover = pyluos.tools.discover:main', + 'pyluos-ws-broker = pyluos.tools.ws_broker:main', + 'pyluos-robus-broker = pyluos.tools.robus_broker:main' ], }, )