RangeSensor¶
__init__(self, position=(8, 0), a=0, max=20, width=1.0, name='sensor', **kwargs)
special
¶
A range sensor calculates both a "distance" and a "reading". The "distance" is the actual distance to an obstacle in CM (when it is within the "max" distance). The reading is a ratio of distance/max. When no obstacle is sensed the "reading" is 1.0 and as the robot approaches an obstacle the "reading" decreases towards 0.0.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
position |
List[int, int] |
the location on the robot in (x, y) |
(8, 0) |
a |
float |
the direction in degrees the sensor is facing. |
0 |
max |
float |
max distance in CM that the range sensor can sense |
20 |
width |
float |
0 for laser, or wider for sonar |
1.0 |
name |
str |
the name of the sensor |
'sensor' |
Source code in robots/devices/rangesensors.py
def __init__(
self, position=(8, 0), a=0, max=20, width=1.0, name="sensor", **kwargs
):
"""
A range sensor calculates both a "distance" and a "reading".
The "distance" is the actual distance to an obstacle in CM (when
it is within the "max" distance).
The reading is a ratio of distance/max. When no obstacle is sensed
the "reading" is 1.0 and as the robot approaches an obstacle the
"reading" decreases towards 0.0.
Args:
position (List[int, int]): the location on the robot in (x, y)
a (float): the direction in degrees the sensor is
facing.
max (float): max distance in CM that the range sensor can sense
width (float): 0 for laser, or wider for sonar
name (str): the name of the sensor
"""
config = {
"position": position,
"a": a, # degrees in the config file
"max": max,
"width": width,
"name": name,
}
config.update(kwargs)
self._watcher = None
self.robot = None
self.initialize()
self.from_json(config)
draw(self, backend)
¶
Draw the device on the backend.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
backend |
Backend |
an aitk drawing backend |
required |
Source code in robots/devices/rangesensors.py
def draw(self, backend):
"""
Draw the device on the backend.
Args:
backend (Backend): an aitk drawing backend
"""
backend.set_fill(Color(128, 0, 128, 64))
dist = self.get_distance()
if self.width > 0:
if self.get_reading() < 1.0:
backend.strokeStyle(Color(255), 1)
else:
backend.strokeStyle(Color(0), 1)
backend.draw_arc(
self.position[0],
self.position[1],
dist,
dist,
self.a - self.width / 2,
self.a + self.width / 2,
)
else:
if self.get_reading() < 1.0:
backend.strokeStyle(Color(255), 1)
else:
backend.strokeStyle(Color(128, 0, 128, 64), 1)
x, y = rotate_around(self.position[0], self.position[1], dist, self.a)
backend.draw_line(
self.position[0], self.position[1], x, y
)
from_json(self, config)
¶
Set the settings from a device config.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
config |
dict |
a config dictionary |
required |
Source code in robots/devices/rangesensors.py
def from_json(self, config):
"""
Set the settings from a device config.
Args:
config (dict): a config dictionary
"""
valid_keys = set([
"position", "a", "max", "width", "name", "class"
])
self.verify_config(valid_keys, config)
if "position" in config:
self.position = config["position"]
# Get location of sensor, doesn't change once position is set:
self.dist_from_center = distance(0, 0, self.position[0], self.position[1])
self.dir_from_center = math.atan2(-self.position[0], self.position[1])
if "a" in config:
self.a = degrees_to_world(config["a"])
if "max" in config:
self.max = config["max"]
if "width" in config:
self.width = config["width"] * PI_OVER_180 # save as radians
if self.width == 0:
self.type = "laser"
if "name" in config:
self.name = config["name"]
self.distance = self.reading * self.max
get_angle(self)
¶
Get the direction in degrees. Use RangeSensor.a to get the raw radians.
Source code in robots/devices/rangesensors.py
def get_angle(self):
"""
Get the direction in degrees. Use RangeSensor.a
to get the raw radians.
"""
return world_to_degrees(self.a)
get_distance(self)
¶
Get the last range distance of the sensor in CM. The distance is between 0 and max.
Source code in robots/devices/rangesensors.py
def get_distance(self):
"""
Get the last range distance of the sensor in CM. The distance is
between 0 and max.
"""
return self.distance
get_max(self)
¶
Get the maximum distance in CM the sensor can sense.
Source code in robots/devices/rangesensors.py
def get_max(self):
"""
Get the maximum distance in CM the sensor can sense.
"""
return self.max
get_name(self)
¶
Get the name of the range sensor.
Source code in robots/devices/rangesensors.py
def get_name(self):
"""
Get the name of the range sensor.
"""
return self.name
get_position(self)
¶
Get the position of the sensor. This represents the location of the sensor in [x, y] CM.
Source code in robots/devices/rangesensors.py
def get_position(self):
"""
Get the position of the sensor. This represents the location
of the sensor in [x, y] CM.
"""
return self.position
get_reading(self)
¶
Get the last range reading of the sensor. The reading is between 0 and 1.
Source code in robots/devices/rangesensors.py
def get_reading(self):
"""
Get the last range reading of the sensor. The reading is between
0 and 1.
"""
return self.reading
get_widget(self, title=None, attributes=None)
¶
Return the dynamically updating widget.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
title |
str |
title of sensor |
None |
Source code in robots/devices/rangesensors.py
def get_widget(self, title=None, attributes=None):
"""
Return the dynamically updating widget.
Args:
title (str): title of sensor
"""
from ..watchers import AttributesWatcher
if self.robot is None or self.robot.world is None:
print("ERROR: can't watch until added to robot, and robot is in world")
return None
if self._watcher is None:
title = title if title is not None else "Range Sensor:"
attributes = attributes if attributes is not None else "all"
self._watcher = AttributesWatcher(
self,
"name",
"reading",
"distance",
title=title,
labels=["Name:", "Reading:", "Distance:"],
attributes=attributes,
)
self.robot.world._watchers.append(self._watcher)
else:
self._watcher.set_arguments(title=title, attributes=attributes)
self._watcher.update()
return self._watcher.widget
get_width(self)
¶
Get the width of the sensor in degrees. Use RangeSensor.width to see raw radians.
Source code in robots/devices/rangesensors.py
def get_width(self):
"""
Get the width of the sensor in degrees. Use
RangeSensor.width to see raw radians.
"""
return self.width * ONE80_OVER_PI
initialize(self)
¶
Internal method to set all settings to default values.
Source code in robots/devices/rangesensors.py
def initialize(self):
"""
Internal method to set all settings to default values.
"""
self.type = "ir"
self.time = 0.0
self.reading = 1.0
self.position = [8, 0]
self.dist_from_center = distance(0, 0, self.position[0], self.position[1])
self.dir_from_center = math.atan2(-self.position[0], self.position[1])
self.a = degrees_to_world(0) # comes in degrees, save as radians
self.max = 20 # CM
self.width = 1.0 # radians
self.name = "sensor"
self.distance = self.reading * self.max
set_angle(self, a)
¶
Set the direction of the sensor.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
a |
float |
the angle of the direction of sensor in degrees |
required |
Source code in robots/devices/rangesensors.py
def set_angle(self, a):
"""
Set the direction of the sensor.
Args:
a (float): the angle of the direction of sensor in degrees
"""
self.a = degrees_to_world(a)
set_distance(self, distance)
¶
Set the distance that the sensor is reading. You would not usually do this manually.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
distance |
float |
distance in CM to sensed object |
required |
Source code in robots/devices/rangesensors.py
def set_distance(self, distance):
"""
Set the distance that the sensor is reading. You would not
usually do this manually.
Args:
distance (float): distance in CM to sensed object
"""
self.distance = distance
self.reading = distance / self.max
set_max(self, max)
¶
Set the maximum distance in CM that this sensor can sense.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
max |
float |
max distance in CM the sensor can sense. |
required |
Source code in robots/devices/rangesensors.py
def set_max(self, max):
"""
Set the maximum distance in CM that this sensor can sense.
Args:
max (float): max distance in CM the sensor can sense.
"""
self.max = max
set_name(self, name)
¶
Set the name of the range sensor.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
name |
str |
the name of the range sensor |
required |
Source code in robots/devices/rangesensors.py
def set_name(self, name):
"""
Set the name of the range sensor.
Args:
name (str): the name of the range sensor
"""
self.name = name
set_position(self, position)
¶
Set the position of the sensor. position must be a list/tuple of length 2 representing [x, y] in CM of the location of the sensor relative to the center of the robot.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
position |
List[int, int] |
the location of the sensor in relationship to the center of the robot. |
required |
Source code in robots/devices/rangesensors.py
def set_position(self, position):
"""
Set the position of the sensor. position must be a
list/tuple of length 2 representing [x, y] in CM of the
location of the sensor relative to the center of the
robot.
Args:
position (List[int, int]): the location
of the sensor in relationship to the center of the
robot.
"""
if len(position) != 2:
raise ValueError("position must be of length two")
self.position = position
self.dist_from_center = distance(0, 0, self.position[0], self.position[1])
self.dir_from_center = math.atan2(-self.position[0], self.position[1])
set_reading(self, reading)
¶
Set the reading that the sensor is reading. You would not usually do this manually.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
reading |
float |
between 0 and 1 |
required |
Source code in robots/devices/rangesensors.py
def set_reading(self, reading):
"""
Set the reading that the sensor is reading. You would not
usually do this manually.
Args:
reading (float): between 0 and 1
"""
self.reading = reading
self.distance = reading * self.max
set_width(self, width)
¶
Set the width of the range sensor in degrees. 0 width is a laser range finder. Larger values indicate the width of an IR sensor. It is measured in three locations: start, middle, and stop. The value of the sensor is the minimum of the three.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
width |
float |
angle in degrees |
required |
Source code in robots/devices/rangesensors.py
def set_width(self, width):
"""
Set the width of the range sensor in degrees. 0 width
is a laser range finder. Larger values indicate the
width of an IR sensor. It is measured in three locations:
start, middle, and stop. The value of the sensor is the
minimum of the three.
Args:
width (float): angle in degrees
"""
self.width = width * PI_OVER_180 # save as radians
if self.width == 0:
self.type = "laser"
else:
self.type = "ir"
to_json(self)
¶
Save the internal settings to a config dictionary.
Source code in robots/devices/rangesensors.py
def to_json(self):
"""
Save the internal settings to a config dictionary.
"""
config = {
"class": self.__class__.__name__,
"position": self.position,
"a": world_to_degrees(self.a),
"max": self.max,
"width": self.width * ONE80_OVER_PI, # save as degrees
"name": self.name,
}
return config
update(self, draw_list=None)
¶
Update the device.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
draw_list |
list |
optional. If given, then the method can add to it for drawing later. |
None |
Source code in robots/devices/rangesensors.py
def update(self, draw_list=None):
"""
Update the device.
Args:
draw_list (list): optional. If given, then the
method can add to it for drawing later.
"""
# Update timestamp:
self.time = self.robot.world.time
# This changes:
p = rotate_around(
self.robot.x,
self.robot.y,
self.dist_from_center,
self.robot.a + self.dir_from_center + PI_OVER_2,
)
if self.robot.world.debug and draw_list is not None:
draw_list.append(("draw_ellipse", (p[0], p[1], 2, 2), {}))
self.set_reading(1.0)
if self.width != 0:
for incr in arange(-self.width / 2, self.width / 2, self.width / 2):
hits = self.robot.cast_ray(
p[0],
p[1],
-self.robot.a + PI_OVER_2 + incr - self.a,
self.max,
)
if hits:
if self.robot.world.debug and draw_list is not None:
draw_list.append(
("draw_ellipse", (hits[-1].x, hits[-1].y, 2, 2), {})
)
# Closest hit:
if hits[-1].distance < self.get_distance():
self.set_distance(hits[-1].distance)
else:
hits = self.robot.cast_ray(
p[0],
p[1],
-self.robot.a + PI_OVER_2 - self.a,
self.max,
)
if hits:
if self.robot.world.debug and draw_list is not None:
draw_list.append(("draw_ellipse", (hits[-1].x, hits[-1].y, 2, 2), {}))
# Closest hit:
if hits[-1].distance < self.get_distance():
self.set_distance(hits[-1].distance)
watch(self, title='Range Sensor:')
¶
Create a dynamically updating view of this sensor.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
title |
str |
title of sensor |
'Range Sensor:' |
Source code in robots/devices/rangesensors.py
def watch(self, title="Range Sensor:"):
"""
Create a dynamically updating view
of this sensor.
Args:
title (str): title of sensor
"""
widget = self.get_widget(title=title)
return display(widget)