Skip to content

Commit 9c1dbb9

Browse files
authored
Improve sensor interval (#192)
* Initial rate improvements * Add test for color sensor * Add test for motors at low intervals * Reduce default interval to 10ms to give 100Hz of data readings * Add toggle to position/speed This is so that we don't end up with a busy loop, which interfers with processing serial data from another thread. I think if there wasn't a GIL this problem might not exist. * Dont need to resend combi setting
1 parent a6ac1fb commit 9c1dbb9

File tree

5 files changed

+115
-16
lines changed

5 files changed

+115
-16
lines changed

buildhat/devices.py

+19-10
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ def __init__(self, port):
5858
Device._setup()
5959
self._simplemode = -1
6060
self._combimode = -1
61+
self._modestr = ""
6162
self._typeid = self._conn.typeid
62-
self._interval = 100
63+
self._interval = 10
6364
if (
6465
self._typeid in Device._device_names
6566
and Device._device_names[self._typeid][0] != type(self).__name__ # noqa: W503
@@ -196,16 +197,10 @@ def get(self):
196197
:raises DeviceError: Occurs if device not in valid mode
197198
"""
198199
self.isconnected()
199-
idx = -1
200-
if self._simplemode != -1:
201-
idx = self._simplemode
202-
elif self._combimode != -1:
203-
idx = self._combimode
204-
else:
200+
if self._simplemode == -1 and self._combimode == -1:
205201
raise DeviceError("Not in simple or combimode")
206202
ftr = Future()
207203
self._hat.portftr[self.port].append(ftr)
208-
self._write(f"port {self.port} ; selonce {idx}\r")
209204
return ftr.result()
210205

211206
def mode(self, modev):
@@ -215,18 +210,32 @@ def mode(self, modev):
215210
"""
216211
self.isconnected()
217212
if isinstance(modev, list):
218-
self._combimode = 0
219213
modestr = ""
220214
for t in modev:
221215
modestr += f"{t[0]} {t[1]} "
222-
self._write(f"port {self.port} ; combi {self._combimode} {modestr}\r")
216+
if self._simplemode == -1 and self._combimode == 0 and self._modestr == modestr:
217+
return
218+
self._write(f"port {self.port}; select\r")
219+
self._combimode = 0
220+
self._write((f"port {self.port} ; combi {self._combimode} {modestr} ; "
221+
f"select {self._combimode} ; "
222+
f"selrate {self._interval}\r"))
223223
self._simplemode = -1
224+
self._modestr = modestr
225+
self._conn.combimode = 0
226+
self._conn.simplemode = -1
224227
else:
228+
if self._combimode == -1 and self._simplemode == int(modev):
229+
return
225230
# Remove combi mode
226231
if self._combimode != -1:
227232
self._write(f"port {self.port} ; combi {self._combimode}\r")
233+
self._write(f"port {self.port}; select\r")
228234
self._combimode = -1
229235
self._simplemode = int(modev)
236+
self._write(f"port {self.port} ; select {int(modev)} ; selrate {self._interval}\r")
237+
self._conn.combimode = -1
238+
self._conn.simplemode = int(modev)
230239

231240
def select(self):
232241
"""Request data from mode

buildhat/motors.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def _run_positional_ramp(self, pos, newpos, speed):
203203
# Collapse speed range to -5 to 5
204204
speed *= 0.05
205205
dur = abs((newpos - pos) / speed)
206-
cmd = (f"port {self.port}; combi 0 {self._combi} ; select 0 ; selrate {self._interval}; "
206+
cmd = (f"port {self.port}; select 0 ; selrate {self._interval}; "
207207
f"pid {self.port} 0 1 s4 0.0027777778 0 5 0 .1 3; "
208208
f"set ramp {pos} {newpos} {dur} 0\r")
209209
ftr = Future()
@@ -259,7 +259,7 @@ def run_to_position(self, degrees, speed=None, blocking=True, direction="shortes
259259

260260
def _run_for_seconds(self, seconds, speed):
261261
self._runmode = MotorRunmode.SECONDS
262-
cmd = (f"port {self.port} ; combi 0 {self._combi} ; select 0 ; selrate {self._interval}; "
262+
cmd = (f"port {self.port} ; select 0 ; selrate {self._interval}; "
263263
f"pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100; "
264264
f"set pulse {speed} 0.0 {seconds} 0\r")
265265
ftr = Future()
@@ -311,7 +311,7 @@ def start(self, speed=None):
311311
raise MotorError("Invalid Speed")
312312
cmd = f"port {self.port} ; set {speed}\r"
313313
if self._runmode == MotorRunmode.NONE:
314-
cmd = (f"port {self.port} ; combi 0 {self._combi} ; select 0 ; selrate {self._interval}; "
314+
cmd = (f"port {self.port} ; select 0 ; selrate {self._interval}; "
315315
f"pid {self.port} 0 0 s1 1 0 0.003 0.01 0 100; "
316316
f"set {speed}\r")
317317
self._runmode = MotorRunmode.FREE

buildhat/serinterface.py

+7
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ def __init__(self):
3131
self.typeid = -1
3232
self.connected = False
3333
self.callit = None
34+
self.simplemode = -1
35+
self.combimode = -1
3436

3537
def update(self, typeid, connected, callit=None):
3638
"""Update connection information for port
@@ -381,6 +383,11 @@ def runit():
381383
else:
382384
if d != "":
383385
newdata.append(int(d))
386+
# Check data was for our current mode
387+
if line[2] == "M" and self.connections[portid].simplemode != int(line[3]):
388+
continue
389+
elif line[2] == "C" and self.connections[portid].combimode != int(line[3]):
390+
continue
384391
callit = self.connections[portid].callit
385392
if callit is not None:
386393
q.put((callit, newdata))

test/color.py

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Test Color Sensor functionality"""
2+
import time
3+
import unittest
4+
5+
from buildhat import ColorSensor
6+
7+
8+
class TestColor(unittest.TestCase):
9+
"""Test color sensor functions"""
10+
11+
def test_color_interval(self):
12+
"""Test color sensor interval"""
13+
color = ColorSensor('A')
14+
color.avg_reads = 1
15+
color.interval = 10
16+
count = 1000
17+
expected_dur = count * color.interval * 1e-3
18+
19+
start = time.time()
20+
for _ in range(count):
21+
color.get_ambient_light()
22+
end = time.time()
23+
diff = abs((end - start) - expected_dur)
24+
self.assertLess(diff, 0.25)
25+
26+
start = time.time()
27+
for _ in range(count):
28+
color.get_color_rgbi()
29+
end = time.time()
30+
diff = abs((end - start) - expected_dur)
31+
self.assertLess(diff, 0.25)
32+
33+
def test_caching(self):
34+
"""Test to make sure we're not reading cached data"""
35+
color = ColorSensor('A')
36+
color.avg_reads = 1
37+
color.interval = 1
38+
39+
for _ in range(100):
40+
color.mode(2)
41+
self.assertEqual(len(color.get()), 1)
42+
color.mode(5)
43+
self.assertEqual(len(color.get()), 4)
44+
45+
46+
if __name__ == '__main__':
47+
unittest.main()

test/motors.py

+39-3
Original file line numberDiff line numberDiff line change
@@ -185,22 +185,28 @@ def test_continuous_start(self):
185185
"""Test starting motor for 5mins"""
186186
t = time.time() + (60 * 5)
187187
m = Motor('A')
188+
toggle = 0
188189
while time.time() < t:
189-
m.start(0)
190+
m.start(toggle)
191+
toggle ^= 1
190192

191193
def test_continuous_degrees(self):
192194
"""Test setting degrees for 5mins"""
193195
t = time.time() + (60 * 5)
194196
m = Motor('A')
197+
toggle = 0
195198
while time.time() < t:
196-
m.run_for_degrees(0)
199+
m.run_for_degrees(toggle)
200+
toggle ^= 1
197201

198202
def test_continuous_position(self):
199203
"""Test setting position of motor for 5mins"""
200204
t = time.time() + (60 * 5)
201205
m = Motor('A')
206+
toggle = 0
202207
while time.time() < t:
203-
m.run_to_position(0)
208+
m.run_to_position(toggle)
209+
toggle ^= 1
204210

205211
def test_continuous_feedback(self):
206212
"""Test feedback of motor for 30mins"""
@@ -211,6 +217,36 @@ def test_continuous_feedback(self):
211217
while time.time() < t:
212218
_ = (m.get_speed(), m.get_position(), m.get_aposition())
213219

220+
def test_interval(self):
221+
"""Test motor interval"""
222+
m = Motor('A')
223+
m.interval = 10
224+
count = 1000
225+
expected_dur = count * m.interval * 1e-3
226+
start = time.time()
227+
for _ in range(count):
228+
m.get_position()
229+
end = time.time()
230+
diff = abs((end - start) - expected_dur)
231+
self.assertLess(diff, expected_dur * 0.1)
232+
233+
def test_dual_interval(self):
234+
"""Test dual motor interval"""
235+
m1 = Motor('A')
236+
m2 = Motor('B')
237+
for interval in [10, 5]:
238+
m1.interval = interval
239+
m2.interval = interval
240+
count = 1000
241+
expected_dur = count * m1.interval * 1e-3
242+
start = time.time()
243+
for _ in range(count):
244+
m1.get_position()
245+
m2.get_position()
246+
end = time.time()
247+
diff = abs((end - start) - expected_dur)
248+
self.assertLess(diff, expected_dur * 0.1)
249+
214250

215251
if __name__ == '__main__':
216252
unittest.main()

0 commit comments

Comments
 (0)