<reponame>mesca/timeflux_serial
import serial
import time
import numpy as np
import pandas as pd
from timeflux.core.exceptions import WorkerInterrupt
from timeflux.helpers import clock
from timeflux.core.node import Node


class SerialDevice(Node):

    """A generic serial device driver.

    Attributes:
        o (Port): Stream from the serial device, provides DataFrame.

    Args:
        port (string): The serial port.
            e.g. ``COM3`` on Windows;  ``/dev/tty.tty.SLAB_USBtoUART`` on MacOS;
            ``/dev/ttyUSB0`` on GNU/Linux.
        rate (int): The device rate in Hz.
            Possible values: ``1``, ``10``, ``100``, ``1000``. Default: ``1000``.

    Example:
        .. literalinclude:: /../test/graphs/test.yaml
           :language: yaml

    Notes:

    .. attention::

        Make sure to set your graph rate to an high-enough value, otherwise the device
        internal buffer may saturate, and data may be lost. A 30Hz graph rate is
        recommended for a 1000Hz device rate.

    """

    def __init__(self, port, rate=1000):

        # Check port
        if not port.startswith('/dev/') and not port.startswith('COM'):
            raise ValueError(f'Invalid serial port: {port}')

        # Check rate
        if not isinstance(rate, int):
            raise ValueError(f'Invalid rate: {rate}')
        self._rate = rate

        # Sample size in bytes
        self.sample_size = 9

        # Connect to device
        self.device = self._connect(port)
        # try:
        #     self.device = self.connect(port)
        # except Exception as e:
        #     raise WorkerInterrupt(e)

        # Set rate
        self._send(f'rate,{rate}')

        # Start Acquisition
        self._send('start')

        # Initialize counters for timestamp indices and continuity checks
        self._sample_counter = 65535
        self._timestamp = clock.now()


    def update(self):

        data, indices = self._read()
        self.o.set(data, indices, ('SEQ', 'UTIME', 'A0'), {'rate': self._rate})


    def _connect(self, port):
        device = serial.Serial(port, 115200)
        msg = device.readline(); # Wait for 'ready\n'
        try:
            msg.decode()
        except UnicodeDecodeError:
            self.logger.error('Unstable state. Please re-plug the device and start again.')
            # https://stackoverflow.com/questions/21073086/wait-on-arduino-auto-reset-using-pyserial
            # https://forum.arduino.cc/index.php?topic=38981.0
            raise WorkerInterrupt()
        return device


    def _send(self, cmd):
        cmd = (cmd + '\n').encode()
        self.device.write(cmd)


    def _read(self):

        """Read all available data"""

        # Check buffer size and limits
        buffer_size = self.device.in_waiting
        if buffer_size == 1020:
            # The OS serial buffer can hold up to 1020 bytes
            self.logger.warn('Internal buffer saturated. Increase graph rate or decrease device rate.')

        # Compute the maximum number of samples we can get
        sample_count = int(buffer_size / self.sample_size)

        # Read raw samples from device
        now = clock.now()
        raw = self.device.read(sample_count * self.sample_size)

        # Initialize the output matrix
        data = np.full((sample_count, 3), np.nan, np.uint32)

        # Parse the raw data
        for sample_number in range(sample_count):

            # Extract sample
            start = sample_number * self.sample_size
            stop = start + self.sample_size
            sample = raw[start:stop]

            # Check (dummy) CRC
            crc = sample[8]
            if crc != 0:
                self.logger.warn('Corrupted sample.')
                continue

            # Parse sample
            data[sample_number, 0] = int.from_bytes(sample[0:2], byteorder='little', signed=False)
            data[sample_number, 1] = int.from_bytes(sample[2:6], byteorder='little', signed=False)
            data[sample_number, 2] = int.from_bytes(sample[6:8], byteorder='little', signed=False)

            # Did we miss any sample?
            # Check for discontinuity in the internal sample counter (2 bytes).
            sample_counter = data[sample_number, 0]
            if sample_counter == self._sample_counter + 1:
                pass
            elif sample_counter == 0 and self._sample_counter == 65535:
                pass
            else:
                self.logger.warn('Missed sample.')
            self._sample_counter = sample_counter

        # Compute indices
        indices = clock.time_range(self._timestamp, now, len(data))
        self._timestamp = now

        return data, indices


    def terminate(self):
        self._send('stop')
        self.device.close()

import serial
import time
import numpy as np
import pandas as pd
from timeflux.core.exceptions import WorkerInterrupt
from timeflux.helpers import clock
from timeflux.core.node import Node


class SerialDevice(Node):

    """A generic serial device driver.

    Attributes:
        o (Port): Stream from the serial device, provides DataFrame.

    Args:
        port (string): The serial port.
            e.g. ``COM3`` on Windows;  ``/dev/tty.tty.SLAB_USBtoUART`` on MacOS;
            ``/dev/ttyUSB0`` on GNU/Linux.
        rate (int): The device rate in Hz.
            Possible values: ``1``, ``10``, ``100``, ``1000``. Default: ``1000``.

    Example:
        .. literalinclude:: /../test/graphs/test.yaml
           :language: yaml

    Notes:

    .. attention::

        Make sure to set your graph rate to an high-enough value, otherwise the device
        internal buffer may saturate, and data may be lost. A 30Hz graph rate is
        recommended for a 1000Hz device rate.

    """

    def __init__(self, port, rate=1000):

        # Check port
        if not port.startswith('/dev/') and not port.startswith('COM'):
            raise ValueError(f'Invalid serial port: {port}')

        # Check rate
        if not isinstance(rate, int):
            raise ValueError(f'Invalid rate: {rate}')
        self._rate = rate

        # Sample size in bytes
        self.sample_size = 9

        # Connect to device
        self.device = self._connect(port)
        # try:
        #     self.device = self.connect(port)
        # except Exception as e:
        #     raise WorkerInterrupt(e)

        # Set rate
        self._send(f'rate,{rate}')

        # Start Acquisition
        self._send('start')

        # Initialize counters for timestamp indices and continuity checks
        self._sample_counter = 65535
        self._timestamp = clock.now()


    def update(self):

        data, indices = self._read()
        self.o.set(data, indices, ('SEQ', 'UTIME', 'A0'), {'rate': self._rate})


    def _connect(self, port):
        device = serial.Serial(port, 115200)
        msg = device.readline(); # Wait for 'ready\n'
        try:
            msg.decode()
        except UnicodeDecodeError:
            self.logger.error('Unstable state. Please re-plug the device and start again.')
            # https://stackoverflow.com/questions/21073086/wait-on-arduino-auto-reset-using-pyserial
            # https://forum.arduino.cc/index.php?topic=38981.0
            raise WorkerInterrupt()
        return device


    def _send(self, cmd):
        cmd = (cmd + '\n').encode()
        self.device.write(cmd)


    def _read(self):

        """Read all available data"""

        # Check buffer size and limits
        buffer_size = self.device.in_waiting
        if buffer_size == 1020:
            # The OS serial buffer can hold up to 1020 bytes
            self.logger.warn('Internal buffer saturated. Increase graph rate or decrease device rate.')

        # Compute the maximum number of samples we can get
        sample_count = int(buffer_size / self.sample_size)

        # Read raw samples from device
        now = clock.now()
        raw = self.device.read(sample_count * self.sample_size)

        # Initialize the output matrix
        data = np.full((sample_count, 3), np.nan, np.uint32)

        # Parse the raw data
        for sample_number in range(sample_count):

            # Extract sample
            start = sample_number * self.sample_size
            stop = start + self.sample_size
            sample = raw[start:stop]

            # Check (dummy) CRC
            crc = sample[8]
            if crc != 0:
                self.logger.warn('Corrupted sample.')
                continue

            # Parse sample
            data[sample_number, 0] = int.from_bytes(sample[0:2], byteorder='little', signed=False)
            data[sample_number, 1] = int.from_bytes(sample[2:6], byteorder='little', signed=False)
            data[sample_number, 2] = int.from_bytes(sample[6:8], byteorder='little', signed=False)

            # Did we miss any sample?
            # Check for discontinuity in the internal sample counter (2 bytes).
            sample_counter = data[sample_number, 0]
            if sample_counter == self._sample_counter + 1:
                pass
            elif sample_counter == 0 and self._sample_counter == 65535:
                pass
            else:
                self.logger.warn('Missed sample.')
            self._sample_counter = sample_counter

        # Compute indices
        indices = clock.time_range(self._timestamp, now, len(data))
        self._timestamp = now

        return data, indices


    def terminate(self):
        self._send('stop')
        self.device.close()
A generic serial device driver.      Attributes:         o (Port): Stream from the serial device, provides DataFrame.      Args:         port (string): The serial port.             e.g. ``COM3`` on Windows;  ``/dev/tty.tty.SLAB_USBtoUART`` on MacOS;             ``/dev/ttyUSB0`` on GNU/Linux.         rate (int): The device rate in Hz.             Possible values: ``1``, ``10``, ``100``, ``1000``. Default: ``1000``.      Example:         .. literalinclude:: /../test/graphs/test.yaml            :language: yaml      Notes:      .. attention::          Make sure to set your graph rate to an high-enough value, otherwise the device         internal buffer may saturate, and data may be lost. A 30Hz graph rate is         recommended for a 1000Hz device rate. # Check port # Check rate # Sample size in bytes # Connect to device # try: #     self.device = self.connect(port) # except Exception as e: #     raise WorkerInterrupt(e) # Set rate # Start Acquisition # Initialize counters for timestamp indices and continuity checks # Wait for 'ready\n' # https://stackoverflow.com/questions/21073086/wait-on-arduino-auto-reset-using-pyserial # https://forum.arduino.cc/index.php?topic=38981.0 Read all available data # Check buffer size and limits # The OS serial buffer can hold up to 1020 bytes # Compute the maximum number of samples we can get # Read raw samples from device # Initialize the output matrix # Parse the raw data # Extract sample # Check (dummy) CRC # Parse sample # Did we miss any sample? # Check for discontinuity in the internal sample counter (2 bytes). # Compute indices