def safe_north_point(self):
        """
        Determines the heading of the north point.
        This function doesn't take into account the declination.

        :return: The heading of the north point measured in degrees. The north point is found at **0** degrees.
        :rtype: int

        .. note::

           In case of an exception occurring within this method, **0** is returned.

        """
        ifMutexAcquire(self.use_mutex)
        try:
            x, y, z = self.read_magnetometer()
        except:
            x, y, z = 0, 0, 0
        finally:
            ifMutexRelease(self.use_mutex)

        # using the x and z axis because the sensor is mounted vertically
        # the sensor's top face is oriented towards the front of the robot

        heading = -atan2(-x, z) * 180 / pi

        # adjust it to 360 degrees range

        if heading < 0:
            heading += 360
        elif heading > 360:
            heading -= 360

        return heading
    def __init__(self, port="I2C", led_state=False, use_mutex=False):
        """
        Constructor for initializing a link to the `Light Color Sensor`_.
        :param str port = "I2C": The port to which the distance sensor is connected to. Can also be connected to ports ``"AD1"`` or ``"AD2"`` of the `GoPiGo3`_. If you're passing an **invalid port**, then the sensor resorts to an ``"I2C"`` connection. Check the :ref:`hardware specs <hardware-interface-section>` for more information about the ports.
        :param bool led_state = False: The LED state. If it's set to ``True``, then the LED will turn on, otherwise the LED will stay off. By default, the LED is turned off.
        :param bool use_mutex = False: When using multiple threads/processes that access the same resource/device, mutexes should be enabled.
        :raises ~exceptions.OSError: When the `Light Color Sensor`_ is not reachable.
        :raises ~exceptions.RuntimeError: When the chip ID is incorrect. This happens when we have a device pointing to the same address, but it's not a `Light Color Sensor`_.
        """

        self.use_mutex = use_mutex

        try:
            bus = ports[port]
        except KeyError:
            bus = "RPI_1SW"

        # in case there's a distance sensor that hasn't been instanciated yet
        # attempt to move it to another address
        ifMutexAcquire(self.use_mutex)
        try:
            VL53L0X.VL53L0X(bus=bus)
        except:
            pass
        ifMutexRelease(self.use_mutex)

        ifMutexAcquire(self.use_mutex)
        try:
            super(self.__class__, self).__init__(led_state=led_state, bus=bus)
        except Exception as e:
            raise
        finally:
            ifMutexRelease(self.use_mutex)

        self.led_state = led_state
Example #3
0
 def calibrate_imu(self, timelimit=20):
     if self.config['imu'] >= SensorStatus.DISABLED or not self.Configured:
         return
     self.stop_all()  #stop everything while calibrating...
     self.CurrentCommand = "calibrate_imu"
     self.log("Move around the robot to calibrate the Compass Sensor...")
     self.imu_status = 0
     elapsed = 0
     start = time.time()
     timelimit = start + timelimit  #maximum of 20 seconds to calibrate compass sensor
     while self.imu_status != 3 and time.time() < timelimit:
         newtime = time.time()
         newelapsed = int(newtime - start)
         if newelapsed > elapsed:
             elapsed = newelapsed
             self.log("Calibrating IMU. Status: " + str(self.imu_status) +
                      " Time: " + str(elapsed))
         ifMutexAcquire(USEMUTEX)
         try:
             self.imu_status = self.imu.BNO055.get_calibration_status()[3]
             self.config['imu'] = SensorStatus.ENABLED
             time.sleep(0.01)
         except Exception as error:
             self.log("IMU Calibration Error: " + str(error))
             self.config['imu'] += 1
         finally:
             ifMutexRelease(USEMUTEX)
     if self.imu_status == 3:
         self.log("IMU Compass Sensor has been calibrated")
         self.Calibrated = True
         return True
     else:
         self.log("Calibration unsuccessful")
         return
     return
    def read(self, representation="raw"):
        """
        Read the sensors' values from either line follower.

        :param str representation="raw": It's set by-default to ``"raw"`` , but it can also be ``"bivariate"``, ``"bivariate-str"`` or ``"weighted-avg"``.
        :raises ~exceptions.OSError: If the line follower sensor is not reachable.

        Each of the line followers' order of the sensors' values is the same as the one in each read method of them both: :py:meth:`di_sensors.line_follower.LineFollower.read_sensors` and :py:meth:`di_sensors.line_follower.LineFollowerRed.read_sensors`.
        

        For ``representation="raw"``
            For this, raw values are returned from the line follower sensor. Values range between **0** and **1** and there can be 5 or 6 values returned depending on what line follower sensor is used.
        
        For ``representation="bivariate"``
            In this case, a list with the length equal to the number of sensors present on the given line follower is returned. Values are either **0** (for black) or **1** (for white). 
            In order to get good results, make sure the line follower is properly calibrated.

        For ``representation="bivariate-str"``
            Same as ``"bivariate"`` except that **0** is replaced with letter `b` (for black) and **1** with `w` (for white).

        For ``representation="weighted-avg"``
            Returns a 2-element tuple. The first element is an estimated position of the line. 
            
            The estimate is computed using a weighted average of each sensor value (regardless of which line follower sensor is used), 
            so that if the black line is on the left of the line follower, the returned value will be in the **0.0-0.5** range and 
            if it's on the right, it's in the **0.5-1.0** range, 
            thus making **0.5** the center point of the black line. 
            Keep in mind that the sensor's orientation is determined by the order
            of the returned sensor values and not by how the sensor is positioned on the robot. 
            Check :py:meth:`~di_sensors.line_follower.LineFollower.read_sensors` and :py:meth:`~di_sensors.line_follower.LineFollowerRed.read_sensors` methods to see
            how the values are returned. 
            
            If the line follower sensor ends up on a surface with an homogeneous color  (or shade of grey), the returned value will circle around **0.5**.
            
            The 2nd element is an integer taking 3 values: **1** if the line follower only detects black, **2** if it only detects white and **0** for the rest of cases.

        """
        ifMutexAcquire(self.use_mutex)
        try:
            if representation == 'raw':
                return self.sensor.read_sensors()

            elif representation == 'bivariate':
                raw_vals = self.sensor.read_sensors()
                return self._bivariate(raw_vals)

            elif representation == 'bivariate-str':
                raw_vals = self.read('bivariate')
                return self._bivariate_str(raw_vals)

            elif representation == 'weighted-avg':
                raw_vals = self.sensor.read_sensors()
                return self._weighted_avg(raw_vals)
            else:
                pass

        except Exception as e:
            raise
        finally:
            ifMutexRelease(self.use_mutex)
Example #5
0
 def reconfig_IMU(self):
     ifMutexAcquire(USEMUTEX)
     try:
         self.imu.BNO055.i2c_bus.reconfig_bus()
         time.sleep(0.1)  #restabalise the sensor
         self.config['imu'] = SensorStatus.ENABLED
     except Exception as error:
         self.log("IMU RECONFIG HAS FAILED" + str(error))
         self.config['imu'] = SensorStatus.DISABLED
     finally:
         ifMutexRelease(USEMUTEX)
     return
    def read_mm(self):
        """
        Reads the distance in millimeters.

        :returns: Distance from target in millimeters.
        :rtype: int

        .. note::

             1. Sensor's range is **5-2300** millimeters.
             2. When the values are out of the range, it returns **3000**.

        """

        # 8190 is what the sensor sends when it's out of range
        # we're just setting a default value
        mm = 8190
        readings = []
        attempt = 0

        # try 3 times to have a reading that is
        # smaller than 8m or bigger than 5 mm.
        # if sensor insists on that value, then pass it on
        while (mm > 8000 or mm < 5) and attempt < 3:
            ifMutexAcquire(self.use_mutex)
            try:
                mm = self.read_range_single()
            except Exception as e:
                print(e)
                mm = 0
            finally:
                ifMutexRelease(self.use_mutex)
            attempt = attempt + 1
            time.sleep(0.001)

        # add the reading to our last 3 readings
        # a 0 value is possible when sensor is not found
        if (mm < 8000 and mm > 5) or mm == 0:
            readings.append(mm)
        if len(readings) > 3:
            readings.pop(0)

        # calculate an average and limit it to 5 > X > 3000
        if len(readings) > 1:  # avoid division by 0
            mm = round(sum(readings) / float(len(readings)))
        if mm > 3000:
            mm = 3000

        return mm
Example #7
0
 def get_orientation_IMU(self):
     readings = (NOREADING,NOREADING,NOREADING)
     if self.config['imu'] >= DISABLED or not self.Configured:
         return readings
     ifMutexAcquire(USEMUTEX)
     try:
         readings = self.imu.read_euler()
         time.sleep(0.01)
         self.config['imu'] = ENABLED
     except Exception as error:
         self.log("IMU Orientation: " + str(error))
         self.config['imu'] += 1
     finally:
         ifMutexRelease(USEMUTEX)
     return readings  
Example #8
0
 def get_gyro_sensor_IMU(self):
     gyro_readings = (NOREADING,NOREADING,NOREADING)
     if self.config['imu'] >= DISABLED or not self.Configured:
         return gyro_readings
     ifMutexAcquire(USEMUTEX)
     try:
         gyro_readings = self.imu.read_gyroscope() #degrees/s
         time.sleep(0.01)
         self.config['imu'] = ENABLED
     except Exception as error:
         self.log("IMU GYRO: " + str(error))
         self.config['imu'] += 1
     finally:
         ifMutexRelease(USEMUTEX)
     return gyro_readings
Example #9
0
 def get_temperature_IMU(self):
     temp = SensorStatus.NOREADING
     if self.config['imu'] >= SensorStatus.DISABLED or not self.Configured:
         return temp
     ifMutexAcquire(USEMUTEX)
     try:
         temp = self.imu.read_temperature()
         time.sleep(0.01)
         self.config['imu'] = SensorStatus.ENABLED
     except Exception as error:
         self.log("IMU Temp: " + str(error))
         self.config['imu'] += 1
     finally:
         ifMutexRelease(USEMUTEX)
     return temp
    def safe_calibration_status(self):
        """
        Returns the calibration level of the magnetometer of the `InertialMeasurementUnit Sensor`_.

        :returns: Calibration level of the magnetometer. Range is **0-3** and **-1** is returned when the sensor can't be accessed.
        :rtype: int

        """
        ifMutexAcquire(self.use_mutex)
        try:
            status = self.BNO055.get_calibration_status()[3]
        except Exception as e:
            status = -1
        finally:
            ifMutexRelease(self.use_mutex)
        return status
    def reconfig_bus(self):
        """
        Use this method when the `InertialMeasurementUnit Sensor`_ becomes unresponsive but it's still plugged into the board.
        There will be times when due to improper electrical contacts, the link between the sensor and the board gets disrupted - using this method restablishes the connection.

        .. note::

           Sometimes the sensor won't work just by calling this method - in this case, switching the port will do the job. This is something that happens
           very rarely, so there's no need to worry much about this scenario.


        """

        ifMutexAcquire(self.use_mutex)
        self.BNO055.i2c_bus.reconfig_bus()
        ifMutexRelease(self.use_mutex)
Example #12
0
 def get_ultra_sensor(self):
     distance = SensorStatus.NOREADING
     if self.config['ultra'] >= SensorStatus.DISABLED or not self.Configured:
         return distance
     bp = self.BP
     ifMutexAcquire(USEMUTEX)
     try:
         distance = bp.get_sensor(self.ultra)
         time.sleep(0.2)
         self.config['ultra'] = SensorStatus.ENABLED
     except brickpi3.SensorError as error:
         self.log("ULTRASONIC: " + str(error))
         self.config['ultra'] += 1
     finally:
         ifMutexRelease(USEMUTEX)
     return distance
Example #13
0
 def get_colour_sensor(self):
     if self.config['colour'] >= SensorStatus.DISABLED:
         return "NOREADING"
     bp = self.BP
     value = 0
     colours = ["NOREADING", "Black", "Blue", "Green", "Yellow", "Red", "White", "Brown"]
     ifMutexAcquire(USEMUTEX)
     try: 
         value = bp.get_sensor(self.colour) 
         time.sleep(0.01)
         self.config['colour'] = SensorStatus.ENABLED
     except Exception as error:
         self.log("COLOUR: " + str(error))
         self.config['colour'] += 1
     ifMutexRelease(USEMUTEX)                
     return colours[value]
 def safe_raw_colors(self):
     """
     Returns the color as read by the `Light Color Sensor`_.
     The colors detected vary depending on the lighting conditions of the nearby environment.
     :returns: The RGBA values from the sensor. RGBA = Red, Green, Blue, Alpha (or Clear). Range of each element is between **0** and **1**. **-1** means an error occured.
     :rtype: tuple(float,float,float,float)
     """
     ifMutexAcquire(self.use_mutex)
     try:
         self.set_led(True, True)  # turn light on to take color readings
         r, g, b, c = self.get_raw_colors()
         self.set_led(self.led_state, True)  # return to default setting
     except:
         r, g, b, c = [-1] * 4
     finally:
         ifMutexRelease(self.use_mutex)
     return (r, g, b, c)
Example #15
0
 def get_colour_sensor(self):
     if self.config['colour'] >= DISABLED or not self.Configured:
         return "NOREADING"
     bp = self.BP
     value = 0
     colours = ["NOREADING", "Black", "Blue", "Green", "Yellow", "Red", "White", "Brown"]
     ifMutexAcquire(USEMUTEX)
     try: 
         value = bp.get_sensor(self.colour) 
         time.sleep(0.01)
         self.config['colour'] = ENABLED
     except brickpi3.SensorError as error:
         self.log("COLOUR: " + str(error))
         self.config['colour'] += 1
     finally:
         ifMutexRelease(USEMUTEX)                
     return colours[value]
Example #16
0
 def get_linear_acceleration_IMU(self):
     readings = (NOREADING,NOREADING,NOREADING)
     if self.config['imu'] >= DISABLED or not self.Configured:
         return readings
     ifMutexAcquire(USEMUTEX)
     try:
         #readings = self.imu.read_accelerometer()
         readings = self.imu.read_linear_acceleration()
         #readings = tuple([int(i*100) for i in readings])
         time.sleep(0.01)
         self.config['imu'] = ENABLED
     except Exception as error:
         self.log("IMU Acceleration: " + str(error))
         self.config['imu'] += 1
     finally:
         ifMutexRelease(USEMUTEX)   
     return readings
    def safe_humidity(self):
        """
        Read the relative humidity as a percentage.

        :returns: Percentage of the relative humidity.
        :rtype: float
        :raises ~exceptions.OSError: When the sensor cannot be reached.

        """
        ifMutexAcquire(self.use_mutex)
        try:
            humidity = self.get_humidity()
        except Exception as e:
            raise
        finally:
            ifMutexRelease(self.use_mutex)

        return round(humidity, 0)
    def safe_pressure(self):
        """
        Read the air pressure in pascals.

        :returns: The air pressure in pascals.
        :rtype: float
        :raises ~exceptions.OSError: When the sensor cannot be reached.

        """
        ifMutexAcquire(self.use_mutex)
        try:
            pressure = self.get_pressure()
        except Exception as e:
            raise
        finally:
            ifMutexRelease(self.use_mutex)

        return round(pressure, 0)
    def safe_fahrenheit(self):
        """
        Read temperature in Fahrenheit degrees.

        :returns: Temperature in Fahrenheit degrees.
        :rtype: float
        :raises ~exceptions.OSError: When the sensor cannot be reached.

        """
        ifMutexAcquire(self.use_mutex)
        try:
            temp = self.get_temperature_fahrenheit()
        except Exception as e:
            raise
        finally:
            ifMutexRelease(self.use_mutex)

        return round(temp, 0)
    def __init__(self, port="I2C", use_mutex=False):
        """
        Creates a :py:class:`~easygopigo3.EasyDistanceSensor` object which can be used for interfacing with a `distance sensor`_.

        :param string bus = ``"I2C"``: the bus for the sensor. For the GoPiGo3, options also include ``"GPG3_AD1"`` and ``"GPG3_AD2"``.
        :param bool use_mutex = False: When using multiple threads/processes that access the same resource/device, mutexes should be enabled. Check the :ref:`hardware specs <hardware-interface-section>` for more information about the ports.
        :raises ~exceptions.OSError: When the distance sensor is not connected to the designated bus/port, where in this case it must be ``"I2C"``. Most probably, this means the distance sensor is not connected at all.

        To see where the ports are located on the `GoPiGo3`_ robot, please take a look at the following diagram: :ref:`hardware-ports-section`.

        """
        self.descriptor = "Distance Sensor"
        self.use_mutex = use_mutex

        # let's be kind to the user who may get confused between ports and buses
        # let's allow for all of them
        possible_ports = {
            "I2C": "RPI_1SW",
            "AD1": "GPG3_AD1",
            "AD2": "GPG3_AD2",
            "RPI_1SW": "RPI_1SW",
            "RPI_1": "RPI_1",
            "RPI_1HW":
            "RPI_1",  # doesn't really exist but can be a reflex for some users as we do have RPI_1SW
            "GPG3_AD1": "GPG3_AD1",
            "GPG3_AD2": "GPG3_AD2"
        }

        port = port.upper()  # force to uppercase
        # switch quietly to default value if we receive gibberish
        if port in possible_ports.keys():
            bus = possible_ports[port]
        else:
            bus = "RPI_1SW"

        ifMutexAcquire(self.use_mutex)
        try:
            distance_sensor.DistanceSensor.__init__(self, bus=bus)
        except Exception as e:
            # remove print so that it doesn't show when starting Scratch
            # print("Distance Sensor init: {}".format(e))
            raise
        finally:
            ifMutexRelease(self.use_mutex)
Example #21
0
    def safe_raw_colors(self):
        """
        Returns the color as read by the `Light Color Sensor`_.

        The colors detected vary depending on the lighting conditions of the nearby environment.

        :returns: The RGBA values from the sensor. RGBA = Red, Green, Blue, Alpha (or Clear). Range of each element is between **0** and **1**.
        :rtype: tuple(float,float,float,float)

        """
        ifMutexAcquire(self.use_mutex)
        try:
            self.set_led(self.led_state)
            r, g, b, c = self.get_raw_colors()
        except:
            pass
        finally:
            ifMutexRelease(self.use_mutex)
        return (r, g, b, c)
Example #22
0
 def update_thermal_sensor(self):
     ifMutexAcquire(USEMUTEX)
     bp = self.BP
     TIR_I2C_ADDR        = 0x0E      # TIR I2C device address 
     TIR_AMBIENT         = 0x00      # Ambient Temp
     TIR_OBJECT          = 0x01      # Object Temp
     TIR_SET_EMISSIVITY  = 0x02      
     TIR_GET_EMISSIVITY  = 0x03
     TIR_CHK_EMISSIVITY  = 0x04
     TIR_RESET           = 0x05
     try:
         bp.transact_i2c(self.thermal, TIR_I2C_ADDR, [TIR_OBJECT], 2)
         time.sleep(0.01)
     except Exception as error:
         self.config['thermal'] = SensorStatus.DISABLED
         self.thread_running = False #exit thread
         self.log("THERMAL UPDATE: " + str(error))
     ifMutexRelease(USEMUTEX) 
     return
    def safe_read_magnetometer(self):
        """
        Read the magnetometer values.

        :returns: Tuple containing X, Y, Z values in *micro-Teslas* units. You can check the X, Y, Z axes on the sensor itself.
        :rtype: (float,float,float)

        .. note::

           In case of an exception occurring within this method, a tuple of 3 elements where all values are set to **0** is returned.

        """
        ifMutexAcquire(self.use_mutex)
        try:
            x, y, z = self.read_magnetometer()
        except Exception as e:
            x, y, z = 0, 0, 0
        finally:
            ifMutexRelease(self.use_mutex)
        return x, y, z
    def safe_read_euler(self):
        """
        Read the absolute orientation.

        :returns: Tuple of euler angles in degrees of *heading*, *roll* and *pitch*.
        :rtype: (float,float,float)
        :raises ~exceptions.OSError: When the sensor is not reachable.

        """

        ifMutexAcquire(self.use_mutex)
        try:
            x, y, z = self.read_euler()
        except Exception as e:
            # print("safe read euler: {}".format(str(e)))
            # x, y, z = 0, 0, 0
            raise
        finally:
            ifMutexRelease(self.use_mutex)
        return x, y, z
Example #25
0
 def get_thermal_sensor(self, usethread=True):
     temp = NOREADING
     if self.config['thermal'] >= DISABLED or not self.Configured:
         return temp
     bp = self.BP
     if not usethread:
         self.update_thermal_sensor() #not necessary if thread is running
     ifMutexAcquire(USEMUTEX)
     try:
         value = bp.get_sensor(self.thermal) # read the sensor values
         time.sleep(0.01)
         self.config['thermal'] = ENABLED
         temp = (float)((value[1] << 8) + value[0]) # join the MSB and LSB part
         temp = temp * 0.02 - 0.01                  # Converting to Celcius
         temp = temp - 273.15                       
     except Exception as error:
         self.log("THERMAL READ: " + str(error))
         self.config['thermal'] += 1
     finally:
         ifMutexRelease(USEMUTEX)    
     return float("%3.f" % temp)
    def __init__(self, use_mutex=False):
        """
        Creates a :py:class:`~easygopigo3.EasyDistanceSensor` object which can be used for interfacing with a `distance sensor`_.

        :param bool use_mutex = False: When using multiple threads/processes that access the same resource/device, mutexes should be enabled. Check the :ref:`hardware specs <hardware-interface-section>` for more information about the ports.
        :raises ~exceptions.OSError: When the distance sensor is not connected to the designated bus/port, where in this case it must be ``"I2C"``. Most probably, this means the distance sensor is not connected at all.

        To see where the ports are located on the `GoPiGo3`_ robot, please take a look at the following diagram: :ref:`hardware-ports-section`.

        """
        self.descriptor = "Distance Sensor"
        self.use_mutex = use_mutex

        ifMutexAcquire(self.use_mutex)
        try:
            distance_sensor.DistanceSensor.__init__(self)
        except Exception as e:
            print("Distance Sensor init: {}".format(e))
            raise
        finally:
            ifMutexRelease(self.use_mutex)
Example #27
0
 def get_compass_IMU(self):
     heading = NOREADING
     if self.config['imu'] >= DISABLED or not self.Configured:
         return heading
     ifMutexAcquire(USEMUTEX)
     try:
         (x, y, z)  = self.imu.read_magnetometer()
         time.sleep(0.01)
         self.config['imu'] = ENABLED
         heading = int(math.atan2(x, y)*(180/math.pi)) + MAGNETIC_DECLINATION 
         #make it 0 - 360 degrees
         if heading < 0:
             heading += 360
         elif heading > 360:
             heading -= 360
     except Exception as error:
         self.log("IMU: " + str(error))
         self.config['imu'] += 1
     finally:
         ifMutexRelease(USEMUTEX)
     return heading
    def __init__(self, port="AD1", use_mutex=False):
        """
        Constructor for initializing link with the `InertialMeasurementUnit Sensor`_.

        :param str port = "AD1": The port to which the IMU sensor gets connected to. Can also be connected to port ``"AD2"`` of a `GoPiGo3`_ robot or to any ``"I2C"`` port of any of our platforms. If you're passing an **invalid port**, then the sensor resorts to an ``"I2C"`` connection. Check the :ref:`hardware specs <hardware-interface-section>` for more information about the ports.
        :param bool use_mutex = False: When using multiple threads/processes that access the same resource/device, mutexes should be enabled.
        :raises RuntimeError: When the chip ID is incorrect. This happens when we have a device pointing to the same address, but it's not a `InertialMeasurementUnit Sensor`_.
        :raises ~exceptions.OSError: When the `InertialMeasurementUnit Sensor`_ is not reachable.

        """
        self.use_mutex = use_mutex

        try:
            bus = ports[port]
        except KeyError:
            bus = "RPI_1SW"

        ifMutexAcquire(self.use_mutex)
        try:
            # print("INSTANTIATING ON PORT {} OR BUS {} WITH MUTEX {}".format(port, bus, use_mutex))
            super(self.__class__, self).__init__(bus=bus)
            # on GPG3 we ask that the IMU be at the back of the robot, facing outward
            # We do not support the IMU on GPG2  but leaving the if statement in case
            if bus != "RPI_1SW":
                self.BNO055.set_axis_remap(BNO055.AXIS_REMAP_X,
                                           BNO055.AXIS_REMAP_Z,
                                           BNO055.AXIS_REMAP_Y,
                                           BNO055.AXIS_REMAP_POSITIVE,
                                           BNO055.AXIS_REMAP_NEGATIVE,
                                           BNO055.AXIS_REMAP_POSITIVE)
        except Exception as e:
            print("Initiating error: " + str(e))
            raise
        finally:
            sleep(
                0.1
            )  # add a delay to let the IMU stabilize before control panel can pull from it
            ifMutexRelease(self.use_mutex)
    def __init__(self, port="I2C", use_mutex=False):
        """
        Constructor for initializing link with the `Temperature Humidity Pressure Sensor`_.

        :param str port = "I2C": The port to which the THP sensor is connected to. Can also be connected to ports ``"AD1"`` or ``"AD2"`` of the `GoPiGo3`_. If you're passing an **invalid port**, then the sensor resorts to an ``"I2C"`` connection. Check the :ref:`hardware specs <hardware-interface-section>` for more information about the ports.
        :param bool use_mutex = False: When using multiple threads/processes that access the same resource/device, mutexes should be enabled.
        :raises ~exceptions.OSError: When the sensor cannot be reached.

        """
        self.use_mutex = use_mutex

        try:
            bus = ports[port]
        except KeyError:
            bus = "RPI_1SW"

        ifMutexAcquire(self.use_mutex)
        try:
            super(self.__class__, self).__init__(bus=bus)
        except Exception as e:
            raise
        finally:
            ifMutexRelease(self.use_mutex)
    def safe_calibrate(self):
        """
        Once called, the method returns when the magnetometer of the `InertialMeasurementUnit Sensor`_ gets fully calibrated. Rotate the sensor in the air to help the sensor calibrate faster.

        .. note::
           Also, this method is not used to trigger the process of calibrating the sensor (the IMU does that automatically),
           but its purpose is to block a given script until the sensor reports it has fully calibrated.

           If you wish to block your code until the sensor calibrates and still have control over your script, use
           :py:meth:`~di_sensors.easy_inertial_measurement_unit.EasyIMUSensor.safe_calibration_status` method along with a ``while`` loop to continuously check it.

        """

        status = -1
        while status < 3:
            ifMutexAcquire(self.use_mutex)
            try:
                new_status = self.BNO055.get_calibration_status()[3]
            except:
                new_status = -1
            finally:
                ifMutexRelease(self.use_mutex)
            if new_status != status:
                status = new_status