From b05f91909f0259569aa220258079265828ee4951 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20Van=C4=9Bk?= <linuxtardis@gmail.com>
Date: Fri, 26 May 2023 00:37:21 +0200
Subject: [PATCH] Add API docs for Python EV3 sensors

---
 lib/ev3/color.py | 69 +++++++++++++++++++++++++++++++++++++++++++++---
 lib/ev3/gyro.py  | 45 +++++++++++++++++++++++++++++++
 lib/ev3/infra.py | 16 +++++++++++
 lib/ev3/sonic.py | 13 +++++++++
 lib/ev3/touch.py |  7 +++++
 lib/ev3/uart.py  | 14 ++++++++++
 6 files changed, 161 insertions(+), 3 deletions(-)

diff --git a/lib/ev3/color.py b/lib/ev3/color.py
index 56655e3..ab90c39 100644
--- a/lib/ev3/color.py
+++ b/lib/ev3/color.py
@@ -19,7 +19,19 @@ COLOR_SENSOR_ID = const(29)
 
 
 class ColorSensorCalibration:
+    """
+    Calibration constants used for offset and gain correction for RGB mode measurements.
+    """
     def __init__(self, max_r: float, max_g: float, max_b: float, min_r: float, min_g: float, min_b: float):
+        """
+        Convert observed min/max RGB values into scaling constants.
+        :param max_r: Maximum measured red ADC value
+        :param max_g: Maximum measured green ADC value
+        :param max_b: Maximum measured blue ADC value
+        :param min_r: Minimum measured red ADC value
+        :param min_g: Minimum measured green ADC value
+        :param min_b: Minimum measured blue ADC value
+        """
         self.r_scale = 100 / (max_r - min_r)
         self.g_scale = 100 / (max_g - min_g)
         self.b_scale = 100 / (max_b - min_b)
@@ -46,26 +58,63 @@ class ColorSensor(EV3UartSensor):
     PYBRICKS_RGB_CALIBRATION = ColorSensorCalibration(388.8, 360.0, 198.3, 1.2, 2.9, 7.1)
 
     def __init__(self, port: int, wait_ms: int | None = None):
+        """
+        Initialize a new EV3 color sensor object at the given port.
+        :param port: Port to which the sensor is attached. Use one of Port.S1~Port.S4.
+        :param wait_ms: Wait for up to this time for the sensor to start communicating.
+                        If not provided, the constructor will return immediately.
+        """
         super().__init__(port, wait_ms, COLOR_SENSOR_ID)
         self.rgb_calibration = ColorSensor.DEFAULT_RGB_CALIBRATION
 
     def reflection(self) -> int:
+        """
+        Measure surfare reflectivity for red light. The values
+        are calibrated within the sensor itself.
+        :return: Reflectivity in %
+        """
         return self.read_raw_mode(ColorSensor.MODE_REFLECT)[0]
 
     def reflection_raw(self) -> int:
+        """
+        Measure surfare reflectivity for red light. The values
+        are uncalibrated. Code using this function must thus perform
+        range scaling/shifting manually.
+
+        This method will likely provide higher resolution than
+        reflection() and therefore it might be advantageous
+        to use this function for line-following applications.
+        :return: Reflectivity in sensor ADC units
+        """
         items = self.read_raw_mode(ColorSensor.MODE_REFLECT_RAW)
         led_on = items[0]
         led_off = items[1]
         return max(led_off - led_on, 0)
 
     def ambient(self) -> int:
+        """
+        Measure ambient light intensity. The values are calibrated
+        within the sensor itself.
+        :return: Light intensity in percent of something
+        """
         return self.read_raw_mode(ColorSensor.MODE_AMBIENT)[0]
 
     def rgb_raw(self) -> tuple:
+        """
+        Measure surface reflectivity for red, green and blue light.
+        Returned values are not calibrated.
+        :return: Uncalibrated reflectivity for all colors in sensor ADC units.
+        """
         values = self.read_raw_mode(ColorSensor.MODE_RGB_RAW)
         return values[0], values[1], values[2]
 
     def rgb(self) -> tuple:
+        """
+        Measure surface reflectivity for red, green and blue light.
+        Returned values are calibrated on Open-Cube according to
+        currently configured `rgb_calibration`.
+        :return: Reflectivity for all colors in % of maximum observed by the sensor
+        """
         r, g, b = self.rgb_raw()
         cal = self.rgb_calibration
 
@@ -76,13 +125,21 @@ class ColorSensor(EV3UartSensor):
         )
 
     def hsv(self) -> tuple:
+        """
+        Measure surface reflectivity for red, green and blue light.
+        Then, convert it to the HSV colorspace.
+        Returned values are calibrated on Open-Cube according to
+        currently configured `rgb_calibration`.
+        :return: Triplet (h,s,v) representing the surface color in the HSV colorspace.
+                 Hue is in degrees, saturation and value are in percent.
+        """
         # See https://dl.acm.org/doi/epdf/10.1145/965139.807361
         R, G, B = self.rgb()
         R = R / 100
         G = G / 100
         B = B / 100
 
-        V = max(R, G, B) # value [0-1]
+        V = max(R, G, B)  # value [0-1]
         X = min(R, G, B)
 
         if V < 0.001:
@@ -90,7 +147,7 @@ class ColorSensor(EV3UartSensor):
             return 0, 0, V * 100
 
         span = (V - X)
-        S = span / V # saturation [0-1]
+        S = span / V  # saturation [0-1]
 
         if S < 0.001:
             # gray, define hue as zero
@@ -119,8 +176,14 @@ class ColorSensor(EV3UartSensor):
 
         return H * 60, S * 100, V * 100
 
-
     def color(self) -> int | None:
+        """
+        Measure the color of a LEGO brick.
+        The measurement is provided by the sensor itself.
+        If this works poorly for your use case, hsv() might be a better choice.
+        :return: None if the sensor cannot identify a color, otherwise
+                 one of lib.ev3.Color constants
+        """
         code = self.read_raw_mode(ColorSensor.MODE_COLOR)[0]
         if code != 0:
             return code
diff --git a/lib/ev3/gyro.py b/lib/ev3/gyro.py
index 174e569..cd92b86 100644
--- a/lib/ev3/gyro.py
+++ b/lib/ev3/gyro.py
@@ -21,21 +21,40 @@ class GyroSensor(EV3UartSensor):
     MODE_TILT_ANGLE = const(6)
 
     def __init__(self, port: int, wait_ms: int | None = None):
+        """
+        Initialize a new EV3 gyro sensor object at the given port.
+        :param port: Port to which the sensor is attached. Use one of Port.S1~Port.S4.
+        :param wait_ms: Wait for up to this time for the sensor to start communicating.
+                        If not provided, the constructor will return immediately.
+        """
         super().__init__(port, wait_ms, GYRO_SENSOR_ID)
         self.offset_value = 0
         self.offset_mode = OFFSET_ANGLE  # sensor boots up in GYRO-ANG
 
     def angle(self) -> int:
+        """
+        Measure current sensor orientation.
+        :return: Sensor angle in degrees
+        """
         if self.offset_mode != OFFSET_ANGLE:
             self.offset_mode = OFFSET_ANGLE
             self.offset_value = 0
         return self.read_raw_mode(GyroSensor.MODE_ANGLE)[0] - self.offset_value
 
     def speed(self) -> int:
+        """
+        Measure current sensor rate of rotation.
+        :return: Sensor angular speed in deg/s
+        """
         self.offset_mode = OFFSET_INVALID
         return self.read_raw_mode(GyroSensor.MODE_RATE)[0]
 
     def reset_angle(self, angle: int):
+        """
+        Adjust Open-Cube maintained zero point of angle()/tilst_angle()/angle_and_rate() readings.
+        :param angle: New value that the reading functions will return
+                      (assuming that the sensor will not move)
+        """
         if self.offset_mode == OFFSET_ANGLERATE:
             self.offset_value = self.read_raw_mode(GyroSensor.MODE_ANGLE_AND_RATE)[0] - angle
         elif self.offset_mode == OFFSET_TILTANGLE:
@@ -45,6 +64,14 @@ class GyroSensor(EV3UartSensor):
             self.offset_mode = OFFSET_ANGLE
 
     def angle_and_speed(self):
+        """
+        Measure both the sensor angle and rate of rotation at once.
+
+        This is better than angle()+speed() as it uses a dedicated
+        sensor mode for measuring both at once.
+
+        :return: (angle, rate) tuple in (deg/s, deg).
+        """
         if self.offset_mode != OFFSET_ANGLERATE:
             self.offset_mode = OFFSET_ANGLERATE
             self.offset_value = 0
@@ -52,14 +79,32 @@ class GyroSensor(EV3UartSensor):
         return values[0] - self.offset_value, values[1]
 
     def coarse_speed(self):
+        """
+        Measure current sensor rate of rotation with lower resolution, but higher range.
+        :return: Sensor angular speed in deg/s
+        """
         self.offset_mode = OFFSET_INVALID
         return self.read_raw_mode(GyroSensor.MODE_FAST_RATE)[0]
 
     def tilt_speed(self):
+        """
+        Measure current sensor rate of rotation along a second axis.
+
+        Beware: not all sensors provide two-axis measurements.
+
+        :return: Sensor angular speed in deg/s
+        """
         self.offset_mode = OFFSET_INVALID
         return self.read_raw_mode(GyroSensor.MODE_TILT_RATE)[0]
 
     def tilt_angle(self):
+        """
+        Measure current sensor orientation along a second axis.
+
+        Beware: not all sensors provide two-axis measurements.
+
+        :return: Sensor angle in degrees
+        """
         if self.offset_mode != OFFSET_TILTANGLE:
             self.offset_mode = OFFSET_TILTANGLE
             self.offset_value = 0
diff --git a/lib/ev3/infra.py b/lib/ev3/infra.py
index 952aa12..e1bacf8 100644
--- a/lib/ev3/infra.py
+++ b/lib/ev3/infra.py
@@ -13,12 +13,28 @@ class InfraredSensor(EV3UartSensor):
     MODE_REMOTE = const(2)
 
     def __init__(self, port: int, wait_ms: int | None = None):
+        """
+        Initialize a new EV3 infrared sensor object at the given port.
+        :param port: Port to which the sensor is attached. Use one of Port.S1~Port.S4.
+        :param wait_ms: Wait for up to this time for the sensor to start communicating.
+                        If not provided, the constructor will return immediately.
+        """
         super().__init__(port, wait_ms, INFRA_SENSOR_ID)
 
     def distance(self) -> int:
+        """
+        Measure the relative proximity to the nearest surface in percent.
+        :return: Proximity in percent
+        """
         return self.read_raw_mode(InfraredSensor.MODE_PROXIMITY)[0]
 
     def seeker(self, channel: int) -> tuple:
+        """
+        Determine where the remote controller for the given channel exists.
+        See also http://docs.ev3dev.org/projects/lego-linux-drivers/en/ev3dev-buster/sensor_data.html#lego-ev3-ir
+        :param channel: Which channel to check (1-4).
+        :return: Tuple (heading, distance). Heading ranges from -25 to +25, distance is 0-100.
+        """
         items = self.read_raw_mode(InfraredSensor.MODE_SEEK)
         pair_offset = 2 * (channel - 1)
         return items[pair_offset + 0], items[pair_offset + 1]
diff --git a/lib/ev3/sonic.py b/lib/ev3/sonic.py
index 2fffb1e..218fb8a 100644
--- a/lib/ev3/sonic.py
+++ b/lib/ev3/sonic.py
@@ -16,15 +16,28 @@ class UltrasonicSensor(EV3UartSensor):
     MODE_SINGLE_CM = const(3)
 
     def __init__(self, port: int, wait_ms: int | None = None):
+        """
+        Initialize a new EV3 ultrasonic sensor object at the given port.
+        :param port: Port to which the sensor is attached. Use one of Port.S1~Port.S4.
+        :param wait_ms: Wait for up to this time for the sensor to start communicating.
+                        If not provided, the constructor will return immediately.
+        """
         super().__init__(port, wait_ms, SONIC_SENSOR_ID)
 
     def distance(self, silent=False) -> int:
+        """
+        Measure the distance to the nearest obstacle in front of the sensor.
+        :return: Distance in millimeters or a magic value of 2550 if the measurement fails.
+        """
         cont_value = self.read_raw_mode(UltrasonicSensor.MODE_CONTINUOUS_CM)[0]
         if silent:
             self.start_set_mode(UltrasonicSensor.MODE_SINGLE_CM)
         return cont_value
 
     def presence(self) -> bool:
+        """
+        Check if another ultrasonic sensor is active within the hearing distance of this sensor.
+        """
         return self.read_raw_mode(UltrasonicSensor.MODE_LISTEN)[0] > 0
 
     # this is likely not needed! distance(silent=True) seems to work OK-ish
diff --git a/lib/ev3/touch.py b/lib/ev3/touch.py
index 8e81c5f..7eee0ef 100644
--- a/lib/ev3/touch.py
+++ b/lib/ev3/touch.py
@@ -5,6 +5,10 @@ from lib.hw_defs.ports import SENSOR_PORT_PINS
 
 class TouchSensor:
     def __init__(self, port: int):
+        """
+        Initialize a new EV3 touch sensor object at the given port.
+        :param port: Port to which the sensor is attached. Use one of Port.S1~Port.S4.
+        """
         if port < 0 or port > 3:
             raise ValueError(f"Sensor port index {port} is out of range")
         pins = SENSOR_PORT_PINS[port]
@@ -12,4 +16,7 @@ class TouchSensor:
         self.btn_pin = Pin(self.btn_pin_no, mode=Pin.IN)
 
     def pressed(self) -> bool:
+        """
+        Checks if the button is pressed.
+        """
         return self.btn_pin.value() == 1
diff --git a/lib/ev3/uart.py b/lib/ev3/uart.py
index 720bc41..bcf9b3e 100644
--- a/lib/ev3/uart.py
+++ b/lib/ev3/uart.py
@@ -2,11 +2,21 @@ import ev3_sensor_ll
 
 
 class SensorMismatchError(RuntimeError):
+    """
+    Incorrect sensor type has been connected to Open-Cube.
+    """
     pass
 
 
 class EV3UartSensor(ev3_sensor_ll.EV3Sensor):
     def __init__(self, port: int, wait_ms: int | None = None, proper_id: int | None = None):
+        """
+        Initialize a new generic EV3 sensor object at the given port.
+        :param port: Port to which the sensor is attached. Use one of Port.S1~Port.S4.
+        :param wait_ms: Wait for up to this time for the sensor to start communicating.
+                        If not provided, the constructor will return immediately.
+        :param proper_id: If set, check that the connected EV3 sensor is using the given ID.
+        """
         super().__init__(port)
         self._port = port
         self._proper_id = proper_id
@@ -18,6 +28,10 @@ class EV3UartSensor(ev3_sensor_ll.EV3Sensor):
                 raise
 
     def wait_for_connection(self, wait_ms: int = 3000):
+        """
+        Wait for up to wait_ms milliseconds for the sensor
+        to start communicating.
+        """
         super().wait_for_connection(wait_ms)
         if self._proper_id is not None and self.sensor_id() != self._proper_id:
             raise SensorMismatchError(f"Wrong EV3 sensor is plugged into port {self._port + 1}")
-- 
GitLab