diff --git a/Python/Examples/EasyDistanceSensor.py b/Python/Examples/EasyDistanceSensor.py new file mode 100644 index 0000000..86ac056 --- /dev/null +++ b/Python/Examples/EasyDistanceSensor.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +# +# https://www.dexterindustries.com +# +# Copyright (c) 2017 Dexter Industries +# Released under the MIT license (http://choosealicense.com/licenses/mit/). +# For more information see https://github.com/DexterInd/DI_Sensors/blob/master/LICENSE.md +# +# Python example program for the Dexter Industries Distance Sensor + +from __future__ import print_function +from __future__ import division + +# import the modules +from di_sensors.easy_distance_sensor import EasyDistanceSensor +from time import sleep + +# instantiate the distance object +my_sensor = EasyDistanceSensor() + +# and read the sensor iteratively +while True: + read_distance = my_sensor.read() + print("distance from object: {} mm".format(read_distance)) + + sleep(0.1) diff --git a/Python/Examples/EasyDistanceSensorMutexes.py b/Python/Examples/EasyDistanceSensorMutexes.py new file mode 100644 index 0000000..d72146e --- /dev/null +++ b/Python/Examples/EasyDistanceSensorMutexes.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# +# https://www.dexterindustries.com +# +# Copyright (c) 2017 Dexter Industries +# Released under the MIT license (http://choosealicense.com/licenses/mit/). +# For more information see https://github.com/DexterInd/DI_Sensors/blob/master/LICENSE.md +# +# Python example program for the Dexter Industries Temperature Humidity Pressure Sensor + +from __future__ import print_function +from __future__ import division + +# do the import stuff +from di_sensors.easy_distance_sensor import EasyDistanceSensor +from time import time, sleep +from threading import Thread, Event, get_ident + +# instantiate the distance object +my_sensor = EasyDistanceSensor(use_mutex = True) +start_time = time() +runtime = 2.0 +# create an event object for triggering the "shutdown" of each thread +stop_event = Event() + +# target function for each thread +def readingSensor(): + while not stop_event.is_set(): + thread_id = get_ident() + distance = my_sensor.read() + print("Thread ID = {} with distance value = {}".format(thread_id, distance)) + sleep(0.001) + +# create an object for each thread +thread1 = Thread(target = readingSensor) +thread2 = Thread(target = readingSensor) + +# and then start them +thread1.start() +thread2.start() + +# let it run for [runtime] seconds +while time() - start_time <= runtime: + sleep(0.1) + +# and then set the stop event variable +stop_event.set() + +# and wait both threads to end +thread1.join() +thread2.join() diff --git a/Python/Examples/LightColorSensor.py b/Python/Examples/EasyLightColorSensor.py similarity index 74% rename from Python/Examples/LightColorSensor.py rename to Python/Examples/EasyLightColorSensor.py index 30b85cb..04bb6b9 100644 --- a/Python/Examples/LightColorSensor.py +++ b/Python/Examples/EasyLightColorSensor.py @@ -1,28 +1,28 @@ -#!/usr/bin/env python -# -# https://www.dexterindustries.com -# -# Copyright (c) 2017 Dexter Industries -# Released under the MIT license (http://choosealicense.com/licenses/mit/). -# For more information see https://github.com/DexterInd/DI_Sensors/blob/master/LICENSE.md -# -# Python example program for the Dexter Industries Light Color Sensor - -from __future__ import print_function -from __future__ import division - -import time -from di_sensors.light_color_sensor import LightColorSensor - -print("Example program for reading a Dexter Industries Light Color Sensor on an I2C port.") - -lcs = LightColorSensor(led_state = True) - -while True: - # Read the R, G, B, C color values - red, green, blue, clear = lcs.get_raw_colors() - - # Print the values - print("Red: {:5.3f} Green: {:5.3f} Blue: {:5.3f} Clear: {:5.3f}".format(red, green, blue, clear)) - - time.sleep(0.02) +#!/usr/bin/env python +# +# https://www.dexterindustries.com +# +# Copyright (c) 2017 Dexter Industries +# Released under the MIT license (http://choosealicense.com/licenses/mit/). +# For more information see https://github.com/DexterInd/DI_Sensors/blob/master/LICENSE.md +# +# Python example program for the Dexter Industries Light Color Sensor + +from __future__ import print_function +from __future__ import division + +from time import sleep +from di_sensors.easy_light_color_sensor import EasyLightColorSensor + +print("Example program for reading a Dexter Industries Light Color Sensor on an I2C port.") + +my_lcs = EasyLightColorSensor(led_state = True) + +while True: + # Read the R, G, B, C color values + red, green, blue, clear = my_lcs.safe_raw_colors() + + # Print the values + print("Red: {:5.3f} Green: {:5.3f} Blue: {:5.3f} Clear: {:5.3f}".format(red, green, blue, clear)) + + sleep(0.02) diff --git a/Python/Examples/EasyTempHumPress.py b/Python/Examples/EasyTempHumPress.py new file mode 100644 index 0000000..63fa117 --- /dev/null +++ b/Python/Examples/EasyTempHumPress.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# +# https://www.dexterindustries.com +# +# Copyright (c) 2017 Dexter Industries +# Released under the MIT license (http://choosealicense.com/licenses/mit/). +# For more information see https://github.com/DexterInd/DI_Sensors/blob/master/LICENSE.md +# +# Python example program for the Dexter Industries Temperature Humidity Pressure Sensor + +from __future__ import print_function +from __future__ import division + +from time import sleep +from di_sensors.easy_temp_hum_press import EasyTHPSensor + +print("Example program for reading a Dexter Industries Temperature Humidity Pressure Sensor on an I2C port.") + +my_thp = EasyTHPSensor() + +while True: + # Read the temperature + temp = my_thp.safe_celsius() + + # Read the relative humidity + hum = my_thp.safe_humidity() + + # Read the pressure + press = my_thp.safe_pressure() + + # Print the values + print("Temperature: {:5.3f} Humidity: {:5.3f} Pressure: {:5.3f}".format(temp, hum, press)) + + sleep(0.02) diff --git a/Python/di_sensors/BME280.py b/Python/di_sensors/BME280.py index c61b698..42c8377 100644 --- a/Python/di_sensors/BME280.py +++ b/Python/di_sensors/BME280.py @@ -134,53 +134,53 @@ def __init__(self, bus = "RPI_1", t_mode = OSAMPLE_1, h_mode = OSAMPLE_1, p_mode def _load_calibration(self): # Read calibration data - self.dig_T1 = self.i2c_bus.read_reg_16u(REG_DIG_T1) - self.dig_T2 = self.i2c_bus.read_reg_16s(REG_DIG_T2) - self.dig_T3 = self.i2c_bus.read_reg_16s(REG_DIG_T3) - - self.dig_P1 = self.i2c_bus.read_reg_16u(REG_DIG_P1) - self.dig_P2 = self.i2c_bus.read_reg_16s(REG_DIG_P2) - self.dig_P3 = self.i2c_bus.read_reg_16s(REG_DIG_P3) - self.dig_P4 = self.i2c_bus.read_reg_16s(REG_DIG_P4) - self.dig_P5 = self.i2c_bus.read_reg_16s(REG_DIG_P5) - self.dig_P6 = self.i2c_bus.read_reg_16s(REG_DIG_P6) - self.dig_P7 = self.i2c_bus.read_reg_16s(REG_DIG_P7) - self.dig_P8 = self.i2c_bus.read_reg_16s(REG_DIG_P8) - self.dig_P9 = self.i2c_bus.read_reg_16s(REG_DIG_P9) - - self.dig_H1 = self.i2c_bus.read_reg_8u(REG_DIG_H1) - self.dig_H2 = self.i2c_bus.read_reg_16s(REG_DIG_H2) - self.dig_H3 = self.i2c_bus.read_reg_8u(REG_DIG_H3) - self.dig_H6 = self.i2c_bus.read_reg_8s(REG_DIG_H7) - - h4 = self.i2c_bus.read_reg_8s(REG_DIG_H4) + self.dig_T1 = self.i2c_bus.read_16(REG_DIG_T1) + self.dig_T2 = self.i2c_bus.read_16(REG_DIG_T2, signed = True) + self.dig_T3 = self.i2c_bus.read_16(REG_DIG_T3, signed = True) + + self.dig_P1 = self.i2c_bus.read_16(REG_DIG_P1) + self.dig_P2 = self.i2c_bus.read_16(REG_DIG_P2, signed = True) + self.dig_P3 = self.i2c_bus.read_16(REG_DIG_P3, signed = True) + self.dig_P4 = self.i2c_bus.read_16(REG_DIG_P4, signed = True) + self.dig_P5 = self.i2c_bus.read_16(REG_DIG_P5, signed = True) + self.dig_P6 = self.i2c_bus.read_16(REG_DIG_P6, signed = True) + self.dig_P7 = self.i2c_bus.read_16(REG_DIG_P7, signed = True) + self.dig_P8 = self.i2c_bus.read_16(REG_DIG_P8, signed = True) + self.dig_P9 = self.i2c_bus.read_16(REG_DIG_P9, signed = True) + + self.dig_H1 = self.i2c_bus.read_8(REG_DIG_H1) + self.dig_H2 = self.i2c_bus.read_16(REG_DIG_H2, signed = True) + self.dig_H3 = self.i2c_bus.read_8(REG_DIG_H3) + self.dig_H6 = self.i2c_bus.read_8(REG_DIG_H7, signed = True) + + h4 = self.i2c_bus.read_8(REG_DIG_H4, signed = True) h4 = (h4 << 4) - self.dig_H4 = h4 | (self.i2c_bus.read_reg_8u(REG_DIG_H5) & 0x0F) + self.dig_H4 = h4 | (self.i2c_bus.read_8(REG_DIG_H5) & 0x0F) - h5 = self.i2c_bus.read_reg_8s(REG_DIG_H6) + h5 = self.i2c_bus.read_8(REG_DIG_H6, signed = True) h5 = (h5 << 4) self.dig_H5 = h5 | ( - self.i2c_bus.read_reg_8u(REG_DIG_H5) >> 4 & 0x0F) + self.i2c_bus.read_8(REG_DIG_H5) >> 4 & 0x0F) def _read_raw_temp(self): # read raw temperature data once it's available - while (self.i2c_bus.read_reg_8u(REG_STATUS) & 0x08): + while (self.i2c_bus.read_8(REG_STATUS) & 0x08): time.sleep(0.002) - data = self.i2c_bus.read_reg_list(REG_TEMP_DATA, 3) + data = self.i2c_bus.read_list(REG_TEMP_DATA, 3) return ((data[0] << 16) | (data[1] << 8) | data[2]) >> 4 def _read_raw_pressure(self): # read raw pressure data once it's available - while (self.i2c_bus.read_reg_8u(REG_STATUS) & 0x08): + while (self.i2c_bus.read_8(REG_STATUS) & 0x08): time.sleep(0.002) - data = self.i2c_bus.read_reg_list(REG_PRESSURE_DATA, 3) + data = self.i2c_bus.read_list(REG_PRESSURE_DATA, 3) return ((data[0] << 16) | (data[1] << 8) | data[2]) >> 4 def _read_raw_humidity(self): # read raw humidity data once it's available - while (self.i2c_bus.read_reg_8u(REG_STATUS) & 0x08): + while (self.i2c_bus.read_8(REG_STATUS) & 0x08): time.sleep(0.002) - data = self.i2c_bus.read_reg_list(REG_HUMIDITY_DATA, 2) + data = self.i2c_bus.read_list(REG_HUMIDITY_DATA, 2) return (data[0] << 8) | data[1] def read_temperature(self): diff --git a/Python/di_sensors/BNO055.py b/Python/di_sensors/BNO055.py index c05be5d..b26df38 100644 --- a/Python/di_sensors/BNO055.py +++ b/Python/di_sensors/BNO055.py @@ -232,10 +232,10 @@ def __init__(self, bus = "RPI_1", address = ADDRESS_A, mode = OPERATION_MODE_NDO self.i2c_bus.write_reg_8(REG_PAGE_ID, 0) # check the chip ID - if ID != self.i2c_bus.read_reg_8u(REG_CHIP_ID): + if ID != self.i2c_bus.read_8(REG_CHIP_ID): raise RuntimeError("BNO055 failed to respond") - if self.i2c_bus.read_reg_8u(REG_TEMP_SOURCE) != 0x01: + if self.i2c_bus.read_8(REG_TEMP_SOURCE) != 0x01: # print("Doing init") # reset the device using the reset command @@ -288,12 +288,12 @@ def get_revision(self): Returns a tuple with revision numbers for Software revision, Bootloader version, Accelerometer ID, Magnetometer ID, and Gyro ID.""" # Read revision values. - accel = self.i2c_bus.read_reg_8u(REG_ACCEL_REV_ID) - mag = self.i2c_bus.read_reg_8u(REG_MAG_REV_ID) - gyro = self.i2c_bus.read_reg_8u(REG_GYRO_REV_ID) - bl = self.i2c_bus.read_reg_8u(REG_BL_REV_ID) - sw_lsb = self.i2c_bus.read_reg_8u(REG_SW_REV_ID_LSB) - sw_msb = self.i2c_bus.read_reg_8u(REG_SW_REV_ID_MSB) + accel = self.i2c_bus.read_8(REG_ACCEL_REV_ID) + mag = self.i2c_bus.read_8(REG_MAG_REV_ID) + gyro = self.i2c_bus.read_8(REG_GYRO_REV_ID) + bl = self.i2c_bus.read_8(REG_BL_REV_ID) + sw_lsb = self.i2c_bus.read_8(REG_SW_REV_ID_LSB) + sw_msb = self.i2c_bus.read_8(REG_SW_REV_ID_MSB) sw = ((sw_msb << 8) | sw_lsb) & 0xFFFF # Return the results as a tuple of all 5 values. return (sw, bl, accel, mag, gyro) @@ -353,20 +353,20 @@ def get_system_status(self, run_self_test = True): # Switch to configuration mode if running self test. self._config_mode() # Perform a self test. - sys_trigger = self.i2c_bus.read_reg_8u(REG_SYS_TRIGGER) + sys_trigger = self.i2c_bus.read_8(REG_SYS_TRIGGER) self.i2c_bus.write_reg_8(REG_SYS_TRIGGER, sys_trigger | 0x1) # Wait for self test to finish. time.sleep(1.0) # Read test result. - self_test = self.i2c_bus.read_reg_8u(REG_SELFTEST_RESULT) + self_test = self.i2c_bus.read_8(REG_SELFTEST_RESULT) # Go back to operation mode. self._operation_mode() else: self_test = None # read status and error values - status = self.i2c_bus.read_reg_8u(REG_SYS_STAT) - error = self.i2c_bus.read_reg_8u(REG_SYS_ERR) + status = self.i2c_bus.read_8(REG_SYS_STAT) + error = self.i2c_bus.read_8(REG_SYS_ERR) # return the results as a tuple of all 3 values return (status, self_test, error) @@ -395,7 +395,7 @@ def get_calibration_status(self): """ # Return the calibration status register value. - cal_status = self.i2c_bus.read_reg_8u(REG_CALIB_STAT) + cal_status = self.i2c_bus.read_8(REG_CALIB_STAT) sys = (cal_status >> 6) & 0x03 gyro = (cal_status >> 4) & 0x03 accel = (cal_status >> 2) & 0x03 @@ -413,7 +413,7 @@ def get_calibration(self): # Switch to configuration mode, as mentioned in section 3.10.4 of datasheet. self._config_mode() # Read the 22 bytes of calibration data - cal_data = self.i2c_bus.read_reg_list(REG_ACCEL_OFFSET_X_LSB, 22) + cal_data = self.i2c_bus.read_list(REG_ACCEL_OFFSET_X_LSB, 22) # Go back to normal operation mode. self._operation_mode() return cal_data @@ -464,12 +464,12 @@ def get_axis_remap(self): |____________|/ """ # Get the axis remap register value. - map_config = self.i2c_bus.read_reg_8u(REG_AXIS_MAP_CONFIG) + map_config = self.i2c_bus.read_8(REG_AXIS_MAP_CONFIG) z = (map_config >> 4) & 0x03 y = (map_config >> 2) & 0x03 x = map_config & 0x03 # Get the axis remap sign register value. - sign_config = self.i2c_bus.read_reg_8u(REG_AXIS_MAP_SIGN) + sign_config = self.i2c_bus.read_8(REG_AXIS_MAP_SIGN) x_sign = (sign_config >> 2) & 0x01 y_sign = (sign_config >> 1) & 0x01 z_sign = sign_config & 0x01 @@ -510,7 +510,7 @@ def set_axis_remap(self, x, y, z, x_sign=AXIS_REMAP_POSITIVE, def _read_vector(self, reg, count = 3): # Read count number of 16-bit signed values starting from the provided # register. Returns a tuple of the values that were read. - data = self.i2c_bus.read_reg_list(reg, count*2) + data = self.i2c_bus.read_list(reg, count*2) result = [0]*count for i in range(count): result[i] = (((data[(i * 2) + 1] & 0xFF) << 8) | (data[(i * 2)] & 0xFF)) & 0xFFFF @@ -573,4 +573,4 @@ def read_temp(self): """Read the temperature Returns the current temperature in degrees celsius.""" - return self.i2c_bus.read_reg_8s(REG_TEMP) + return self.i2c_bus.read_8(REG_TEMP, signed = True) diff --git a/Python/di_sensors/PCA9570.py b/Python/di_sensors/PCA9570.py index 2b97729..fe3b92a 100644 --- a/Python/di_sensors/PCA9570.py +++ b/Python/di_sensors/PCA9570.py @@ -36,4 +36,4 @@ def get_pins(self): """Get the output pin states Returns the bit values for the 4 outputs""" - return (self.i2c_bus.read_8u() & 0x0F) + return (self.i2c_bus.read_8() & 0x0F) diff --git a/Python/di_sensors/TCS34725.py b/Python/di_sensors/TCS34725.py index 7af989e..fc143c4 100644 --- a/Python/di_sensors/TCS34725.py +++ b/Python/di_sensors/TCS34725.py @@ -82,7 +82,7 @@ def __init__(self, integration_time = 0.0024, gain = GAIN_16X, bus = "RPI_1"): self.i2c_bus = dexter_i2c.Dexter_I2C(bus = bus, address = ADDRESS, big_endian = False) # Make sure we're connected to the right sensor. - chip_id = self.i2c_bus.read_reg_8u((COMMAND_BIT | ID)) + chip_id = self.i2c_bus.read_8((COMMAND_BIT | ID)) if chip_id != 0x44: raise RuntimeError('Incorrect chip ID.') @@ -103,7 +103,7 @@ def enable(self): def disable(self): """Disable the sensor""" # Clear the power and enable bits. - reg = self.i2c_bus.read_reg_8u((COMMAND_BIT | ENABLE)) + reg = self.i2c_bus.read_8((COMMAND_BIT | ENABLE)) reg &= ~(ENABLE_PON | ENABLE_AEN) self.i2c_bus.write_reg_8((COMMAND_BIT | ENABLE), reg) @@ -129,7 +129,7 @@ def set_gain(self, gain): def set_interrupt(self, state): self.i2c_bus.write_reg_8((COMMAND_BIT | PERS), PERS_NONE) - enable = self.i2c_bus.read_reg_8u((COMMAND_BIT | ENABLE)) + enable = self.i2c_bus.read_8((COMMAND_BIT | ENABLE)) if state: enable |= ENABLE_AIEN else: @@ -150,10 +150,10 @@ def get_raw_data(self, delay = True): div = ((256 - self.integration_time_val) * 1024) # Read each color register. - r = self.i2c_bus.read_reg_16u((COMMAND_BIT | RDATAL)) / div - g = self.i2c_bus.read_reg_16u((COMMAND_BIT | GDATAL)) / div - b = self.i2c_bus.read_reg_16u((COMMAND_BIT | BDATAL)) / div - c = self.i2c_bus.read_reg_16u((COMMAND_BIT | CDATAL)) / div + r = self.i2c_bus.read_16((COMMAND_BIT | RDATAL)) / div + g = self.i2c_bus.read_16((COMMAND_BIT | GDATAL)) / div + b = self.i2c_bus.read_16((COMMAND_BIT | BDATAL)) / div + c = self.i2c_bus.read_16((COMMAND_BIT | CDATAL)) / div if r > 1: r = 1 if g > 1: diff --git a/Python/di_sensors/VL53L0X.py b/Python/di_sensors/VL53L0X.py index efa7fcc..186775d 100644 --- a/Python/di_sensors/VL53L0X.py +++ b/Python/di_sensors/VL53L0X.py @@ -151,7 +151,7 @@ def set_address(self, address): self.i2c_bus.set_address(self.ADDRESS) def init(self): - self.i2c_bus.write_reg_8(VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV, (self.i2c_bus.read_reg_8u(VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01)) # set bit 0 + self.i2c_bus.write_reg_8(VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV, (self.i2c_bus.read_8(VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01)) # set bit 0 # "Set I2C standard mode" self.i2c_bus.write_reg_8(0x88, 0x00) @@ -159,13 +159,13 @@ def init(self): self.i2c_bus.write_reg_8(0x80, 0x01) self.i2c_bus.write_reg_8(0xFF, 0x01) self.i2c_bus.write_reg_8(0x00, 0x00) - self.stop_variable = self.i2c_bus.read_reg_8u(0x91) + self.stop_variable = self.i2c_bus.read_8(0x91) self.i2c_bus.write_reg_8(0x00, 0x01) self.i2c_bus.write_reg_8(0xFF, 0x00) self.i2c_bus.write_reg_8(0x80, 0x00) # disable SIGNAL_RATE_MSRC (bit 1) and SIGNAL_RATE_PRE_RANGE (bit 4) limit checks - self.i2c_bus.write_reg_8(MSRC_CONFIG_CONTROL, (self.i2c_bus.read_reg_8u(MSRC_CONFIG_CONTROL) | 0x12)) + self.i2c_bus.write_reg_8(MSRC_CONFIG_CONTROL, (self.i2c_bus.read_8(MSRC_CONFIG_CONTROL) | 0x12)) # set final range signal rate limit to 0.25 MCPS (million counts per second) self.set_signal_rate_limit(0.25) @@ -183,7 +183,7 @@ def init(self): # The SPAD map (RefGoodSpadMap) is read by VL53L0X_get_info_from_device() in # the API, but the same data seems to be more easily readable from # GLOBAL_CONFIG_SPAD_ENABLES_REF_0 through _6, so read it from there - ref_spad_map = self.i2c_bus.read_reg_list(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, 6) + ref_spad_map = self.i2c_bus.read_list(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, 6) # -- VL53L0X_set_reference_spads() begin (assume NVM values are valid) @@ -315,7 +315,7 @@ def init(self): # -- VL53L0X_SetGpioConfig() begin self.i2c_bus.write_reg_8(SYSTEM_INTERRUPT_CONFIG_GPIO, 0x04) - self.i2c_bus.write_reg_8(GPIO_HV_MUX_ACTIVE_HIGH, self.i2c_bus.read_reg_8u(GPIO_HV_MUX_ACTIVE_HIGH) & ~0x10) # active low + self.i2c_bus.write_reg_8(GPIO_HV_MUX_ACTIVE_HIGH, self.i2c_bus.read_8(GPIO_HV_MUX_ACTIVE_HIGH) & ~0x10) # active low self.i2c_bus.write_reg_8(SYSTEM_INTERRUPT_CLEAR, 0x01) # -- VL53L0X_SetGpioConfig() end @@ -378,7 +378,7 @@ def get_spad_info(self): self.i2c_bus.write_reg_8(0x00, 0x00) self.i2c_bus.write_reg_8(0xFF, 0x06) - self.i2c_bus.write_reg_8(0x83, self.i2c_bus.read_reg_8u(0x83) | 0x04) + self.i2c_bus.write_reg_8(0x83, self.i2c_bus.read_8(0x83) | 0x04) self.i2c_bus.write_reg_8(0xFF, 0x07) self.i2c_bus.write_reg_8(0x81, 0x01) @@ -387,19 +387,19 @@ def get_spad_info(self): self.i2c_bus.write_reg_8(0x94, 0x6b) self.i2c_bus.write_reg_8(0x83, 0x00) self.start_timeout() - while (self.i2c_bus.read_reg_8u(0x83) == 0x00): + while (self.i2c_bus.read_8(0x83) == 0x00): if (self.check_timeout_expired()): return 0, 0, False self.i2c_bus.write_reg_8(0x83, 0x01) - tmp = self.i2c_bus.read_reg_8u(0x92) + tmp = self.i2c_bus.read_8(0x92) count = tmp & 0x7f type_is_aperture = (tmp >> 7) & 0x01 self.i2c_bus.write_reg_8(0x81, 0x00) self.i2c_bus.write_reg_8(0xFF, 0x06) - self.i2c_bus.write_reg_8(0x83, self.i2c_bus.read_reg_8u(0x83 & ~0x04)) + self.i2c_bus.write_reg_8(0x83, self.i2c_bus.read_8(0x83 & ~0x04)) self.i2c_bus.write_reg_8(0xFF, 0x01) self.i2c_bus.write_reg_8(0x00, 0x01) @@ -463,7 +463,7 @@ def get_measurement_timing_budget(self): # Get sequence step enables # based on VL53L0X_get_sequence_step_enables() def get_sequence_step_enables(self): - sequence_config = self.i2c_bus.read_reg_8u(SYSTEM_SEQUENCE_CONFIG) + sequence_config = self.i2c_bus.read_8(SYSTEM_SEQUENCE_CONFIG) SequenceStepEnables = {"tcc":0, "msrc":0, "dss":0, "pre_range":0, "final_range":0} SequenceStepEnables["tcc"] = (sequence_config >> 4) & 0x1 SequenceStepEnables["dss"] = (sequence_config >> 3) & 0x1 @@ -480,15 +480,15 @@ def get_sequence_step_timeouts(self, pre_range): SequenceStepTimeouts = {"pre_range_vcsel_period_pclks":0, "final_range_vcsel_period_pclks":0, "msrc_dss_tcc_mclks":0, "pre_range_mclks":0, "final_range_mclks":0, "msrc_dss_tcc_us":0, "pre_range_us":0, "final_range_us":0} SequenceStepTimeouts["pre_range_vcsel_period_pclks"] = self.get_vcsel_pulse_period(self.VcselPeriodPreRange) - SequenceStepTimeouts["msrc_dss_tcc_mclks"] = self.i2c_bus.read_reg_8u(MSRC_CONFIG_TIMEOUT_MACROP) + 1 + SequenceStepTimeouts["msrc_dss_tcc_mclks"] = self.i2c_bus.read_8(MSRC_CONFIG_TIMEOUT_MACROP) + 1 SequenceStepTimeouts["msrc_dss_tcc_us"] = self.timeout_mclks_to_microseconds(SequenceStepTimeouts["msrc_dss_tcc_mclks"], SequenceStepTimeouts["pre_range_vcsel_period_pclks"]) - SequenceStepTimeouts["pre_range_mclks"] = self.decode_timeout(self.i2c_bus.read_reg_16u(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI)) + SequenceStepTimeouts["pre_range_mclks"] = self.decode_timeout(self.i2c_bus.read_16(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI)) SequenceStepTimeouts["pre_range_us"] = self.timeout_mclks_to_microseconds(SequenceStepTimeouts["pre_range_mclks"], SequenceStepTimeouts["pre_range_vcsel_period_pclks"]) SequenceStepTimeouts["final_range_vcsel_period_pclks"] = self.get_vcsel_pulse_period(self.VcselPeriodFinalRange) - SequenceStepTimeouts["final_range_mclks"] = self.decode_timeout(self.i2c_bus.read_reg_16u(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI)) + SequenceStepTimeouts["final_range_mclks"] = self.decode_timeout(self.i2c_bus.read_16(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI)) if (pre_range): SequenceStepTimeouts["final_range_mclks"] -= SequenceStepTimeouts["pre_range_mclks"] @@ -507,9 +507,9 @@ def decode_vcsel_period(self, reg_val): # based on VL53L0X_get_vcsel_pulse_period() def get_vcsel_pulse_period(self, type): if type == self.VcselPeriodPreRange: - return self.decode_vcsel_period(self.i2c_bus.read_reg_8u(PRE_RANGE_CONFIG_VCSEL_PERIOD)) + return self.decode_vcsel_period(self.i2c_bus.read_8(PRE_RANGE_CONFIG_VCSEL_PERIOD)) elif type == self.VcselPeriodFinalRange: - return self.decode_vcsel_period(self.i2c_bus.read_reg_8u(FINAL_RANGE_CONFIG_VCSEL_PERIOD)) + return self.decode_vcsel_period(self.i2c_bus.read_8(FINAL_RANGE_CONFIG_VCSEL_PERIOD)) else: return 255 @@ -637,7 +637,7 @@ def perform_single_ref_calibration(self, vhv_init_byte): self.i2c_bus.write_reg_8(SYSRANGE_START, 0x01 | vhv_init_byte) # VL53L0X_REG_SYSRANGE_MODE_START_STOP self.start_timeout() - while ((self.i2c_bus.read_reg_8u(RESULT_INTERRUPT_STATUS) & 0x07) == 0): + while ((self.i2c_bus.read_8(RESULT_INTERRUPT_STATUS) & 0x07) == 0): if self.check_timeout_expired(): return False @@ -670,7 +670,7 @@ def start_continuous(self, period_ms = 0): # VL53L0X_SetInterMeasurementPeriodMilliSeconds() begin - osc_calibrate_val = self.i2c_bus.read_reg_16u(OSC_CALIBRATE_VAL) + osc_calibrate_val = self.i2c_bus.read_16(OSC_CALIBRATE_VAL) if osc_calibrate_val != 0: period_ms *= osc_calibrate_val @@ -689,14 +689,14 @@ def start_continuous(self, period_ms = 0): # single-shot range measurement) def read_range_continuous_millimeters(self): self.start_timeout() - while ((self.i2c_bus.read_reg_8u(RESULT_INTERRUPT_STATUS) & 0x07) == 0): + while ((self.i2c_bus.read_8(RESULT_INTERRUPT_STATUS) & 0x07) == 0): if self.check_timeout_expired(): self.did_timeout = True raise IOError("read_range_continuous_millimeters timeout") # assumptions: Linearity Corrective Gain is 1000 (default) # fractional ranging is not enabled - range = self.i2c_bus.read_reg_16u(RESULT_RANGE_STATUS + 10) + range = self.i2c_bus.read_16(RESULT_RANGE_STATUS + 10) self.i2c_bus.write_reg_8(SYSTEM_INTERRUPT_CLEAR, 0x01) @@ -843,7 +843,7 @@ def set_vcsel_pulse_period(self, type, period_pclks): # "Perform the phase calibration. This is needed after changing on vcsel period." # VL53L0X_perform_phase_calibration() begin - sequence_config = self.i2c_bus.read_reg_8u(SYSTEM_SEQUENCE_CONFIG) + sequence_config = self.i2c_bus.read_8(SYSTEM_SEQUENCE_CONFIG) self.i2c_bus.write_reg_8(SYSTEM_SEQUENCE_CONFIG, 0x02) self.perform_single_ref_calibration(0x0) self.i2c_bus.write_reg_8(SYSTEM_SEQUENCE_CONFIG, sequence_config) @@ -868,7 +868,7 @@ def read_range_single_millimeters(self): # "Wait until start bit has been cleared" self.start_timeout() - while (self.i2c_bus.read_reg_8u(SYSRANGE_START) & 0x01): + while (self.i2c_bus.read_8(SYSRANGE_START) & 0x01): if self.check_timeout_expired(): self.did_timeout = true raise IOError("read_range_single_millimeters timeout") diff --git a/Python/di_sensors/dexter_i2c.py b/Python/di_sensors/dexter_i2c.py index 3ae0f76..4f07392 100644 --- a/Python/di_sensors/dexter_i2c.py +++ b/Python/di_sensors/dexter_i2c.py @@ -215,100 +215,116 @@ def write_reg_list(self, reg, list): arr = [reg] arr.extend(list) self.transfer(arr) - - def read_8u(self): - """Read an 8-bit value - - Returns the value""" - val = self.transfer([], 1) - return val - - def read_reg_8u(self, reg): - """Read an 8-bit unsigned value from a register - - Keyword arguments: - reg -- regester to read from - - Returns the value - """ - val = self.transfer([reg], 1) - return val[0] - - def read_reg_8s(self, reg): - """Read an 8-bit signed value from a register - + + def read_8(self, reg = None, signed = False): + """Read a 8-bit value + Keyword arguments: - reg -- regester to read from - + reg (default None) -- Register to read from or None + signed (default False) -- True (signed) or False (unsigned) + Returns the value """ - val = self.read_reg_8u(reg) - if val & 0x80: - return val - 0x100 - return val - - def read_reg_16u(self, reg, big_endian = None): - """Read a 16-bit unsigned value from a register - + # write the register to read from? + if reg != None: + outArr = [reg] + else: + outArr = [] + + val = self.transfer(outArr, 1) + + value = val[0] + + # signed value? + if signed: + # negative value? + if value & 0x80: + value = value - 0x100 + + return value + + def read_16(self, reg = None, signed = False, big_endian = None): + """Read a 16-bit value + Keyword arguments: - reg -- regester to read from + reg (default None) -- Register to read from or None + signed (default False) -- True (signed) or False (unsigned) big_endian (default None) -- True (big endian), False (little endian), or None (use the pre-defined endianness for the object) - + Returns the value """ - val = self.transfer([reg], 2) + # write the register to read from? + if reg != None: + outArr = [reg] + else: + outArr = [] + + val = self.transfer(outArr, 2) + if big_endian == None: big_endian = self.big_endian + + # big endian? if big_endian: - return (val[0] << 8) | val[1] + value = (val[0] << 8) | val[1] else: - return (val[1] << 8) | val[0] - - def read_reg_16s(self, reg, big_endian = None): - """Read a 16-bit signed value from a register - + value = (val[1] << 8) | val[0] + + # signed value? + if signed: + # negative value? + if value & 0x8000: + value = value - 0x10000 + + return value + + def read_32(self, reg = None, signed = False, big_endian = None): + """Read a 32-bit value + Keyword arguments: - reg -- regester to read from + reg (default None) -- Register to read from or None + signed (default False) -- True (signed) or False (unsigned) big_endian (default None) -- True (big endian), False (little endian), or None (use the pre-defined endianness for the object) - + Returns the value """ - val = self.read_reg_16u(reg, big_endian) - if val & 0x8000: - return val - 0x10000 - return val - - def read_32u(self, big_endian = None): - """Read a 32-bit unsigned value - - Keyword arguments: - big_endian (default None) -- True (big endian), False (little endian), or None (use the pre-defined endianness for the object) - - Returns the value - """ - val = self.transfer([], 4) + # write the register to read from? + if reg != None: + outArr = [reg] + else: + outArr = [] + + val = self.transfer(outArr, 4) + if big_endian == None: big_endian = self.big_endian + + # big endian? if big_endian: - return (val[0] << 24) | (val[1] << 16) | (val[2] << 8) | val[3] + value = (val[0] << 24) | (val[1] << 16) | (val[2] << 8) | val[3] else: - return (val[3] << 24) | (val[2] << 16) | (val[1] << 8) | val[0] - - def read_list(self, len): - """Read a list of bytes - - Keyword arguments: - len -- the number of bytes to read - - Returns a list of the bytes read""" - return self.transfer([], len) - - def read_reg_list(self, reg, len): + value = (val[3] << 24) | (val[2] << 16) | (val[1] << 8) | val[0] + + # signed value? + if signed: + # negative value? + if value & 0x80000000: + value = value - 0x100000000 + + return value + + def read_list(self, reg, len): """Read a list of bytes from a register - + Keyword arguments: - reg -- the regester to read from - len -- the number of bytes to read - + reg -- Register to read from or None + len -- Number of bytes to read + Returns a list of the bytes read""" - return self.transfer([reg], len) + + # write the register to read from? + if reg != None: + outArr = [reg] + else: + outArr = [] + return self.transfer(outArr, len) diff --git a/Python/di_sensors/distance_sensor.py b/Python/di_sensors/distance_sensor.py index 9bc1b2b..de9eb88 100644 --- a/Python/di_sensors/distance_sensor.py +++ b/Python/di_sensors/distance_sensor.py @@ -27,7 +27,7 @@ def __init__(self, bus = "RPI_1"): """ self.VL53L0X = VL53L0X.VL53L0X(bus = bus) - # set to long range (about 2 meters) + # set to long range (about 2.3 meters) self.VL53L0X.set_signal_rate_limit(0.1) self.VL53L0X.set_vcsel_pulse_period(self.VL53L0X.VcselPeriodPreRange, 18) self.VL53L0X.set_vcsel_pulse_period(self.VL53L0X.VcselPeriodFinalRange, 14) @@ -52,7 +52,7 @@ def read_range_continuous(self): """ Read the detected range while the sensor is taking continuous measurements at the set rate. - :returns: The detected range of the sensor as measured in millimeters. The range can go up to 2 meters. + :returns: The detected range of the sensor as measured in millimeters. The range can go up to 2.3 meters. :rtype: int :raises ~exceptions.OSError: When the distance sensor is not reachable or when the :py:meth:`~di_sensors.distance_sensor.DistanceSensor.start_continuous` hasn't been called before. This exception gets raised also when the user is trying to poll data faster than how it was initially set with the :py:meth:`~di_sensors.distance_sensor.DistanceSensor.start_continuous` method. @@ -72,7 +72,7 @@ def read_range_single(self, safe_infinity=True): :param boolean safe_infinity = True: As sometimes the distance sensor returns a small value when there's nothing in front of it, we need to poll again and again to confirm the presence of an obstacle. Setting ``safe_infinity`` to ``False`` will avoid that extra polling. - :returns: The detected range of the sensor as measured in millimeters. The range can go up to 2 meters. + :returns: The detected range of the sensor as measured in millimeters. The range can go up to 2.3 meters. :rtype: int :raises ~exceptions.OSError: When the distance sensor is not reachable. diff --git a/Python/di_sensors/easy_distance_sensor.py b/Python/di_sensors/easy_distance_sensor.py new file mode 100644 index 0000000..226a642 --- /dev/null +++ b/Python/di_sensors/easy_distance_sensor.py @@ -0,0 +1,126 @@ +# https://www.dexterindustries.com +# +# Copyright (c) 2018 Dexter Industries +# Released under the MIT license (http://choosealicense.com/licenses/mit/). +# For more information see https://github.com/DexterInd/DI_Sensors/blob/master/LICENSE.md +# + +from di_sensors.easy_mutex import ifMutexAcquire, ifMutexRelease +import time +from di_sensors import distance_sensor + + +class EasyDistanceSensor(distance_sensor.DistanceSensor): + """ + Class for the `Distance Sensor`_ device. + + This class compared to :py:class:`~di_sensors.distance_sensor.DistanceSensor` uses mutexes that allows a given + object to be accessed simultaneously from multiple threads/processes. + Apart from this difference, there may also be functions that are more user-friendly than the latter. + + """ + 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 ` 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) + + # Returns the values in cms + 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 + + def read(self): + """ + Reads the distance in centimeters. + + :returns: Distance from target in centimeters. + :rtype: int + + .. note:: + + 1. Sensor's range is **0-230** centimeters. + 2. When the values are out of the range, it returns **300**. + + """ + + cm = self.read_mm()//10 + return (cm) + + def read_inches(self): + """ + Reads the distance in inches. + + :returns: Distance from target in inches. + :rtype: float with one decimal + + .. note:: + + 1. Sensor's range is **0-90** inches. + 2. Anything that's bigger than **90** inches is returned when the sensor can't detect any target/surface. + + """ + cm = self.read() + return round(cm / 2.54, 1) diff --git a/Python/di_sensors/easy_inertial_measurement_unit.py b/Python/di_sensors/easy_inertial_measurement_unit.py new file mode 100644 index 0000000..8d0417d --- /dev/null +++ b/Python/di_sensors/easy_inertial_measurement_unit.py @@ -0,0 +1,249 @@ +# https://www.dexterindustries.com +# +# Copyright (c) 2018 Dexter Industries +# Released under the MIT license (http://choosealicense.com/licenses/mit/). +# For more information see https://github.com/DexterInd/DI_Sensors/blob/master/LICENSE.md +# + +# EASIER WRAPPERS FOR: +# IMU SENSOR, +# LIGHT AND COLOR SENSOR +# TEMPERATURE, HUMIDITY and PRESSURE SENSOR + +# MUTEX SUPPORT WHEN NEEDED + + +from di_sensors import inertial_measurement_unit +from di_sensors import BNO055 +import I2C_mutex +from math import atan2, pi +from time import sleep + +''' +MUTEX HANDLING +''' +from di_sensors.easy_mutex import ifMutexAcquire, ifMutexRelease + +''' +PORT TRANSLATION +''' +ports = { + "AD1": "GPG3_AD1", + "AD2": "GPG3_AD2" +} + +class EasyIMUSensor(inertial_measurement_unit.InertialMeasurementUnit): + ''' + Class for interfacing with the `InertialMeasurementUnit Sensor`_. + + This class compared to :py:class:`~di_sensors.inertial_measurement_unit.InertialMeasurementUnit` uses mutexes that allows a given + object to be accessed simultaneously from multiple threads/processes. + Apart from this difference, there may + also be functions that are more user-friendly than the latter. + ''' + + 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 ` 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_1" + + 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_1": + 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 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) + + 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 + + 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 convert_heading(self, in_heading): + """ + This method takes in a heading in degrees and return the name of the corresponding heading. + :param float in_heading: the value in degree that needs to be converted to a string. + + :return: The heading of the sensor as a string. + :rtype: str + + The possible strings that can be returned are: ``"North"``, ``"North East"``, ``"East"``, + ``"South East"``, ``"South"``, ``"South West"``, ``"West"``, ``"North West"``, ``"North"``. + + .. note:: + + First use :py:meth:`~di_sensors.easy_inertial_measurement_unit.EasyIMUSensor.safe_calibrate` or :py:meth:`~di_sensors.easy_inertial_measurement_unit.EasyIMUSensor.safe_calibration_status` + methods to determine if the magnetometer sensor is fully calibrated. + + """ + + headings = ["North", "North East", + "East", "South East", + "South", "South West", + "West", "North West", + "North"] + + nb_headings = len(headings)-1 # North is listed twice + heading_index = int(round(in_heading/(360.0/nb_headings),0)) + # sometimes the IMU will return a in_heading of -1000 and higher. + if heading_index < 0: + heading_index = 0 + # print("heading {} index {}".format(in_heading, heading_index)) + # print(" {} ".format( headings[heading_index])) + return(headings[heading_index]) + + 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 + + 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_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 diff --git a/Python/di_sensors/easy_light_color_sensor.py b/Python/di_sensors/easy_light_color_sensor.py new file mode 100644 index 0000000..d7ad6f0 --- /dev/null +++ b/Python/di_sensors/easy_light_color_sensor.py @@ -0,0 +1,234 @@ +# https://www.dexterindustries.com +# +# Copyright (c) 2018 Dexter Industries +# Released under the MIT license (http://choosealicense.com/licenses/mit/). +# For more information see https://github.com/DexterInd/DI_Sensors/blob/master/LICENSE.md +# + +# EASIER WRAPPERS FOR: +# IMU SENSOR, +# LIGHT AND COLOR SENSOR +# TEMPERATURE, HUMIDITY and PRESSURE SENSOR + +# MUTEX SUPPORT WHEN NEEDED + + +from di_sensors import light_color_sensor +from di_sensors import VL53L0X +import I2C_mutex +from time import sleep +from math import sqrt + +''' +MUTEX HANDLING +''' +from di_sensors.easy_mutex import ifMutexAcquire, ifMutexRelease + +''' +PORT TRANSLATION +''' +ports = { + "AD1": "GPG3_AD1", + "AD2": "GPG3_AD2" +} + +class EasyLightColorSensor(light_color_sensor.LightColorSensor): + """ + Class for interfacing with the `Light Color Sensor`_. + + This class compared to :py:class:`~di_sensors.light_color_sensor.LightColorSensor` uses mutexes that allows a given + object to be accessed simultaneously from multiple threads/processes. + Apart from this difference, there may also be functions that are more user-friendly than the latter. + + """ + + #: The 6 colors that :py:meth:`~di_sensors.easy_light_color_sensor.EasyLightColorSensor.guess_color_hsv` + #: method may return upon reading and interpreting a new set of color values. + known_colors = { + "red":(255,0,0), + "green":(0,255,0), + "blue":(0,0,255), + "yellow":(255,255,0), + "cyan":(0,255,255), + "fuchsia":(255,0,255) + } + + known_hsv = { + "red":(0,100,100), + "green":(120,100,100), + "blue":(240,100,100), + "yellow":(60,100,100), + "cyan":(180,100,100), + "fuchsia":(300,100,100) + } + + 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 ` 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_1" + + # 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 + + def translate_to_hsv(self, in_color): + """ + Standard algorithm to switch from one color system (**RGB**) to another (**HSV**). + + :param tuple(float,float,float) in_color: The RGB tuple list that gets translated to HSV system. The values of each element of the tuple is between **0** and **1**. + :return: The translated HSV tuple list. Returned values are *H(0-360)*, *S(0-100)*, *V(0-100)*. + :rtype: tuple(int, int, int) + + .. important:: + + For finding out the differences between **RGB** *(Red, Green, Blue)* color scheme and **HSV** *(Hue, Saturation, Value)* + please check out `this link `__. + + """ + r,g,b = in_color + + min_channel = min((r,g,b)) + max_channel = max((r,g,b)) + + v = max_channel + delta = max_channel - min_channel + if delta < 0.0001: + s = 0 + h = 0 + else: + if max_channel > 0: + s = delta / max_channel + if r >= max_channel: + h = (g - b) / delta + elif g >= max_channel: + h = 2.0 + (b - r) / delta + else: + h = 4 + (r - g ) / delta + + h = h * 60 + if h < 0: + h = h + 360 + + else: + s = 0 + h = 0 + + return (h,s*100,v*100) + + + 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) + + def safe_rgb(self): + """ + Detect the RGB color off of the `Light Color Sensor`_. + + :returns: The RGB color in 8-bit format. + :rtype: tuple(int,int,int) + """ + colors = self.safe_raw_colors() + r,g,b,c = list(map(lambda c: int(c*255/colors[3]), colors)) + return r,g,b + + def guess_color_hsv(self, in_color): + """ + Determines which color `in_color` parameter is closest to in the :py:attr:`~di_sensors.easy_light_color_sensor.EasyLightColorSensor.known_colors` list. + + This method uses the euclidean algorithm for detecting the nearest center to it out of :py:attr:`~di_sensors.easy_light_color_sensor.EasyLightColorSensor.known_colors` list. + It does work exactly the same as KNN (K-Nearest-Neighbors) algorithm, where `K = 1`. + + :param tuple(float,float,float,float) in_color: A 4-element tuple list for the *Red*, *Green*, *Blue* and *Alpha* channels. The elements are all valued between **0** and **1**. + :returns: The detected color in string format and then a 3-element tuple describing the color in RGB format. The values of the RGB tuple are between **0** and **1**. + :rtype: tuple(str,(float,float,float)) + + .. important:: + + For finding out the differences between **RGB** *(Red, Green, Blue)* color scheme and **HSV** *(Hue, Saturation, Value)* + please check out `this link `__. + + """ + + r,g,b,c = in_color + # print("incoming: {} {} {} {}".format(r,g,b,c)) + + # handle black + # luminosity is too low, or all color readings are too low + if c < 0.04 or (r/c < 0.10 and g/c < 0.10 and b/c < 0.10): + return ("black",(0,0,0)) + + # handle white + # luminosity is high, or all color readings are high + if c > 0.95 or (r/c > 0.9 and g/c > 0.9 and b/c > 0.9): + return ("white",(255,255,255)) + + # divide by luminosity(clarity) to minimize variations + h,s,v = self.translate_to_hsv((r/c, g/c, b/c)) + + # another black is possible + # black has a value of 0 and a saturation of 100 + # values of 15 and 95 chosen randomly. They may need to be tweaked + if v < 15 and s > 95: + return ("Black",(0,0,0)) + + # so is another white + # white has a value of 100 and a saturation of 0 + # values of 95 and 10 chosen randomly. They may need to be tweaked + if v > 95 and s < 10: + return ("White",(255,255,255)) + + min_distance = 255 + for color in self.known_hsv: + # print ("Testing {}".format(color)) + distance_to_hsv = sqrt((h - self.known_hsv[color][0])**2 ) + # print((h,s,v), distance_to_hsv) + if distance_to_hsv < min_distance: + min_distance = distance_to_hsv + candidate = color + + return (candidate, self.known_colors[candidate]) diff --git a/Python/di_sensors/easy_mutex.py b/Python/di_sensors/easy_mutex.py new file mode 100644 index 0000000..c05e4ac --- /dev/null +++ b/Python/di_sensors/easy_mutex.py @@ -0,0 +1,22 @@ +''' +MUTEX HANDLING +''' +import I2C_mutex +mutex = I2C_mutex.Mutex(debug = False) + +def ifMutexAcquire(mutex_enabled = False): + """ + Acquires the I2C if the ``use_mutex`` parameter of the constructor was set to ``True``. + Always acquires if system-wide mutex has been set. + + """ + if mutex_enabled or mutex.overall_mutex()==True: + mutex.acquire() + +def ifMutexRelease(mutex_enabled = False): + """ + Releases the I2C if the ``use_mutex`` parameter of the constructor was set to ``True``. + + """ + if mutex_enabled or mutex.overall_mutex()==True: + mutex.release() \ No newline at end of file diff --git a/Python/di_sensors/easy_temp_hum_press.py b/Python/di_sensors/easy_temp_hum_press.py new file mode 100644 index 0000000..07bb60d --- /dev/null +++ b/Python/di_sensors/easy_temp_hum_press.py @@ -0,0 +1,138 @@ +# https://www.dexterindustries.com +# +# Copyright (c) 2018 Dexter Industries +# Released under the MIT license (http://choosealicense.com/licenses/mit/). +# For more information see https://github.com/DexterInd/DI_Sensors/blob/master/LICENSE.md +# + +# EASIER WRAPPERS FOR: +# IMU SENSOR, +# LIGHT AND COLOR SENSOR +# TEMPERATURE, HUMIDITY and PRESSURE SENSOR + +# MUTEX SUPPORT WHEN NEEDED + + +from di_sensors import temp_hum_press +from time import sleep + +from di_sensors.easy_mutex import ifMutexAcquire, ifMutexRelease + +''' +PORT TRANSLATION +''' +ports = { + "AD1": "GPG3_AD1", + "AD2": "GPG3_AD2" +} + +class EasyTHPSensor(temp_hum_press.TempHumPress): + """ + Class for interfacing with the `Temperature Humidity Pressure Sensor`_. + + This class compared to :py:class:`~di_sensors.temp_hum_press.TempHumPress` uses mutexes that allows a given + object to be accessed simultaneously from multiple threads/processes. + Apart from this difference, there may + also be functions that are more user-friendly than the latter. + + """ + + 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 ` 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_1" + + ifMutexAcquire(self.use_mutex) + try: + super(self.__class__, self).__init__(bus = bus) + except Exception as e: + raise + finally: + ifMutexRelease(self.use_mutex) + + def safe_celsius(self): + """ + Read temperature in Celsius degrees. + + :returns: Temperature in Celsius degrees. + :rtype: float + :raises ~exceptions.OSError: When the sensor cannot be reached. + + """ + ifMutexAcquire(self.use_mutex) + try: + temp = self.get_temperature_celsius() + except Exception as e: + raise + finally: + ifMutexRelease(self.use_mutex) + + return round(temp,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 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_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) diff --git a/Python/di_sensors/temp_hum_press.py b/Python/di_sensors/temp_hum_press.py index 51d41cd..3bd8079 100644 --- a/Python/di_sensors/temp_hum_press.py +++ b/Python/di_sensors/temp_hum_press.py @@ -21,7 +21,7 @@ def __init__(self, bus = "RPI_1"): """ Constructor for initializing link with the `Temperature Humidity Pressure Sensor`_. - :param str bus = "RPI_1": The bus to which the distance sensor is connected to. By default, it's set to bus ``"RPI_1"``. Check the :ref:`hardware specs ` for more information about the ports. + :param str bus = "RPI_1": The bus to which the THP sensor is connected to. By default, it's set to bus ``"RPI_1"``. Check the :ref:`hardware specs ` for more information about the ports. :raises ~exceptions.OSError: When the sensor cannot be reached. """ diff --git a/Python/setup.py b/Python/setup.py index 33e5d21..d3918a6 100644 --- a/Python/setup.py +++ b/Python/setup.py @@ -53,6 +53,5 @@ packages = find_packages() ) -# package_dir = {"grove_rgb_lcd" : "grove_rgb_lcd/", "distance_sensor" : "Distance_Sensor/Software/Python", "DHT" : "DHT_Sensor/",}, - # packages=["grove_rgb_lcd", "distance_sensor", "DHT"] - #install_requires=open('requirements.txt').readlines(), + +#install_requires=open('requirements.txt').readlines(), diff --git a/README.md b/README.md index b604c4d..c525a3a 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ In order to quick install the `DI_Sensors` repository, open up a terminal and ty 1. For installing the python packages of the `DI_Sensors` with root privileges (except any other settings that can come with), use the following command: ``` -sudo sh -c "curl -kL dexterindustries.com/update_sensors | bash" +curl -kL dexterindustries.com/update_sensors | sudo bash" ``` 2. For installing the python packages of the `DI_Sensors` without root privileges (except any other settings that can come with), use the following command: diff --git a/docs/source/api.rst b/docs/source/api-advanced.rst similarity index 64% rename from docs/source/api.rst rename to docs/source/api-advanced.rst index 97fe640..cc7ae28 100644 --- a/docs/source/api.rst +++ b/docs/source/api-advanced.rst @@ -1,33 +1,6 @@ -.. _api-chapter: - -############## -API DI-Sensors -############## - -============ -Requirements -============ - -Before you check the API for the DI-Sensors, please make sure you have the ``DI-Sensors`` package installed. You can do this by checking with ``pip`` by typing the following command. - -.. code-block:: bash - - pip show DI-Sensors - -If there's nothing to be shown, then please check the :ref:`Getting Started ` section and follow the instructions. - -.. _hardware-interface-section: - -================== -Hardware interface -================== - -Instantiating the :ref:`4 sensors` in Python is a matter of choosing the right bus. Thus, there are 3 buses to choose from, depending on the context: - - * The ``"RPI_1"`` bus - this bus can be used on all 4 platforms we have (the GoPiGo3, GoPiGo, BrickPi3 & GrovePi). This bus corresponds to the ``"I2C"`` port. - * The ``"GPG3_AD1"``/``"GPG3_AD2"`` buses - these buses can **only** be used on the GoPiGo3 platform. The advantage of using these ones is that the interface between the Raspberry Pi and the sensor is more stable. These buses correspond to the ``"AD1"`` and ``"AD2"`` ports of the GoPiGo3. - -For seeing where the ``"AD1"``/``"AD2"`` are located on the GoPiGo3, please check the GoPiGo3's `documentation `__. +########################################### +API DI-Sensors - Advanced +########################################### ============== DistanceSensor @@ -35,6 +8,7 @@ DistanceSensor .. autoclass:: di_sensors.distance_sensor.DistanceSensor :members: + :show-inheritance: :special-members: :exclude-members: __weakref__ @@ -44,6 +18,7 @@ LightColorSensor .. autoclass:: di_sensors.light_color_sensor.LightColorSensor :members: + :show-inheritance: :special-members: :exclude-members: __weakref__ @@ -53,6 +28,7 @@ TempHumPress .. autoclass:: di_sensors.temp_hum_press.TempHumPress :members: + :show-inheritance: :special-members: :exclude-members: __weakref__ @@ -62,6 +38,7 @@ InertialMeasurementUnit .. autoclass:: di_sensors.inertial_measurement_unit.InertialMeasurementUnit :members: + :show-inheritance: :special-members: :exclude-members: __weakref__ diff --git a/docs/source/api-basic.rst b/docs/source/api-basic.rst new file mode 100644 index 0000000..1832d46 --- /dev/null +++ b/docs/source/api-basic.rst @@ -0,0 +1,52 @@ +.. _api-chapter: + +########################################### +API DI-Sensors - Basic +########################################### + +================== +EasyDistanceSensor +================== + +.. autoclass:: di_sensors.easy_distance_sensor.EasyDistanceSensor + :members: + :show-inheritance: + :special-members: + :exclude-members: __weakref__ + +==================== +EasyLightColorSensor +==================== + +.. autoclass:: di_sensors.easy_light_color_sensor.EasyLightColorSensor + :members: + :show-inheritance: + :special-members: + :exclude-members: __weakref__ + +============= +EasyIMUSensor +============= + +.. autoclass:: di_sensors.easy_inertial_measurement_unit.EasyIMUSensor + :members: + :show-inheritance: + :special-members: + :exclude-members: __weakref__ + +============== +EasyTHPSsensor +============== + +.. autoclass:: di_sensors.easy_temp_hum_press.EasyTHPSensor + :members: + :show-inheritance: + :special-members: + :exclude-members: __weakref__ + +.. _distance sensor: https://www.dexterindustries.com/shop/distance-sensor/ +.. _light color sensor: https://www.dexterindustries.com/shop/light-color-sensor/ +.. _temperature humidity pressure sensor: https://www.dexterindustries.com/shop/temperature-humidity-pressure-sensor/ +.. _inertialmeasurementunit sensor: https://www.dexterindustries.com/shop/imu-sensor/ +.. _github repo: https://github.com/DexterInd/DI_Sensors +.. _gopigo3: https://www.dexterindustries.com/shop/gopigo3-robot-base-kit/ diff --git a/docs/source/conf.py b/docs/source/conf.py index 3ea6a93..aeede29 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -20,6 +20,9 @@ # import sys # sys.path.insert(0, os.path.abspath('.')) +# When building the documentation on Windows with graphviz in mind, use +# <> command +# That should be the path on Windows, but it can vary depending on your system's configuration # -- General configuration ------------------------------------------------ @@ -32,16 +35,16 @@ # ones. import os import sys -from mock import Mock as MagicMock sys.path.insert(0, os.path.abspath('../../Python')) +from mock import Mock as MagicMock class Mock(MagicMock): @classmethod def __getattr__(cls, name): return MagicMock() -MOCK_MODULES = ['periphery'] +MOCK_MODULES = ['periphery', 'I2C_mutex', 'easy_sensors'] sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES) extensions = ['sphinx.ext.autodoc', @@ -50,7 +53,30 @@ def __getattr__(cls, name): 'sphinx.ext.coverage', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', - 'sphinx.ext.viewcode'] + 'sphinx.ext.viewcode', + 'sphinx.ext.inheritance_diagram', + 'sphinx.ext.graphviz', + 'sphinx.ext.autosummary'] + +def _check_deps(): + names = {"six": 'six', + "shutil": 'shutil', + "di_sensors": 'di_sensors',} + missing = [] + for name in names: + try: + __import__(name) + except ImportError: + missing.append(names[name]) + if missing: + raise ImportError( + "The following dependencies are missing to build the " + "documentation: {}".format(", ".join(missing))) + +_check_deps() + +# Import only after checking for dependencies. +import di_sensors autodoc_member_order = "bysource" @@ -76,9 +102,9 @@ def __getattr__(cls, name): # built documents. # # The short X.Y version. -version = u'1.0.0' +# version = u'1.0.0' # The full version, including alpha/beta/rc tags. -release = u'1.0.0' +# release = u'1.0.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -180,5 +206,6 @@ def __getattr__(cls, name): # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/': None} -#intersphinx_mapping = {'gopigo3': ('http://gopigo3.readthedocs.io/en/latest/', None)} +# intersphinx_mapping = {'https://docs.python.org/': None} +intersphinx_mapping = {'python': ('https://docs.python.org/3', None), + 'gopigo3' : ('http://gopigo3.readthedocs.io/en/master/', None)} diff --git a/docs/source/examples/dist_sensor.rst b/docs/source/examples/dist_sensor.rst index 99ba470..fc09200 100644 --- a/docs/source/examples/dist_sensor.rst +++ b/docs/source/examples/dist_sensor.rst @@ -4,15 +4,32 @@ Using the Distance Sensor ######################### +************* +Basic Example +************* + +Before going to the more advanced example program of using the `Distance Sensor`_, we're going to give an example of the easiest way to read from the sensor. + +The following code snippet reads values off of the `Distance Sensor`_ and prints them iteratively in the console. As you'll see, this is far easier than the +following examples, which are more complex to use, but have a more granular control over the device. + +In this example program, connect the `Distance Sensor`_ to an I2C port on whichever platform (`GoPiGo3`_, `GrovePi`_ or `BrickPi3`_) +and then run the following script. + +.. literalinclude:: ../../../Python/Examples/EasyDistanceSensor.py + :language: python + :lines: 14- + +The source file for this example program can be found `here on github `__ + *************** Continuous-mode *************** -In this example program, connect the `Distance Sensor`_ to an I2C port on whichever platform (`GoPiGo3`_, `GrovePi`_ or `BrickPi3`_) -and then run the following script. +Again, just like in the previous example program, connect the `Distance Sensor`_ to an I2C port on whichever platform before running the following script. -The advantage of this script over the one in the following section is that the time taken for reading the distance -can be fine-tuned by the user - for instance, it can be made to run as fast as possible (see the API to see how fast it can read) or it can be made to go very slow. +The advantage of this script over the ones in the following and previous sections is that the time taken for reading the distance +can be fine-tuned by the user - for instance, it can be made to run as fast as possible (to see how fast it can read see the API of :py:class:`~di_sensors.distance_sensor.DistanceSensor`) or it can be made to go very slow. Each fine-tune has its benefits and disadvantages, so the user has to experiment with the sensor and determine what setting suits him best. .. literalinclude:: ../../../Python/Examples/DistanceSensorContinuous.py @@ -21,14 +38,12 @@ Each fine-tune has its benefits and disadvantages, so the user has to experiment The source code for this example program can be found `here on github `__. -Here's how the console output of the script should look like: - *********** Single-mode *********** -In this second example, we have the same physical arrangement as in the first one, the only difference being in how we communicate with the sensor. -This time, we take single-shot readings, which for the user is the simplest way of reading data off the sensor. The only disadvantage is that +In this third example, we have the same physical arrangement as in the second one, the only difference being in how we communicate with the sensor. +This time, we take single-shot readings, which for the user is simpler than having to tune the distance sensor first and then read off of it. The only disadvantage is that there's no fine-control over how fast the sensor is making the readings. .. literalinclude:: ../../../Python/Examples/DistanceSensorSingleShot.py @@ -41,7 +56,7 @@ The source code for this example program can be found `here on github ` before jumping into these example programs. In all these examples, you will be required to use one of the 4 documented sensors and optionally, a `GoPiGo3`_. @@ -22,5 +23,6 @@ In all these examples, you will be required to use one of the 4 documented senso light_color temp_hum imu + mutexes .. _gopigo3: https://www.dexterindustries.com/shop/gopigo-advanced-starter-kit/ diff --git a/docs/source/examples/light_color.rst b/docs/source/examples/light_color.rst index 7270520..7d16c6f 100644 --- a/docs/source/examples/light_color.rst +++ b/docs/source/examples/light_color.rst @@ -4,12 +4,12 @@ Using the Light and Color Sensor ################################ -In order to run this example program, connect the `Light and Color Sensor`_ to an I2C port on whichever platform (`GoPiGo3`_, `GrovePi`_ or `BrickPi3`_) -and then run the following script. +In this short section, we get to see how one can read data off of the `Light and Color Sensor`_ without having to fine-tune the sensor or to deal with hard-to-understand concepts. +Before anything else, connect the `Light and Color Sensor`_ to an I2C port on whichever platform (be it a `GoPiGo3`_, `GrovePi`_ or a `BrickPi3`_) and then run the following script. -The source file for this example program can be found `here on github `__. +The source file for this example program can be found `here on github `__. -.. literalinclude:: ../../../Python/Examples/LightColorSensor.py +.. literalinclude:: ../../../Python/Examples/EasyLightColorSensor.py :language: python :lines: 14- diff --git a/docs/source/examples/mutexes.rst b/docs/source/examples/mutexes.rst new file mode 100644 index 0000000..aeb6c8d --- /dev/null +++ b/docs/source/examples/mutexes.rst @@ -0,0 +1,45 @@ +############# +Using Mutexes +############# + +In this section, we are showing how handy mutexes are when we're trying to access the same resource (a device, for instance a `Distance Sensor`_) +simultaneously from multiple threads. All :ref:`Easy classes ` are thread-safe - what one has to do is to activate the use of mutexes by passing a boolean +parameter to each of the classes' constructor. + +In the following example program, 2 threads are accessing the resource of an :py:class:`~di_sensors.easy_distance_sensor.EasyDistanceSensor` object. +``use_mutex`` parameter is set to ``True`` so that the resource can be accessed from multiple threads/processes (this is what we would call a thread-safe class). +Each of these 2 threads run for ``runtime`` seconds - we didn't make it so one can stop the program while it's running, because that would have been more complex. + +Without the mutex mechanism, accessing the same resource from multiple processes/threads would not be possible. + +.. literalinclude:: ../../../Python/Examples/EasyDistanceSensorMutexes.py + :language: python + :lines: 14- + +.. important:: + + There was no need to use mutexes in the above example, but for the sake of an example, it is a good thing. The idea is that CPython's implementation + has what it's called a **GIL** (*Global Interpreter Lock*) and this only allows one thread to run at once, which is a skewed way of envisioning how threads work, + but it's the reality in Python still. Ideally, a thread can run concurrently with another one. You can read more on the :term:`Global Interpreter Lock here`. + + Still, the implementation we have with mutexes proves to be useful when one wants to launch multiple processes at a time - at that moment, we can talk of true concurrency. + This can happen when multiple instances of Python scripts are launched and when each process tries to access the same resource as the other one. + +The output on the console should look like this - the thread IDs don't mean anything and they are merely just a number used to identify threads. + +.. code-block:: bash + + Thread ID = 1883501680 with distance value = 44 + Thread ID = 1873802352 with distance value = 44 + Thread ID = 1873802352 with distance value = 44 + Thread ID = 1883501680 with distance value = 44 + Thread ID = 1873802352 with distance value = 46 + Thread ID = 1883501680 with distance value = 46 + Thread ID = 1873802352 with distance value = 45 + Thread ID = 1883501680 with distance value = 45 + Thread ID = 1883501680 with distance value = 44 + Thread ID = 1873802352 with distance value = 44 + Thread ID = 1883501680 with distance value = 45 + Thread ID = 1873802352 with distance value = 45 + +.. _distance sensor: https://www.dexterindustries.com/shop/distance-sensor/ diff --git a/docs/source/examples/temp_hum.rst b/docs/source/examples/temp_hum.rst index bcd36dc..09015ce 100644 --- a/docs/source/examples/temp_hum.rst +++ b/docs/source/examples/temp_hum.rst @@ -7,9 +7,9 @@ Temperature Humidity and Pressure Sensor In order to run this example program, connect the `Temperature Humidity and Pressure Sensor`_ to an I2C port on whichever platform (`GoPiGo3`_, `GrovePi`_ or `BrickPi3`_) and then run the following script. -The source file for this example program can be found `here on github `__. +The source file for this example program can be found `here on github `__. -.. literalinclude:: ../../../Python/Examples/TempHumPress.py +.. literalinclude:: ../../../Python/Examples/EasyTempHumPress.py :language: python :lines: 14- diff --git a/docs/source/index.rst b/docs/source/index.rst index aa9f584..1956f7f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -17,7 +17,9 @@ Dexter Industries DI-Sensors Documentation! about quickstart examples/index - api + structure + api-basic + api-advanced devguide faq diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst index a8d5436..001c4d4 100644 --- a/docs/source/quickstart.rst +++ b/docs/source/quickstart.rst @@ -51,7 +51,7 @@ In order to install the DI-Sensors package you need to open up a terminal on you .. code-block:: bash - sudo sh -c "curl -kL dexterindustries.com/update_sensors | bash" + curl -kL dexterindustries.com/update_sensors | sudo bash Enter the command and follow the instructions given, if provided. This command can also be used for updating the package with the latest changes. diff --git a/docs/source/structure.rst b/docs/source/structure.rst new file mode 100644 index 0000000..2def5b7 --- /dev/null +++ b/docs/source/structure.rst @@ -0,0 +1,212 @@ +.. _structure-chapter: + +########################################### +On Library & Hardware +########################################### + +============ +Requirements +============ + +Before you check the API for the DI-Sensors, please make sure you have the ``DI-Sensors`` package installed. You can do this by checking with ``pip`` by typing the following command. + +.. code-block:: bash + + pip show DI-Sensors + +Or you can check by trying to import the package in a Python console the following way: + +.. code-block:: python + + import di_sensors + +If there's nothing to be shown when ``pip show``-ing or you get an import error on the :py:mod:`di_sensors` package, then please check the :ref:`Getting Started ` section and follow the instructions. + +.. _hardware-interface-section: + +================== +Hardware interface +================== + +Instantiating the :ref:`4 sensors` in Python is a matter of choosing the right bus. Thus, there are 3 buses to choose from, depending on the context: + + * The ``"RPI_1"`` bus - this bus can be used on all 4 platforms we have (the GoPiGo3, GoPiGo, BrickPi3 & GrovePi). This bus corresponds to the ``"I2C"`` port. + * The ``"GPG3_AD1"``/``"GPG3_AD2"`` buses - these buses can **only** be used on the GoPiGo3 platform. The advantage of using these ones is that the interface between the Raspberry Pi and the sensor is more stable. These buses correspond to the ``"AD1"`` and ``"AD2"`` ports of the GoPiGo3. + +.. important:: + + These notations for ports (``"RPI_1"``, ``"GPG3_AD1"`` and ``"GPG3_AD2"``) are only required for classes that *don't start* with the **Easy** word, + specifically for: + + * :py:class:`~di_sensors.distance_sensor.DistanceSensor` + * :py:class:`~di_sensors.inertial_measurement_unit.InertialMeasurementUnitSensor` + * :py:class:`~di_sensors.light_color_sensor.LightColorSensor` + * :py:class:`~di_sensors.temp_hum_press.TempHumPress` + + If you choose to use a sensor library *that starts* with the **Easy** word, you can use the same notations as those used and mentioned in the GoPiGo3's :ref:`documentation `, such as: + + * ``"I2C"`` instead of ``"RPI_1"``. + * ``"AD1/AD2"`` instead of ``"GPG3_AD1/GPG3_AD2"``. + +For seeing where the ``"AD1"``/``"AD2"`` are located on the GoPiGo3, please check the GoPiGo3's :ref:`documentation `. + +================== +Library Structure +================== + +------------------ +Classes Short-List +------------------ + +The classes that are more likely to be of interest are graphically displayed shortly after this. In this graphic you can also notice inheritance links +between different classes. We can notice 3 groups of classes: + +* Those that start with the **Easy** word in them and are easier to use and may provide some high-level functionalities. +* Those that don't start with the **Easy** word and yet are related to those that are. These are generally intented for power users. +* Those that look like they might represent a model number (that belong to modules such as :py:mod:`di_sensors.VL53L0X`, :py:mod:`di_sensors.BME280`, etc). + These are intented for those who want to extend the functionalities of our library and are not documented here. + +.. inheritance-diagram:: + di_sensors.easy_distance_sensor + di_sensors.distance_sensor + di_sensors.easy_inertial_measurement_unit + di_sensors.easy_temp_hum_press + di_sensors.inertial_measurement_unit + di_sensors.easy_light_color_sensor + di_sensors.light_color_sensor + di_sensors.easy_mutex + di_sensors.temp_hum_press + di_sensors.VL53L0X + di_sensors.BME280 + di_sensors.BNO055 + di_sensors.PCA9570 + di_sensors.TCS34725 + +.. note:: + + Since this is an interactive graphic, you can click on the displayed classes and it'll take you to the documentation of a given class, if provided. + +-------------------- +Functions Short-List +-------------------- + +Here's a short summary of all classes and methods. There's a list going on for each class. We first start off by listing the **Easy** classes/methods +and then we end up showing the classes/methods for power users. +In this short summary, we're not covering the low-level classes that are not even documented in this documentation. + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Easy - TempHumPress +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + + di_sensors.easy_temp_hum_press.EasyTHPSensor + di_sensors.easy_temp_hum_press.EasyTHPSensor.__init__ + di_sensors.easy_temp_hum_press.EasyTHPSensor.safe_celsius + di_sensors.easy_temp_hum_press.EasyTHPSensor.safe_fahrenheit + di_sensors.easy_temp_hum_press.EasyTHPSensor.safe_pressure + di_sensors.easy_temp_hum_press.EasyTHPSensor.safe_humidity + + +^^^^^^^^^^^^^^^^^^^^ +Easy - Light & Color +^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + + di_sensors.easy_light_color_sensor.EasyLightColorSensor + di_sensors.easy_light_color_sensor.EasyLightColorSensor.__init__ + di_sensors.easy_light_color_sensor.EasyLightColorSensor.translate_to_hsv + di_sensors.easy_light_color_sensor.EasyLightColorSensor.safe_raw_colors + di_sensors.easy_light_color_sensor.EasyLightColorSensor.safe_rgb + di_sensors.easy_light_color_sensor.EasyLightColorSensor.guess_color_hsv + +^^^^^^^^^^^^^^^^^^^^ +Easy - Distance +^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + + di_sensors.easy_distance_sensor.EasyDistanceSensor + di_sensors.easy_distance_sensor.EasyDistanceSensor.__init__ + di_sensors.easy_distance_sensor.EasyDistanceSensor.read_mm + di_sensors.easy_distance_sensor.EasyDistanceSensor.read + di_sensors.easy_distance_sensor.EasyDistanceSensor.read_inches + +^^^^^^^^^^^^^^^^^^^^ +Easy - IMU +^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + + di_sensors.easy_inertial_measurement_unit.EasyIMUSensor + di_sensors.easy_inertial_measurement_unit.EasyIMUSensor.__init__ + di_sensors.easy_inertial_measurement_unit.EasyIMUSensor.reconfig_bus + di_sensors.easy_inertial_measurement_unit.EasyIMUSensor.safe_calibrate + di_sensors.easy_inertial_measurement_unit.EasyIMUSensor.safe_calibration_status + di_sensors.easy_inertial_measurement_unit.EasyIMUSensor.convert_heading + di_sensors.easy_inertial_measurement_unit.EasyIMUSensor.safe_read_euler + di_sensors.easy_inertial_measurement_unit.EasyIMUSensor.safe_read_magnetometer + di_sensors.easy_inertial_measurement_unit.EasyIMUSensor.safe_north_point + +^^^^^^^^^^^^^^^^^^^^^^^^^^^ +TempHumPress +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + + di_sensors.temp_hum_press.TempHumPress + di_sensors.temp_hum_press.TempHumPress.__init__ + di_sensors.temp_hum_press.TempHumPress.get_temperature_celsius + di_sensors.temp_hum_press.TempHumPress.get_temperature_fahrenheit + di_sensors.temp_hum_press.TempHumPress.get_pressure + di_sensors.temp_hum_press.TempHumPress.get_humidity + di_sensors.temp_hum_press.TempHumPress.get_humidity + +^^^^^^^^^^^^^^^^^^^^ +Light & Color +^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + + di_sensors.light_color_sensor.LightColorSensor + di_sensors.light_color_sensor.LightColorSensor.__init__ + di_sensors.light_color_sensor.LightColorSensor.set_led + di_sensors.light_color_sensor.LightColorSensor.get_raw_colors + +^^^^^^^^^^^^^^^^^^^^ +Distance +^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + + di_sensors.distance_sensor.DistanceSensor + di_sensors.distance_sensor.DistanceSensor.__init__ + di_sensors.distance_sensor.DistanceSensor.start_continuous + di_sensors.distance_sensor.DistanceSensor.read_range_continuous + di_sensors.distance_sensor.DistanceSensor.read_range_single + di_sensors.distance_sensor.DistanceSensor.timeout_occurred + +^^^^^^^^^^^^^^^^^^^^ +IMU +^^^^^^^^^^^^^^^^^^^^ + +.. autosummary:: + + di_sensors.inertial_measurement_unit.InertialMeasurementUnit + di_sensors.inertial_measurement_unit.InertialMeasurementUnit.__init__ + di_sensors.inertial_measurement_unit.InertialMeasurementUnit.read_euler + di_sensors.inertial_measurement_unit.InertialMeasurementUnit.read_magnetometer + di_sensors.inertial_measurement_unit.InertialMeasurementUnit.read_gyroscope + di_sensors.inertial_measurement_unit.InertialMeasurementUnit.read_accelerometer + di_sensors.inertial_measurement_unit.InertialMeasurementUnit.read_linear_acceleration + di_sensors.inertial_measurement_unit.InertialMeasurementUnit.read_gravity + di_sensors.inertial_measurement_unit.InertialMeasurementUnit.read_quaternion + di_sensors.inertial_measurement_unit.InertialMeasurementUnit.read_temperature + + +.. _distance sensor: https://www.dexterindustries.com/shop/distance-sensor/ +.. _imu sensor: https://www.dexterindustries.com/shop/imu-sensor/ +.. _inertialmeasurementunit sensor: https://www.dexterindustries.com/shop/imu-sensor/ +.. _light color sensor: https://www.dexterindustries.com/shop/light-color-sensor/ +.. _temperature humidity pressure sensor: https://www.dexterindustries.com/shop/temperature-humidity-pressure-sensor/ diff --git a/environment.yml b/environment.yml new file mode 100644 index 0000000..aad018a --- /dev/null +++ b/environment.yml @@ -0,0 +1,9 @@ +name: readthedocs +dependencies: +- python=3 +- pip +- graphviz +- mock +- six +- sphinx +- sphinx_rtd_theme diff --git a/readthedocs.yml b/readthedocs.yml new file mode 100644 index 0000000..aebb9e0 --- /dev/null +++ b/readthedocs.yml @@ -0,0 +1,2 @@ +conda: + file: environment.yml