Skip to content

Commit 097f5fb

Browse files
committed
Improve and document get_sensor
The new Brick.get_sensor allows to give an explicit class when autodetection can not work.
1 parent e2fc88f commit 097f5fb

File tree

4 files changed

+75
-51
lines changed

4 files changed

+75
-51
lines changed

docs/api/brick.rst

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ Brick
3636
Motors and Sensors
3737
------------------
3838

39-
This part is still a work in progress.
40-
4139
.. automethod:: Brick.get_motor
4240
.. automethod:: Brick.get_sensor
4341

docs/migration.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ NXT-Python 2 NXT-Python 3
117117
:attr:`!Mode.MASK_SLOPE` Removed
118118
=============================== ============================
119119

120+
You can now create :mod:`~nxt.sensor` objects using
121+
:meth:`nxt.brick.Brick.get_sensor`, however direct creation still works. For
122+
digital sensors with identification information, this can automatically detect
123+
the sensor type as with previous version. The new `cls` argument allows
124+
creating a sensor object using another class.
125+
120126

121127
Text String or Binary String
122128
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

nxt/brick.py

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -272,20 +272,6 @@ def find_modules(self, pattern="*.*"):
272272
finally:
273273
self.module_close(handle)
274274

275-
def get_sensor(self, port):
276-
"""Tries to detect the sensor type and return the correct sensor object.
277-
278-
:param nxt.sensor.Port port: Input port identifier.
279-
:return: A sensor object.
280-
:rtype: nxt.sensor.Sensor
281-
:raises nxt.sensor.digital.SearchError: When sensor can not be identified.
282-
283-
Only work for digital sensors with identification information.
284-
"""
285-
base_sensor = nxt.sensor.digital.BaseDigitalSensor(self, port, False)
286-
info = base_sensor.get_sensor_info()
287-
return nxt.sensor.digital.find_class(info)(self, port, check_compatible=False)
288-
289275
def get_motor(self, port):
290276
"""Return a motor object connected to one of the brick output port.
291277
@@ -295,6 +281,38 @@ def get_motor(self, port):
295281
"""
296282
return nxt.motor.Motor(self, port)
297283

284+
def get_sensor(self, port, cls=None, *args, **kwargs):
285+
"""Return a sensor object connected to one of the brick input port.
286+
287+
:param nxt.sensor.Port port: Input port identifier.
288+
:param cls: Sensor class, or None to autodetect.
289+
:type cls: typing.Type[nxt.sensor.Sensor] or None
290+
:param args: Additional constructor positional arguments when `cls` is given.
291+
:param kwargs: Additional constructor keyword arguments when `cls` is given.
292+
:return: A sensor object.
293+
:rtype: nxt.sensor.Sensor
294+
:raises nxt.sensor.digital.SearchError: When sensor can not be identified.
295+
296+
When `cls` is not given or ``None``, try to detect the sensor type and return
297+
the correct sensor object. This only works for digital sensors with
298+
identification information.
299+
300+
For autodetection to work, the module containing the sensor class must be
301+
imported at least once. See modules in :mod:`nxt.sensor`.
302+
"""
303+
if cls is None:
304+
if args or kwargs:
305+
raise ValueError("extra arguments with autodetect")
306+
base_sensor = nxt.sensor.digital.BaseDigitalSensor(
307+
self, port, check_compatible=False
308+
)
309+
info = base_sensor.get_sensor_info()
310+
return nxt.sensor.digital.find_class(info)(
311+
self, port, check_compatible=False
312+
)
313+
else:
314+
return cls(self, port, *args, **kwargs)
315+
298316
def _cmd(self, tgram):
299317
"""Send a message to the NXT brick and read reply.
300318

tests/test_sensors.py

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class TestGeneric:
3939
"""Test non digital sensors."""
4040

4141
def test_analog(self, mbrick):
42-
s = nxt.sensor.analog.BaseAnalogSensor(mbrick, Port.S1)
42+
s = mbrick.get_sensor(Port.S1, nxt.sensor.analog.BaseAnalogSensor)
4343
mbrick.get_input_values.side_effect = [
4444
(Port.S1, True, False, Type.SWITCH, Mode.BOOL, 1, 2, 3, 4),
4545
]
@@ -66,7 +66,7 @@ def test_touch(self, mbrick):
6666
assert (
6767
nxt.sensor.generic.Touch.get_sample is nxt.sensor.generic.Touch.is_pressed
6868
)
69-
s = nxt.sensor.generic.Touch(mbrick, Port.S1)
69+
s = mbrick.get_sensor(Port.S1, nxt.sensor.generic.Touch)
7070
mbrick.get_input_values.side_effect = [
7171
(Port.S1, True, False, Type.SWITCH, Mode.BOOL, 1023, 1023, 0, 1023),
7272
(Port.S1, True, False, Type.SWITCH, Mode.BOOL, 183, 183, 1, 183),
@@ -84,7 +84,7 @@ def test_light(self, mbrick):
8484
nxt.sensor.generic.Light.get_sample
8585
is nxt.sensor.generic.Light.get_lightness
8686
)
87-
s = nxt.sensor.generic.Light(mbrick, Port.S1)
87+
s = mbrick.get_sensor(Port.S1, nxt.sensor.generic.Light)
8888
mbrick.get_input_values.side_effect = [
8989
(Port.S1, True, False, Type.LIGHT_ACTIVE, Mode.RAW, 726, 250, 250, 250),
9090
(Port.S1, True, False, Type.LIGHT_INACTIVE, Mode.RAW, 823, 107, 107, 107),
@@ -103,7 +103,7 @@ def test_sound(self, mbrick):
103103
assert (
104104
nxt.sensor.generic.Sound.get_sample is nxt.sensor.generic.Sound.get_loudness
105105
)
106-
s = nxt.sensor.generic.Sound(mbrick, Port.S1)
106+
s = mbrick.get_sensor(Port.S1, nxt.sensor.generic.Sound)
107107
mbrick.get_input_values.side_effect = [
108108
(Port.S1, True, False, Type.SOUND_DBA, Mode.RAW, 999, 15, 15, 15),
109109
(Port.S1, True, False, Type.SOUND_DB, Mode.RAW, 999, 15, 15, 15),
@@ -120,7 +120,7 @@ def test_sound(self, mbrick):
120120

121121
def test_color(self, mbrick):
122122
assert nxt.sensor.generic.Color.get_sample is nxt.sensor.generic.Color.get_color
123-
s = nxt.sensor.generic.Color(mbrick, Port.S1)
123+
s = mbrick.get_sensor(Port.S1, nxt.sensor.generic.Color)
124124
mbrick.get_input_values.side_effect = [
125125
(Port.S1, True, False, Type.COLOR_FULL, Mode.RAW, 0, 0, 4, 0),
126126
(Port.S1, True, False, Type.COLOR_FULL, Mode.RAW, 0, 0, 4, 0),
@@ -153,7 +153,7 @@ class TestDigital:
153153
sensor_type_bin = b"Sonar\0\0\0"
154154

155155
def test_get_sensor_info(self, mbrick):
156-
s = nxt.sensor.digital.BaseDigitalSensor(mbrick, Port.S1, False)
156+
s = mbrick.get_sensor(Port.S1, nxt.sensor.digital.BaseDigitalSensor, False)
157157
mbrick.ls_get_status.return_value = 8
158158
mbrick.ls_read.side_effect = [
159159
self.version_bin,
@@ -198,7 +198,7 @@ class DummySensor(nxt.sensor.digital.BaseDigitalSensor):
198198
assert "WARNING" in caplog.text
199199

200200
def test_write_value(self, mbrick):
201-
s = nxt.sensor.digital.BaseDigitalSensor(mbrick, Port.S1, False)
201+
s = mbrick.get_sensor(Port.S1, nxt.sensor.digital.BaseDigitalSensor, False)
202202
s.I2C_ADDRESS = dict(s.I2C_ADDRESS, command=(0x41, "B"))
203203
s.write_value("command", (0x12,))
204204
assert mbrick.mock_calls == [
@@ -207,7 +207,7 @@ def test_write_value(self, mbrick):
207207
]
208208

209209
def test_not_ready(self, mbrick):
210-
s = nxt.sensor.digital.BaseDigitalSensor(mbrick, Port.S1, False)
210+
s = mbrick.get_sensor(Port.S1, nxt.sensor.digital.BaseDigitalSensor, False)
211211
mbrick.ls_get_status.side_effect = [nxt.error.I2CPendingError("pending"), 8]
212212
mbrick.ls_read.return_value = self.product_id_bin
213213
assert s.read_value("product_id") == (self.product_id_bin,)
@@ -220,7 +220,7 @@ def test_not_ready(self, mbrick):
220220
]
221221

222222
def test_status_timeout(self, mbrick):
223-
s = nxt.sensor.digital.BaseDigitalSensor(mbrick, Port.S1, False)
223+
s = mbrick.get_sensor(Port.S1, nxt.sensor.digital.BaseDigitalSensor, False)
224224
mbrick.ls_get_status.side_effect = (
225225
[nxt.error.I2CPendingError("pending")] * 30 * 3
226226
)
@@ -234,7 +234,7 @@ def test_status_timeout(self, mbrick):
234234
assert mbrick.mock_calls == mock_calls
235235

236236
def test_read_error(self, mbrick):
237-
s = nxt.sensor.digital.BaseDigitalSensor(mbrick, Port.S1, False)
237+
s = mbrick.get_sensor(Port.S1, nxt.sensor.digital.BaseDigitalSensor, False)
238238
mbrick.ls_get_status.side_effect = [8, 8]
239239
mbrick.ls_read.side_effect = [self.product_id_bin[1:], self.product_id_bin]
240240
assert s.read_value("product_id") == (self.product_id_bin,)
@@ -250,7 +250,7 @@ def test_read_error(self, mbrick):
250250
]
251251

252252
def test_read_timeout(self, mbrick):
253-
s = nxt.sensor.digital.BaseDigitalSensor(mbrick, Port.S1, False)
253+
s = mbrick.get_sensor(Port.S1, nxt.sensor.digital.BaseDigitalSensor, False)
254254
mbrick.ls_get_status.return_value = 8
255255
mbrick.ls_read.return_value = self.product_id_bin[1:]
256256
with pytest.raises(nxt.error.I2CError):
@@ -321,7 +321,9 @@ def test_ultrasonic(self, mbrick, mdigital):
321321
nxt.sensor.generic.Ultrasonic.get_sample
322322
is nxt.sensor.generic.Ultrasonic.get_distance
323323
)
324-
s = nxt.sensor.generic.Ultrasonic(mbrick, Port.S1, check_compatible=False)
324+
s = mbrick.get_sensor(
325+
Port.S1, nxt.sensor.generic.Ultrasonic, check_compatible=False
326+
)
325327
mdigital.read_value.side_effect = [
326328
(42,),
327329
(b"10E-2m\0",),
@@ -351,7 +353,7 @@ def test_temperature(self, mbrick, mdigital):
351353
nxt.sensor.generic.Temperature.get_sample
352354
is nxt.sensor.generic.Temperature.get_deg_c
353355
)
354-
s = nxt.sensor.generic.Temperature(mbrick, Port.S1)
356+
s = mbrick.get_sensor(Port.S1, nxt.sensor.generic.Temperature)
355357
mdigital.read_value.return_value = (1600 * 16,)
356358
assert s.get_deg_c() == 100
357359
assert s.get_deg_f() == 212
@@ -365,7 +367,7 @@ class TestMindsensors:
365367
"""Test Mindsensors sensors."""
366368

367369
def test_sumoeyes(self, mbrick):
368-
s = nxt.sensor.mindsensors.SumoEyes(mbrick, Port.S1)
370+
s = mbrick.get_sensor(Port.S1, nxt.sensor.mindsensors.SumoEyes)
369371
mbrick.get_input_values.side_effect = [
370372
(Port.S1, True, False, Type.LIGHT_ACTIVE, Mode.RAW, 0, 0, 0, 0),
371373
(Port.S1, True, False, Type.LIGHT_ACTIVE, Mode.RAW, 0, 350, 0, 0),
@@ -396,7 +398,7 @@ def test_compassv2(self, mbrick, mdigital):
396398
nxt.sensor.mindsensors.Compassv2.get_sample
397399
is nxt.sensor.mindsensors.Compassv2.get_heading
398400
)
399-
s = nxt.sensor.mindsensors.Compassv2(mbrick, Port.S1, False)
401+
s = mbrick.get_sensor(Port.S1, nxt.sensor.mindsensors.Compassv2, False)
400402
mdigital.read_value.return_value = (300,)
401403
# TODO: should return degrees (divide by 10).
402404
assert s.get_heading() == 300
@@ -413,7 +415,7 @@ def test_dist(self, mbrick, mdigital):
413415
nxt.sensor.mindsensors.DIST.get_sample
414416
is nxt.sensor.mindsensors.DIST.get_distance
415417
)
416-
s = nxt.sensor.mindsensors.DIST(mbrick, Port.S1, False)
418+
s = mbrick.get_sensor(Port.S1, nxt.sensor.mindsensors.DIST, False)
417419
# TODO: get rid of ord, adapt format.
418420
mdigital.read_value.side_effect = [(100,), (ord("2"),), (42,), (43,), (44,)]
419421
assert s.get_distance() == 100
@@ -432,7 +434,7 @@ def test_dist(self, mbrick, mdigital):
432434
]
433435

434436
def test_rtc(self, mbrick, mdigital):
435-
s = nxt.sensor.mindsensors.RTC(mbrick, Port.S1)
437+
s = mbrick.get_sensor(Port.S1, nxt.sensor.mindsensors.RTC)
436438
# TODO: this one is completely broken:
437439
# - Return str instead of int.
438440
# - Bad handling of hour format.
@@ -447,7 +449,7 @@ def test_accl(self, mbrick, mdigital):
447449
nxt.sensor.mindsensors.ACCL.get_sample
448450
is nxt.sensor.mindsensors.ACCL.get_all_accel
449451
)
450-
s = nxt.sensor.mindsensors.ACCL(mbrick, Port.S1, False)
452+
s = mbrick.get_sensor(Port.S1, nxt.sensor.mindsensors.ACCL, False)
451453
# TODO: get rid of ord, adapt format.
452454
mdigital.read_value.side_effect = [
453455
(ord("2"),),
@@ -483,7 +485,7 @@ def test_accl(self, mbrick, mdigital):
483485

484486
def test_mtrmux(self, mbrick, mdigital):
485487
assert not hasattr(nxt.sensor.mindsensors.MTRMUX, "get_sample")
486-
s = nxt.sensor.mindsensors.MTRMUX(mbrick, Port.S1, False)
488+
s = mbrick.get_sensor(Port.S1, nxt.sensor.mindsensors.MTRMUX, False)
487489
mdigital.read_value.side_effect = [(1,), (2,)]
488490
s.command(s.Commands.FLOAT)
489491
s.set_direction(1, 1)
@@ -503,7 +505,7 @@ def test_lineleader(self, mbrick, mdigital):
503505
nxt.sensor.mindsensors.LineLeader.get_sample
504506
is nxt.sensor.mindsensors.LineLeader.get_reading_all
505507
)
506-
s = nxt.sensor.mindsensors.LineLeader(mbrick, Port.S1, False)
508+
s = mbrick.get_sensor(Port.S1, nxt.sensor.mindsensors.LineLeader, False)
507509
mdigital.read_value.side_effect = [
508510
(-10,),
509511
(50,),
@@ -541,7 +543,7 @@ def test_lineleader(self, mbrick, mdigital):
541543

542544
def test_servo(self, mbrick, mdigital):
543545
assert not hasattr(nxt.sensor.mindsensors.Servo, "get_sample")
544-
s = nxt.sensor.mindsensors.Servo(mbrick, Port.S1, False)
546+
s = mbrick.get_sensor(Port.S1, nxt.sensor.mindsensors.Servo, False)
545547
# TODO: command can not work, can not fit two bytes in one byte.
546548
mdigital.read_value.side_effect = [(1,), (42,), (43,)]
547549
assert s.get_bat_level() == 1
@@ -561,7 +563,7 @@ def test_servo(self, mbrick, mdigital):
561563

562564
def test_mmx(self, mbrick, mdigital):
563565
assert not hasattr(nxt.sensor.mindsensors.MMX, "get_sample")
564-
s = nxt.sensor.mindsensors.MMX(mbrick, Port.S1, False)
566+
s = mbrick.get_sensor(Port.S1, nxt.sensor.mindsensors.MMX, False)
565567
mdigital.read_value.side_effect = [
566568
(1,),
567569
(0xAA,),
@@ -607,7 +609,7 @@ def test_mmx(self, mbrick, mdigital):
607609

608610
def test_hid(self, mbrick, mdigital):
609611
assert not hasattr(nxt.sensor.mindsensors.HID, "get_sample")
610-
s = nxt.sensor.mindsensors.HID(mbrick, Port.S1, False)
612+
s = mbrick.get_sensor(Port.S1, nxt.sensor.mindsensors.HID, False)
611613
s.command(s.Commands.ASCII_MODE)
612614
s.set_modifier(42)
613615
s.write_data("a")
@@ -618,7 +620,7 @@ def test_hid(self, mbrick, mdigital):
618620
]
619621

620622
def test_ps2(self, mbrick, mdigital):
621-
s = nxt.sensor.mindsensors.PS2(mbrick, Port.S1, False)
623+
s = mbrick.get_sensor(Port.S1, nxt.sensor.mindsensors.PS2, False)
622624
mdigital.read_value.side_effect = [
623625
(42,),
624626
(0x55,),
@@ -671,7 +673,7 @@ def test_compass(self, mbrick, mdigital):
671673
nxt.sensor.hitechnic.Compass.get_sample
672674
is nxt.sensor.hitechnic.Compass.get_heading
673675
)
674-
s = nxt.sensor.hitechnic.Compass(mbrick, Port.S1, False)
676+
s = mbrick.get_sensor(Port.S1, nxt.sensor.hitechnic.Compass, False)
675677
mdigital.read_value.return_value = (10,)
676678
assert s.get_heading() == 30
677679
assert s.get_relative_heading(0) == 30
@@ -698,7 +700,7 @@ def test_accelerometer(self, mbrick, mdigital):
698700
nxt.sensor.hitechnic.Accelerometer.get_sample
699701
is nxt.sensor.hitechnic.Accelerometer.get_acceleration
700702
)
701-
s = nxt.sensor.hitechnic.Accelerometer(mbrick, Port.S1, False)
703+
s = mbrick.get_sensor(Port.S1, nxt.sensor.hitechnic.Accelerometer, False)
702704
mdigital.read_value.return_value = (0x12, 0x23, -0x32, 0x3, 0x0, 0x2)
703705
v = s.get_acceleration()
704706
assert (v.x, v.y, v.z) == (75, 140, -198)
@@ -711,7 +713,7 @@ def test_irreceiver(self, mbrick, mdigital):
711713
nxt.sensor.hitechnic.IRReceiver.get_sample
712714
is nxt.sensor.hitechnic.IRReceiver.get_speeds
713715
)
714-
s = nxt.sensor.hitechnic.IRReceiver(mbrick, Port.S1, False)
716+
s = mbrick.get_sensor(Port.S1, nxt.sensor.hitechnic.IRReceiver, False)
715717
mdigital.read_value.return_value = (0, -16, 30, -44, 58, -72, 100, -128)
716718
v = s.get_speeds()
717719
assert (v.m1A, v.m1B) == (0, -16)
@@ -732,7 +734,7 @@ def test_irseekerv2(self, mbrick, mdigital):
732734
nxt.sensor.hitechnic.IRSeekerv2.get_sample
733735
is nxt.sensor.hitechnic.IRSeekerv2.get_ac_values
734736
)
735-
s = nxt.sensor.hitechnic.IRSeekerv2(mbrick, Port.S1, False)
737+
s = mbrick.get_sensor(Port.S1, nxt.sensor.hitechnic.IRSeekerv2, False)
736738
mdigital.read_value.side_effect = [
737739
(5, 42, 43, 44, 45, 46, 44),
738740
(5, 42, 43, 44, 45, 46),
@@ -772,7 +774,7 @@ def test_eopd(self, mbrick):
772774
nxt.sensor.hitechnic.EOPD.get_sample
773775
is nxt.sensor.hitechnic.EOPD.get_scaled_value
774776
)
775-
s = nxt.sensor.hitechnic.EOPD(mbrick, Port.S1)
777+
s = mbrick.get_sensor(Port.S1, nxt.sensor.hitechnic.EOPD)
776778
mbrick.get_input_values.side_effect = [
777779
(Port.S1, True, False, Type.LIGHT_INACTIVE, Mode.RAW, 523, 0, 0, 0),
778780
(Port.S1, True, False, Type.LIGHT_INACTIVE, Mode.RAW, 398, 0, 0, 0),
@@ -800,7 +802,7 @@ def test_colorv2(self, mbrick, mdigital):
800802
nxt.sensor.hitechnic.Colorv2.get_sample
801803
is nxt.sensor.hitechnic.Colorv2.get_active_color
802804
)
803-
s = nxt.sensor.hitechnic.Colorv2(mbrick, Port.S1, False)
805+
s = mbrick.get_sensor(Port.S1, nxt.sensor.hitechnic.Colorv2, False)
804806
mdigital.read_value.side_effect = [
805807
(8, 100, 50, 0, 75, 42, 66, 33, 0),
806808
(100, 50, 0, 75),
@@ -835,7 +837,7 @@ def test_giro(self, mbrick):
835837
nxt.sensor.hitechnic.Gyro.get_sample
836838
is nxt.sensor.hitechnic.Gyro.get_rotation_speed
837839
)
838-
s = nxt.sensor.hitechnic.Gyro(mbrick, Port.S1)
840+
s = mbrick.get_sensor(Port.S1, nxt.sensor.hitechnic.Gyro)
839841
mbrick.get_input_values.side_effect = [
840842
(Port.S1, True, False, Type.ANGLE, Mode.RAW, 0, 0, 42, 0),
841843
(Port.S1, True, False, Type.ANGLE, Mode.RAW, 0, 0, 42, 0),
@@ -857,7 +859,7 @@ def test_giro(self, mbrick):
857859

858860
def test_prototype(self, mbrick, mdigital):
859861
assert not hasattr(nxt.sensor.hitechnic.Prototype, "get_sample")
860-
s = nxt.sensor.hitechnic.Prototype(mbrick, Port.S1, False)
862+
s = mbrick.get_sensor(Port.S1, nxt.sensor.hitechnic.Prototype, False)
861863
mdigital.read_value.side_effect = [
862864
(42, 43, 44, 45, 46),
863865
(0x2A,),
@@ -890,7 +892,7 @@ def test_prototype(self, mbrick, mdigital):
890892

891893
def test_servocon(self, mbrick, mdigital):
892894
assert not hasattr(nxt.sensor.hitechnic.ServoCon, "get_sample")
893-
s = nxt.sensor.hitechnic.ServoCon(mbrick, Port.S1, False)
895+
s = mbrick.get_sensor(Port.S1, nxt.sensor.hitechnic.ServoCon, False)
894896
mdigital.read_value.side_effect = [
895897
(1,),
896898
(43,),
@@ -910,7 +912,7 @@ def test_servocon(self, mbrick, mdigital):
910912

911913
def test_motorcon(self, mbrick, mdigital):
912914
assert not hasattr(nxt.sensor.hitechnic.MotorCon, "get_sample")
913-
s = nxt.sensor.hitechnic.MotorCon(mbrick, Port.S1, False)
915+
s = mbrick.get_sensor(Port.S1, nxt.sensor.hitechnic.MotorCon, False)
914916
mdigital.read_value.side_effect = [
915917
(123456,),
916918
(654321,),
@@ -953,7 +955,7 @@ def test_angle(self, mbrick, mdigital):
953955
nxt.sensor.hitechnic.Angle.get_sample
954956
is nxt.sensor.hitechnic.Angle.get_angle
955957
)
956-
s = nxt.sensor.hitechnic.Angle(mbrick, Port.S1, False)
958+
s = mbrick.get_sensor(Port.S1, nxt.sensor.hitechnic.Angle, False)
957959
mdigital.read_value.side_effect = [
958960
(21, 1),
959961
(123456789,),

0 commit comments

Comments
 (0)