class CyberGlove(object): """Interface the Cyberglove via a serial port. Parameters (TODO) ---------- s_port : str, optional, default: None Serial port name (e.g., 'COM1' in Windows). If set to None, the first one available will be used. baud_rate : int, optional, default: 115200 Baud rate. Attributes ---------- TODO """ def __init__(self, n_df=None, s_port=None, baud_rate=115200, buffered=True, buf_size=1., calibration_file=None): # If n_df is not given assume 18-DOF Cyberglove but issue warning if n_df == None: warnings.warn("Cyberglove: number of DOFs not given, assuming 18.") self.n_df = 18 else: if n_df not in [18, 22]: raise ValueError( "Cyberglove can have either 18 or 22 degrees-of-freedom.") else: self.n_df = n_df # if port is not given use the first one available if s_port == None: try: s_port = serial.tools.list_ports.comports().next()[0] except StopIteration: print("No serial ports found.") self.si = serial.Serial(port=None, baudrate=baud_rate, timeout=0.05, writeTimeout=0.05) self.si.port = s_port self.buffered = buffered self.buf_size = buf_size self.calibration_file = calibration_file self.__srate = 100 # Hardware sampling rate. TODO: Double-check this is correct if self.n_df == 18: self.__bytesPerRead = 20 # First and last bytes are reserved elif self.n_df == 22: self.__bytesPerRead = 24 # First and last bytes are reserved if self.buffered: self.__buf_size_samples = int(np.ceil(self.__srate * self.buf_size)) self.data = Buffer((self.__buf_size_samples, self.n_df)) self.time = Buffer((self.__buf_size_samples, )) else: self.data = np.zeros((self.n_df, )) self.time = np.zeros((1, )) if self.calibration_file is None: self.calibration_ = False else: self.calibration_ = True (self.calibration_offset_, self.calibration_gain_) = load_calibration_file( calibration_file, self.n_df) def __repr__(self): """TODO""" raise NotImplementedError def __str__(self): """TODO""" raise NotImplementedError def __del__(self): """Call stop() on destruct.""" self.stop() def start(self): """Open port and perform check.""" self.__networking = True self.si.open() self._startTime_ = timeit.default_timer() self.si.flushOutput() self.si.flushInput() thread.start_new_thread(self.networking, ()) def stop(self): """Close port.""" self.__networking = False time.sleep( 0.1 ) # Wait 100 ms before closing the port just in case data are being transmitted self._stopTime_ = timeit.default_timer() if self.si.isOpen(): self.si.flushInput() self.si.flushOutput() self.si.close() def networking(self): while self.__networking: data = self.raw_measurement() if self.calibration_ is True: data = calibrate_data(data, self.calibration_offset_, self.calibration_gain_) timestamp = np.asarray([timeit.default_timer()]) if self.buffered is True: self.data.push(data) self.time.push(timestamp) else: self.data = data self.time = timestamp time.sleep(1. / self.__srate ) # Wait 10 ms until before sending the next command def raw_measurement(self): """Performs a single measurment read from device (all sensor values). If this fails, it tries again. Returns the raw data (after reserved bytes have been removed). """ fmt = '@' + "B" * self.__bytesPerRead # Format for unpacking binary data self.si.flushInput() raw_measurement = None while raw_measurement is None: nb = self.si.write(bytes('\x47')) if nb == 1: msg = self.si.read(size=self.__bytesPerRead) if len(msg) is self.__bytesPerRead: raw_measurement = struct.unpack(fmt, msg) raw_measurement = np.asarray(raw_measurement) return raw_measurement[1:-1] # First and last bytes are reserved
class MovingAverage(object): """Moving average smoother. Parameters ---------- shape : tuple Shape of signal to be smoothed. k : integer Number of windows to use for smoothing. weights : array, optional (default ones), shape = (k,) Weight vector. First element corresponds to weight for most recent observation. """ def __init__(self, shape, k, weights=None): self.shape = shape self.__check_k_weights(k, weights) self.k = k self.weights = np.ones((k,)) if weights is None else weights self.buffer_ = Buffer(size=self.__get_buf_size()) def smooth(self, x): """Smooths data in x. Parameters ---------- x : array Most recent raw measurement. Returns ------- x_smoothed : array Smoothed measurement. """ self.buffer_.push(x) return np.dot(np.flipud(self.weights), self.buffer_.buffer) / self.k def __check_k_weights(self, k, weights): if not isinstance(k, int): raise ValueError("k must be integer.") if weights is not None: weights = np.asarray(weights) if weights.ndim != 1 or weights.size != k: raise ValueError("Weight array must have shape ({},)".format(k)) def __get_buf_size(self): """Returns a flattened tuple to be used as the buffer size.""" return tuple(np.append(self.k, np.asarray([element for element in self.shape])))