def __init__(self, hostname: str):
     super().__init__(hostname)
     self._logger.info(f"Starting {self.__class__.__name__}")
     self._clients = []
     self.__client_list_lock = threading.Lock()
     self._thread_manager = ThreadManager()
     self._thread_manager.add_thread("thread_cleanup", self.__task_cleanup_clients)
示例#2
0
    def __init__(self, host_name: str, address: str):
        super().__init__()
        self._logger = logging.getLogger(self.__class__.__name__)
        self._host_name = host_name
        self._address = address
        self._validator = Validator()

        self.__split_handler = SplitRequestHandler()

        self.__out_queue = Queue()
        self.__in_queue = Queue()

        self._thread_manager = ThreadManager()
        self._thread_manager.add_thread("send_thread", self.__task_send)
        self._thread_manager.add_thread("receive_thread", self.__task_receive)
        self._thread_manager.add_thread("publish_thread", self.__task_publish)
示例#3
0
def test_thread_manager_count(manager: ThreadManager):

    assert manager.get_thread_count() == 0

    manager.add_thread(THREAD_ID, test_method)
    time.sleep(0.1)

    assert manager.get_thread_count() == 1

    manager.remove_thread(THREAD_ID)
    time.sleep(0.1)

    assert manager.get_thread_count() == 0

    manager.add_thread(THREAD_ID, test_method)
    time.sleep(0.1)

    assert manager.get_thread_count() == 1
示例#4
0
def test_thread_manager_running(manager: ThreadManager):
    assert manager.get_thread(THREAD_ID) is None

    manager.add_thread(THREAD_ID, test_method)
    time.sleep(0.1)
    saved_thread = manager.get_thread(THREAD_ID)

    assert saved_thread.is_running() is False

    manager.start_threads()

    assert saved_thread.is_running() is True

    manager.__del__()

    assert saved_thread.is_running() is False
示例#5
0
    def __init__(self,
                 network_name: str,
                 mqtt_credentials: MqttCredentialsContainer,
                 response_timeout: Optional[int] = None):
        super().__init__()
        self._response_timeout = response_timeout
        self._send_lock = threading.Lock()
        self._network_name = network_name
        self._mqtt_credentials = mqtt_credentials
        self._characteristic_update_callback = None
        self._received_responses = Queue()
        self._received_messages = Queue()

        self._mqtt_client = mqtt.Client(self._network_name + "_HomeBridge")
        self._logger.info("Connecting to homebridge mqtt broker")
        if self._mqtt_credentials.has_auth():
            self._mqtt_client.username_pw_set(self._mqtt_credentials.username,
                                              self._mqtt_credentials.password)
            self._logger.info("Using auth for mqtt connection")
        try:
            self._mqtt_client.connect(self._mqtt_credentials.ip,
                                      self._mqtt_credentials.port, 15)
        except OSError:
            raise MqttConnectionError(self._mqtt_credentials.ip,
                                      self._mqtt_credentials.port)
        self._mqtt_client.on_message = self._gen_message_handler()
        self._mqtt_client.on_connect = self._generate_connect_callback()
        self._mqtt_client.on_disconnect = self._generate_disconnect_callback()
        self._mqtt_client.subscribe("homebridge/from/response")
        self._mqtt_client.subscribe("homebridge/from/set")
        self._mqtt_client.loop_start()

        self._thread_manager = ThreadManager()
        self._thread_manager.add_thread("received_request_handler",
                                        self._request_handler_thread)
        self._thread_manager.start_threads()
示例#6
0
def test_thread_manager_flag(manager: ThreadManager):
    global _test_flag
    _test_flag = False
    time.sleep(0.1)

    assert _test_flag is False

    manager.add_thread(THREAD_ID, test_method)
    time.sleep(0.1)

    assert _test_flag is False

    manager.start_threads()
    time.sleep(0.1)

    assert _test_flag is True

    manager.remove_thread(THREAD_ID)
    time.sleep(0.1)
    _test_flag = False
    time.sleep(0.1)

    assert _test_flag is False
class NetworkServer(NetworkConnector, ABC):

    _validator: Validator
    _hostname: str
    _clients: [NetworkServerClient]
    _thread_manager: ThreadManager
    __client_list_lock: threading.Lock

    def __init__(self, hostname: str):
        super().__init__(hostname)
        self._logger.info(f"Starting {self.__class__.__name__}")
        self._clients = []
        self.__client_list_lock = threading.Lock()
        self._thread_manager = ThreadManager()
        self._thread_manager.add_thread("thread_cleanup", self.__task_cleanup_clients)

    def __del__(self):
        self._logger.info(f"Stopping {self.__class__.__name__}")
        self._thread_manager.__del__()
        while self._clients:
            client = self._clients[0]
            self._remove_client(client.get_address())

    def __task_cleanup_clients(self):
        for client in self._clients:
            if not client.is_connected():
                self._logger.info(f"Lost connection to '{client.get_address()}'")
                self._remove_client(client.get_address())
        time.sleep(1)

    def _add_client(self, client: NetworkServerClient):
        with self.__client_list_lock:
            self._clients.append(client)
            client.subscribe(self)
        self._logger.info(f"New connection from: {client.get_address()}")

    def _remove_client(self, address: str):
        client_index = 0
        with self.__client_list_lock:
            for client in self._clients:
                if address == client.get_address():
                    self._logger.info(f"Removing Client '{address}'")
                    buf_client: NetworkServerClient = self._clients.pop(client_index)
                    buf_client.__del__()
                    return
                client_index += 1

    def _send_data(self, req: Request):
        remove_clients: [str] = []

        with self.__client_list_lock:
            for client in self._clients:
                try:  # TODO: Remove if possible
                    client.send_request(req)
                except ClientDisconnectedError:
                    self._logger.info(f"Connection to '{client.get_address()}' was lost")
                    # Save clients index for removal
                    remove_clients.append(client.get_address())

        # remove 'dead' clients
        if remove_clients:
            self._logger.info(f"Removing stored data of {len(remove_clients)} clients")
            for client_address in remove_clients:
                self._remove_client(client_address)

    def get_client_count(self) -> int:
        return len(self._clients)

    def get_client_addresses(self) -> list[str]:
        addresses = []
        for client in self._clients:
            addresses.append(client.get_address())
        return addresses
示例#8
0
class NetworkServerClient(Publisher):
    _host_name: str
    _address: str
    _response_method: response_callback_type
    _validator: Validator
    _logger: logging.Logger

    _thread_manager: ThreadManager

    __split_handler: SplitRequestHandler

    __out_queue: Queue
    __in_queue: Queue

    def __init__(self, host_name: str, address: str):
        super().__init__()
        self._logger = logging.getLogger(self.__class__.__name__)
        self._host_name = host_name
        self._address = address
        self._validator = Validator()

        self.__split_handler = SplitRequestHandler()

        self.__out_queue = Queue()
        self.__in_queue = Queue()

        self._thread_manager = ThreadManager()
        self._thread_manager.add_thread("send_thread", self.__task_send)
        self._thread_manager.add_thread("receive_thread", self.__task_receive)
        self._thread_manager.add_thread("publish_thread", self.__task_publish)

    def __del__(self):
        self._thread_manager.__del__()

    def __task_send(self):
        if not self.__out_queue.empty():
            out_req = self.__out_queue.get()
            self._send(out_req)

    def __task_receive(self):
        in_req = self._receive()
        if in_req:
            self.__in_queue.put(in_req)

    def __task_publish(self):
        if not self.__in_queue.empty():
            in_req = self.__in_queue.get()
            req = self.__split_handler.handle(in_req)
            if req:
                if req.get_sender() != self._host_name:
                    req.set_callback_method(self._respond_to)
                    self._forward_request(req)

    def _respond_to(self,
                    req: Request,
                    payload: dict,
                    path: Optional[str] = None):
        if path:
            out_path = path
        else:
            out_path = req.get_path()

        receiver = req.get_sender()

        out_req = Request(out_path, req.get_session_id(), self._host_name,
                          receiver, payload)

        self._send(out_req)

    def _forward_request(self, req: Request):
        self._logger.debug(
            f"Received Request by '{req.get_sender()}' at '{req.get_path()}': {req.get_payload()}"
        )
        self._publish(req)

    def send_request(self, req: Request):
        self.__out_queue.put(req)

    @abstractmethod
    def _send(self, req: Request):
        pass

    @abstractmethod
    def _receive(self) -> Optional[Request]:
        pass

    @abstractmethod
    def is_connected(self) -> bool:
        pass

    def get_address(self) -> str:
        return self._address
示例#9
0
def test_thread_manager_add_remove(manager: ThreadManager):

    manager.add_thread(THREAD_ID, test_method)
    time.sleep(0.1)

    assert manager.get_thread_count() == 1

    exception_flag = False
    try:
        manager.add_thread(THREAD_ID, test_method)
    except ThreadIdAlreadyInUseException:
        exception_flag = True
    saved_thread = manager.get_thread(THREAD_ID)

    assert exception_flag is True
    assert manager.get_thread_count() == 1
    assert saved_thread.is_running() is False
    assert saved_thread.get_name() == THREAD_ID

    manager.start_threads()
    exception_flag = False
    try:
        manager.add_thread(THREAD_ID, test_method)
    except ThreadIdAlreadyInUseException:
        exception_flag = True
    saved_thread = manager.get_thread(THREAD_ID)

    assert exception_flag is True
    assert manager.get_thread_count() == 1
    assert saved_thread.is_running() is True

    manager.remove_thread(WRONG_THREAD_ID)

    assert manager.get_thread_count() == 1

    manager.remove_thread(THREAD_ID)

    assert saved_thread.is_running() is False
    assert manager.get_thread_count() == 0
示例#10
0
def manager():
    manager = ThreadManager()
    yield manager
    manager.__del__()
示例#11
0
class HomebridgeNetworkConnector(LoggingInterface):

    _send_lock: threading.Lock
    _network_name: str
    _mqtt_client = mqtt.Client
    _mqtt_credentials: MqttCredentialsContainer

    _received_responses: Queue
    _received_messages: Queue
    _response_timeout: Optional[int]

    _thread_manager: ThreadManager

    _characteristic_update_callback: Optional[CharacteristicUpdateCallback]

    def __init__(self,
                 network_name: str,
                 mqtt_credentials: MqttCredentialsContainer,
                 response_timeout: Optional[int] = None):
        super().__init__()
        self._response_timeout = response_timeout
        self._send_lock = threading.Lock()
        self._network_name = network_name
        self._mqtt_credentials = mqtt_credentials
        self._characteristic_update_callback = None
        self._received_responses = Queue()
        self._received_messages = Queue()

        self._mqtt_client = mqtt.Client(self._network_name + "_HomeBridge")
        self._logger.info("Connecting to homebridge mqtt broker")
        if self._mqtt_credentials.has_auth():
            self._mqtt_client.username_pw_set(self._mqtt_credentials.username,
                                              self._mqtt_credentials.password)
            self._logger.info("Using auth for mqtt connection")
        try:
            self._mqtt_client.connect(self._mqtt_credentials.ip,
                                      self._mqtt_credentials.port, 15)
        except OSError:
            raise MqttConnectionError(self._mqtt_credentials.ip,
                                      self._mqtt_credentials.port)
        self._mqtt_client.on_message = self._gen_message_handler()
        self._mqtt_client.on_connect = self._generate_connect_callback()
        self._mqtt_client.on_disconnect = self._generate_disconnect_callback()
        self._mqtt_client.subscribe("homebridge/from/response")
        self._mqtt_client.subscribe("homebridge/from/set")
        self._mqtt_client.loop_start()

        self._thread_manager = ThreadManager()
        self._thread_manager.add_thread("received_request_handler",
                                        self._request_handler_thread)
        self._thread_manager.start_threads()

    def __del__(self):
        self._thread_manager.__del__()
        self._mqtt_client.disconnect()
        self._mqtt_client.__del__()

    def _gen_message_handler(self):
        """
        Generates a response method to attach to the mqtt-connector as 'on_message' callback

        :return: The function handler
        """
        def on_message(client, userdata, message):
            self._logger.debug(f"Received message at '{message.topic}'")
            topic = message.topic
            json_str = message.payload.decode("utf-8")

            try:
                body = json.loads(json_str)
            except json.decoder.JSONDecodeError:
                self._logger.error(
                    "Couldn't decode json: '{}'".format(json_str))
                return

            buf_req = HomeBridgeRequest(topic, body)
            if buf_req.topic == "homebridge/from/response":
                self._received_responses.put(buf_req)
            else:
                self._received_messages.put(buf_req)

        return on_message

    def _generate_connect_callback(self):
        def connect_callback(client, userdata, reasonCode, properties=None):
            self._logger.info("MQTT connected.")

        return connect_callback

    def _generate_disconnect_callback(self):
        def disconnect_callback(client, userdata, reasonCode, properties=None):
            self._logger.info("MQTT disconnected.")

        return disconnect_callback

    def _request_handler_thread(self):
        if not self._received_messages.empty():
            req = self._received_messages.get()
            self._handle_request(req)

    def _handle_request(self, req: HomeBridgeRequest):
        """
        Handles a request received by the mqtt client

        :param req: Request to handle
        :return: None
        """
        # Check handle characteristic updates
        if req.topic == "homebridge/from/set":
            # TODO: validate with json schema
            if self._characteristic_update_callback:
                self._characteristic_update_callback(
                    req.message["name"], req.message["characteristic"],
                    req.message["value"])

    def _send_request(self, req: HomeBridgeRequest, wait_for_response: Optional[int] = None) ->\
            Optional[HomeBridgeRequest]:
        """
        Sends a homebridge request and waits for an answer if wanted

        :param req: Request to be sent
        :param wait_for_response: The time that should be waited to receive an response before NoResponseError is raised
        :return: The response that was received
        :raises AckFalseError: If wait_for_response != None and ack 'False' was sent back
        :raises NoResponseError: If wait_for_response != None and no response was received
        """
        with self._send_lock:
            req_id = random.randint(0, 10000)
            if wait_for_response is not None:
                req.set_request_id(req_id)
                self._received_responses = Queue()
            info = self._mqtt_client.publish(topic=req.topic,
                                             payload=json.dumps(req.message))
            info.wait_for_publish()
            if wait_for_response is None:
                return None
            else:
                start_time = datetime.now()
                while start_time + timedelta(
                        seconds=wait_for_response) > datetime.now():
                    if not self._received_responses.empty():
                        res: HomeBridgeRequest = self._received_responses.get()
                        if res.get_request_id() == req_id:
                            return res
                raise NoResponseError(req)

    def attach_characteristic_update_callback(
            self, callback: CharacteristicUpdateCallback):
        """
        Attaches a callback to the connector to receive characteristic updates triggered on the external data source

        :param callback: Callback function to attach
        :return: None
        """
        self._characteristic_update_callback = callback

    def add_gadget(self, gadget: Gadget) -> bool:
        try:
            buf_payload = HomebridgeEncoder().encode_gadget(gadget)
        except GadgetEncodeError as err:
            self._logger.error(err.args[0])
            return False
        buf_req = HomeBridgeRequest("homebridge/to/add", buf_payload)
        try:
            response = self._send_request(buf_req, self._response_timeout)
            if response.get_ack() is True:
                return True
            return False
        except NoResponseError as err:
            self._logger.error(err.args[0])
            return False

    def remove_gadget(self, gadget_name: str) -> bool:
        """
        Removes the gadget from the external data source

        :param gadget_name: Name of the gadget that should be deleted
        :return: Whether deleting the gadget was successful or not
        """
        buf_req = HomeBridgeRequest("homebridge/to/remove",
                                    {"name": gadget_name})
        try:
            response = self._send_request(buf_req, self._response_timeout)
            if response.get_ack() is True:
                return True
            return False
        except NoResponseError as err:
            self._logger.error(err.args[0])
            return False

    def get_gadget_info(self, gadget_name: str) -> Optional[dict]:
        """
        Gets information about the selected gadget from the external source

        :param gadget_name: Name of the gadget to get information for
        :return: The information received about the gadget
        """
        buf_req = HomeBridgeRequest("homebridge/to/get", {"name": "*_props"})
        try:
            response = self._send_request(buf_req, self._response_timeout)
            if gadget_name not in response.message:
                return None
            return response.message[gadget_name]
        except NoResponseError as err:
            self._logger.error(err.args[0])
            return None

    def update_characteristic(self, gadget_name: str, characteristic: str,
                              value: int):
        """
        Updates a characteristic of the selected gadget on the external data source

        :param gadget_name: Name of the gadget the characteristic belongs to
        :param characteristic: The characteristic to change
        :param value: The new value of the characteristic
        :return: Whether the change of the characteristic was successful or not
        """
        buf_payload = {
            "name": gadget_name,
            "service_name": gadget_name,
            "characteristic": characteristic,
            "value": value
        }
        buf_req = HomeBridgeRequest("homebridge/to/set", buf_payload)
        self._send_request(buf_req, None)