• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pybricks / pybricksdev / 18762957957

23 Oct 2025 09:49PM UTC coverage: 53.33% (-0.02%) from 53.352%
18762957957

Pull #124

github

web-flow
Merge 1eb446d8b into a58f94d1c
Pull Request #124: Typing updates

65 of 253 branches covered (25.69%)

Branch coverage included in aggregate %.

1985 of 3591 relevant lines covered (55.28%)

0.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.33
pybricksdev/ble/lwp3/bytecodes.py
1
# SPDX-License-Identifier: MIT
2
# Copyright (c) 2021-2024 The Pybricks Authors
3
# Some portions of the documentation:
4
# Copyright (c) 2018 LEGO System A/S
5

6
"""
7
The LWP3 :mod:`.bytecodes` module contains enums for interpreting binary values
8
used in the `LWP3 protocol`_.
9

10
.. _LWP3 protocol: https://lego.github.io/lego-ble-wireless-protocol-docs/
11
"""
12

13
from enum import IntEnum, IntFlag, unique
1✔
14

15

16
def _create_pseudo_member_(cls: type[IntEnum], value: int) -> IntEnum:
1✔
17
    """
18
    Creates a new enum member at runtime for ``IntEnum``s.
19
    """
20
    pseudo_member = cls._value2member_map_.get(value, None)
1✔
21
    if pseudo_member is None:
1✔
22
        # construct singleton pseudo-members
23
        pseudo_member = int.__new__(cls, value)
1✔
24
        pseudo_member._name_ = str(value)
1✔
25
        pseudo_member._value_ = value
1✔
26
        # use setdefault in case another thread already created a composite
27
        # with this value
28
        pseudo_member = cls._value2member_map_.setdefault(value, pseudo_member)
1✔
29
    return pseudo_member
1✔
30

31

32
MAX_NAME_SIZE = 14
1✔
33
"""The maximum allowable size of the hub name in bytes."""
1✔
34

35

36
class Version(int):
1✔
37
    """An encoded version number."""
38

39
    @property
1✔
40
    def major(self) -> int:
1✔
41
        """Gets the major version component."""
42
        return (self >> 28) & 0xF
1✔
43

44
    @property
1✔
45
    def minor(self) -> int:
1✔
46
        """Gets the minor version component."""
47
        return (self >> 24) & 0xF
1✔
48

49
    @property
1✔
50
    def bug(self) -> int:
1✔
51
        """Gets the bug fix version component."""
52
        # uses BCD, so convert to string and back to int
53
        return int(f"{((self >> 16) & 0xFF):X}")
1✔
54

55
    @property
1✔
56
    def build(self) -> int:
1✔
57
        """Gets the bug fix version component."""
58
        # uses BCD, so convert to string and back to int
59
        return int(f"{(self & 0xFFFF):X}")
1✔
60

61
    @staticmethod
1✔
62
    def parse(version: str) -> "Version":
1✔
63
        """Parses a string with the format ``X.X.XX.XXXX``."""
64
        major, minor, bug, build = version.split(".")
1✔
65
        major = int(major) << 28
1✔
66
        minor = int(minor) << 24
1✔
67
        bug = int(bug, 16) << 16
1✔
68
        build = int(build, 16)
1✔
69
        return Version(major | minor | bug | build)
1✔
70

71
    def __str__(self) -> str:
1✔
72
        """Returns the version in ``X.X.XX.XXXX`` format."""
73
        ver = list(f"{self:08X}")
1✔
74
        ver.insert(4, ".")
1✔
75
        ver.insert(2, ".")
1✔
76
        ver.insert(1, ".")
1✔
77
        return "".join(ver)
1✔
78

79
    def __repr__(self) -> str:
1✔
80
        return f"{self.__class__.__name__}(0x{self:08X})"
1✔
81

82

83
class LWPVersion(int):
1✔
84
    """The LEGO Wireless Protocol (LWP) version."""
85

86
    @property
1✔
87
    def major(self) -> int:
1✔
88
        """Gets the major version component."""
89
        # uses BCD, so convert to string and back to int
90
        return int(f"{((self >> 8) & 0xFF):X}")
1✔
91

92
    @property
1✔
93
    def minor(self) -> int:
1✔
94
        """Gets the minor version component."""
95
        # uses BCD, so convert to string and back to int
96
        return int(f"{(self & 0xFF):X}")
1✔
97

98
    @staticmethod
1✔
99
    def parse(version: str) -> "LWPVersion":
1✔
100
        """Parses a string with the format ``XX.XX``."""
101
        major, minor = version.split(".")
1✔
102
        major = int(major, 16) << 8
1✔
103
        minor = int(minor, 16)
1✔
104
        return LWPVersion(major | minor)
1✔
105

106
    def __str__(self) -> str:
1✔
107
        """Returns the version in ``XX.XX`` format."""
108
        ver = list(f"{self:04X}")
1✔
109
        ver.insert(2, ".")
1✔
110
        return "".join(ver)
1✔
111

112
    def __repr__(self) -> str:
1✔
113
        return f"{self.__class__.__name__}(0x{self:04X})"
1✔
114

115

116
class BluetoothAddress(bytes):
1✔
117
    """
118
    A Bluetooth address.
119

120
    These addresses use the same EUI-48 format as MAC addresses but are for
121
    identifying individual Bluetooth devices instead of network cards.
122
    """
123

124
    def __new__(cls, value: str | bytes) -> "BluetoothAddress":
1✔
125
        if isinstance(value, str):
1✔
126
            # if it is a string, assume the format "XX:XX:XX:XX:XX:XX"
127
            value = [int(x, 16) for x in value.split(":")]
1✔
128

129
        if len(value) != 6:
1✔
130
            raise TypeError("requires exactly 6 bytes")
×
131

132
        return bytes.__new__(BluetoothAddress, value)
1✔
133

134
    def __str__(self) -> str:
1✔
135
        return ":".join(f"{b:02X}" for b in self)
1✔
136

137
    def __repr__(self) -> str:
1✔
138
        return f"{self.__class__.__name__}({repr(str(self))})"
1✔
139

140

141
@unique
1✔
142
class SystemKind(IntEnum):
1✔
143
    """
144
    Indicates the system that a hub belongs to.
145

146
    This is encoded in :class:`HubKind` and is not used as a bytecode on its own.
147
    """
148

149
    WEDO2 = 0 << 5
1✔
150
    """LEGO WeDo 2.0"""
1✔
151

152
    DUPLO = 1 << 5
1✔
153
    """LEGO Duplo"""
1✔
154

155
    SYSTEM = 2 << 5
1✔
156
    """LEGO System"""
1✔
157

158
    SYSTEM_ = 3 << 5
1✔
159
    """LEGO System"""
1✔
160

161
    TECHNIC = 4 << 5
1✔
162
    """LEGO Technic"""
1✔
163

164
    LEGACY = 7 << 5
1✔
165
    """Pre-Powered Up (unofficial Pybricks addition)"""
1✔
166

167

168
@unique
1✔
169
class HubKind(IntEnum):
1✔
170
    """
171
    Indicates the kind of hub.
172

173
    This is used both in advertising data an in messages.
174
    """
175

176
    WEDO2 = 0x00
1✔
177
    """LEGO WeDo 2.0 Hub."""
1✔
178

179
    DUPLO_TRAIN = 0x20
1✔
180
    """LEGO Duplo Train."""
1✔
181

182
    BOOST = 0x40
1✔
183
    """LEGO BOOST Move Hub."""
1✔
184

185
    CITY = 0x41
1✔
186
    """LEGO 2-port Hub."""
1✔
187

188
    HANDSET = 0x42
1✔
189
    """LEGO 2-port Handset (remote control)."""
1✔
190

191
    MARIO = 0x43
1✔
192
    """LEGO Mario Hub."""
1✔
193

194
    LUIGI = 0x44
1✔
195
    """LEGO Luigi Hub."""
1✔
196

197
    PEACH = 0x45
1✔
198
    """LEGO Peach Hub."""
1✔
199

200
    TECHNIC = 0x80
1✔
201
    """LEGO 4-port Technic Hub."""
1✔
202

203
    TECHNIC_LARGE = 0x81
1✔
204
    """LEGO SPIKE Prime Hub and MINDSTORMS Robot Inventor hubs."""
1✔
205

206
    TECHNIC_SMALL = 0x83
1✔
207
    """LEGO SPIKE Essential Hub."""
1✔
208

209
    TECHNIC_MOVE = 0x84
1✔
210
    """LEGO Technic Move Hub."""
1✔
211

212
    RCX = 0xE0
1✔
213
    """LEGO MINDSTORMS RCX brick. (unofficial Pybricks addition)"""
1✔
214

215
    NXT = 0xE1
1✔
216
    """LEGO MINDSTORMS NXT brick. (unofficial Pybricks addition)"""
1✔
217

218
    EV3 = 0xE2
1✔
219
    """LEGO MINDSTORMS EV3 brick. (unofficial Pybricks addition)"""
1✔
220

221
    @property
1✔
222
    def system(self) -> SystemKind:
1✔
223
        """
224
        Gets ths system that this hub belongs to.
225
        """
226
        # system is encoded in the first 3 bits
227
        return SystemKind(self & (7 << 5))
×
228

229

230
@unique
1✔
231
class Capabilities(IntFlag):
1✔
232
    """
233
    Describes the capabilities of a hub.
234

235
    These flags are used in advertising data.
236
    """
237

238
    CENTRAL = 1 << 0
1✔
239
    """The hub supports the central role."""
1✔
240

241
    PERIPHERAL = 1 << 1
1✔
242
    """The hub supports the peripheral role."""
1✔
243

244
    IO = 1 << 2
1✔
245
    """The hub has I/O ports for connecting sensors/motors."""
1✔
246

247
    REMOTE = 1 << 3
1✔
248
    """The hub acts as a remote control."""
1✔
249

250

251
@unique
1✔
252
class LastNetwork(IntEnum):
1✔
253
    """
254
    Describes the last network ID used in the LWP3 pairing process.
255

256
    This is used both in advertising data and messages.
257
    """
258

259
    NONE = 0
1✔
260
    """No connection has been made yet."""
1✔
261

262
    LOCKED = 251
1✔
263
    """Locked (default 1)."""
1✔
264

265
    NOT_LOCKED = 252
1✔
266
    """Not locked (default 2)."""
1✔
267

268
    RSSI = 253
1✔
269
    """RSSI dependant (default 3)."""
1✔
270

271
    DISABLE_HW_NET = 254
1✔
272
    """Disable hardware network (default 4)."""
1✔
273

274
    DONT_CARE = 255
1✔
275
    """Don't care (not implemented)."""
1✔
276

277
    @classmethod
1✔
278
    def _missing_(cls, value):
1✔
279
        # only 1 to 250 are valid "last connection" IDs
280
        if value <= cls.NONE or value >= cls.LOCKED:
1✔
281
            return None
×
282
        return _create_pseudo_member_(cls, value)
1✔
283

284

285
@unique
1✔
286
class Status(IntFlag):
1✔
287
    """
288
    Indicates the status of a connection request.
289

290
    This is used both in advertising data and in messages.
291
    """
292

293
    PERIPHERAL = 0x01
1✔
294
    """The requesting device can be a peripheral."""
1✔
295

296
    CENTRAL = 0x02
1✔
297
    """The requesting device can be a central."""
1✔
298

299
    REQUEST_WINDOW = 0x20
1✔
300
    """A button press is extending the request window."""
1✔
301

302
    REQUEST_CONNECT = 0x40
1✔
303
    """A hard-coded request."""
1✔
304

305

306
@unique
1✔
307
class BatteryKind(IntEnum):
1✔
308
    """The kind of battery."""
309

310
    NORMAL = 0x00
1✔
311
    """Standard AA or AAA batteries."""
1✔
312

313
    RECHARGEABLE = 0x01
1✔
314
    """Rechargeable Li-ion or Li-po battery pack."""
1✔
315

316

317
@unique
1✔
318
class MessageKind(IntEnum):
1✔
319
    """
320
    Indicates the kind of message.
321
    """
322

323
    # Hub related
324

325
    HUB_PROPERTY = 0x01
1✔
326
    """Messages that get or set hub a hub property."""
1✔
327

328
    HUB_ACTION = 0x02
1✔
329
    """Messages that perform a hub action."""
1✔
330

331
    HUB_ALERT = 0x03
1✔
332
    """Messages to subscribe or retrieve hub alerts."""
1✔
333

334
    HUB_ATTACHED_IO = 0x04
1✔
335
    """Messages received when an I/O device is attached."""
1✔
336

337
    ERROR = 0x05
1✔
338
    """Generic error messages."""
1✔
339

340
    HW_NET_CMD = 0x08
1✔
341
    """Commands used for hardware networks."""
1✔
342

343
    FW_UPDATE = 0x10
1✔
344
    """Message to put the hub in firmware update mode."""
1✔
345

346
    FW_LOCK = 0x011
1✔
347
    """Message to lock the bootloader flash memory (factory use)."""
1✔
348

349
    FW_LOCK_STATUS_REQ = 0x12
1✔
350
    """Message to request the bootloader flash memory lock state."""
1✔
351

352
    FW_LOCK_STATUS = 0x13
1✔
353
    """Reply to :attr:`FW_LOCK_STATUS_REQ`."""
1✔
354

355
    # port related
356

357
    PORT_INFO_REQ = 0x21
1✔
358
    """Messages that request port information."""
1✔
359

360
    PORT_MODE_INFO_REQ = 0x22
1✔
361
    """Messages tha request mode information."""
1✔
362

363
    PORT_INPUT_FMT_SETUP = 0x41
1✔
364
    """Message to set up input format for a single mode."""
1✔
365

366
    PORT_INPUT_FMT_SETUP_COMBO = 0x42
1✔
367
    """Message to set up input format for a mode combo."""
1✔
368

369
    PORT_INFO = 0x43
1✔
370
    """Reply to a :attr:`PORT_INFO_REQ`."""
1✔
371

372
    PORT_MODE_INFO = 0x44
1✔
373
    """Reply to a :attr:`PORT_MODE_INFO_REQ`."""
1✔
374

375
    PORT_VALUE = 0x45
1✔
376
    """Value update from a single mode."""
1✔
377

378
    PORT_VALUE_COMBO = 0x46
1✔
379
    """Value update from a mode combo."""
1✔
380

381
    PORT_INPUT_FMT = 0x47
1✔
382
    """Reply to a :attr:`PORT_INPUT_FMT_SETUP`."""
1✔
383

384
    PORT_INPUT_FMT_COMBO = 0x48
1✔
385
    """Reply to a :attr:`PORT_INPUT_FMT_SETUP_COMBO`."""
1✔
386

387
    VIRTUAL_PORT_SETUP = 0x61
1✔
388
    """Message to set up virtual port that provides synchronization of two ports."""
1✔
389

390
    PORT_OUTPUT_CMD = 0x81
1✔
391
    """Messages to execute port output commands."""
1✔
392

393
    PORT_OUTPUT_CMD_FEEDBACK = 0x82
1✔
394
    """Message that indicate a port output command has completed."""
1✔
395

396

397
@unique
1✔
398
class HubProperty(IntEnum):
1✔
399
    """
400
    Properties used in :attr:`MessageKind.HUB_PROPERTY` messages.
401
    """
402

403
    NAME = 0x01
1✔
404
    """The hub name."""
1✔
405

406
    BUTTON = 0x02
1✔
407
    """The button state."""
1✔
408

409
    FW_VERSION = 0x03
1✔
410
    """The firmware version."""
1✔
411

412
    HW_VERSION = 0x04
1✔
413
    """The hardware version."""
1✔
414

415
    RSSI = 0x05
1✔
416
    """Radio signal strength indication."""
1✔
417

418
    BATTERY_VOLTAGE = 0x06
1✔
419
    """Battery voltage (as percent)."""
1✔
420

421
    BATTERY_KIND = 0x07
1✔
422
    """Battery type."""
1✔
423

424
    MFG_NAME = 0x08
1✔
425
    """Manufacturer name."""
1✔
426

427
    RADIO_FW_VERSION = 0x09
1✔
428
    """Bluetooth radio firmware version."""
1✔
429

430
    LWP_VERSION = 0x0A
1✔
431
    """LEGO Wireless Protocol version."""
1✔
432

433
    HUB_KIND = 0x0B
1✔
434
    """The kind of hub."""
1✔
435

436
    HW_NET_ID = 0x0C
1✔
437
    """Hardware network ID."""
1✔
438

439
    BDADDR = 0x0D
1✔
440
    """Bluetooth address."""
1✔
441

442
    BOOTLOADER_BDADDR = 0x0E
1✔
443
    """Bootloader Bluetooth address."""
1✔
444

445
    HW_NET_FAMILY = 0x0F
1✔
446
    """Hardware network family."""
1✔
447

448
    VOLUME = 0x12
1✔
449
    """Sound volume level."""
1✔
450

451

452
@unique
1✔
453
class HubPropertyOperation(IntEnum):
1✔
454
    """
455
    Operations for hub property messages.
456
    """
457

458
    SET = 0x01
1✔
459
    """Sets a property."""
1✔
460

461
    ENABLE_UPDATES = 0x02
1✔
462
    """Enables continuous updates for a property."""
1✔
463

464
    DISABLE_UPDATES = 0x03
1✔
465
    """Disables continuous updates for a property."""
1✔
466

467
    RESET = 0x04
1✔
468
    """Resets a property to the default value."""
1✔
469

470
    REQUEST_UPDATE = 0x05
1✔
471
    """Requests a single property update."""
1✔
472

473
    UPDATE = 0x06
1✔
474
    """Contains the current value of a property."""
1✔
475

476

477
@unique
1✔
478
class HubAction(IntEnum):
1✔
479
    """
480
    Instructs the hub to perform an action.
481
    """
482

483
    # sent to hub
484

485
    POWER_OFF = 0x01
1✔
486
    """Switches the hub off."""
1✔
487

488
    DISCONNECT = 0x02
1✔
489
    """Instructs the hub to disconnect Bluetooth."""
1✔
490

491
    PORT_VCC_ON = 0x03
1✔
492
    """Turns on 3.3V on I/O port pin 4 for all ports."""
1✔
493

494
    PORT_VCC_OFF = 0x04
1✔
495
    """Turns off 3.3V on I/O port pin 4 for all ports."""
1✔
496

497
    SET_BUSY = 0x05
1✔
498
    """Sets busy indication on status light."""
1✔
499

500
    RESET_BUSY = 0x06
1✔
501
    """Resets busy indication on status light."""
1✔
502

503
    FAST_POWER_OFF = 0x2F
1✔
504
    """Fast power off (factory use)."""
1✔
505

506
    # received from hub
507

508
    WILL_POWER_OFF = 0x30
1✔
509
    """The hub will switch off."""
1✔
510

511
    WILL_DISCONNECT = 0x31
1✔
512
    """The hub will disconnect."""
1✔
513

514
    WILL_UPDATE = 0x32
1✔
515
    """The hub will restart in firmware update mode."""
1✔
516

517

518
@unique
1✔
519
class AlertKind(IntEnum):
1✔
520
    """
521
    Alert conditions received from the hub.
522
    """
523

524
    LOW_VOLTAGE = 0x01
1✔
525
    """The battery voltage is low."""
1✔
526

527
    HIGH_CURRENT = 0x02
1✔
528
    """The battery current is high."""
1✔
529

530
    LOW_SIGNAL = 0x03
1✔
531
    """The Bluetooth signal strength is low."""
1✔
532

533
    OVER_POWER = 0x04
1✔
534
    """The hub is using too much power."""
1✔
535

536

537
@unique
1✔
538
class AlertOperation(IntEnum):
1✔
539
    """
540
    Operations that can be done with hub alerts.
541
    """
542

543
    ENABLE_UPDATES = 0x01
1✔
544
    """Enables updates from the hub."""
1✔
545

546
    DISABLE_UPDATES = 0x02
1✔
547
    """Disables updates from the hub."""
1✔
548

549
    REQUEST_UPDATE = 0x03
1✔
550
    """Requests a single update from the hub."""
1✔
551

552
    UPDATE = 0x04
1✔
553
    """Contains an update received from the hub."""
1✔
554

555

556
@unique
1✔
557
class AlertStatus(IntEnum):
1✔
558
    """
559
    Indicates if the alert is present or not."""
560

561
    OK = 0x00
1✔
562
    """Status is OK."""
1✔
563

564
    ALERT = 0xFF
1✔
565
    """The alert condition is present."""
1✔
566

567

568
@unique
1✔
569
class PortID(IntEnum):
1✔
570
    """
571
    Represents the ID of an external or internal I/O port.
572

573
    All enum members are dynamic. (0 to 49 are external and 50 - 100 are internal)
574
    """
575

576
    names = ()
1✔
577

578
    @property
1✔
579
    def internal(self) -> bool:
1✔
580
        """Tests if the port is internal."""
581
        return 50 <= self <= 100
1✔
582

583
    @classmethod
1✔
584
    def _missing_(cls, value):
1✔
585
        # 8-bit value, 101-255 is reserved
586
        if value < 0 or value > 100:
1✔
587
            return None
×
588
        return _create_pseudo_member_(cls, value)
1✔
589

590

591
@unique
1✔
592
class IOEvent(IntEnum):
1✔
593
    """
594
    Events from I/O ports.
595
    """
596

597
    DETACHED = 0x00
1✔
598
    """An I/O device was detached."""
1✔
599

600
    ATTACHED = 0x01
1✔
601
    """An I/O device was attached."""
1✔
602

603
    ATTACHED_VIRTUAL = 0x02
1✔
604
    """A virtual I/O device was attached."""
1✔
605

606

607
@unique
1✔
608
class IODeviceKind(IntEnum):
1✔
609
    """
610
    The kind of attached I/O device.
611
    """
612

613
    NONE = 0x00
1✔
614
    """No device."""
1✔
615

616
    MEDIUM_MOTOR = 0x01
1✔
617
    """Powered Up Medium Motor."""
1✔
618

619
    TRAIN_MOTOR = 0x02
1✔
620
    """Powered Up Train Motor"""
1✔
621

622
    LIGHTS = 0x08
1✔
623
    """Powered Up Lights."""
1✔
624

625
    HUB_BATTERY_VOLTAGE = 0x14
1✔
626
    """Powered Up Hub battery voltage."""
1✔
627

628
    HUB_BATTERY_CURRENT = 0x15
1✔
629
    """Powered Up Hub battery current."""
1✔
630

631
    HUB_PIEZO = 0x16
1✔
632
    """Powered Up Hub piezo buzzer."""
1✔
633

634
    HUB_STATUS_LIGHT = 0x17
1✔
635
    """Powered Up Hub status light."""
1✔
636

637
    EV3_COLOR_SENSOR = 0x1D
1✔
638
    """EV3 Color Sensor."""
1✔
639

640
    EV3_ULTRASONIC_SENSOR = 0x1E
1✔
641
    """EV3 Ultrasonic Sensor."""
1✔
642

643
    EV3_GYRO_SENSOR = 0x20
1✔
644
    """EV3 Gyro Sensor."""
1✔
645

646
    EV3_IR_SENSOR = 0x21
1✔
647
    """EV3 Infrared Sensor."""
1✔
648

649
    WEDO_TILT_SENSOR = 0x22
1✔
650
    """WeDo 2.0 Tilt Sensor."""
1✔
651

652
    WEDO_MOTION_SENSOR = 0x23
1✔
653
    """WeDo 2.0 Motion Sensor."""
1✔
654

655
    WEDO_GENERIC = 0x24
1✔
656
    """WeDo 2.0 generic device."""
1✔
657

658
    BOOST_COLOR_DISTANCE_SENSOR = 0x25
1✔
659
    """BOOST Color and Distance Sensor"""
1✔
660

661
    BOOST_INTERACTIVE_MOTOR = 0x26
1✔
662
    """BOOST Interactive Motor"""
1✔
663

664
    BOOST_HUB_MOTOR = 0x27
1✔
665
    """BOOST Move Hub built-in motor."""
1✔
666

667
    BOOST_HUB_ACCEL = 0x28
1✔
668
    """BOOST Move Hub built-in accelerometer (tilt sensor)."""
1✔
669

670
    DUPLO_TRAIN_MOTOR = 0x29
1✔
671
    """DUPLO Train hub built-in motor."""
1✔
672

673
    DUPLO_TRAIN_BEEPER = 0x2A
1✔
674
    """DUPLO Train hub built-in beeper."""
1✔
675

676
    DUPLO_TRAIN_COLOR_SENSOR = 0x2B
1✔
677
    """DUPLO Train hub built-in color sensor."""
1✔
678

679
    DUPLO_TRAIN_SPEED = 0x2C
1✔
680
    """DUPLO Train hub built-in speed sensor."""
1✔
681

682
    TECHNIC_LARGE_MOTOR = 0x2E
1✔
683
    """Technic Control+ Large Motor."""
1✔
684

685
    TECHNIC_XL_MOTOR = 0x2F
1✔
686
    """Technic Control+ XL Motor."""
1✔
687

688
    SPIKE_MEDIUM_MOTOR = 0x30
1✔
689
    """SPIKE Prime Medium Motor."""
1✔
690

691
    SPIKE_LARGE_MOTOR = 0x31
1✔
692
    """SPIKE Prime Large Motor."""
1✔
693

694
    HUB_UNKNOWN_X32 = 0x32
1✔
695
    """Technic Control+ Hub ?"""
1✔
696

697
    HUB_IMU_GESTURE = 0x36
1✔
698
    """Powered Up hub built-int IMU gesture."""
1✔
699

700
    REMOTE_BUTTONS = 0x37
1✔
701
    """Powered Up Handset Buttons."""
1✔
702

703
    HUB_RSSI = 0x38
1✔
704
    """Powered Up hub Bluetooth RSSI"""
1✔
705

706
    HUB_IMU_ACCEL = 0x39
1✔
707
    """Powered Up hub built-in IMU accelerometer."""
1✔
708

709
    HUB_IMU_GYRO = 0x3A
1✔
710
    """Powered Up hub built-in IMU gyro"""
1✔
711

712
    HUB_IMU_ORIENTATION = 0x3B
1✔
713
    """Powered Up hub built-in IMU orientation."""
1✔
714

715
    HUB_IMU_TEMPERATURE = 0x3C
1✔
716
    """Powered Up hub built-in IMU temperature."""
1✔
717

718
    SPIKE_COLOR_SENSOR = 0x3D
1✔
719
    """SPIKE/MINDSTORMS Color Sensor."""
1✔
720

721
    SPIKE_ULTRASONIC_SENSOR = 0x3E
1✔
722
    """SPIKE/MINDSTORMS Ultrasonic Distance Sensor."""
1✔
723

724
    SPIKE_FORCE_SENSOR = 0x3F
1✔
725
    """SPIKE/MINDSTORMS Force Sensor."""
1✔
726

727
    TECHNIC_MEDIUM_ANGULAR_MOTOR = 0x4B
1✔
728
    """Technic Medium Angular Motor, gray."""
1✔
729

730
    TECHNIC_LARGE_ANGULAR_MOTOR = 0x4C
1✔
731
    """Technic Large Angular motor, gray."""
1✔
732

733
    @classmethod
1✔
734
    def _missing_(cls, value):
1✔
735
        # allow missing here for home-made devices
736
        if value < 0 or value > 65535:
×
737
            return None
×
738
        return _create_pseudo_member_(cls, value)
×
739

740

741
@unique
1✔
742
class ErrorCode(IntEnum):
1✔
743
    """
744
    Generic error codes.
745
    """
746

747
    ACK = 0x01
1✔
748

749
    NACK = 0x02
1✔
750

751
    BUFFER_OVERFLOW = 0x03
1✔
752

753
    TIMEOUT = 0x04
1✔
754

755
    UNKNOWN_COMMAND = 0x05
1✔
756
    """The command is not supported."""
1✔
757

758
    INVALID = 0x06
1✔
759
    """Invalid use (e.g. invalid parameters)."""
1✔
760

761
    OVER_CURRENT = 0x07
1✔
762

763
    INTERNAL_ERROR = 0x08
1✔
764

765

766
@unique
1✔
767
class HwNetCmd(IntEnum):
1✔
768
    """
769
    Hardware network commands.
770

771
    These commands are for creating hub-to-hub connections, not hub to phone/tablet/computer
772
    connections.
773
    """
774

775
    CONNECTION_REQUEST = 0x02
1✔
776
    """
1✔
777
    This message is used to route/gate a connection request from a peripheral
778
    device up to the controlling Central device. The user should be able to
779
    press the button on any devices in the current connected network for
780
    requesting a connection. Both the Pressed and the Release is send.
781
    """
782

783
    FAMILY_REQUEST = 0x03
1✔
784
    """
1✔
785
    When send upstream (from any device in the H/W network) this message is a
786
    request for a new Family and SubFamily, if possible.
787
    """
788

789
    FAMILY_SET = 0x04
1✔
790
    """
1✔
791
    This command is send from the controlling Central device and has the new
792
    family added as payload.
793
    """
794

795
    JOIN_DENIED = 0x05
1✔
796
    """
1✔
797
    This command is send to a peripheral trying to connect to a network where
798
    the maximum of nodes is reached (i.e. no further connections available).
799
    """
800

801
    GET_FAMILY = 0x06
1✔
802
    """
1✔
803
    Requesting the previous used Family for a specific (the addressed) device,
804
    just entered a network - no family decided yet.
805
    """
806

807
    FAMILY = 0x07
1✔
808
    """
1✔
809
    This is the answer returned for above command (Get Family 0x06). Used by the
810
    Central Role device to decide the Family at auto connection.
811
    """
812

813
    GET_SUBFAMILY = 0x08
1✔
814
    """
1✔
815
    Requesting the previous used SubFamily (Function) on a specific (addressed)
816
    device. E.g. a 2 port R/C to a 4 port hub. I.e. addressing a specific port
817
    (A+B or C+D)
818
    """
819

820
    SUBFAMILY = 0x09
1✔
821
    """
1✔
822
    This is the answer returned for above command (Get SubFamily 0x08). Used by
823
    the Central Role device to decide the SubFamily at auto connection.
824
    """
825

826
    SUBFAMILY_SET = 0x0A
1✔
827
    """
1✔
828
    This command is send from the controlling Central device and has the new
829
    SubFamily added as payload.
830
    """
831

832
    GET_EXTENDED_FAMILY = 0x0B
1✔
833
    """
1✔
834
    Requesting the previous used Family and SubFamily for a specific (the
835
    addressed) device. The user can get both the Family and the SubFamily in
836
    one tx/rx. Further both are only using 3 bits for the addressing leaving
837
    one bit each for future use and/or escape.
838
    """
839

840
    EXTENDED_FAMILY = 0x0C
1✔
841
    """
1✔
842
    This is the answer returned for above command (Get Extended Family 0x0A).
843
    """
844

845
    EXTENDED_FAMILY_SET = 0x0D
1✔
846
    """
1✔
847
    The user can set both the Family and the SubFamily in one tx/rx. Further
848
    both are only using 3 bits for the addressing leaving one bit each for
849
    future use and/or escape (extended use).
850
    """
851

852
    RESET_LONG_PRESS = 0x0E
1✔
853
    """
1✔
854
    The timing of the user CONNECTION PRESS may be stretched (timer reset) when
855
    connecting in a Bluetooth active environment. I.e. the LONG PRESS normally
856
    is used for powering down a device, but also used for gating key presses to
857
    the Central in a connection sequence (allowing a longer connection time
858
    without powering down.
859
    """
860

861

862
class HwNetFamily(IntEnum):
1✔
863
    """
864
    Hardware network family.
865

866
    Families are denoted by the status light color.
867
    """
868

869
    WHITE = 0x00
1✔
870
    GREEN = 0x01
1✔
871
    YELLOW = 0x02
1✔
872
    RED = 0x03
1✔
873
    BLUE = 0x04
1✔
874
    PURPLE = 0x05
1✔
875
    CYAN = 0x06
1✔
876
    TEAL = 0x07
1✔
877
    PINK = 0x08
1✔
878

879
    def __add__(self, x: int) -> int:
1✔
880
        if isinstance(x, HwNetSubfamily):
1✔
881
            return HwNetExtFamily.from_parts(self, x)
1✔
882

883
        return super().__add__(x)
×
884

885

886
class HwNetSubfamily(IntEnum):
1✔
887
    """
888
    Hardware network subfamily.
889

890
    Subfamilies are denoted by the number of flashes of the status light.
891
    """
892

893
    FLASH_0 = 0x00
1✔
894
    FLASH_1 = 0x01
1✔
895
    FLASH_2 = 0x02
1✔
896
    FLASH_3 = 0x03
1✔
897
    FLASH_4 = 0x04
1✔
898
    FLASH_5 = 0x05
1✔
899
    FLASH_6 = 0x06
1✔
900
    FLASH_7 = 0x07
1✔
901

902
    def __add__(self, x: int) -> int:
1✔
903
        if isinstance(x, HwNetFamily):
×
904
            return HwNetExtFamily.from_parts(x, self)
×
905

906
        return super().__add__(x)
×
907

908

909
class HwNetExtFamily(IntEnum):
1✔
910
    """
911
    Combination of :class:`HwNetFamily` and :class:`HwNetSubfamily` encoded in
912
    a single byte.
913
    """
914

915
    names = ()
1✔
916

917
    @property
1✔
918
    def family(self) -> HwNetFamily:
1✔
919
        return HwNetFamily(self & 0x0F)
1✔
920

921
    @property
1✔
922
    def subfamily(self) -> HwNetSubfamily:
1✔
923
        return HwNetSubfamily((self >> 4) & 0x07)
1✔
924

925
    @staticmethod
1✔
926
    def from_parts(family: HwNetFamily, subfamily: HwNetSubfamily) -> "HwNetExtFamily":
1✔
927
        return HwNetExtFamily((subfamily << 4) | family)
1✔
928

929
    @classmethod
1✔
930
    def _missing_(cls, value):
1✔
931
        try:
1✔
932
            family = HwNetFamily(value & 0xF)
1✔
933
            subfamily = HwNetSubfamily((value >> 4) & 0x7)
1✔
934
            if ((subfamily << 4) | family) != value:
1✔
935
                return None
1✔
936
        except Exception as ex:
×
937
            return ex
×
938

939
        return _create_pseudo_member_(cls, value)
1✔
940

941
    def __repr__(self) -> str:
1✔
942
        return f"({repr(self.family)} + {repr(self.subfamily)})"
×
943

944

945
class InfoKind(IntEnum):
1✔
946
    PORT_VALUE = 0x00
1✔
947
    MODE_INFO = 0x01
1✔
948
    COMBOS = 0x02
1✔
949

950

951
class ModeInfoKind(IntEnum):
1✔
952
    NAME = 0x00
1✔
953
    RAW = 0x01
1✔
954
    PCT = 0x02
1✔
955
    SI = 0x03
1✔
956
    SYMBOL = 0x04
1✔
957
    MAPPING = 0x05
1✔
958
    INTERNAL_USE = 0x06
1✔
959
    MOTOR_BIAS = 0x07
1✔
960
    CAPABILITIES = 0x08
1✔
961
    UNK9 = 0x09
1✔
962
    UNK10 = 0x0A
1✔
963
    UNK11 = 0x0B
1✔
964
    UNK12 = 0x0C
1✔
965
    FORMAT = 0x80
1✔
966

967

968
class PortInfoFormatSetupCommand(IntEnum):
1✔
969
    SET = 0x01
1✔
970
    """Set mode and data format combos."""
1✔
971

972
    LOCK = 0x02
1✔
973
    """Lock I/O device for setup."""
1✔
974

975
    UNLOCK_ENABLED = 0x03
1✔
976
    """Unlock and start with updates enabled."""
1✔
977

978
    UNLOCK_DISABLED = 0x04
1✔
979
    """Unlock and start with updates disabled."""
1✔
980

981
    RESERVED = 0x05
1✔
982
    """Not used."""
1✔
983

984
    RESET = 0x06
1✔
985
    """Reset I/O device."""
1✔
986

987

988
class ModeCapabilities(IntFlag):
1✔
989
    OUTPUT = 1 << 0
1✔
990
    INPUT = 1 << 1
1✔
991
    LOGICAL_COMBINABLE = 1 << 2
1✔
992
    LOGICAL_SYNCHRONIZEABLE = 1 << 3
1✔
993

994

995
class IODeviceMapping(IntFlag):
1✔
996
    DISCRETE = 1 << 2
1✔
997
    RELATIVE = 1 << 3
1✔
998
    ABSOLUTE = 1 << 4
1✔
999
    SUPPORTS_MAPPING_V2 = 1 << 6
1✔
1000
    SUPPORTS_NULL = 1 << 7
1✔
1001

1002

1003
class IODeviceCapabilities(IntFlag):
1✔
1004
    """
1005
    Sensor capabilities flags. (48-bit)
1006
    """
1007

1008
    names = ()
1✔
1009

1010

1011
class DataFormat(IntEnum):
1✔
1012
    """
1013
    I/O Device data format.
1014
    """
1015

1016
    DATA8 = 0x00
1✔
1017
    """8-bit signed integer."""
1✔
1018

1019
    DATA16 = 0x01
1✔
1020
    """16-bit signed integer, little-endian."""
1✔
1021

1022
    DATA32 = 0x02
1✔
1023
    """32-bit signed integer, little-endian."""
1✔
1024

1025
    DATAF = 0x03
1✔
1026
    """32-bit floating point, little-endian."""
1✔
1027

1028

1029
class VirtualPortSetupCommand(IntEnum):
1✔
1030
    DISCONNECT = 0x00
1✔
1031
    CONNECT = 0x01
1✔
1032

1033

1034
class PortOutputCommand(IntEnum):
1✔
1035
    START_POWER = 0x01
1✔
1036
    START_POWER_2 = 0x02
1✔
1037
    SET_ACC_TIME = 0x05
1✔
1038
    SET_DEC_TIME = 0x06
1✔
1039
    START_SPEED = 0x07
1✔
1040
    START_SPEED_2 = 0x08
1✔
1041
    START_SPEED_FOR_TIME = 0x09
1✔
1042
    START_SPEED_FOR_TIME_2 = 0x0A
1✔
1043
    START_SPEED_FOR_DEGREES = 0x0B
1✔
1044
    START_SPEED_FOR_DEGREES_2 = 0x0C
1✔
1045
    GOTO_ABS_POS = 0x0D
1✔
1046
    GOTO_ABS_POS_2 = 0x0E
1✔
1047
    PRESET_ENCODER = 0x13
1✔
1048
    PRESET_ENCODER_2 = 0x14
1✔
1049
    WRITE_DIRECT = 0x50
1✔
1050
    WRITE_DIRECT_MODE_DATA = 0x51
1✔
1051

1052

1053
class StartInfo(IntEnum):
1✔
1054
    BUFFER = 0x00
1✔
1055
    IMMEDIATE = 0x10
1✔
1056

1057

1058
class EndInfo(IntEnum):
1✔
1059
    NO_ACTION = 0x00
1✔
1060
    FEEDBACK = 0x01
1✔
1061

1062

1063
class Feedback(IntFlag):
1✔
1064
    BUFFER_EMPTY_IN_PROGRESS = 1 << 0
1✔
1065
    BUFFER_EMPTY_COMPLETED = 1 << 1
1✔
1066
    DISCARDED = 1 << 2
1✔
1067
    IDLE = 1 << 3
1✔
1068
    BUSY = 1 << 4
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc