예제 #1
0
    def encode_gadget(self, gadget: Gadget) -> dict:
        """
        Serializes a gadget according to api specification

        :param gadget: The gadget to serialize
        :return: The serialized version of the gadget as dict
        :raises GadgetEncodeError: If anything goes wrong during the serialization process
        """
        try:
            identifier = self.encode_gadget_identifier(gadget)
        except IdentifierEncodeError as err:
            self._logger.error(err.args[0])
            raise GadgetEncodeError(gadget.__class__.__name__, gadget.get_name())

        characteristics_json = [self.encode_characteristic(x) for x in gadget.get_characteristics()]

        mapping_json = {}
        for mapping in gadget.get_event_mapping():
            if mapping.get_id() in mapping_json:
                self._logger.error(f"found double mapping for {mapping.get_id()}")
                continue
            mapping_json[mapping.get_id()] = mapping.get_list()

        gadget_json = {"type": int(identifier),
                       "id": gadget.get_name(),
                       "characteristics": characteristics_json,
                       "event_map": mapping_json}

        return gadget_json
예제 #2
0
    def merge_gadgets(self, old_gadget: Gadget, new_gadget: Gadget) -> Gadget:
        """
        Merges two gadgets (typically one 'original' and a new one with update information) into a new gadget
        containing with name, client and class of the old and characteristics of the new one.

        :param old_gadget: Original gadget to base merged gadget on
        :param new_gadget: Gadget wth update information
        :return: The emrged gadget
        :raise GadgetMergeError: If anything goes wrong during merges
        """
        encoder = ApiEncoder()
        if not isinstance(
                new_gadget,
                AnyGadget) and old_gadget.__class__ != new_gadget.__class__:
            raise GadgetMergeError(
                f"Cannot merge gadgets with different classes {old_gadget.__class__.__name__} "
                f"and {new_gadget.__class__.__name__}")
        try:
            gadget_type = encoder.encode_gadget_identifier(old_gadget)
        except IdentifierEncodeError as err:
            raise GadgetMergeError(err.args[0])
        try:
            merged_gadget = self.create_gadget(
                gadget_type, old_gadget.get_name(),
                old_gadget.get_host_client(), new_gadget.get_characteristics())
        except GadgetCreationError as err:
            raise GadgetMergeError(err.args[0])
        return merged_gadget
예제 #3
0
    def receive_gadget(self, gadget: Gadget):
        if self._last_published_gadget is not None and self._last_published_gadget == gadget.get_name(
        ):
            return
        fetched_gadget = self._fetch_gadget_data(gadget.get_name())
        if fetched_gadget is None:
            # Gadget with given name does not exist on the remote system, or is broken somehow
            try:
                self.create_gadget(gadget)
            except GadgetCreationError as err:
                self._logger.error(err.args[0])
                raise GadgetUpdateError(gadget.get_name())
        else:
            if self._gadget_needs_update(gadget, fetched_gadget):
                # Gadget needs to be recreated due to characteristic boundaries changes
                try:
                    self.remove_gadget(gadget.get_name())
                except GadgetDeletionError as err:
                    self._logger.error(err.args[0])

                try:
                    self.create_gadget(gadget)
                except GadgetCreationError as err:
                    self._logger.error(err.args[0])
                    raise GadgetUpdateError(gadget.get_name())

            else:
                # Gadget does not need to be re-created, only updated
                for characteristic in gadget.get_characteristics():
                    fetched_characteristic = fetched_gadget.get_characteristic(
                        characteristic.get_type())
                    if fetched_characteristic.get_true_value(
                    ) != characteristic.get_true_value():
                        self._update_characteristic(gadget,
                                                    characteristic.get_type())
 def receive_gadget(self, gadget: Gadget):
     found_gadget = self.get_gadget(gadget.get_name())
     if found_gadget is None:
         if not isinstance(gadget, AnyGadget):
             self._logger.info(f"Adding gadget '{gadget.get_name()}'")
             self._gadgets.append(gadget)
             self._publish_gadget(gadget)
         else:
             self._logger.error(
                 f"Received sync data for unknown gadget '{gadget.get_name()}'"
             )
     else:
         if gadget.__class__ == found_gadget.__class__ and gadget.equals_in_characteristic_values(
                 found_gadget):
             return
         self._logger.info(f"Syncing existing gadget '{gadget.get_name()}'")
         self._gadgets.remove(found_gadget)
         try:
             factory = GadgetFactory()
             merged_gadget = factory.merge_gadgets(found_gadget, gadget)
         except GadgetCreationError as err:
             self._logger.error(err.args[0])
             return
         except NotImplementedError:
             self._logger.error(
                 f"Merging gadgets of the type '{gadget.__class__.__name__}' is not implemented"
             )
             return
         self._gadgets.append(merged_gadget)
         self._publish_gadget(merged_gadget)
예제 #5
0
 def create_gadget(self, gadget: Gadget):
     self._logger.info(
         f"Creating gadget '{gadget.get_name()}' on external source")
     adding_successful = self._network_connector.add_gadget(gadget)
     if not adding_successful:
         raise GadgetCreationError(gadget.get_name())
     for characteristic in gadget.get_characteristics():
         self._update_characteristic(gadget, characteristic.get_type())
예제 #6
0
    def _gadget_needs_update(local_gadget: Gadget, fetched_gadget: Gadget):
        """
        Checks if the gadget on the remote storage needs an update by comparing the gadgets characteristics boundaries

        :param local_gadget: The local gadget (master)
        :param fetched_gadget: The fetched gadget to compare it with
        :return: Whether the fetched gadget needs to be updated
        """
        needs_update = local_gadget.get_characteristics(
        ) != fetched_gadget.get_characteristics()
        return needs_update
예제 #7
0
    def encode_gadget_update(self, gadget: Gadget) -> dict:
        """
        Serializes gadget update information according to api specification

        :param gadget: The gadget to serialize
        :return: The serialized version of the changeable gadget information as dict
        :raises GadgetEncodeError: If anything goes wrong during the serialization process
        """
        characteristics_json = [self.encode_characteristic_update(x) for x in gadget.get_characteristics()]

        gadget_json = {"id": gadget.get_name(),
                       "characteristics": characteristics_json}

        return gadget_json
예제 #8
0
def test_api_handle_gadget_update(api: ApiManager,
                                  network: DummyNetworkConnector,
                                  delegate: DummyApiDelegate):
    delegate.add_gadget(
        Gadget(GADGET_NAME, "spongolopolus",
               [Characteristic(CharacteristicIdentifier.status, 0, 1, 1, 1)]))

    network.mock_receive(ApiURIs.update_gadget.uri, REQ_SENDER,
                         {"gadget": {
                             "yolo": "blub"
                         }})
    assert delegate.get_last_gadget_update() is None

    network.mock_receive(ApiURIs.update_gadget.uri, REQ_SENDER,
                         GADGET_UPDATE_ERR)
    assert delegate.get_last_gadget_update() is None

    network.mock_receive(ApiURIs.update_gadget.uri, REQ_SENDER,
                         GADGET_UPDATE_ERR_UNKNOWN)
    assert delegate.get_last_gadget_update() is None

    network.mock_receive(ApiURIs.update_gadget.uri, REQ_SENDER,
                         GADGET_UPDATE_OK)
    assert delegate.get_last_gadget_update() is not None
    assert delegate.get_last_gadget_update().get_characteristic_types(
    )[0] == GADGET_CHARACTERISTIC_TYPE
예제 #9
0
    def _update_characteristic(self, gadget: Gadget,
                               characteristic: CharacteristicIdentifier):
        """
        Updates a specific characteristic on from the gadget on the remote storage

        :param gadget: Gadget to get characteristic information from
        :param characteristic: Characteristic to update
        :return: None
        :raises CharacteristicParsingError: If selected characteristic could not be parsed correctly
        """
        characteristic_value = gadget.get_characteristic(
            characteristic).get_true_value()
        characteristic_str = HomebridgeCharacteristicTranslator.type_to_string(
            characteristic)
        self._network_connector.update_characteristic(gadget.get_name(),
                                                      characteristic_str,
                                                      characteristic_value)
    def receive_gadget_update(self, gadget: Gadget):
        found_gadget = self.get_gadget(gadget.get_name())
        if found_gadget is None:
            self._logger.error(
                f"Received update data for unknown gadget '{gadget.get_name()}'"
            )
            return
        changed_characteristics = []
        for c in found_gadget.get_characteristics():
            buf_characteristic = gadget.get_characteristic(c.get_type())
            if buf_characteristic is None:
                continue
            if c.get_step_value() != buf_characteristic.get_step_value():
                c.set_step_value(buf_characteristic.get_step_value())
                changed_characteristics.append(c)

        if changed_characteristics:
            buf_gadget = AnyGadget(gadget.get_name(), "any",
                                   changed_characteristics)
            self._publish_gadget_update(buf_gadget)
예제 #11
0
 def _encode_characteristic(
         gadget: Gadget,
         characteristic_type: CharacteristicIdentifier) -> dict:
     gadget_characteristic = gadget.get_characteristic(characteristic_type)
     value_range = gadget_characteristic.get_max(
     ) - gadget_characteristic.get_min()
     min_step = value_range // gadget_characteristic.get_steps()
     return {
         "minValue": gadget_characteristic.get_min(),
         "maxValue": gadget_characteristic.get_max(),
         "minStep": min_step
     }
    def _remove_gadget_from_publishers(self, gadget: Gadget):
        """
        Removes a gadget from all publishers

        :param gadget: The gadget that should be removed
        :return: None
        """
        self._logger.info(
            f"Removing gadget '{gadget.get_name()}' from {len(self._gadget_publishers)} publishers"
        )
        for publisher in self._gadget_publishers:
            publisher.remove_gadget(gadget.get_name())
def test_gadget_publisher_homebridge_network(
        publisher_network: GadgetPublisherHomeBridge, gadget: Gadget):
    with pytest.raises(GadgetDeletionError):
        publisher_network.remove_gadget(gadget.get_name())

    publisher_network.create_gadget(gadget)

    with pytest.raises(GadgetCreationError):
        publisher_network.create_gadget(gadget)

    publisher_network.remove_gadget(gadget.get_name())

    fan_speed = gadget.get_characteristic(CharacteristicIdentifier.fan_speed)
    fan_speed.set_step_value(2)

    publisher_network.receive_gadget(gadget)

    fan_speed.set_step_value(3)

    publisher_network.receive_gadget(gadget)

    publisher_network.remove_gadget(gadget.get_name())
예제 #14
0
def test_api_gadget_de_serialization(f_validator: Validator,
                                     encoder: ApiEncoder, decoder: ApiDecoder,
                                     f_any_gadget: Gadget,
                                     f_dummy_gadget: Gadget):
    serialized_data = encoder.encode_gadget(f_any_gadget)
    f_validator.validate(serialized_data, "api_gadget_data")

    with pytest.raises(NotImplementedError):
        deserialized_gadget = decoder.decode_gadget(
            serialized_data, f_any_gadget.get_host_client())
    # assert deserialized_gadget.equals(f_any_gadget)

    with pytest.raises(GadgetEncodeError):
        encoder.encode_gadget(f_dummy_gadget)
예제 #15
0
 def _get_base_info(gadget: Gadget) -> dict:
     return {
         "name": gadget.get_name(),
         "service_name": gadget.get_name(),
     }
예제 #16
0
 def _publish_gadget(self, gadget: Gadget):
     with self.__publish_lock:
         self._last_published_gadget = gadget.get_name()
         super()._publish_gadget(gadget)
         self._last_published_gadget = None
예제 #17
0
 def receive_gadget_update(self, gadget: Gadget):
     for identifier in [x.get_type() for x in gadget.get_characteristics()]:
         try:
             self._update_characteristic(gadget, identifier)
         except CharacteristicParsingError as err:
             self._logger.info(err.args[0])