def device_state_report_handler(message, listener):
    """Handle a device trying to report it's state

    Todo:
        AWS iot has other error codes for this, copied from HTTP error codes. we
        could do that as well
    """
    try:
        message.device.update_reported_state_from_message(message)
    except exceptions.BadMessageSchemaError:
        logger.exception("Device tried to update state with incorrect schema")

        wrapped = {
            "body": {
                "status": "failure",
                "error": "Invalid message schema",
            },
            "device": message.device,
            "category": "reported_state_failure",
            "timestamp": datetime.datetime.utcnow(),
        }
        response = Message.from_dict(wrapped)
        response.send_to_device()

        raise
    except Exception:
        logger.exception("Unknown error processing")

        wrapped = {
            "body": {
                "status": "failure",
                "error": "Unknown error",
            },
            "device": message.device,
            "category": "reported_state_failure",
            "timestamp": datetime.datetime.utcnow(),
        }
        response = Message.from_dict(wrapped)
        response.send_to_device()

        raise
    else:
        logger.info("successfully processed device reporting state update")

        wrapped = {
            "body": {
                "status": "success",
            },
            "device": message.device,
            "category": "reported_state_success",
            "timestamp": datetime.datetime.utcnow(),
        }
        response = Message.from_dict(wrapped)
        response.send_to_device()
    def test_to_device(self, fakedevice):
        with mock.patch(IBM + ".connect"), \
             mock.patch(IBM + ".send_message") as send:

            sender = get_sender()

            sender.to_device("cat", {"a": "b"},
                             device_id=fakedevice.get_iot_id(),
                             device_type=fakedevice.product.iot_name)
            send.assert_called_once_with(
                "cat", {"a": "b"},
                device_id=str(fakedevice.id),
                device_type=fakedevice.product.iot_name)

            send.reset_mock()
            sender.to_device("cat", {"a": "b"}, device=fakedevice)
            send.assert_called_once_with("cat", {"a": "b"},
                                         device_id=str(fakedevice.id),
                                         device_type="testproduct123")

            send.reset_mock()
            sender.to_device("example-category", {"foo": "bar"})
            send.assert_not_called()

            send.reset_mock()
            message = Message("incoming-category", {"a": "b"}, fakedevice)
            sender.to_device("cat", {"a": "b"}, incoming_message=message)
            send.assert_called_once_with("cat", {"a": "b"},
                                         device_id=str(fakedevice.id),
                                         device_type="testproduct123")
Beispiel #3
0
    def send_ts_data(self, serializer, device):
        """
        Create a ZConnect Message from the incoming data and process with
        the celery worker.
        """

        timestamp = serializer.data["timestamp"]
        if not isinstance(timestamp, datetime):
            timestamp = parser.parse(timestamp)

        message = Message(
            category="periodic",
            device=device,
            body=serializer.data["data"],
            timestamp=timestamp,
        )
        process_message.apply_async(args=[message.as_dict()])
    def test_message_callback(self, fakedevice):
        with mock.patch(IBM + ".connect"):
            greenlet = get_listener()
            mocked = mock.Mock()
            message = Message("cat", {"a": "b"}, fakedevice)
            greenlet.client.message_handlers = {"cat": [mocked]}
            greenlet.client._message_callback(message)

            mocked.assert_called_once_with(message, greenlet.client)
def get_message(device, event_def):
    return Message(
        category="event",
        device=device,
        body={
            "event_id": "{}:{}".format(device.id, event_def.id),
            "source": "server",
            "current": device.get_latest_ts_data()
        },
    )
    def test_generate_event_callback(self, fakedevice, interface):
        with mock.patch(IBM + ".construct_zconnect_message") as construct:

            mocked = mock.Mock()

            message = Message("cat", {"a": "b"}, fakedevice)
            construct.return_value = message
            callback = interface.generate_event_callback(mocked)
            callback("test")
            mocked.assert_called_once_with(message)
    def fix_fake_message(self, fakedevice):
        test_message = Message.from_dict({
            "category":
            "report_state",
            "timestamp":
            datetime.datetime.utcnow().isoformat(),
            "device":
            fakedevice.id,
            "body": {
                "tag": 123,
            }
        })

        return test_message
Beispiel #8
0
    def update_reported_state(self, new_state, verify=False):
        """Given a new reported state from a device, create a new DeviceState
        object in the database

        If the reported state changes between us reading the last state and us
        trying to save a new state, there is another race condition somewhere
        which is a fairly fatal error which we can't do anything about. We don't
        care if the desired state changes though, as that should be handled
        somewhere else or on the device

        Args:
            new_state (dict): The 'reported' state from the device. This should
                be the RAW state, ie no 'state' or 'reported' key
            verify (bool): If True, verification will be done to make sure it
                matches the expected Product state layout. If not, assume it has
                already been validated.

        Raises:
            BadMessageSchemaError: If verify is True and message validation
                fails

        Returns:
            DeviceState: new state document
        """

        if verify:
            wrapped = {
                "body": {
                    "state": {
                        "reported": new_state
                    },
                },
                "device": self.pk,
                "category": "reported_state",
                "timestamp": datetime.datetime.utcnow(),
            }
            message = Message.from_dict(wrapped)
            verify_message_schema(message)

        return self._attempt_device_state_update("reported", new_state)
Beispiel #9
0
    def update_desired_state(self,
                             new_state,
                             verify=True,
                             error_on_reported_change=False):
        """Given a new desired state for a device, create a new DeviceState
        object in the database

        If the state update succeeds, it will send a message to the device to
        tell it that it should try and change to the desired state

        If the state is updated but it raises an integrity error, another
        thread/process might have changed the state. In this case, if it is just
        the device reporting that it's state has changed and
        error_on_reported_change is True, raise an error. This will always be
        raised if the desired state changes.

        If the reported state changes but error_on_reported_change is False,
        just set the new state document to contain that reported state and
        return to the user. note that this does hide information from the
        developer, but the reported state change should be handled in another
        process anyway.

        Todo:
            What if it can save to the DB, but the MQTT message can't be sent?
            If we send the message before saving the state there is a race
            condition, but if we save the state before sending we need to buffer
            the 'change state' message somehow so it can be sent

        Args:
            new_state (dict): The new 'desired' state for the device. This should
                be the RAW state, ie no 'state' or 'reported' key
            verify (bool): If True, verification will be done to make sure it
                matches the expected Product state layout. If not, assume it has
                already been validated.

        Raises:
            BadMessageSchemaError: If verify is True and message validation
                fails
            StateConflictError: If the state is changed elsewhere while trying
                to save a new state (according to rules described above)

        Returns:
            DeviceState: new state document
        """

        wrapped = {
            "body": {
                "state": {
                    "desired": new_state
                },
            },
            "device": self.pk,
            "category": "desired_state",
            "timestamp": datetime.datetime.utcnow(),
        }

        if verify:
            v_dict = copy.deepcopy(wrapped)
            v_dict["body"]["state"]["reported"] = v_dict["body"]["state"].pop(
                "desired")
            v_msg = Message.from_dict(v_dict)
            verify_message_schema(v_msg)

        new_latest_state = self._attempt_device_state_update(
            "desired", new_state, error_on_reported_change)

        message = Message.from_dict(wrapped)
        message.send_to_device()

        return new_latest_state