|
| 1 | +import easy_sensors |
| 2 | +from I2C_mutex import Mutex |
| 3 | +import time |
| 4 | +from os import path |
| 5 | +# from numpy import mean, std |
| 6 | + |
| 7 | +try: |
| 8 | + from di_sensors import line_follower |
| 9 | +except: |
| 10 | + # think about what we should do |
| 11 | + # goal is to support both line followers seamlessly |
| 12 | + raise ImportError("Line Follower library not found") |
| 13 | + exit() |
| 14 | + |
| 15 | +class EasyLineFollower(Sensor): |
| 16 | + """ |
| 17 | + Class for interacting with the `Line Follower`_ sensor. |
| 18 | + With this sensor, you can make your robot follow a black line on a white background. |
| 19 | +
|
| 20 | + The `Line Follower`_ sensor has 5 IR sensors. |
| 21 | + Each IR sensor is capable of diferentiating a black surface from a white one. |
| 22 | +
|
| 23 | + In order to create an object of this class, we would do it like in the following example. |
| 24 | +
|
| 25 | + .. code-block:: python |
| 26 | +
|
| 27 | + # initialize an EasyGoPiGo3 object |
| 28 | + gpg3_obj = EasyGoPiGo3() |
| 29 | +
|
| 30 | + # and then initialize the LineFollower object |
| 31 | + line_follower = gpg3_obj.init_line_follower() |
| 32 | +
|
| 33 | + # use it however you want it |
| 34 | + line_follower.read_raw_sensors() |
| 35 | +
|
| 36 | + .. warning:: |
| 37 | +
|
| 38 | + This class requires the :py:mod:`line_sensor` library. |
| 39 | +
|
| 40 | + """ |
| 41 | + |
| 42 | + DIR_PATH="/home/pi/Dexter/" |
| 43 | + # If for some reason this got installed in a weird way, |
| 44 | + # then default to pi home dir |
| 45 | + if not path.isdir(DIR_PATH): |
| 46 | + DIR_PATH = "/home/pi/" |
| 47 | + |
| 48 | + FILE_BLACK=dir_path+'black_line.txt' |
| 49 | + FILE_WHITE=dir_path+'white_line.txt' |
| 50 | + FILE_RANGE=dir_path+'range_line.txt' |
| 51 | + |
| 52 | + def __init__(self, port="I2C", gpg=None, use_mutex=False): |
| 53 | + """ |
| 54 | + Constructor for initalizing a :py:class:`~easygopigo3.LineFollower` object. |
| 55 | +
|
| 56 | + :param str port = "I2C": The port to which we have connected the `Line Follower`_ sensor. |
| 57 | + :param easygopigo3.EasyGoPiGo3 gpg = None: The :py:class:`~easygopigo3.EasyGoPiGo3` object that we need for instantiating this object. |
| 58 | + :param bool use_mutex = False: When using multiple threads/processes that access the same resource/device, mutexes should be enabled. |
| 59 | + :raises ImportError: If the :py:mod:`line_follower` module couldn't be found. |
| 60 | + :raises TypeError: If the ``gpg`` parameter is not a :py:class:`~easygopigo3.EasyGoPiGo3` object. |
| 61 | + :raises IOError: If the line follower is not responding. |
| 62 | +
|
| 63 | +
|
| 64 | + The only value the ``port`` parameter can take is ``"I2C"``. |
| 65 | +
|
| 66 | + The I2C ports' location on the `GoPiGo3`_ robot can be seen in the following graphical representation: :ref:`hardware-ports-section`. |
| 67 | +
|
| 68 | + """ |
| 69 | + try: |
| 70 | + self.set_descriptor("Line Follower") |
| 71 | + easy_sensors.Sensor.__init__(self, port, "INPUT", gpg, use_mutex) |
| 72 | + if line_sensor.read_sensor() == [-1, -1, -1, -1, -1]: |
| 73 | + raise IOError("Line Follower not responding") |
| 74 | + except: |
| 75 | + raise |
| 76 | + |
| 77 | + # Function for removing outlier values |
| 78 | + # For bigger std_factor_threshold, the filtering is less aggressive |
| 79 | + # For smaller std_factor_threshold, the filtering is more aggressive |
| 80 | + # std_factor_threshold must be bigger than 0 |
| 81 | + # def _statisticalNoiseReduction(values, std_factor_threshold = 2): |
| 82 | + # if len(values) == 0: |
| 83 | + # return [] |
| 84 | + |
| 85 | + # calculated_mean = mean(values) |
| 86 | + # standard_deviation = std(values) |
| 87 | + |
| 88 | + # # just return if we only got constant values |
| 89 | + # if standard_deviation == 0: |
| 90 | + # return values |
| 91 | + |
| 92 | + # # remove outlier values which are less than the average but bigger than the calculated threshold |
| 93 | + # filtered_values = [element for element in values if element > mean - std_factor_threshold * standard_deviation] |
| 94 | + # # the same but in the opposite direction |
| 95 | + # filtered_values = [element for element in filtered_values if element < mean + std_factor_threshold * standard_deviation] |
| 96 | + |
| 97 | + # return filtered_values |
| 98 | + |
| 99 | + def read_raw_sensors(self): |
| 100 | + """ |
| 101 | + Read the 6 IR sensors of the `Line Follower`_ sensor. |
| 102 | + Note: the old line follower (with red board) has 5 sensors, not 6. |
| 103 | +
|
| 104 | + :returns: A list with 5 10-bit numbers that represent the readings from the line follower device. |
| 105 | + :rtype: list[int] |
| 106 | + :raises IOError: If the line follower is not responding. |
| 107 | +
|
| 108 | + """ |
| 109 | + |
| 110 | + try: |
| 111 | + sensor_vals = line_sensor.read_sensor() |
| 112 | + except IOError as e: |
| 113 | + print(e) |
| 114 | + sensor_vals = [-1]*6 |
| 115 | + |
| 116 | + if five_vals != [-1, -1, -1, -1, -1]: |
| 117 | + return five_vals |
| 118 | + else: |
| 119 | + raise IOError("Line Follower not responding") |
| 120 | + |
| 121 | + def get_white_calibration(self): |
| 122 | + """ |
| 123 | + Place the `GoPiGo3`_ robot on top of a white-colored surface. |
| 124 | + After that, call this method for calibrating the robot on a white surface. |
| 125 | +
|
| 126 | + :returns: A list with 5 10-bit numbers that represent the readings of line follower sensor. |
| 127 | + :rtype: int |
| 128 | +
|
| 129 | + Also, for fully calibrating the sensor, the :py:class:`~easygopigo3.LineFollower.get_black_calibration` method also needs to be called. |
| 130 | +
|
| 131 | + """ |
| 132 | + return line_sensor.get_white_line() |
| 133 | + |
| 134 | + def get_black_calibration(self): |
| 135 | + """ |
| 136 | + Place the `GoPiGo3`_ robot on top of a black-colored surface. |
| 137 | + After that, call this method for calibrating the robot on a black surface. |
| 138 | +
|
| 139 | + :returns: A list with 5 10-bit numbers that represent the readings of line follower sensor. |
| 140 | + :rtype: int |
| 141 | +
|
| 142 | + Also, for fully calibrating the sensor, the :py:class:`~easygopigo3.LineFollower.get_white_calibration` method also needs to be called. |
| 143 | +
|
| 144 | + """ |
| 145 | + return line_sensor.get_black_line() |
| 146 | + |
| 147 | + def read(self): |
| 148 | + """ |
| 149 | + Reads the 5 IR sensors of the `Line Follower`_ sensor. |
| 150 | +
|
| 151 | + :returns: A list with 5 numbers that represent the readings of the line follower device. The values are either **0** (for black) or **1** (for white). |
| 152 | + :rtype: list[int] |
| 153 | +
|
| 154 | + .. warning:: |
| 155 | +
|
| 156 | + If an error occurs, a list of **5 numbers** with values set to **-1** will be returned. |
| 157 | + This may be caused by bad calibration values. |
| 158 | +
|
| 159 | + Please use :py:meth:`~easygopigo3.LineFollower.get_black_calibration` or :py:meth:`~easygopigo3.LineFollower.get_white_calibration` methods before calling this method. |
| 160 | +
|
| 161 | + """ |
| 162 | + five_vals = scratch_line.absolute_line_pos() |
| 163 | + |
| 164 | + return five_vals |
| 165 | + |
| 166 | + def read_position(self): |
| 167 | + """ |
| 168 | + Returns a string telling to which side the black line that we're following is located. |
| 169 | +
|
| 170 | + :returns: String that's indicating the location of the black line. |
| 171 | + :rtype: str |
| 172 | +
|
| 173 | + The strings this method can return are the following: |
| 174 | +
|
| 175 | + * ``"center"`` - when the line is found in the middle. |
| 176 | + * ``"black"`` - when the line follower sensor only detects black surfaces. |
| 177 | + * ``"white"`` - when the line follower sensor only detects white surfaces. |
| 178 | + * ``"left"`` - when the black line is located on the left of the sensor. |
| 179 | + * ``"right"`` - when the black line is located on the right of the sensor. |
| 180 | +
|
| 181 | + .. note:: |
| 182 | +
|
| 183 | + This isn't the most "intelligent" algorithm for following a black line, but it proves the point and it works. |
| 184 | +
|
| 185 | + """ |
| 186 | + five_vals = [-1, -1, -1, -1, -1] |
| 187 | + |
| 188 | + |
| 189 | + five_vals = self.read() |
| 190 | + |
| 191 | + if five_vals == [0, 0, 1, 0, 0] or five_vals == [0, 1, 1, 1, 0]: |
| 192 | + return "center" |
| 193 | + if five_vals == [1, 1, 1, 1, 1]: |
| 194 | + return "black" |
| 195 | + if five_vals == [0, 0, 0, 0, 0]: |
| 196 | + return "white" |
| 197 | + if five_vals == [0, 1, 1, 0, 0] or \ |
| 198 | + five_vals == [0, 1, 0, 0, 0] or \ |
| 199 | + five_vals == [1, 0, 0, 0, 0] or \ |
| 200 | + five_vals == [1, 1, 0, 0, 0] or \ |
| 201 | + five_vals == [1, 1, 1, 0, 0] or \ |
| 202 | + five_vals == [1, 1, 1, 1, 0]: |
| 203 | + return "left" |
| 204 | + if five_vals == [0, 0, 0, 1, 0] or \ |
| 205 | + five_vals == [0, 0, 1, 1, 0] or \ |
| 206 | + five_vals == [0, 0, 0, 0, 1] or \ |
| 207 | + five_vals == [0, 0, 0, 1, 1] or \ |
| 208 | + five_vals == [0, 0, 1, 1, 1] or \ |
| 209 | + five_vals == [0, 1, 1, 1, 1]: |
| 210 | + return "right" |
| 211 | + return "unknown" |
| 212 | + |
| 213 | + def read_position_str(self): |
| 214 | + """ |
| 215 | + returns a string of five letters indicating what the line sensor is seeing. |
| 216 | + 'b' indicates that specific sensor has detected a black line. |
| 217 | + 'w' indicates that specific sensor has not detected a black line. |
| 218 | +
|
| 219 | + :returns: String indicating what the line follower just read. |
| 220 | + :rtype: str |
| 221 | +
|
| 222 | + Here's an example of what could get returned: |
| 223 | + * ``'bbbbb'`` - when the line follower reads black on all sensors. |
| 224 | + * ``'wwbww'`` - when the line follower is perfectly centered. |
| 225 | + * ``'bbbww'`` - when the line follower reaches an intersection. |
| 226 | + """ |
| 227 | + five_vals = self.read() |
| 228 | + out_str = "".join(["b" if sensor_val == 1 else "w" for sensor_val in five_vals]) |
| 229 | + return out_str |
| 230 | +########################## |
0 commit comments