Esempio n. 1
0
class Lidar():
    '''
    A combination of a VL53L1X laser distance sensor and a micro servo as a LIDAR.

    The zero position is straight forward, with negative (minimum servo position)
    sweeping to port, positive (maximum servo position) to starboard.

    The 'reverse_movement' parameter is used in case the servo movement is backwards.
    '''
    def __init__(self, config, level):
        self._log = Logger('lidar', Level.INFO)
        self._config = config
        if self._config:
            self._log.info('configuration provided.')
            _config = self._config['ros'].get('lidar')
            self._reverse_movement = _config.get('reverse_movement')
            self._double_sweep = _config.get('double_sweep')
            self._min_angle = _config.get('min_angle')
            self._max_angle = _config.get('max_angle')
            self._degree_step = _config.get('degree_step')
            self._step_delay_sec = _config.get('step_delay_sec')
            _range_value = _config.get('tof_range')
            _range = Range.from_str(_range_value)
            _servo_number = _config.get('servo_number')
        else:
            self._log.warning('no configuration provided.')
            self._log.info('play sound disabled.')
            self._reverse_movement = False
            self._double_sweep = False
            self._min_angle = -60.0
            self._max_angle = 60.0
            self._degree_step = 3.0
            self._step_delay_sec = 0.01
            _range = Range.PERFORMANCE
            #           _range = Range.LONG
            #           _range = Range.MEDIUM
            #           _range = Range.SHORT
            _servo_number = -1

        self._log.info(
            'scan range of {} from {:>4.1f}° to {:>4.1f}° with step of {:>4.1f}°'
            .format(_range, self._min_angle, self._max_angle,
                    self._degree_step))
        if _servo_number > 0:
            self._servo = Servo(self._config, _servo_number, level)
        else:
            self._servo = None

        _range = Range.PERFORMANCE
        #       _range = Range.LONG
        #       _range = Range.MEDIUM
        #       _range = Range.SHORT

        self._tof = TimeOfFlight(_range, Level.WARN)
        self._error_range = 0.067
        self._enabled = False
        self._closed = False
        self._log.info('ready.')

    # ..........................................................................
    def _in_range(self, p, q):
        return p >= (q - self._error_range) and p <= (q + self._error_range)

    # ..........................................................................
    def _set_servo_position(self, angle):
        if self._servo:
            if self._reverse_movement:
                self._servo.set_position(-1.0 * angle)
            else:
                self._servo.set_position(angle)

    # ..........................................................................
    def _get_servo_position(self, default_angle):
        if self._servo:
            if self._reverse_movement:
                return -1.0 * self._servo.get_position(-1.0 * default_angle)
            else:
                return self._servo.get_position(default_angle)

    # ..........................................................................
    def scan(self):
        if not self._enabled:
            self._log.warning('cannot scan: disabled.')
            return None
        try:
            if self._servo:
                start = time.time()
                _min_mm = 9999.0
                _angle_at_min = 0.0
                _max_mm = 0
                _angle_at_max = 0.0

                self._set_servo_position(self._min_angle)
                time.sleep(0.3)
                #               wait_count = 0
                #               while ( not self._in_range(self._get_servo_position(self._min_angle), self._min_angle) ) and ( wait_count < 20 ):
                #                   wait_count += 1
                #                   self._set_servo_position(self._min_angle)
                #                   self._log.info(Fore.MAGENTA + Style.BRIGHT + 'waiting for match at degrees: {:>5.2f}°: waited: {:d}'.format(self._get_servo_position(-1), wait_count))
                #                   time.sleep(1.0)
                #               time.sleep(0.05)
                #               self._log.info(Fore.YELLOW + Style.BRIGHT + 'starting scan from degrees: {:>5.2f}°: waited: {:d}'.format(self._get_servo_position(-1), wait_count))

                # sweep from minimum to maximum ........................................................
                self._log.info('sweep fore...')
                #               self._log.info('sweep min to max...')
                for degrees in numpy.arange(self._min_angle,
                                            self._max_angle + 0.01,
                                            self._degree_step):
                    self._set_servo_position(degrees)
                    wait_count = 0
                    while not self._in_range(self._get_servo_position(degrees),
                                             degrees) and wait_count < 10:
                        time.sleep(0.0025)
                        wait_count += 1
                    self._log.debug(Fore.GREEN + Style.BRIGHT + 'measured degrees: {:>5.2f}°: \ttarget: {:>5.2f}°; waited: {:d}'.format(\
                            self._get_servo_position(degrees), degrees, wait_count))
                    mm = self._tof.read_distance()
                    self._log.info('distance at {:>5.2f}°: \t{}mm'.format(
                        degrees, mm))
                    # capture min and max at angles
                    _min_mm = min(_min_mm, mm)
                    if mm == _min_mm:
                        _angle_at_min = degrees
                    _max_mm = max(_max_mm, mm)
                    if mm == _max_mm:
                        _angle_at_max = degrees
                    time.sleep(self._step_delay_sec)

                if self._double_sweep:
                    self._log.info('sweep back...')
                    #                   self._log.info('sweep max to min...')
                    # sweep from maximum to minimum ........................................................
                    for degrees in numpy.arange(self._max_angle,
                                                self._min_angle + 0.01,
                                                -1.0 * self._degree_step):
                        self._set_servo_position(degrees)
                        wait_count = 0
                        while not self._in_range(
                                self._get_servo_position(degrees),
                                degrees) and wait_count < 10:
                            time.sleep(0.0025)
                            wait_count += 1
                        self._log.debug(Fore.GREEN + Style.BRIGHT + 'measured degrees: {:>5.2f}°: \ttarget: {:>5.2f}°; waited: {:d}'.format(\
                                self._get_servo_position(degrees), degrees, wait_count))
                        mm = self._tof.read_distance()
                        self._log.info('distance at {:>5.2f}°: \t{}mm'.format(
                            degrees, mm))
                        # capture min and max at angles
                        _min_mm = min(_min_mm, mm)
                        if mm == _min_mm:
                            _angle_at_min = degrees
                        _max_mm = max(_max_mm, mm)
                        if mm == _max_mm:
                            _angle_at_max = degrees
                        time.sleep(self._step_delay_sec)

                time.sleep(0.1)
                #           self._log.info('complete.')
                elapsed = time.time() - start
                self._log.info('min. distance at {:>5.2f}°:\t{}mm'.format(
                    _angle_at_min, _min_mm))
                self._log.info('max. distance at {:>5.2f}°:\t{}mm'.format(
                    _angle_at_max, _max_mm))
                self._log.info(
                    'scan complete: {:>5.2f}sec elapsed.'.format(elapsed))
                self._set_servo_position(0.0)
                #           self._servo.set_position(_angle_at_max)
                #           self.close()
                return [_angle_at_min, _min_mm, _angle_at_max, _max_mm]
            else:
                _degrees = 0
                _start = dt.now()
                mm = self._tof.read_distance()
                _elapsed_ms = (dt.now() - _start).total_seconds() * 1000.0
                self._log.info('distance at {:>5.2f}°: \t{}mm'.format(
                    _degrees, mm))
                self._log.info(
                    'scan complete: {:>5.2f}ms elapsed.'.format(_elapsed_ms))
                return [-1.0, mm, -1.0, mm]
        except KeyboardInterrupt:
            self._log.info('caught Ctrl-C.')
            self.close()
            self._log.info('interrupted: incomplete.')

    # ..........................................................................
    def enable(self):
        if self._closed:
            self._log.warning('cannot enable: closed.')
            return
        self._tof.enable()
        self._enabled = True

    # ..........................................................................
    def disable(self):
        self._enabled = False
        if self._servo:
            self._servo.disable()
        self._tof.disable()

    # ..........................................................................
    def close(self):
        self.disable()
        if self._servo:
            self._servo.close()
        self._closed = True
Esempio n. 2
0
class UltrasonicScanner():
    def __init__(self, config, level):
        self._log = Logger('uscanner', Level.INFO)
        self._config = config
        if self._config:
            self._log.info('configuration provided.')
            _config = self._config['ros'].get('ultrasonic_scanner')
            self._min_angle = _config.get('min_angle')
            self._max_angle = _config.get('max_angle')
            self._degree_step = _config.get('degree_step')
            self._use_raw_distance = _config.get('use_raw_distance')
            self._read_delay_sec = _config.get('read_delay_sec')
            self._log.info('read delay: {:>4.1f} sec'.format(
                self._read_delay_sec))
            _servo_number = _config.get('servo_number')
        else:
            self._log.warning('no configuration provided.')
            self._min_angle = -60.0
            self._max_angle = 60.0
            self._degree_step = 3.0
            self._use_raw_distance = False
            self._read_delay_sec = 0.1
            _servo_number = 2

        self._log.info(
            'scan from {:>5.2f} to {:>5.2f} with step of {:>4.1f}°'.format(
                self._min_angle, self._max_angle, self._degree_step))
        self._servo = Servo(self._config, _servo_number, level)
        self._ub = self._servo.get_ultraborg()
        #       self._tof = TimeOfFlight(_range, Level.WARN)
        self._error_range = 0.067
        self._enabled = False
        self._closed = False
        self._log.info('ready.')

    # ..........................................................................
    def _in_range(self, p, q):
        return p >= (q - self._error_range) and p <= (q + self._error_range)

    # ..........................................................................
    def scan(self):
        if not self._enabled:
            self._log.warning('cannot scan: disabled.')
            return

        start = time.time()
        _min_mm = 9999.0
        _angle_at_min = 0.0
        _max_mm = 0
        _angle_at_max = 0.0
        _max_retries = 10

        self._servo.set_position(self._min_angle)
        time.sleep(0.3)
        _slack = 0.1
        self._log.info(
            Fore.BLUE + Style.BRIGHT +
            'scanning from {:>5.2f} to {:>5.2f} with step of {:>4.1f}°...'.
            format(self._min_angle, self._max_angle, self._degree_step))
        for degrees in numpy.arange(self._min_angle, self._max_angle + _slack,
                                    self._degree_step):
            self._log.debug('loop set to : {:>5.2f}°...'.format(degrees))
            if self._servo.is_in_range(degrees):
                self._servo.set_position(degrees)
                wait_count = 0
                while not self._in_range(self._servo.get_position(degrees),
                                         degrees) and wait_count < 10:
                    time.sleep(0.0025)
                    wait_count += 1
                self._log.debug(Fore.GREEN + Style.BRIGHT + 'measured degrees: {:>5.2f}°: \ttarget: {:>5.2f}°; waited: {:d}'.format(\
                        self._servo.get_position(degrees), degrees, wait_count))
                _mm = self._servo.get_distance(_max_retries,
                                               self._use_raw_distance)
                # if we get a zero we keep trying...
                if _mm is None:
                    self._log.info(
                        'failed to read distance at {:>5.2f}°.'.format(
                            degrees))
                else:
                    if _mm <= 0.01:
                        retry_count = 0
                        while _mm == 0.0 and retry_count < _max_retries:
                            _mm = self._servo.get_distance(
                                _max_retries, self._use_raw_distance)
                            if _mm is None:
                                self._log.info(
                                    'failed to read distance at {:>5.2f}°: '.
                                    format(degrees) + Fore.RED +
                                    '\t retries: {:d}'.format(retry_count))
                            else:
                                if _mm <= 0.01:
                                    self._log.info(
                                        'distance at {:>5.2f}°: '.format(
                                            degrees) + Fore.RED +
                                        '\t{:>6.0f}mm'.format(_mm) +
                                        '\t retries: {:d}'.format(retry_count))
                                else:
                                    self._log.info(
                                        'distance at {:>5.2f}°: \t{:>6.0f}mm'.
                                        format(degrees, _mm) + Fore.BLACK +
                                        ' (on retry)')
                            retry_count += 1
                            time.sleep(0.1)
                    else:
                        self._log.info(
                            'distance at {:>5.2f}°: \t{:>6.0f}mm'.format(
                                degrees, _mm))
                    if _mm is not None:
                        # capture min and max at angles
                        _min_mm = min(_min_mm, _mm)
                        if _mm == _min_mm:
                            _angle_at_min = degrees
                        _max_mm = max(_max_mm, _mm)
                        if _mm == _max_mm:
                            _angle_at_max = degrees
                    time.sleep(self._read_delay_sec)
#                   time.sleep(0.1)
            else:
                self._log.warning(
                    'requested position: {:>5.2f}° out range of servo movement.'
                    .format(degrees))

        time.sleep(0.1)
        #           self._log.info('complete.')
        elapsed = time.time() - start
        self._log.info('scan complete: {:>5.2f}sec elapsed.'.format(elapsed))

        self._log.info(
            Fore.CYAN + Style.BRIGHT +
            'mix. distance at {:>5.2f}°:\t{}mm'.format(_angle_at_min, _min_mm))
        self._log.info(
            Fore.CYAN + Style.BRIGHT +
            'max. distance at {:>5.2f}°:\t{}mm'.format(_angle_at_max, _max_mm))
        self._servo.set_position(0.0)
        return [_angle_at_min, _min_mm, _angle_at_max, _max_mm]

    # ..........................................................................
    def enable(self):
        if self._closed:
            self._log.warning('cannot enable: closed.')
            return
#       self._tof.enable()
        self._enabled = True

    # ..........................................................................
    def disable(self):
        self._enabled = False
        self._servo.disable()
#       self._tof.disable()

# ..........................................................................

    def close(self):
        self.disable()
        self._servo.close()
        self._closed = True
Esempio n. 3
0
class CatScan():
    '''
        Uses an infrared PIR sensor to scan for cats. This involves raising a
        servo arm holding the sensor.
    '''
    def __init__(self, config, queue, level):
        self._log = Logger('cat-scan', Level.INFO)
        self._config = config
        self._queue = queue
        if self._config:
            self._log.info('configuration provided.')
            _config = self._config['ros'].get('cat_scan')
            self._active_angle = _config.get('active_angle')
            self._inactive_angle = _config.get('inactive_angle')
            _pir_pin = _config.get('pir_pin')
            _servo_number = _config.get('servo_number')
        else:
            self._log.warning('no configuration provided.')
            self._active_angle = -90.0
            self._inactive_angle = 90.0
            _pir_pin = 15
            _servo_number = 3

        _threshold_value = 0.1  # the value above which the device will be considered “on”
        self._log.info(
            'cat scan active angle: {:>5.2f}°; inactive angle: {:>5.2f}°'.
            format(self._active_angle, self._inactive_angle))
        self._servo = Servo(self._config, _servo_number, level)
        #       set up pin where PIR is connected
        self._sensor = MotionSensor(_pir_pin,
                                    threshold=_threshold_value,
                                    pull_up=False)
        self._sensor.when_motion = self._activated
        self._sensor.when_no_motion = None  #self._deactivated
        self._scan_enabled = False
        self._disabling = False
        self._enabled = False
        self._closed = False
        # arm behaviour
        self._arm_movement_degree_step = 5.0
        self._arm_up_delay = 0.09
        self._arm_down_delay = 0.04
        self._rgbmatrix = RgbMatrix(Level.INFO)
        self._rgbmatrix.set_display_type(DisplayType.SCAN)
        self._log.info('ready.')

    # ..........................................................................
    def _warning_display(self):
        self._rgbmatrix.disable()
        self._rgbmatrix.set_display_type(DisplayType.RANDOM)
        self._rgbmatrix.enable()

    # ..........................................................................
    def _activated(self):
        '''
            The default function called when the sensor is activated.
        '''
        if self._enabled and self._scan_enabled:
            self._log.info('cat sensed.')
            self._log.info(Fore.RED + Style.BRIGHT + 'detected CAT!')
            self._queue.add(Message(Event.CAT_SCAN))
            self.set_mode(False)
            self._warning_display()
        else:
            self._log.info('[DISABLED] activated catscan.')

    # ..........................................................................
    def _deactivated(self):
        '''
            The default function called when the sensor is deactivated.
        '''
        if self._enabled:
            self._log.info('deactivated catscan.')
        else:
            self._log.debug('[DISABLED] deactivated catscan.')

    # ..........................................................................
    def is_waiting(self):
        '''
            Returns true if the scan is in progress.
        '''
        return self._scan_enabled

    # ..........................................................................
    def set_mode(self, active):
        '''
            Sets the mode to active or inactive. This raises or lowers the servo arm.
        '''
        if not self._enabled:
            self._log.warning('cannot scan: disabled.')
            return

        _current_angle = self._servo.get_position(-999.0)
        self._log.critical('current angle: {:>4.1f}°'.format(_current_angle))
        if active:
            if _current_angle != -999.0 and _current_angle is not self._active_angle:
                with _mutex:
                    #                   for degrees in numpy.arange(self._inactive_angle, self._active_angle + 0.01, -1.0 * self._arm_movement_degree_step):
                    for degrees in numpy.arange(
                            _current_angle, self._active_angle + 0.01,
                            -1.0 * self._arm_movement_degree_step):
                        self._log.debug(
                            'position set to: {:>4.1f}°'.format(degrees))
                        self._servo.set_position(degrees)
                        time.sleep(self._arm_up_delay)
                self._log.info('position set to active.')
            else:
                self._log.warning('position already set to active.')
            # wait until it settles

            self._log.info('waiting for motion detector to settle...')
            time.sleep(1.0)
            while self._sensor.motion_detected:
                self._log.debug('still waiting...')
                time.sleep(0.1)
#           time.sleep(5.0)
            self._scan_enabled = True
            self._rgbmatrix.enable()
        else:
            self._scan_enabled = False
            self._rgbmatrix.disable()
            if _current_angle != -999.0 and _current_angle is not self._inactive_angle:
                with _mutex:
                    for degrees in numpy.arange(
                            self._active_angle, self._inactive_angle + 0.1,
                            self._arm_movement_degree_step):
                        self._log.debug(
                            'position set to: {:>4.1f}°'.format(degrees))
                        self._servo.set_position(degrees)
                        time.sleep(self._arm_down_delay)
                self._log.info('position set to inactive.')
            else:
                self._log.warning('position already set to inactive.')

    # ..........................................................................
    def enable(self):
        self._log.debug('enabling...')
        if self._closed:
            self._log.warning('cannot enable: closed.')
            return
#       self._tof.enable()
        self._enabled = True
        self._log.debug('enabled.')

    # ..........................................................................
    def disable(self):
        if self._disabling:
            self._log.warning('already disabling.')
        else:
            self._disabling = True
            self._enabled = False
            self._rgbmatrix.disable()
            self._log.debug('disabling...')
            self.set_mode(False)
            self._servo.disable()
            self._servo.close()
            self._disabling = False
            self._log.debug('disabled.')

    # ..........................................................................
    def close(self):
        self.disable()
        self._closed = True