def discover_devices_hcitool(timeout=5): """Discover Bluetooth Low Energy Devices nearby on Linux Using ``hcitool`` from Bluez in subprocess, which requires root privileges. However, ``hcitool`` can be allowed to do scan without elevated permission. Install linux capabilities manipulation tools: .. code-block:: bash $ sudo apt-get install libcap2-bin Sets the missing capabilities on the executable quite like the setuid bit: .. code-block:: bash $ sudo setcap 'cap_net_raw,cap_net_admin+eip' `which hcitool` **References:** * `StackExchange, hcitool without sudo <https://unix.stackexchange.com/questions/96106/bluetooth-le-scan-as-non-root>`_ * `StackOverflow, hcitool lescan with timeout <https://stackoverflow.com/questions/26874829/hcitool-lescan-will-not-print-in-real-time-to-a-file>`_ :param int timeout: Duration of scanning. :return: List of tuples with `(address, name)`. :rtype: list """ p = subprocess.Popen(["hcitool", "lescan"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) time.sleep(timeout) os.kill(p.pid, signal.SIGINT) out, err = p.communicate() if len(out) == 0 and len(err) > 0: if err == b'Set scan parameters failed: Operation not permitted\n': raise PyMetaWearException("Missing capabilites for hcitool!") if err == b'Set scan parameters failed: Input/output error\n': raise PyMetaWearException("Could not perform scan.") ble_devices = list( set([ tuple(x.split(' ')) for x in filter(None, out.decode('utf8').split('\n')[1:]) ])) filtered_devices = {} for d in ble_devices: if d[0] not in filtered_devices: filtered_devices[d[0]] = d[1] else: if filtered_devices.get(d[0]) == '(unknown)': filtered_devices[d[0]] = d[1] return [(k, v) for k, v in filtered_devices.items()]
def notifications(self, callback=None): """No subscriptions possible for LED module. :raises: :py:exc:`~PyMetaWearException` """ raise PyMetaWearException("No notifications available for LED module.")
def notifications(self, callback=None): """No subscriptions possible for Haptic module. :raises: :py:exc:`~PyMetaWearException` """ raise PyMetaWearException("Haptic module has no notifications.")
def notifications(self, callback=None): """Toggle notifications/subscriptions to data signals on the MetaWear board. :param callable callback: The function to call when data signal notification arrives. If ``None``, an unsubscription to notifications is sent. """ data_signal = self.data_signal if callback is not None: if self._debug: log.debug("Subscribing to {0} changes. (Sig#: {1})".format( self.module_name, data_signal)) if self.callback is not None: raise PyMetaWearException( "Subscription to {0} signal already in place!") self.callback = (callback, Fn_DataPtr(callback)) libmetawear.mbl_mw_datasignal_subscribe(data_signal, self.callback[1]) else: if self._debug: log.debug("Unsubscribing to {0} changes. (Sig#: {1})".format( self.module_name, data_signal)) if self.callback is None: return libmetawear.mbl_mw_datasignal_unsubscribe(data_signal) self.callback = None
def wrapper(data): if data.contents.type_id == DataTypeId.CARTESIAN_FLOAT: epoch = int(data.contents.epoch) data_ptr = cast(data.contents.value, POINTER(CartesianFloat)) func((epoch, (data_ptr.contents.x, data_ptr.contents.y, data_ptr.contents.z))) elif data.contents.type_id == DataTypeId.QUATERNION: epoch = int(data.contents.epoch) data_ptr = cast(data.contents.value, POINTER(Quaternion)) func((epoch, (data_ptr.contents.w, data_ptr.contents.x, data_ptr.contents.y, data_ptr.contents.z))) elif data.contents.type_id == DataTypeId.CORRECTED_CARTESIAN_FLOAT: epoch = int(data.contents.epoch) data_ptr = cast(data.contents.value, POINTER(CorrectedCartesianFloat)) func((epoch, (data_ptr.contents.x, data_ptr.contents.y, data_ptr.contents.z, data_ptr.contents.accuracy))) elif data.contents.type_id == DataTypeId.EULER_ANGLES: epoch = int(data.contents.epoch) data_ptr = cast(data.contents.value, POINTER(EulerAngle)) func((epoch, (data_ptr.contents.heading, data_ptr.contents.pitch, data_ptr.contents.roll, data_ptr.contents.yaw))) else: raise PyMetaWearException('Incorrect data type id: {0}'.format( data.contents.type_id))
def wrapper(data): if data.contents.type_id == DataTypeId.FLOAT: epoch = int(data.contents.epoch) data_ptr = cast(data.contents.value, POINTER(c_float)) func((epoch, data_ptr.contents.value)) else: raise PyMetaWearException('Incorrect data type id: {0}'.format( data.contents.type_id))
def __init__(self, address, backend='pygatt', interface='hci0', timeout=None, connect=True, debug=False): """Constructor.""" self._address = address self._debug = debug self._initialized = False if self._debug: add_stream_logger() log.info("Creating MetaWearClient for {0}...".format(address)) # Handling of timeout. if timeout is None: timeout = os.environ.get('PYMETAWEAR_TIMEOUT', None) if timeout is not None: try: timeout = float(timeout) except: timeout = None if backend == 'pygatt': self._backend = PyGattBackend(self._address, interface=interface, timeout=timeout, debug=debug) elif backend == 'pybluez': self._backend = PyBluezBackend(self._address, interface=interface, timeout=timeout, debug=debug) else: raise PyMetaWearException("Unknown backend: {0}".format(backend)) log.info( "Backend starter with {0} for device address {1} with timeout {2}..." .format(backend, address, timeout)) self.firmware_version = None self.model_version = None self.accelerometer = None #self.gpio = None self.gyroscope = None self.magnetometer = None self.barometer = None self.ambient_light = None self.switch = None self.settings = None self.temperature = None self.haptic = None self.led = None self.sensorfusion = None if connect: self.connect()
def data_signal(self): """Returns the data signal pointer value for the switch module. :returns: The pointer value. (Long if on x64 architecture.) :rtype: :py:class:`ctypes.c_long` or :py:class:`ctypes.c_int` """ raise PyMetaWearException( "No data signal exists for {0} module.".format(self))
def wrapper(data): if data.contents.type_id == DataTypeId.CARTESIAN_FLOAT: epoch = int(data.contents.epoch) data_ptr = cast(data.contents.value, POINTER(CartesianFloat)) func((epoch, (data_ptr.contents.x, data_ptr.contents.y, data_ptr.contents.z))) else: raise PyMetaWearException('Incorrect data type id: {0}'.format( data.contents.type_id))
def wrapper(data): if data.contents.type_id == DataTypeId.BATTERY_STATE: epoch = int(data.contents.epoch) data_ptr = cast(data.contents.value, POINTER(BatteryState)) func((epoch, (int(data_ptr.contents.voltage), int(data_ptr.contents.charge)))) else: raise PyMetaWearException('Incorrect data type id: {0}'.format( data.contents.type_id))
def set_sample_delay(self, data_source, delay=None, differential=False): """ Change the delay between samples using the onboard time processor module to change the effective sampling rate of a specific data source. :param data_source: A data source from sensor.SensorFusion :param delay: The delay in ms between samples, or None to reset to default :param differential: Set Time Preprocessor mode to differential, instead of the default, absolute """ global current_processor global waiting_for_processor if self._data_source_signals[data_source] is None: log.debug("Getting data signal for data source {0}".format( data_source )) self._data_source_signals[data_source] = \ libmetawear.mbl_mw_sensor_fusion_get_data_signal( self.board, data_source ) if delay is not None: mode = processor.Time.MODE_DIFFERENTIAL if differential else \ processor.Time.MODE_ABSOLUTE waiting_for_processor = True log.debug("Creating time dataprocessor for signal {0}".format( self._data_source_signals[data_source] )) libmetawear.mbl_mw_dataprocessor_time_create( self._data_source_signals[data_source], mode, delay, Fn_VoidPtr(processor_set)) wait_time = 0 while waiting_for_processor and wait_time < max_processor_wait_time: sleeptime = 0.1 time.sleep(sleeptime) wait_time += sleeptime if current_processor is not None: self._data_source_signals[data_source] = current_processor current_processor = None else: raise PyMetaWearException("Can't set data processor!") else: data_signal = libmetawear.mbl_mw_sensor_fusion_get_data_signal( self.board, data_source) if self._data_source_signals[data_source] != data_signal: libmetawear.mbl_mw_dataprocessor_remove( self._data_source_signals[data_source] ) self._data_source_signals[data_source] = data_signal
def _callback(data): """Handle a (x,y,z) gyroscope tuple.""" if data.contents.type_id == DataTypeId.CARTESIAN_FLOAT: data_ptr = cast(data.contents.value, POINTER(CartesianFloat)) print("X: {0}, Y: {1}, Z: {2}".format(*(data_ptr.contents.x, data_ptr.contents.y, data_ptr.contents.z))) else: raise PyMetaWearException('Incorrect data type id: ' + str(data.contents.type_id))
def set_sample_delay(self, data_source, delay=None, differential=False): """ Change the delay between samples using the onboard time processor module to change the effective sampling rate of a specific data source. :param data_source: A data source from sensor.SensorFusion :param delay: The delay in ms between samples, or None to reset to default :param differential: Set Time Preprocessor mode to differential, instead of the default, absolute """ if self._data_source_signals[data_source] is None: log.debug("Getting data signal for data source {0}".format( data_source )) self._data_source_signals[data_source] = \ libmetawear.mbl_mw_sensor_fusion_get_data_signal( self.board, data_source ) if delay is not None: mode = TimeMode.DIFFERENTIAL if differential else \ TimeMode.ABSOLUTE log.debug("Creating time dataprocessor for signal {0}".format( self._data_source_signals[data_source] )) _done = Event() def _processor_set(processor): """ Set global variables as the libmetawear callback can't handle the self parameter of instance methods. :param processor: The processor that was created """ self._data_source_signals[data_source] = processor _done.set() libmetawear.mbl_mw_dataprocessor_time_create( self._data_source_signals[data_source], mode, delay, FnVoid_VoidP(_processor_set)) _done.wait(timeout=PROCESSOR_SET_WAIT_TIME) if self._data_source_signals[data_source] is None: raise PyMetaWearException("Can't set data processor!") else: data_signal = libmetawear.mbl_mw_sensor_fusion_get_data_signal( self.board, data_source) if self._data_source_signals[data_source] != data_signal: libmetawear.mbl_mw_dataprocessor_remove( self._data_source_signals[data_source] ) self._data_source_signals[data_source] = data_signal
def handle_notify_char_output(self, handle, value): self._log("Notify", handle, value, 0) if handle == self._notify_char_handle: sb = self._response_2_string_buffer(value) libmetawear.mbl_mw_metawearboard_notify_char_changed( self.board, sb.raw, len(sb.raw)) else: raise PyMetaWearException( "Notification on unexpected handle: {0}".format(handle))
def handle_notify_char_output(self, handle, value): self._log("Notify", handle, value, 0) if handle == self._notify_char_handle: sb_temp = self._response_2_string_buffer(value) sb = (ctypes.c_ubyte * len(sb_temp)).from_buffer_copy(sb_temp) libmetawear.mbl_mw_metawearboard_notify_char_changed( self.board, sb, len(sb)) else: raise PyMetaWearException( "Notification on unexpected handle: {0}".format(handle))
def start_logging(self): """Setup and start logging of data signals on the MetaWear board""" data_signal = self.data_signal if getattr(self, 'high_frequency_stream', False): raise PyMetaWearException( "Cannot log on high frequency stream signal.") self._logger_ready_event = Event() logger_ready = FnVoid_VoidP_VoidP(context_callback(self._logger_ready)) libmetawear.mbl_mw_datasignal_log(self.data_signal, None, logger_ready) self._logger_ready_event.wait() if self._logger_address is None: raise PyMetaWearException( 'Failed to start logging for {0} module!'.format( self.module_name)) log.debug("Start Logger (Logger#: {0}, Signal#: {1})".format( self._logger_address, data_signal)) self._logger_running = True self._download_done = False libmetawear.mbl_mw_logging_start(self.board, 0) self.toggle_sampling(True) self.start()
def __init__(self, address, interface=None, timeout=None, debug=False): if _import_failure is not None: raise PyMetaWearException( "pybluez[ble] package error: {0}".format(_import_failure)) self.name = 'pybluez/gattlib' self._primary_services = {} self._characteristics_cache = {} self._response = GATTResponse() if debug: log.setLevel(logging.DEBUG) super(PyBluezBackend, self).__init__(address, interface, 10.0 if timeout is None else timeout, debug)
def log(): client = MetaWearClient(str(address), debug=False) print("New client created: {0}".format(client)) settings = client.accelerometer.get_possible_settings() print("Possible accelerometer settings of client:") for k, v in settings.items(): print(k, v) print("Write accelerometer settings...") client.accelerometer.set_settings(data_rate=400, data_range=4.0) settings = client.accelerometer.get_current_settings() print("Accelerometer settings of client: {0}".format(settings)) client.accelerometer.high_frequency_stream = False client.accelerometer.start_logging() print("Logging accelerometer data...") for i in range(5): time.sleep(1.0) print(i) client.accelerometer.stop_logging() print("Logging stopped.") print("Downloading data...") download_done = False n = 0 data = None while (not download_done) and n < 3: try: data = client.accelerometer.download_log() download_done = True except PyMetaWearDownloadTimeout: print("Download of log interrupted. Trying to reconnect...") client.disconnect() client.connect() n += 1 if data is None: raise PyMetaWearException("Download of logging data failed.") print("Disconnecting...") client.disconnect() with open('data.txt', 'w') as f: for d in data: f.write(str(d) + "\n")
def __init__(self, address, interface=None, timeout=None, debug=False): if _import_failure is not None: raise PyMetaWearException( "pygatt[GATTTOOL] package error: {0}".format(_import_failure)) self.name = 'pygatt' log.info("PyGattBackend: Creating new GATTToolBackend and starting GATTtool process...") self._backend = None if debug: log.setLevel(logging.DEBUG) super(PyGattBackend, self).__init__( address, interface, gatttool.DEFAULT_CONNECT_TIMEOUT_S if timeout is None else timeout, debug)
def get_handle(self, characteristic_uuid, notify_handle=False): """Get handle for a characteristic UUID. :param uuid.UUID characteristic_uuid: The UUID for the characteristic to look up. :param bool notify_handle: :return: The handle for this UUID. :rtype: int """ if isinstance(characteristic_uuid, string_types): characteristic_uuid = characteristic_uuid.UUID(characteristic_uuid) handle = self._characteristics_cache.get( characteristic_uuid, [None, None])[int(notify_handle)] if handle is None: raise PyMetaWearException("Incorrect characteristic.") else: return handle
def check_and_change_callback(self, data_signal, callback): if callback is not None: if self._debug: log.debug("Subscribing to {0} changes. (Sig#: {1})".format( self.module_name, data_signal)) if data_signal in self._callbacks: raise PyMetaWearException( "Subscription to {0} signal already in place!") self._callbacks[data_signal] = (callback, Fn_DataPtr(callback)) libmetawear.mbl_mw_datasignal_subscribe( data_signal, self._callbacks[data_signal][1]) else: if data_signal not in self._callbacks: return if self._debug: log.debug("Unsubscribing to {0} changes. (Sig#: {1})".format( self.module_name, data_signal)) libmetawear.mbl_mw_datasignal_unsubscribe(data_signal) del self._callbacks[data_signal]
def connect(self, clean_connect=True): """Connect this client to the MetaWear device. :param bool clean_connect: If old backend components should be replaced. Default is ``False``. """ if self.backend.is_connected: return self.backend.connect(clean_connect=clean_connect) self.deserialize() if self._debug: log.debug("Waiting for MetaWear board to be fully initialized...") while not self.backend.initialized: self.backend.sleep(0.1) # Check if initialization has been completed successfully. if self.backend.initialization_status != Const.STATUS_OK: if self.backend.initialization_status == Const.STATUS_ERROR_TIMEOUT: raise PyMetaWearConnectionTimeout( "libmetawear initialization status 16: Timeout") else: raise PyMetaWearException( "libmetawear initialization status {0}".format( self.backend.initialization_status)) self.serialize() # Read out firmware and model version. self.firmware_version_str = self.backend.read_gatt_char_by_uuid( specs.DEV_INFO_FIRMWARE_CHAR[1]).decode() self.firmware_version = tuple( [int(x) for x in self.firmware_version_str.split('.')]) self.model_version = int( self.backend.read_gatt_char_by_uuid( specs.DEV_INFO_MODEL_CHAR[1]).decode()) self.model_name = _model_names[ libmetawear.mbl_mw_metawearboard_get_model(self.board) + 1] self._initialize_modules()
def wrapper(*args, **kwargs): if getattr(args[0], 'gyro_r_class', None) is None: raise PyMetaWearException("There is not Gyroscope " "module of your MetaWear board!") return f(*args, **kwargs)
def wrapper(*args, **kwargs): if getattr(args[0], 'available', False) is False: raise PyMetaWearException("There is no Sensor Fusion " "module on your MetaWear board!") return f(*args, **kwargs)
print("Downloading data...") download_done = False n = 0 data = None while (not download_done) and n < 3: try: data = c.sensorfusion.download_log() download_done = True except PyMetaWearDownloadTimeout: print("Download of log interrupted. Trying to reconnect...") c.disconnect() c.connect() n += 1 if data is None: raise PyMetaWearException("Download of logging data failed.") for d in data: print(d) #v = d['value'] # print(str(v)) # dat = str(v) # print(type(dat)) # #print(dat[4:10]) # x = float(dat[4:10]) # print(x) # print(type(x)) # #print(dat[15:22])
def wrapper(*args, **kwargs): if getattr(args[0], 'mag_class', None) is None: raise PyMetaWearException("There is not Magnetometer " "module on your MetaWear board!") return f(*args, **kwargs)
def set_settings(self, **kwargs): raise PyMetaWearException( "No settings exists for {0} module.".format(self))
def wrapper(*args, **kwargs): if getattr(args[0], 'ambient_light_gain_class', None) is None: raise PyMetaWearException("There is no Ambient Light " "module on your MetaWear board!") return f(*args, **kwargs)