Ejemplo n.º 1
0
    def _check_parameter(self):
        if self.api_url is None:
            raise RegistrationError('api_url is required')

        if self.device_model is None:
            raise RegistrationError('device_model not given.')

        if isinstance(self.device_addr, UUID):
            self.device_addr = str(self.device_addr)
        elif self.device_addr:
            try:
                UUID(self.device_addr)
            except ValueError:
                try:
                    self.device_addr = str(UUID(int=int(self.device_addr, 16)))
                except ValueError:
                    log.warning(
                        'Invalid device_addr. Change device_addr to None.')
                    self.device_addr = None

        if self.persistent_binding and self.device_addr is None:
            msg = ('In case of `persistent_binding` set to `True`, '
                   'the `device_addr` should be set and fixed.')
            raise ValueError(msg)

        if not self.device_features.keys():
            raise RegistrationError('Neither idf_list nor odf_list is empty.')

        return True
Ejemplo n.º 2
0
    def deregister(self):
        ''' Deregister from an IoTtalk server.

        This function will block until the offline message published and
        DELETE request finished.

        :raises: RegistrationError if not registered or deregistration failed
        '''
        log.debug("Deregisteration triggered")
        ctx = self.context

        if not ctx.mqtt_client:
            raise RegistrationError('Not registered')

        # FIXME: replace lock with ``wait_for_publish``
        self._disconn_lock = Lock()
        self._disconn_lock.acquire()

        ctx.mqtt_client.on_publish = self._on_offline_pub
        ctx.mqtt_client.publish(
            ctx.i_chans['ctrl'],
            json.dumps({'state': 'offline', 'rev': ctx.rev}),
            retain=True,
            qos=2,
        ).wait_for_publish()

        try:
            response = requests.delete(
                '{}/{}'.format(ctx.url, ctx.app_id),
                headers={
                    'Content-Type': 'application/json'
                },
                data=json.dumps({'rev': ctx.rev})
            )

            if response.status_code != 200:
                raise RegistrationError(response.json()['reason'])
        except requests.exceptions.ConnectionError:
            raise RegistrationError('ConnectionError')
        except (KeyError, json.JSONDecodeError):
            raise RegistrationError('Invalid response from server')

        self._disconn_lock.acquire()  # wait for disconnect finished
        del self._disconn_lock
        ctx.mqtt_client = None

        if ctx.on_deregister:
            ctx.on_deregister(self)

        return response.json()
Ejemplo n.º 3
0
    def push(self, idf, data, block=False):
        '''
        Push data to IoTtalk server.

        :param block: if ``True``, block mqtt publishing util finished
        :returns: ``True`` if publishing fired, ``False`` if failed
        :raises: RegistrationError if not registered
        '''
        ctx = self.context
        if not ctx.mqtt_client:
            raise RegistrationError('Not registered')

        if ctx.i_chans.get(idf) is None:
            return False

        data = data if isinstance(data, list) else [data]
        data = json.dumps(data)

        # TODO: make qos configurable
        pub = ctx.mqtt_client.publish(
            self.context.i_chans[idf],
            data,
        )

        if block:
            pub.wait_for_publish()

        return True
Ejemplo n.º 4
0
    def f(p):
        if isinstance(p, str):
            df_name, param_type = p, None
        elif isinstance(p, tuple) and len(p) == 2:
            df_name, param_type = p
        else:
            raise RegistrationError('Invalid `{}_list`, usage: [df_name, ...] '
                                    'or [(df_name, type), ...]'.format(typ))

        on_data = push_data = getattr(sa, DAI.df_func_name(df_name), None)

        df = DeviceFeature(df_name=df_name,
                           df_type=typ,
                           param_type=param_type,
                           push_data=push_data,
                           on_data=on_data)
        return df_name, df
Ejemplo n.º 5
0
 def df_type(self, value):
     if value not in ['idf', 'odf']:
         msg = '<{df_name}>: df_type must be "idf" or "odf"'.format(df_name=self.df_name)
         raise RegistrationError(msg)
     self._df_type = value
Ejemplo n.º 6
0
    def register(self, url, on_signal, on_data,
                 id_=None, name=None,
                 idf_list=None, odf_list=None,
                 accept_protos=None,
                 profile=None, register_callback=None,
                 on_register=None, on_deregister=None,
                 on_connect=None, on_disconnect=None):
        ''' Register to an IoTtalk server.

        :param url: the url of Iottalk server
        :param on_signal: the signal handler
        :param on_data: the data handler
        :param id_: the uuid used to identify an application, if not provided,
                    this function generates one and return
        :param name: the name of the application
        :param idf_list: the Input Device Feature list of the application.
                         Every element should be a tuple,
                         with the feature name and unit information provided,
                         e.g. ('meow', ('dB'))
        :param odf_list: the Output Device Feature list of the application.
        :param accept_protos: the protocols accepted by the application.
                              default is ``['mqtt']``.
        :param profile: an abitrary json data field
        :param on_register: the callable function invoked
                            while the registeration succeeded.
        :param register_callback: this is deprecated, please use ``on_register``
                                  instead.
        :param on_deregister: the callable function invoked
                              while the deregistration succeeded.
        :param on_connect: the callable function invoked while the MQTT
                           client connected.
                           Note that this function might be called multiple
                           times if the client keep reconnecting.
        :param on_disconnect: the callable function invoked while the MQTT
                              client disconnected.
                              Note that this function might be called multiple
                              times if the client lose the connection.
        :type url: str
        :type on_signal: Function
        :type on_data: Function
        :type id_: str
        :type name: str
        :type idf_list: List[Tuple[str, List[str]]]
        :type odf_list: List[Tuple[str, List[str]]]
        :type accept_protos: List[str]
        :type profile: dict
        :returns: the json object responsed from server if registration succeed
        :raises: RegistrationError if already registered or registration failed
        '''
        ctx = self.context

        if ctx.mqtt_client:
            raise RegistrationError('Already registered')

        ctx.url = url
        if _invalid_url(ctx.url):
            raise RegistrationError('Invalid url: "{}"'.format(ctx.url))

        try:
            ctx.app_id = UUID(id_) if id_ else uuid4()
        except ValueError:
            raise RegistrationError('Invalid UUID: {!r}'.format(id_))

        body = {}
        if name:
            body['name'] = name

        if idf_list:
            body['idf_list'] = idf_list

        if odf_list:
            body['odf_list'] = odf_list

        body['accept_protos'] = accept_protos if accept_protos else ['mqtt']

        if profile:
            body['profile'] = profile

        _reg_msg = 'register_callback is deprecated, please use `on_register` instead.'
        if on_register and register_callback:
            raise RegistrationError(_reg_msg)
        elif on_register:
            ctx.on_register = on_register
        elif register_callback:
            log.warning(_reg_msg)
            ctx.on_register = register_callback

        # other callbacks
        ctx.on_deregister = on_deregister
        ctx.on_connect = on_connect
        ctx.on_disconnect = on_disconnect

        try:
            response = requests.put(
                '{}/{}'.format(ctx.url, ctx.app_id),
                headers={
                    'Content-Type': 'application/json',
                },
                data=json.dumps(body)
            )

            if response.status_code != 200:
                raise RegistrationError(response.json()['reason'])
        except requests.exceptions.ConnectionError:
            raise RegistrationError('ConnectionError')
        except (KeyError, json.JSONDecodeError):
            raise RegistrationError('Invalid response from server')

        metadata = response.json()
        ctx.name = metadata['name']
        ctx.mqtt_host = metadata['url']['host']
        ctx.mqtt_port = metadata['url']['port']
        ctx.i_chans['ctrl'] = metadata['ctrl_chans'][0]
        ctx.o_chans['ctrl'] = metadata['ctrl_chans'][1]
        ctx.rev = rev = metadata['rev']
        ctx.mqtt_client = mqtt.Client(client_id='iottalk-py-{}'.format(uuid4().hex))
        ctx.mqtt_client.on_message = self._on_message
        ctx.mqtt_client.on_connect = self._on_connect
        ctx.mqtt_client.on_disconnect = self._on_disconnect

        ctx.mqtt_client.enable_logger(log)
        ctx.mqtt_client.will_set(
            self.context.i_chans['ctrl'],
            json.dumps({'state': 'offline', 'rev': rev}),
            retain=True,
        )
        ctx.mqtt_client.connect(
            self.context.mqtt_host,
            port=self.context.mqtt_port,
        )

        ctx.mqtt_client.loop_start()

        ctx.on_signal = on_signal
        ctx.on_data = on_data

        try:
            msg = ctx._mqueue.get(timeout=5)
            msg.wait_for_publish()
        except queue.Empty:
            log.error('MQTT connection timeout')
            raise
        log.debug('Online message published')

        if ctx.on_register:
            ctx.on_register(self)

        return ctx
Ejemplo n.º 7
0
 def push_data(self, value):
     if value is None or not callable(value):
         msg = '<{df_name}>: function not found.'.format(df_name=self.df_name)
         raise RegistrationError(msg)
     self._push_data = value