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
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()
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
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
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
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
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