bricknil.sensor

Actual sensor and motor peripheral definitions from Boost and PoweredUp

Classes

Button(name[, port, capabilities]) Register to be notified of button presses on the Hub (Boost or PoweredUp)
CurrentSensor(name[, port, capabilities]) Voltage sensor
DuploSpeaker(name[, port, capabilities]) Plays one of five preset sounds through the Duplo built-in speaker
DuploSpeedSensor(name[, port, capabilities]) Speedometer on Duplo train base that measures front wheel speed.
DuploTrainMotor(name[, port, capabilities]) Train Motor on Duplo Trains
DuploVisionSensor(name[, port, capabilities]) Access the Duplo Vision/Distance Sensor
ExternalMotor(name[, port, capabilities]) Access the stand-alone Boost motors
InternalMotor(name[, port, capabilities]) Access the internal motor(s) in the Boost Move Hub.
InternalTiltSensor(name[, port, capabilities]) Access the internal tilt sensor in the Boost Move Hub.
LED(name[, port, capabilities]) Changes the LED color on the Hubs.
Light(name[, port, capabilities]) Connects to the external light.
RemoteButtons(name[, port, capabilities]) Represents one set of ‘+’, ‘-‘, ‘red’ buttons on the PoweredHub Remote
TrainMotor(name[, port, capabilities]) Connects to the train motors.
VisionSensor(name[, port, capabilities]) Access the Boost Vision/Distance Sensor
VoltageSensor(name[, port, capabilities]) Voltage sensor

Members

class bricknil.sensor.InternalMotor(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.TachoMotor

Access the internal motor(s) in the Boost Move Hub.

Unlike the train motors, these motors (as well as the stand-alone Boost motors ExternalMotor) have a built-in sensor/tachometer for sending back the motor’s current speed and position. However, you don’t need to use the sensors, and can treat this motor strictly as an output device.

Examples:

# Basic connection to the motor on Port A
@attach(InternalMotor, name='left_motor', port=InternalMotor.Port.A)

# Basic connection to both motors at the same time (virtual I/O port).
# Any speed command will cause both motors to rotate at the same speed
@attach(InternalMotor, name='motors', port=InternalMotor.Port.AB)

# Report back when motor speed changes. You must have a motor_change method defined
@attach(InternalMotor, name='motor', port=InternalMotor.Port.A, capabilities=['sense_speed'])

# Only report back when speed change exceeds 5 units
@attach(InternalMotor, name='motors', port=InternalMotor.Port.A, capabilities=[('sense_speed', 5)])
And within the run body you can control the motor output::
await self.motor.set_speed(50) # Setting the speed await self.motor.ramp_speed(80, 2000) # Ramp speed to 80 over 2 seconds await self.motor.set_pos(90, speed=20) # Turn clockwise to 3 o’clock position await self.motor.rotate(60, speed=-50) # Turn 60 degrees counter-clockwise from current position

See also

Maps the port names A, B, AB to hard-coded port numbers

class Port

Bases: enum.Enum

Address either motor A or Motor B, or both AB at the same time

A = 0
AB = 2
B = 1
class bricknil.sensor.ExternalMotor(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.TachoMotor

Access the stand-alone Boost motors

These are similar to the InternalMotor with build-in tachometer and sensor for sending back the motor’s current speed and position. You don’t need to use the sensors, and can treat this as strictly an output.

Examples:

# Basic connection to the motor on Port A
@attach(ExternalMotor, name='motor')

# Report back when motor speed changes. You must have a motor_change method defined
@attach(ExternalMotor, name='motor', capabilities=['sense_speed'])

# Only report back when speed change exceeds 5 units, and position changes (degrees)
@attach(ExternalMotor, name='motor', capabilities=[('sense_speed', 5), 'sense_pos'])

And then within the run body:

await self.motor.set_speed(50)   # Setting the speed
await self.motor.ramp_speed(80, 2000)  # Ramp speed to 80 over 2 seconds
await self.motor.set_pos(90, speed=20) # Turn clockwise to 3 o'clock position
await self.motor.rotate(60, speed=-50) # Turn 60 degrees counter-clockwise from current position

See also

class bricknil.sensor.VisionSensor(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.Peripheral

Access the Boost Vision/Distance Sensor

Only the sensing capabilities of this sensor is supported right now.

  • sense_color: Returns one of the 10 predefined colors
  • sense_distance: Distance from 0-7 in roughly inches
  • sense_count: Running count of waving your hand/item over the sensor (32-bit)
  • sense_reflectivity: Under distances of one inch, the inverse of the distance
  • sense_ambient: Distance under one inch (so inverse of the preceeding)
  • sense_rgb: R, G, B values (3 sets of uint16)

Any combination of sense_color, sense_distance, sense_count, sense_reflectivity, and sense_rgb is supported.

Examples:

# Basic distance sensor
@attach(VisionSensor, name='vision', capabilities=['sense_color'])
# Or use the capability Enum
@attach(VisionSensor, name='vision', capabilities=[VisionSensor.capability.sense_color])

# Distance and color sensor
@attach(VisionSensor, name='vision', capabilities=['sense_color', 'sense_distance'])

# Distance and rgb sensor with different thresholds to trigger updates
@attach(VisionSensor, name='vision', capabilities=[('sense_color', 1), ('sense_rgb', 5)])

The values returned by the sensor will always be available in the instance variable self.value. For example, when the sense_color and sense_rgb capabilities are enabled, the following values will be stored and updated:

self.value = { VisionSensor.capability.sense_color:  uint8,
               VisionSensor.capability.sense_rgb:
                                [ uint16, uint16, uint16 ]
             }

Notes

The actual modes supported by the sensor are as follows:

  • 0 = color (0-10)
  • 1 = IR proximity (0-7)
  • 2 = count (32-bit int)
  • 3 = Reflt (inverse of distance when closer than 1”)
  • 4 = Amb (distance when closer than 1”)
  • 5 = COL (output) ?
  • 6 = RGB I
  • 7 = IR tx (output) ?
  • 8 = combined: Color byte, Distance byte, 0xFF, Reflected light
class capability

Bases: enum.Enum

An enumeration.

sense_ambient = 4
sense_color = 0
sense_count = 2
sense_distance = 1
sense_reflectivity = 3
sense_rgb = 6
datasets = {<capability.sense_color: 0>: (1, 1), <capability.sense_distance: 1>: (1, 1), <capability.sense_count: 2>: (1, 4), <capability.sense_reflectivity: 3>: (1, 1), <capability.sense_ambient: 4>: (1, 1), <capability.sense_rgb: 6>: (3, 2)}
allowed_combo = [<capability.sense_color: 0>, <capability.sense_distance: 1>, <capability.sense_count: 2>, <capability.sense_reflectivity: 3>, <capability.sense_rgb: 6>]
class bricknil.sensor.InternalTiltSensor(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.Peripheral

Access the internal tilt sensor in the Boost Move Hub.

The various modes are:

  • sense_angle - X, Y angles. Both are 0 if hub is lying flat with button up
  • sense_tilt - value from 0-9 if hub is tilted around any of its axis. Seems to be a rough mesaure of how much the hub is tilted away from lying flat. There is no update for just a translation along an axis
  • sense_orientation - returns one of the nine orientations below (0-9)
    • InternalTiltSensor.orientation.up = flat with button on top
    • InternalTiltSensor.orientation.right - standing up on side closest to button
    • InternalTiltSensor.orientation.left - standing up on side furthest from button
    • InternalTiltSensor.orientation.far_side - on long face facing away
    • InternalTiltSensor.orientation.near_side - on long face facing you
    • InternalTiltSensor.orientation.down - upside down
  • sense_impact - 32-bit count of impacts to sensor
  • sense_acceleration_3_axis - 3 bytes of raw accelerometer data.

Any combination of the above modes are allowed.

Examples:

# Basic tilt sensor
@attach(InternalTiltSensor, name='tilt', capabilities=['sense_tilt'])
# Or use the capability Enum
@attach(InternalTiltSensor, name='tilt', capabilities=[InternalTiltSensor.sense_tilt])

# Tilt and orientation sensor
@attach(InternalTiltSensor, name='tilt', capabilities=['sense_tilt, sense_orientation'])

The values returned by the sensor will always be available in the instance variable self.value. For example, when the sense_angle and sense_orientation capabilities are enabled, the following values will be stored and updated:

self.value = { InternalTiltSensor.capability.sense_angle:  [uint8, uint8],
               InternalTiltSensor.capability.sense_orientation:
                                Enum(InternalTiltSensor.orientation)
             }
class capability

Bases: enum.Enum

An enumeration.

sense_acceleration_3_axis = 4
sense_angle = 0
sense_impact = 3
sense_orientation = 2
sense_tilt = 1
datasets = {<capability.sense_angle: 0>: (2, 1), <capability.sense_tilt: 1>: (1, 1), <capability.sense_orientation: 2>: (1, 1), <capability.sense_impact: 3>: (1, 4), <capability.sense_acceleration_3_axis: 4>: (3, 1)}
allowed_combo = [<capability.sense_angle: 0>, <capability.sense_tilt: 1>, <capability.sense_orientation: 2>, <capability.sense_impact: 3>, <capability.sense_acceleration_3_axis: 4>]
class orientation

Bases: enum.Enum

An enumeration.

down = 5
far_side = 3
left = 2
near_side = 4
right = 1
up = 0
update_value(msg_bytes)[source]

If sense_orientation, then substitute the IntenalTiltSensor.orientation enumeration value into the self.value dict. Otherwise, don’t do anything special to the self.value dict.

class bricknil.sensor.LED(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.Peripheral

Changes the LED color on the Hubs:

@attach(LED, name='hub_led')

self.hub_led.set_output(Color.red)

set_color(color: bricknil.const.Color)[source]

Converts a Color enumeration to a color value

class bricknil.sensor.Light(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.Peripheral

Connects to the external light.

Example:

@attach(Light, name='light')

And then within the run body, use:

await self.light.set_brightness(brightness)
set_brightness(brightness: int)[source]

Sets the brightness of the light.

Parameters:brightness (int) – A value between -100 and 100 where 0 is off and -100 or 100 are both maximum brightness.
class bricknil.sensor.TrainMotor(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.Motor

Connects to the train motors.

TrainMotor has no sensing capabilities and only supports a single output mode that sets the speed.

Examples:

@attach(TrainMotor, name='train')

And then within the run body, use:

await self.train.set_speed(speed)
speed

Keep track of the current speed in order to ramp it

Type:int

See also

InternalMotor

class bricknil.sensor.RemoteButtons(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.Peripheral

Represents one set of ‘+’, ‘-‘, ‘red’ buttons on the PoweredHub Remote

Each remote has two sets of buttons, on the left and right side. Pick the one your want to attach to by using the port argument with either Port.L or Port.R.

There are actually a few different modes that the hardware supports, but we are only going to use one of them called ‘KEYSD’ (see the notes in the documentation on the raw values reported by the hub). This mode makes the remote send three values back in a list. To access each button state, there are three helper methods provided (see below)

Examples:

# Basic connection to the left buttons
@attach(RemoteButtons, name='left_buttons', port=RemoteButtons.Port.L)

# Getting values back in the handler
async def left_buttons_change(self):

    is_plus_pressed = self.left_buttons.plus_pressed()
    is_minus_pressed = self.left_buttons.minus_pressed()
    is_red_pressed = self.left_buttons.red_pressed()

Maps the port names L, R

class Port

Bases: enum.Enum

An enumeration.

L = 0
R = 1
class Button

Bases: enum.IntEnum

The button index in the value list returned by the sensor

MINUS = 2
PLUS = 0
RED = 1
class capability

Bases: enum.Enum

An enumeration.

sense_press = 4
datasets = {<capability.sense_press: 4>: (3, 1)}
allowed_combo = []
plus_pressed()[source]

Return whether value reflects that the PLUS button is pressed

minus_pressed()[source]

Return whether value reflects that the MINUS button is pressed

red_pressed()[source]

Return whether value reflects that the RED button is pressed

class bricknil.sensor.Button(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.Peripheral

Register to be notified of button presses on the Hub (Boost or PoweredUp)

This is actually a slight hack, since the Hub button is not a peripheral that is attached like other sensors in the Lego protocol. Instead, the buttons are accessed through Hub property messages. We abstract away these special messages to make the button appear to be like any other peripheral sensor.

Examples:

@attach(Button, name='hub_btn')

Notes

Since there is no attach I/O message from the hub to trigger the activate_updates() method, we instead insert a fake “attaach” message from this fake sensor on port 255 in the BLEventQ.get_messages method that is used to register for updates from a given sensor.

Call super-class with port set to 255

class capability

Bases: enum.Enum

An enumeration.

sense_press = 0
datasets = {<capability.sense_press: 0>: (1, 1)}
allowed_combo = [<capability.sense_press: 0>]
activate_updates()[source]

Use a special Hub Properties button message updates activation message

class bricknil.sensor.DuploTrainMotor(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.Motor

Train Motor on Duplo Trains

Make sure that the train is sitting on the ground (the front wheels need to keep rotating) in order to keep the train motor powered. If you pick up the train, the motor will stop operating withina few seconds.

Examples:

@attach(DuploTrainMotor, name='motor')

And then within the run body, use:

await self.train.set_speed(speed)
speed

Keep track of the current speed in order to ramp it

Type:int

See also

TrainMotor for connecting to a PoweredUp train motor

class bricknil.sensor.DuploSpeedSensor(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.Peripheral

Speedometer on Duplo train base that measures front wheel speed.

This can measure the following values:

  • sense_speed: Returns the speed of the front wheels
  • sense_count: Keeps count of the number of revolutions the front wheels have spun

Either or both can be enabled for measurement.

Examples:

# Report speed changes
@attach(DuploSpeedSensor, name='speed_sensor', capabilities=['sense_speed'])

# Report all
@attach(DuploSpeedSensor, name='speed_sensor', capabilities=['sense_speed', 'sense_count'])

The values returned by the sensor will be in self.value. For the first example, get the current speed by:

speed = self.speed_sensor.value

For the second example, the two values will be in a dict:

speed = self.speed_sensor.value[DuploSpeedSensor.sense_speed]
revs  = self.speed_sensor.value[DuploSpeedSensor.sense_count]
class capability

Bases: enum.Enum

An enumeration.

sense_count = 1
sense_speed = 0
datasets = {<capability.sense_speed: 0>: (1, 2), <capability.sense_count: 1>: (1, 4)}
allowed_combo = [<capability.sense_speed: 0>, <capability.sense_count: 1>]
class bricknil.sensor.DuploVisionSensor(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.Peripheral

Access the Duplo Vision/Distance Sensor

  • sense_color: Returns one of the 10 predefined colors
  • sense_ctag: Returns one of the 10 predefined tags
  • sense_reflectivity: Under distances of one inch, the inverse of the distance
  • sense_rgb: R, G, B values (3 sets of uint16)

Any combination of sense_color, sense_ctag, sense_reflectivity, and sense_rgb is supported.

Examples:

# Basic color sensor
@attach(DuploVisionSensor, name='vision', capabilities=['sense_color'])
# Or use the capability Enum
@attach(DuploVisionSensor, name='vision', capabilities=[DuploVisionSensor.capability.sense_color])

# Ctag and reflectivity sensor
@attach(DuploVisionSensor, name='vision', capabilities=['sense_ctag', 'sense_reflectivity'])

# Distance and rgb sensor with different thresholds to trigger updates
@attach(DuploVisionSensor, name='vision', capabilities=[('sense_color', 1), ('sense_rgb', 5)])

The values returned by the sensor will always be available in the instance variable self.value. For example, when the sense_color and sense_rgb capabilities are enabled, the following values will be stored and updated:

self.value = { DuploVisionSensor.capability.sense_color:  uint8,
               DuploVisionSensor.capability.sense_rgb:
                                [ uint16, uint16, uint16 ]
             }

Notes

The actual modes supported by the sensor are as follows:

  • 0 = color (0-10)
  • 1 = ctag (32-bit int)
  • 2 = Reflt (inverse of distance when closer than 1”)
  • 3 = RGB I
class capability

Bases: enum.Enum

An enumeration.

sense_color = 0
sense_ctag = 1
sense_reflectivity = 2
sense_rgb = 3
datasets = {<capability.sense_color: 0>: (1, 1), <capability.sense_ctag: 1>: (1, 1), <capability.sense_reflectivity: 2>: (1, 1), <capability.sense_rgb: 3>: (3, 2)}
allowed_combo = [<capability.sense_color: 0>, <capability.sense_ctag: 1>, <capability.sense_reflectivity: 2>, <capability.sense_rgb: 3>]
class bricknil.sensor.DuploSpeaker(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.Peripheral

Plays one of five preset sounds through the Duplo built-in speaker

See sounds for the list.

Examples:

@attach(DuploSpeaker, name='speaker')
...
await self.speaker.play_sound(DuploSpeaker.sounds.brake)

Notes

Uses Mode 1 to play the presets

class sounds

Bases: enum.Enum

An enumeration.

brake = 3
horn = 9
station = 5
steam = 10
water = 7
activate_updates()[source]

For some reason, even though the speaker is an output device we need to send a Port Input Format Setup command (0x41) to enable notifications. Otherwise, none of the sound output commands will play. This function is called automatically after this sensor is attached.

play_sound(sound)[source]
class bricknil.sensor.VoltageSensor(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.Peripheral

Voltage sensor

Returns the raw mV value (0-3893) which probably needs to be scaled to 0-9600.

It contains two capabilities, although they both appear to do the same thing: * sense_l * sense_s

Examples:

@attach(VoltageSensor, name='volts', capabilities=['sense_l'])
class capability

Bases: enum.Enum

An enumeration.

sense_l = 1
sense_s = 0
datasets = {<capability.sense_s: 0>: (1, 2), <capability.sense_l: 1>: (1, 2)}
allowed_combo = []
class bricknil.sensor.CurrentSensor(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.Peripheral

Voltage sensor

Returns the raw mA value (0-4095) which probably needs to be scaled to 0-2444.

It contains two capabilities, although they both appear to do the same thing: * sense_l * sense_s

Examples:

@attach(CurrentSensor, name='cur', capabilities=['sense_l'])
class capability

Bases: enum.Enum

An enumeration.

sense_l = 1
sense_s = 0
datasets = {<capability.sense_s: 0>: (1, 2), <capability.sense_l: 1>: (1, 2)}
allowed_combo = []