Skip to content

Commit 8cf9170

Browse files
authored
Merge pull request #29 from RaspberryPiFoundation/color-sensor
Initial colour sensor implementation
2 parents 1b8a610 + c03894f commit 8cf9170

File tree

6 files changed

+213
-4
lines changed

6 files changed

+213
-4
lines changed

buildhat/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
from .motors import Motor, MotorPair
22
from .distance import DistanceSensor
33
from .force import ForceSensor
4+
from .color import ColorSensor

buildhat/color.py

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
from .devices import PortDevice
2+
import math
3+
import threading
4+
5+
class ColorSensor(PortDevice):
6+
"""Color sensor
7+
8+
:param port: Port of device
9+
:raises RuntimeError: Occurs if there is no color sensor attached to port
10+
"""
11+
def __init__(self, port):
12+
super().__init__(port)
13+
self._device.mode(6)
14+
self.avg_reads = 15
15+
self._old_color = None
16+
17+
def segment_color(self, h, s, v):
18+
"""Returns the color name from HSV
19+
20+
:return: Name of the color as a string
21+
:rtype: str
22+
"""
23+
if h < 15:
24+
return "red"
25+
elif h < 30:
26+
return "orange"
27+
elif h < 75:
28+
return "yellow"
29+
elif h < 140:
30+
return "green"
31+
elif h < 260:
32+
return "blue"
33+
elif h < 330:
34+
return "magenta"
35+
else:
36+
return "red"
37+
38+
def rgb_to_hsv(self, r, g, b):
39+
"""Convert RGB to HSV
40+
41+
Based on https://www.rapidtables.com/convert/color/rgb-to-hsv.html algorithm
42+
43+
:return: HSV representation of color
44+
:rtype: tuple
45+
"""
46+
r, g, b = r/255.0, g/255.0, b/255.0
47+
cmax = max(r, g, b)
48+
cmin = min(r, g, b)
49+
delt = cmax - cmin
50+
if cmax == cmin:
51+
h = 0
52+
elif cmax == r:
53+
h = 60 * (((g - b) / delt) % 6)
54+
elif cmax == g:
55+
h = 60 * ((((b - r) / delt)) + 2)
56+
elif cmax == b:
57+
h = 60 * ((((r - g) / delt)) + 4)
58+
if cmax == 0:
59+
s = 0
60+
else:
61+
s = delt / cmax
62+
v = cmax
63+
return int(h), int(s*100), int(v*100)
64+
65+
def get_color(self):
66+
"""Returns the color
67+
68+
:return: Name of the color as a string
69+
:rtype: str
70+
"""
71+
hue, sat, val = self.get_color_hsv()
72+
return self.segment_color(hue, sat, val)
73+
74+
def get_ambient_light(self):
75+
"""Returns the ambient light
76+
77+
:return: Ambient light
78+
:rtype: int
79+
"""
80+
self._device.mode(2)
81+
readings = []
82+
for i in range(self.avg_reads):
83+
readings.append(self._device.get(self._device.FORMAT_SI)[0])
84+
return int(sum(readings)/len(readings))
85+
86+
def get_reflected_light(self):
87+
"""Returns the reflected light
88+
89+
:return: Reflected light
90+
:rtype: int
91+
"""
92+
self._device.mode(1)
93+
readings = []
94+
for i in range(self.avg_reads):
95+
readings.append(self._device.get(self._device.FORMAT_SI)[0])
96+
return int(sum(readings)/len(readings))
97+
98+
def get_color_rgbi(self):
99+
"""Returns the color
100+
101+
:return: RGBI representation
102+
:rtype: tuple
103+
"""
104+
self._device.mode(5)
105+
readings = []
106+
for i in range(self.avg_reads):
107+
readings.append(self._device.get(self._device.FORMAT_SI))
108+
rgbi = []
109+
for i in range(4):
110+
rgbi.append(int(sum([rgbi[i] for rgbi in readings]) / len(readings)))
111+
return rgbi
112+
113+
def get_color_hsv(self):
114+
"""Returns the color
115+
116+
:return: HSV representation
117+
:rtype: tuple
118+
"""
119+
self._device.mode(6)
120+
readings = []
121+
for i in range(self.avg_reads):
122+
readings.append(self._device.get(self._device.FORMAT_SI))
123+
s = c = 0
124+
for hsv in readings:
125+
hue = hsv[0]
126+
s += math.sin(math.radians(hue))
127+
c += math.cos(math.radians(hue))
128+
129+
hue = int((math.degrees((math.atan2(s,c))) + 360) % 360)
130+
sat = int(sum([hsv[1] for hsv in readings]) / len(readings))
131+
val = int(sum([hsv[2] for hsv in readings]) / len(readings))
132+
return (hue, sat, val)
133+
134+
def wait_until_color(self, color):
135+
"""Waits until specific color
136+
137+
:param color: Color to look for
138+
"""
139+
self._device.mode(5)
140+
lock = threading.Lock()
141+
142+
def both(lst):
143+
r, g, b = lst[2:]
144+
r, g, b = int((r/1024)*255), int((g/1024)*255), int((b/1024)*255)
145+
h, s, v = self.rgb_to_hsv(r, g, b)
146+
seg = self.segment_color(h, s, v)
147+
if seg == color:
148+
lock.release()
149+
150+
self._device.callback(both)
151+
lock.acquire()
152+
lock.acquire()
153+
self._device.callback(None)
154+
155+
def wait_for_new_color(self):
156+
"""Waits for new color or returns immediately if first call
157+
"""
158+
self._device.mode(5)
159+
160+
if self._old_color is None:
161+
self._old_color = self.get_color()
162+
return self._old_color
163+
164+
lock = threading.Lock()
165+
166+
def both(lst):
167+
r, g, b = lst[2:]
168+
r, g, b = int((r/1024)*255), int((g/1024)*255), int((b/1024)*255)
169+
h, s, v = self.rgb_to_hsv(r, g, b)
170+
seg = self.segment_color(h, s, v)
171+
if seg != self._old_color:
172+
self._old_color = seg
173+
lock.release()
174+
175+
self._device.callback(both)
176+
lock.acquire()
177+
lock.acquire()
178+
self._device.callback(None)
179+
return self._old_color

buildhat/motors.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ class Motor(PortDevice):
1010
def __init__(self, port):
1111
super().__init__(port)
1212
self._motor = self._port.motor
13-
self._device.mode([(1, 0), (2, 0)])
1413
self.default_speed = 100
1514
self._when_rotated = None
1615

@@ -85,7 +84,7 @@ def get_position(self):
8584
:return: Position of motor
8685
:rtype: int
8786
"""
88-
return self._motor.get()[1]
87+
return self._motor.get()[2]
8988

9089
def get_speed(self):
9190
"""Gets speed of motor
@@ -108,7 +107,7 @@ def when_rotated(self):
108107
@when_rotated.setter
109108
def when_rotated(self, value):
110109
"""Calls back, when motor has been rotated"""
111-
self._when_rotated = lambda lst: value(lst[0], lst[1])
110+
self._when_rotated = lambda lst: value(lst[0], lst[2])
112111
self._device.callback(self._when_rotated)
113112

114113

docs/shortcake/index.rst

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ Wrapper
1010

1111
.. toctree::
1212
:maxdepth: 2
13-
13+
14+
wrapper/colorsensor.rst
1415
wrapper/distancesensor.rst
1516
wrapper/forcesensor.rst
1617
wrapper/motor.rst

docs/shortcake/wrapper/color.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from buildhat import ColorSensor
2+
3+
color = ColorSensor('B')
4+
5+
print("HSV", color.get_color_hsv())
6+
print("RGBI", color.get_color_rgbi())
7+
print("Ambient", color.get_ambient_light())
8+
print("Reflected", color.get_reflected_light())
9+
print("Color", color.get_color())
10+
11+
print("Waiting for color red")
12+
color.wait_until_color("red")
13+
print("Found color red!")
14+
15+
while True:
16+
c = color.wait_for_new_color()
17+
print("Found new color", c)
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
ColorSensor
2+
===========
3+
4+
.. autoclass:: buildhat.ColorSensor
5+
:members:
6+
:inherited-members:
7+
8+
Example
9+
-------
10+
11+
.. literalinclude:: color.py
12+

0 commit comments

Comments
 (0)