# The MIT License (MIT) # # Copyright (c) 2015-2018 Niklas Rosenstein # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. import threading import warnings from ._ffi import EventType, Pose, VibrationType from .utils import TimeoutManager from .math import Vector, Quaternion class DeviceListener(object): """ Base class for device listeners -- objects that listen to Myo device events. """ def on_event(self, event): if event.type.name: # An event type that we know of. attr = 'on_' + event.type.name try: method = getattr(self, attr) except AttributeError: pass else: return method(event) warnings.warn('unhandled event: {}'.format(event)) return True # continue def on_paired(self, event): pass def on_unpaired(self, event): pass def on_connected(self, event): pass def on_disconnected(self, event): pass def on_arm_synced(self, event): pass def on_arm_unsynced(self, event): pass def on_unlocked(self, event): pass def on_locked(self, event): pass def on_pose(self, event): pass def on_orientation(self, event): pass def on_rssi(self, event): pass def on_battery_level(self, event): pass def on_emg(self, event): pass def on_warmup_completed(self, event): pass class DeviceProxy(object): """ Stateful container for Myo device data. """ def __init__(self, device, timestamp, firmware_version, mac_address, condition_class=threading.Condition): self._device = device self._mac_address = mac_address self._cond = condition_class() self._pair_time = timestamp self._unpair_time = None self._connect_time = None self._disconnect_time = None self._emg = None self._orientation_update_index = 0 self._orientation = Quaternion.identity() self._acceleration = Vector(0, 0, 0) self._gyroscope = Vector(0, 0, 0) self._pose = Pose.rest self._arm = None self._x_direction = None self._rssi = None self._battery_level = None self._firmware_version = firmware_version self._name = None def __repr__(self): with self._cond: con = 'connected' if self._connected else 'disconnected' return '<DeviceProxy ({}) name={!r}>'.format(con, self.name) @property def _connected(self): return self._connect_time is not None and self._disconnect_time is None @property def connected(self): with self._cond: return self._connected @property def paired(self): with self._cond: return self._unpair_time is not None @property def mac_address(self): return self._mac_address @property def pair_time(self): return self._pair_time @property def unpair_time(self): with self._cond: return self._unpair_time @property def connect_time(self): return self._connect_time @property def disconnect_time(self): with self._cond: return self._disconnect_time @property def firmware_version(self): return self._firmware_version @property def orientation_update_index(self): with self._cond: return self._orientation_update_index @property def orientation(self): with self._cond: return self._orientation.copy() @property def acceleration(self): with self._cond: return self._acceleration.copy() @property def gyroscope(self): with self._cond: return self._gyroscope.copy() @property def pose(self): with self._cond: return self._pose @property def arm(self): with self._cond: return self._arm @property def x_direction(self): with self._cond: return self._x_direction @property def rssi(self): with self._cond: return self._rssi @property def emg(self): with self._cond: return self._emg def set_locking_policy(self, policy): self._device.set_locking_policy(policy) def stream_emg(self, type): self._device.stream_emg(type) def vibrate(self, type=VibrationType.short): self._device.vibrate(type) def request_rssi(self): with self._cond: self._rssi = None self._device.request_rssi() def request_battery_level(self): with self._cond: self._battery_level = None self._device.request_battery_level() class ApiDeviceListener(DeviceListener): def __init__(self, condition_class=threading.Condition): self._condition_class = condition_class self._cond = condition_class() self._devices = {} @property def devices(self): with self._cond: return list(self._devices.values()) @property def connected_devices(self): with self._cond: return [x for x in self._devices.values() if x.connected] def wait_for_single_device(self, timeout=None, interval=0.5): """ Waits until a Myo is was paired **and** connected with the Hub and returns it. If the *timeout* is exceeded, returns None. This function will not return a Myo that is only paired but not connected. # Parameters timeout: The maximum time to wait for a device. interval: The interval at which the function should exit sleeping. We can not sleep endlessly, otherwise the main thread can not be exit, eg. through a KeyboardInterrupt. """ timer = TimeoutManager(timeout) with self._cond: # As long as there are no Myo's connected, wait until we # get notified about a change. while not timer.check(): # Check if we found a Myo that is connected. for device in self._devices.values(): if device.connected: return device self._cond.wait(timer.remainder(interval)) return None def on_event(self, event): with self._cond: if event.type == EventType.paired: device = DeviceProxy(event.device, event.timestamp, event.firmware_version, self._condition_class) self._devices[device._device.handle] = device self._cond.notify_all() return else: try: if event.type == EventType.unpaired: device = self._devices.pop(event.device.handle) else: device = self._devices[event.device.handle] except KeyError: message = 'Myo device not in the device list ({})' warnings.warn(message.format(event), RuntimeWarning) return if event.type == EventType.unpaired: with device._cond: device._unpair_time = event.timestamp self._cond.notify_all() with device._cond: if event.type == EventType.connected: device._connect_time = event.timestamp elif event.type == EventType.disconnected: device._disconnect_time = event.timestamp elif event.type == EventType.emg: device._emg = event.emg elif event.type == EventType.arm_synced: device._arm = event.arm device._x_direction = event.x_direction elif event.type == EventType.rssi: device._rssi = event.rssi elif event.type == EventType.battery_level: device._battery_level = event.battery_level elif event.type == EventType.pose: device._pose = event.pose elif event.type == EventType.orientation: device._orientation_update_index += 1 device._orientation = event.orientation device._gyroscope = event.gyroscope device._acceleration = event.acceleration