bricknil.peripheral

Base class for all sensors and motors

Classes

Motor(name[, port, capabilities]) Utility class for common functions shared between Train Motors, Internal Motors, and External Motors
Peripheral(name[, port, capabilities]) Abstract base class for any Lego Boost/PoweredUp/WeDo peripherals
TachoMotor(name[, port, capabilities])

Members

class bricknil.peripheral.Peripheral(name, port=None, capabilities=[])[source]

Bases: bricknil.process.Process

Abstract base class for any Lego Boost/PoweredUp/WeDo peripherals

A LEGO sensor can provide either a single sensing capability, or a combined mode where it returns multiple sensing values. All the details can be found in the official protocol description.

  • Single capability - This is the easiest to handle:
    • Send a 0x41 Port Input Format Setup command to put the sensor port into the respective mode and activate updates
    • Read back the 0x45 Port Value(Single) messages with updates from the sensor on the respective mode
  • Multiple capabilities - This is more complicated because we need to put the sensor port into CombinedMode
    • Send a [0x42, port, 0x02] message to lock the port
    • Send multiple 0x41 messages to activate each capability/mode we want updates from
    • Send a [0x42, port, 0x01, ..] message with the following bytes:
      • 0x00 = Row entry 0 in the supported combination mode table
        (hard-coded for simplicity here because LEGO seems to only use this entry most of the time)
      • For each mode/capability, send a byte like the following:
        • Upper 4-bits is mode number
        • Lower 4-bits is the dataset number
        • For example, for getting RGB values, it’s mode 6, and we want all three datasets
          (for each color), so we’d add three bytes [0x60, 0x61, 0x62]. If you just wanted the Red value, you just append [0x60]
    • Send a [0x42, port, 0x03] message to unlock the port
    • Now, when the sensor sends back values, it uses 0x46 messages with the following byte sequence:
      • Port id
      • 16-bit entry where the true bits mark which mode has values included in this message
        (So 0x00 0x05 means values from Modes 2 and 0)
      • Then the set of values from the sensor, which are ordered by Mode number
        (so the sensor reading from mode 0 would come before the reading from mode 2)
      • Each set of values includes however many bytes are needed to represent each dataset
        (for example, up to 3 for RGB colors), and the byte-width of each value (4 bytes for a 32-bit int)
Parameters:
  • capabilities

    can be input in the following formats (where the number in the tuple can be a threshold to trigger updates)

    • [‘sense_color’, ‘sense_distannce’]
    • [capability.sense_color, capability.sense_distance]
    • [(‘sense_color’, 1), (‘sense_distance’, 2)]
  • name (str) – Human readable name
  • port (int) – Port to connect to (otherwise will connect to first matching peripheral with defined sensor_id)
port

Physical port on the hub this Peripheral attaches to

Type:int
sensor_name

Name coming out of const.DEVICES

Type:str
value

Sensor readings get dumped into this dict

Type:dict
message_handler

Outgoing message queue to BLEventQ that’s set by the Hub when an attach message is seen

Type:func
capabilites

Support capabilities

Type:list [ capability ]
thresholds

Integer list of thresholds for updates for each of the sensing capabilities

Type:list [ int ]
activate_updates()[source]

Send a message to the sensor to activate updates

Called via an ‘attach’ message from bricknil.messages.Message.parse_attached_io() that triggers this call from bricknil.hub.Hub.peripheral_message_loop()

See class description for explanation on how Combined Mode updates are done.

Returns:None
send_message(msg, msg_bytes)[source]

Send outgoing message to BLEventQ

set_output(mode, value)[source]

Don’t change this unless you’re changing the way you do a Port Output command

Outputs the following sequence to the sensor
  • 0x00 = hub id from common header
  • 0x81 = Port Output Command
  • port
  • 0x11 = Upper nibble (0=buffer, 1=immediate execution), Lower nibble (0=No ack, 1=command feedback)
  • 0x51 = WriteDirectModeData
  • mode
  • value(s)
update_value(msg_bytes)[source]

Callback from message parser to update a value from a sensor incoming message Depending on the number of capabilities enabled, we end up with different processing:

If zero, then just set the self.value field to the raw message.

If one, then:
  • Parse the single sensor message which may have multiple data items (like an RGB color value)
  • self.value dict entry for this capability becomes a list of these values
If multiple, then:
  • Parse multiple sensor messages (could be any combination of the enabled modes)
  • Set each dict entry to self.value to either a list of multiple values or a single value
class bricknil.peripheral.Motor(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.Peripheral

Utility class for common functions shared between Train Motors, Internal Motors, and External Motors

ramp_speed(target_speed, ramp_time_ms)[source]

Ramp the speed by 10 units in the time given

set_speed(speed)[source]

Validate and set the train speed

If there is an in-progress ramp, and this command is not part of that ramp, then cancel that in-progress ramp first, before issuing this set_speed command.

Parameters:speed (int) – Range -100 to 100 where negative numbers are reverse. Use 0 to put the motor into neutral. 255 will do a hard brake
class bricknil.peripheral.TachoMotor(name, port=None, capabilities=[])[source]

Bases: bricknil.peripheral.Motor

class capability

Bases: enum.Enum

An enumeration.

sense_pos = 2
sense_speed = 1
datasets = {<capability.sense_speed: 1>: (1, 1), <capability.sense_pos: 2>: (1, 4)}

Dict of (num_datasets, bytes_per_dataset). sense_speed (1-byte), and sense_pos (uint32)

allowed_combo = [<capability.sense_speed: 1>, <capability.sense_pos: 2>]
ramp_speed2(target_speed, ramp_time_ms)[source]

Experimental function, not implemented yet DO NOT USE

rotate(degrees, speed, max_power=50)[source]

Rotate the given number of degrees from current position, with direction given by sign of speed

Examples:

await self.motor.rotate(90, speed=50)   # Rotate 90 degrees clockwise (looking from end of shaft towards motor)
await self.motor.set_pos(90, speed=-50)  # Rotate conter-clockwise 90 degrees
await self.motor.set_pos(720, speed=50)  # Rotate two full circles clockwise
Parameters:
  • degrees (uint) – Relative number of degrees to rotate
  • speed (int) – -100 to 100
  • max_power (int) – Max percentage power that will be applied (0-100%)

Notes

Use command StartSpeedForDegrees
  • 0x00 = hub id
  • 0x81 = Port Output command
  • port
  • 0x11 = Upper nibble (0=buffer, 1=immediate execution), Lower nibble (0=No ack, 1=command feedback)
  • 0x0b = Subcommand
  • degrees (int32) 0..1000000
  • speed -100 - 100%
  • max_power abs(0-100%)
  • endstate = 0 (float), 126 (hold), 127 (brake)
  • Use Accel profile = (bit 0 = acc profile, bit 1 = decc profile)
set_pos(pos, speed=50, max_power=50)[source]

Set the absolute position of the motor

Everytime the hub is powered up, the zero-angle reference will be reset to the motor’s current position. When you issue this command, the motor will rotate to the position given in degrees. The sign of the pos tells you which direction to rotate: (1) a positive number will rotate clockwise as looking from end of shaft towards the motor, (2) a negative number will rotate counter-clockwise

Examples:

await self.motor.set_pos(90)   # Rotate 90 degrees clockwise (looking from end of shaft towards motor)
await self.motor.set_pos(-90)  # Rotate conter-clockwise 90 degrees
await self.motor.set_pos(720)  # Rotate two full circles clockwise
Parameters:
  • pos (int) – Absolute position in degrees.
  • speed (int) – Absolute value from 0-100
  • max_power (int) – Max percentage power that will be applied (0-100%)

Notes

Use command GotoAbsolutePosition
  • 0x00 = hub id
  • 0x81 = Port Output command
  • port
  • 0x11 = Upper nibble (0=buffer, 1=immediate execution), Lower nibble (0=No ack, 1=command feedback)
  • 0x0d = Subcommand
  • abs_pos (int32)
  • speed -100 - 100
  • max_power abs(0-100%)
  • endstate = 0 (float), 126 (hold), 127 (brake)
  • Use Accel profile = (bit 0 = acc profile, bit 1 = decc profile)