diff --git a/panduza/core/client.py b/panduza/core/client.py index 93b09e8..f4cdad5 100644 --- a/panduza/core/client.py +++ b/panduza/core/client.py @@ -12,11 +12,11 @@ import threading import traceback -import json +import json, time import queue import paho.mqtt.client as mqtt -from .core import Core +from .core import Core, Panduza_local_broker_discovery # ┌────────────────────────────────────────┐ # │ Utilities │ @@ -32,7 +32,7 @@ class Client: - def __init__(self, broker_alias=None, interface_alias=None, url=None, port=None): + def __init__(self, broker_alias=None, interface_alias=None, url=None, port=None, platform_name=None): """Client Constructor The client can be build from @@ -40,22 +40,41 @@ def __init__(self, broker_alias=None, interface_alias=None, url=None, port=None) - Alias OR - Url + Port + OR + - Platform name + OR + - Nothing Args: alias (str, optional): connection alias. Defaults to None. url (str, optional): broker url. Defaults to None. port (str, optional): port url. Defaults to None. + platform_name (str, optional): name of the platform. Defaults to None. """ - # Manage double way of loading client information + # Manage for way of loading client information + + # Using broker alias if broker_alias: self.url, self.port = Core.BrokerInfoFromBrokerAlias(broker_alias) elif interface_alias: self.url, self.port = Core.BrokerInfoFromInterfaceAlias( interface_alias) - else: + + # Using url and port directly + elif (url != None and port != None): self.url = str(url) self.port = int(port) + # Look for platforms on the local network and use the broker informations + # of the first platform found with the given platform_name + elif (platform_name != None): + self.url, self.port = Panduza_local_broker_discovery.get_broker_info_with_name(platform_name=platform_name) + + # Look for platforms on the local network and use the broker informations of the + # first platform found during discovery + else: + self.url, self.port = Panduza_local_broker_discovery.get_first_broker_info() + # Set flags self.is_connected = False @@ -90,7 +109,7 @@ def __init__(self, broker_alias=None, interface_alias=None, url=None, port=None) # └────────────────────────────────────────┘ def connect(self): - self.log.debug("Connect to broker") + self.log.debug("Connect to broker {self.url}:" + str(self.port)) self.client.connect(self.url, self.port) self.client.loop_start() diff --git a/panduza/core/core.py b/panduza/core/core.py index 03b4feb..4a8568d 100644 --- a/panduza/core/core.py +++ b/panduza/core/core.py @@ -1,10 +1,188 @@ import re -import json +import socket, json, time import logging import panduza.core.log +# ┌────────────────────────────────────────┐ +# │ Local broker discovery │ +# └────────────────────────────────────────┘ + +class Panduza_local_broker_discovery: + + # Should be with other consts + PORT_LOCAL_DISCOVERY = 53035 + + + def panduza_local_broker_discovery(): + """ Return the addresses of brokers discover on the local network + + Raises: + Exception: raise if connection alias not loaded + + Returns: + List[str, int]: url, port + """ + + broker_addrs = [] + + # Get every network interfaces + interfaces = socket.getaddrinfo(host=socket.gethostname(), port=None, family=socket.AF_INET) + ips = [ip[-1][0] for ip in interfaces] + + request_payload = json.dumps({"search": True}) + request_payload_utf8 = request_payload.encode( + encoding="utf-8" + ) + + for ip in ips: + try: + # Send broadcast local discovery request on the local networks + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) + sock.setblocking(False) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + sock.bind((ip, 0)) + sock.sendto(request_payload_utf8, ("255.255.255.255", Panduza_local_broker_discovery.PORT_LOCAL_DISCOVERY)) + time.sleep(1) + + answer_payload, broker_addr_port = sock.recvfrom(1000) + + # Get the name in the payload + json_answer = answer_payload.decode( + encoding="utf-8" + ) + + # add the platform addr, port and name to the list of broker detected + json_answer = json.loads(json_answer) + platform_info = json_answer["platform"] + broker_info = json_answer["broker"] + + if (platform_info != None and broker_info != None): + platform_name = platform_info["name"] + broker_addr = broker_addr_port[0] + broker_port = broker_info["port"] + + if (platform_name != None and broker_addr != None and broker_port != None): + broker_addrs.append(((broker_addr, broker_port), platform_name)) + + + except Exception as e: + pass + + sock.close() + + return broker_addrs + + def get_broker_info_with_name(platform_name): + """ Get the broker info of the platform discover on the local network with the given name + and chosen by the user with keyboard input + + Arg: + - platform_name (str, required): name of the platform. + + Raises: + NameError: if any platform find on the local network with the given name + + Returns: + str, int: url, port + """ + + # Find the platform with the platform name asked + + list_info_brokers = Panduza_local_broker_discovery.panduza_local_broker_discovery() + brokers_with_given_name = [] + + for info_broker in list_info_brokers: + platform_name_detected = info_broker[1] + if (platform_name_detected == platform_name): + tmp_url, tmp_port = info_broker[0][0], 1883 + # add every platform with this name to this list + brokers_with_given_name.append((tmp_url, tmp_port)) + + + # If any platform find with the given platform_name raise a error + if (not brokers_with_given_name): + raise NameError("Any platform find on the local platform with the name: " + platform_name) + + while (True): + # Print every platform discovered + print("########################################") + for i in range(len(brokers_with_given_name)): + tmp_url, tmp_port = brokers_with_given_name[i][0], brokers_with_given_name[i][1] + print(f"{i + 1} - Platform name : {platform_name}, {tmp_url}:{tmp_port}") + print("########################################\n") + + # Ask to the user which platform he wants to use + print(f"Which platform do you want to use ? Enter the number at the left of the platform you want to use ? [1", end="") + for i in range(2, len(brokers_with_given_name) + 1): + print(f" or {i}", end="") + print("]") + user_choice = input() + + # Check if the user has input a correct platform number else ask him + # to enter his choice a second time + if ((user_choice.isnumeric()) and (int(user_choice) >= 1) and (int(user_choice) <= len(brokers_with_given_name))): + broker_choosen = brokers_with_given_name[int(user_choice) - 1] + url, port = broker_choosen[0], broker_choosen[1] + nbr_plat = int(user_choice) + break + + print(f"Platform number {nbr_plat} has been chosen with url/port : {url}:{port} and name : {platform_name}") + + return url, port + + def get_first_broker_info(): + """ Get the broker info of the platform discover on the local network and chosen + by the user with keyboard input + + Raises: + Exception: if any platform find on the local network + + Returns: + str, int: url, port + """ + url = None + port = None + + list_info_brokers = Panduza_local_broker_discovery.panduza_local_broker_discovery() + if (len(list_info_brokers) == 0): + # Maybe create a exception class for not findind any local platform + raise Exception("Any platform find on the local platform with the name") + + while (True): + # Print every platform discovered + print("########################################") + for i in range(len(list_info_brokers)): + # Need to change the local discovery to get the broker port and not the one + # of the local discovery service + platform_name = list_info_brokers[i][1] + tmp_url, tmp_port = list_info_brokers[i][0][0], 1883 + print(f"{i + 1} - Platform name : {platform_name}, {tmp_url}:{tmp_port}") + print("########################################\n") + + # Ask to the user which platform he wants to use + print(f"Which platform do you want to use ? Enter the number at the left of the platform you want to use ? [1", end="") + for i in range(2, len(list_info_brokers) + 1): + print(f" or {i}", end="") + print("]") + user_choice = input() + + # Check if the user has input a correct platform number else ask him + # to enter his choice a second time + if ((user_choice.isnumeric()) and (int(user_choice) >= 1) and (int(user_choice) <= len(list_info_brokers))): + broker_choosen = list_info_brokers[int(user_choice) - 1] + url, port = broker_choosen[0][0], broker_choosen[0][1] + platform_name = broker_choosen[1] + nbr_platform = int(user_choice) + break + + print(f"Platform number {nbr_platform} has been chosen with url/port : {url}:{port} and name : {platform_name}") + + return url, port + + # Create the logger for core events CoreLog = logging.getLogger(f"pza.core") @@ -103,10 +281,30 @@ def __LoadAliasesFromDict(connections): # Load connection CoreLog.info(f" Load connection : {co_name} & {co_data}") - Core.Connections[co_name] = { - "url": co_data["url"], - "port": co_data["port"] - } + + # if url and port precise inside alias config use it + if ("url" in co_data.keys()) and ("port" in co_data.keys()): + Core.Connections[co_name] = { + "url": co_data["url"], + "port": co_data["port"] + } + # Use the local discovery to find the broker link to the platform + # with the given name + elif ("platform_name" in co_data.keys()): + url, port = Panduza_local_broker_discovery.get_broker_info_with_name(co_data["platform_name"]) + Core.Connections[co_name] = { + "url": url, + "port": port + } + # Use the local discovery to find the first platform discovered + else: + url, port = Panduza_local_broker_discovery.get_first_broker_info() + Core.Connections[co_name] = { + "url": url, + "port": port + } + + # create automaticaly url, # Load aliases for it in co_data["interfaces"]: @@ -142,7 +340,7 @@ def BrokerInfoFromBrokerAlias(alias): ########################################################################### def BrokerInfoFromInterfaceAlias(alias): - """Return the broker information to reach reach the interface from its alias + """Return the broker information to reach the interface from its alias """ # Get alias if alias not in Core.Aliases.keys(): diff --git a/panduza/interfaces/relay.py b/panduza/interfaces/relay.py index b96f634..d29d3ee 100644 --- a/panduza/interfaces/relay.py +++ b/panduza/interfaces/relay.py @@ -35,3 +35,9 @@ def __post_init__(self): if self.ensure: self.ensure_init() + def get_state_open(self): + return self.state.open.get() + + def set_state_open(self, value): + self.state.open.set(value) + diff --git a/tools/test_local_discovery.py b/tools/test_local_discovery.py new file mode 100644 index 0000000..e094dcb --- /dev/null +++ b/tools/test_local_discovery.py @@ -0,0 +1,28 @@ +from panduza import Client + +# Test the comportement of local discovery to connect a +# client without precising url and port, the user can choose +# to use a plaform name to look for a platform with his given +# name on the local network, or just using it without parameters +# to look fot the first platform found +if __name__ == "__main__": + + platform_name = "panduza_platform" + + # Test with platform name + + print(f"Try to find a platform on the local network with the name {platform_name}") + + client = Client(platform_name=platform_name) + client.connect() + + print(f"Success to connecting to the broker of the platform with the name {platform_name}\n") + + # Test looking for the fist platform found + + print(f"Try to find a platform on the local network") + + client2 = Client() + client2.connect() + + print("Success to connect to the first platform found on the local network")