btbricks 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
btbricks/bthub.py ADDED
@@ -0,0 +1,218 @@
1
+ import struct
2
+ try:
3
+ from micropython import const
4
+ from time import sleep_ms
5
+ except:
6
+ def const(x):
7
+ return x
8
+ def sleep_ms(x):
9
+ pass
10
+ try:
11
+ from .bt import BLEHandler
12
+ except:
13
+ from bt import BLEHandler
14
+
15
+ __HUB_NOTIFY_DESC = const(0x0F)
16
+ __REMOTE_NOTIFY_DESC = const(0x0C)
17
+ __MARIO_NOTIFY_DESC = const(14)
18
+ __HUB_PORT_ACC = const(0x61)
19
+ __HUB_PORT_GYRO = const(0x62)
20
+ __HUB_PORT_TILT = const(0x63)
21
+
22
+ _MODE = const(0)
23
+ _MODE_BYTE = const(1)
24
+ _MODE_DATA_SETS = const(2)
25
+ _MODE_DATA_SET_TYPE = const(3)
26
+
27
+ OFF = const(0)
28
+ PINK = const(1)
29
+ PURPLE = const(2)
30
+ DARK_BLUE = const(3)
31
+ BLUE = const(4)
32
+ TEAL = const(5)
33
+ GREEN = const(6)
34
+ YELLOW = const(7)
35
+ ORANGE = const(8)
36
+ RED = const(9)
37
+ WHITE = const(10)
38
+
39
+ def clamp_int(n, floor=-100, ceiling=100):
40
+ return max(min(round(n), ceiling), floor)
41
+
42
+ class BtHub():
43
+ """
44
+ BtHub
45
+
46
+ A class for connecting to and controlling LEGO Hub devices via Bluetooth Low Energy (BLE).
47
+
48
+ This class provides an interface to communicate with LEGO Smart Hubs running standard LEGO firmware.
49
+ It handles BLE connection management, motor control, sensor data subscription, and LED control.
50
+
51
+ :param ble_handler: Optional BLEHandler instance for managing BLE connections. If None, a new BLEHandler will be created.
52
+ :type ble_handler: BLEHandler, optional
53
+
54
+ :raises ConnectionError: If the BLE connection to the hub fails.
55
+
56
+ Example::
57
+
58
+ hub = BtHub()
59
+ hub.connect()
60
+ hub.dc(1, 50) # Run motor on port 1 at 50%
61
+ acceleration = hub.acc() # Get accelerometer data
62
+ hub.disconnect()
63
+
64
+ Attributes:
65
+ ble_handler (BLEHandler): Handler for BLE communication operations
66
+ _conn_handle: Internal connection handle for the active BLE connection
67
+ acc_sub (bool): Flag indicating if accelerometer subscription is active
68
+ gyro_sub (bool): Flag indicating if gyroscope subscription is active
69
+ tilt_sub (bool): Flag indicating if tilt subscription is active
70
+ hub_data (dict): Dictionary storing sensor data by port
71
+ mode_info (dict): Dictionary storing mode information by port
72
+ """
73
+
74
+ __PORTS = {
75
+ 1:0, 2:1, 3:2, 4:3,
76
+ "A":0, "B":1, "C":2, "D":3}
77
+
78
+ def __init__(self, ble_handler:BLEHandler=None):
79
+ if ble_handler is None:
80
+ ble_handler = BLEHandler()
81
+ self.ble_handler = ble_handler
82
+ self._conn_handle = None
83
+ self.acc_sub = False
84
+ self.gyro_sub = False
85
+ self.tilt_sub = False
86
+ self.hub_data = {}
87
+ self.mode_info = {}
88
+
89
+ def is_connected(self):
90
+ return self._conn_handle is not None
91
+
92
+ def connect(self):
93
+ self._conn_handle = self.ble_handler.connect_lego()
94
+ if self._conn_handle is not None:
95
+ sleep_ms(500)
96
+ # Subscribe to motion data of SMART Hubs
97
+ self.write(0x0A, 0x00, 0x41, __HUB_PORT_ACC, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01)
98
+ sleep_ms(200)
99
+ self.write(0x0A, 0x00, 0x41, __HUB_PORT_GYRO, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01)
100
+ sleep_ms(200)
101
+ self.write(0x0A, 0x00, 0x41, __HUB_PORT_TILT, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01)
102
+ sleep_ms(200)
103
+
104
+ # Initialize all ports with mode 0
105
+ mode = 0
106
+ for i in range(4):
107
+ # SUBSCRIBE_MODE
108
+ self.write(0x0A,0x00,0x41, i, mode, 0x01, 0x00, 0x00, 0x00, 0x01)
109
+ sleep_ms(100)
110
+ # GET_MODE_INFO
111
+ self.write(0x06, 0x00, 0x22, i, mode, 0x80)
112
+ sleep_ms(100)
113
+
114
+ # Enable notify on smart hubs
115
+ self.ble_handler.enable_notify(self._conn_handle, __HUB_NOTIFY_DESC, self.__on_notify)
116
+ sleep_ms(200)
117
+ self.set_led_color(GREEN)
118
+ else:
119
+ print("Connection failed")
120
+
121
+ def disconnect(self):
122
+ if self._conn_handle is not None:
123
+ self.ble_handler.disconnect(self._conn_handle)
124
+ self._conn_handle = None
125
+
126
+ def write(self, *data):
127
+ self.ble_handler.lego_write(
128
+ struct.pack("%sB" % len(data), *data),
129
+ self._conn_handle
130
+ )
131
+
132
+ def set_led_color(self, idx):
133
+ self.write(0x08, 0x00, 0x81, 0x32, 0x11, 0x51, 0x00, idx)
134
+
135
+ def set_remote_led_color(self, idx):
136
+ self.write(0x08, 0x00, 0x81, 0x34, 0x11, 0x51, 0x00, idx)
137
+
138
+ def __on_notify(self, data):
139
+ # hub = data[1]
140
+ message_type = data[2]
141
+ port = data[3]
142
+ payload = data[4:]
143
+ if message_type == 0x45:
144
+ self.hub_data[port] = payload
145
+ elif message_type == 0x44:
146
+ self.mode_info[port] = {
147
+ _MODE : payload[0],
148
+ _MODE_BYTE : payload[1],
149
+ _MODE_DATA_SETS : payload[2],
150
+ _MODE_DATA_SET_TYPE : payload[3],
151
+ }
152
+
153
+ def unpack_data(self, port, fmt="3h"):
154
+ if port in self.hub_data.keys():
155
+ return struct.unpack(fmt, self.hub_data[port])
156
+
157
+ def acc(self):
158
+ return self.unpack_data(__HUB_PORT_ACC)
159
+
160
+ def gyro(self):
161
+ return self.unpack_data(__HUB_PORT_GYRO)
162
+
163
+ def tilt(self):
164
+ return self.unpack_data(__HUB_PORT_TILT)
165
+
166
+ def dc(self, port, pct):
167
+ self.write(0x06, 0x00, 0x81, self.__PORTS[port], 0x11, 0x51, 0x00, clamp_int(pct))
168
+
169
+ def run_target(self, port, degrees, speed=50, max_power=100, acceleration=100, deceleration=100, stop_action=0):
170
+ degree_bits = struct.unpack("<BBBB", struct.pack("<i", degrees))
171
+ self.write(0x0D, 0x00, 0x81, self.__PORTS[port], 0x11, 0x0D, degree_bits[0], degree_bits[1], degree_bits[2], degree_bits[3], speed, max_power, 0x7E)
172
+
173
+ def mode(self, port, mode, *data):
174
+ # set_mode
175
+ self.write(0x0A,0x00,0x41, self.__PORTS[port], mode, 0x01, 0x00, 0x00, 0x00, 0x01)
176
+ sleep_ms(100)
177
+ if data:
178
+ self.write(7+len(data), 0x00, 0x81, self.__PORTS[port], 0x00, 0x51, mode, *data)
179
+ sleep_ms(100)
180
+ # request_mode_info
181
+ self.write(0x06, 0x00, 0x22, self.__PORTS[port], mode, 0x80)
182
+ sleep_ms(100)
183
+
184
+ def run(self, port, speed, max_power=100, acceleration=100, deceleration=100):
185
+ # Start motor at given speed
186
+ self.write(0x09, 0x00, 0x81, self.__PORTS[port], 0x11, 0x07, clamp_int(speed), max_power, 0x00)
187
+
188
+ def run_time(self, port, time, speed=50, max_power=100, acceleration=100, deceleration=100, stop_action=0):
189
+ # Rotate motor for a given time
190
+ time_bits = struct.unpack("<BB", struct.pack("<H", time))
191
+ self.write(0x0B, 0x00, 0x81, self.__PORTS[port], 0x11, 0x09, time_bits[0], time_bits[1], speed, max_power, 0x00)
192
+
193
+ def run_angle(self, port, degrees, speed=50, max_power=100, acceleration=100, deceleration=100, stop_action=0):
194
+ # Rotate motor for a given number of degrees relative to current position
195
+ degree_bits = struct.unpack("<BBBB", struct.pack("<i", degrees))
196
+ self.write(0x0D, 0x00, 0x81, self.__PORTS[port], 0x11, 0x0B, degree_bits[0], degree_bits[1], degree_bits[2], degree_bits[3], speed, max_power, 0x7E)
197
+
198
+ def get(self, port):
199
+ port = self.__PORTS[port]
200
+ if port in self.hub_data:
201
+ value = None
202
+ payload = self.hub_data[port]
203
+ no_data_sets = None
204
+ data_set_type = 0
205
+ if port in self.mode_info:
206
+ data_set_type = self.mode_info[port][_MODE_DATA_SET_TYPE]
207
+ no_data_sets = self.mode_info[port][_MODE_DATA_SETS]
208
+
209
+ if data_set_type == 0x00:
210
+ message = struct.unpack("%sb" % len(payload), payload)
211
+ value = message[:no_data_sets]
212
+ elif data_set_type == 0x01:
213
+ message = struct.unpack("%sh" % (len(payload)//2), payload)
214
+ value = message[:no_data_sets]
215
+ elif data_set_type == 0x02:
216
+ message = struct.unpack("%si" % (len(payload)//4), payload)
217
+ value = message[:no_data_sets]
218
+ return value
btbricks/ctrl_plus.py ADDED
@@ -0,0 +1,8 @@
1
+ from .bthub import BtHub
2
+
3
+ # Backward compatibility
4
+ class SmartHub(BtHub):
5
+ """
6
+ This class is kept for backward compatibility. Use :class:`BtHub` instead.
7
+ """
8
+ pass
@@ -0,0 +1,232 @@
1
+ Metadata-Version: 2.4
2
+ Name: btbricks
3
+ Version: 0.2.1
4
+ Summary: A MicroPython Bluetooth library for remote controlling LEGO hubs via BLE
5
+ Home-page: https://github.com/antonvh/btbricks
6
+ Author: Anton Vanhoucke
7
+ Author-email: Anton Vanhoucke <anton@antonsmindstorms.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/antonvh/btbricks
10
+ Project-URL: Documentation, https://docs.antonsmindstorms.com/en/latest/Software/btbricks/docs/index.html
11
+ Project-URL: Repository, https://github.com/antonvh/btbricks.git
12
+ Project-URL: Issues, https://github.com/antonvh/btbricks/issues
13
+ Keywords: micropython,bluetooth,ble,lego,hub,control
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Education
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: Implementation :: MicroPython
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Topic :: System :: Hardware
22
+ Requires-Python: >=3.7
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=7.0; extra == "dev"
27
+ Requires-Dist: pytest-asyncio>=0.20.0; extra == "dev"
28
+ Requires-Dist: black>=23.0; extra == "dev"
29
+ Requires-Dist: flake8>=5.0; extra == "dev"
30
+ Requires-Dist: mypy>=1.0; extra == "dev"
31
+ Provides-Extra: docs
32
+ Requires-Dist: sphinx>=4.0; extra == "docs"
33
+ Requires-Dist: sphinx-rtd-theme>=1.0; extra == "docs"
34
+ Requires-Dist: sphinx-autodoc-typehints>=1.12; extra == "docs"
35
+ Dynamic: author
36
+ Dynamic: home-page
37
+ Dynamic: license-file
38
+ Dynamic: requires-python
39
+
40
+ # btbricks
41
+
42
+ <img alt="btbricks logo" src="https://raw.githubusercontent.com/antonvh/btbricks/master/img/btbricks.png" width="200">
43
+
44
+ [![PyPI Version](https://img.shields.io/pypi/v/btbricks.svg)](https://pypi.org/project/btbricks/)
45
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
46
+ [![MicroPython](https://img.shields.io/badge/MicroPython-compatible-orange.svg)](https://micropython.org/)
47
+
48
+ A MicroPython Bluetooth library. It implements BLE (Bluetooth 5, Bluetooth Low Energy). Of the know BLE services, this library implements Nordic Uart Service (NUS), LEGO Service and MIDI service. The library contains both the BLE Central (client) and BLE Peripheral (server) classes.
49
+
50
+ These BLE services allow for controlling LEGO hubs, running official firmware. The services also allow creating custom Bluetooth peripherals: RC controllers, MIDI devices, etc. To control the LEGO hubs, you can best use a [hub expansion board, like the LMS-ESP32](https://www.antonsmindstorms.com/product/wifi-python-esp32-board-for-mindstorms/).
51
+
52
+ ## Table of Contents
53
+
54
+ - [Features](#features)
55
+ - [Installation](#installation)
56
+ - [Quick Start](#quick-start)
57
+ - [Documentation](#documentation-and-api-reference)
58
+ - [Supported Platforms](#supported-platforms)
59
+ - [Firmware Notes](#firmware-notes)
60
+ - [License](#license)
61
+ - [Author](#author)
62
+
63
+ ## Features
64
+
65
+ - 🔌 **BLE Communication**: Comprehensive Bluetooth Low Energy support via MicroPython's `ubluetooth`
66
+ - 🎮 **Hub Control**: Control LEGO MINDSTORMS hubs, SPIKE sets, and smart hubs over Bluetooth
67
+ - 📱 **Custom Peripherals**: Create RC controllers, MIDI controllers, and other BLE peripherals compatible with LEGO hubs
68
+ - 🚀 **MicroPython Ready**: Optimized for MicroPython on ESP32, LEGO SPIKE, and other platforms
69
+ - 📡 **LEGO Protocol**: Full support for LEGO Bluetooth protocols (LPF2, LPUP, CTRL+)
70
+ - 🎛️ **Multiple Interfaces**: Nordic UART, MIDI, RC control, and native LEGO hub communication
71
+ - ⚙️ **Advanced BLE**: Automatic MTU negotiation, descriptor handling, and efficient payload management
72
+
73
+ ## Installation
74
+
75
+ ### On LMS-ESP32
76
+
77
+ The module should be included in the latest Micropython firmware from <https://wwww.antonsmindstorms.com>. If not, use ViperIDE or Thonny and create a new file called rcservo.py.
78
+ Copy the contents from the same file in this repository inside.
79
+
80
+ ### On MicroPython device using `micropip` from PyPI
81
+
82
+ ```python
83
+ import micropip
84
+ await micropip.install("btbricks")
85
+ ```
86
+
87
+ Note: `micropip` must be available on the target board and may require an internet connection from the device.
88
+
89
+ ### On SPIKE Legacy or MINDSTORMS Robot Inventor
90
+
91
+ Use the installer script in mpy-robot-tools: <https://github.com/antonvh/mpy-robot-tools/blob/master/Installer/install_mpy_robot_tools.py>
92
+
93
+ ## Quick Start
94
+
95
+ ### Connect to a LEGO Hub
96
+
97
+ ```python
98
+ from btbricks import BtHub
99
+
100
+ # Create hub instance
101
+ hub = BtHub()
102
+
103
+ # Connect to a nearby hub
104
+ hub.connect()
105
+
106
+ if hub.is_connected():
107
+ # Set hub LED to green
108
+ hub.set_led_color(6) # GREEN constant
109
+
110
+ # Read accelerometer data
111
+ acc = hub.acc()
112
+ if acc:
113
+ print(f"Accelerometer: {acc}")
114
+
115
+ # Control motor on port A with 50% power
116
+ hub.dc("A", 50)
117
+
118
+ hub.disconnect()
119
+ ```
120
+
121
+ ### Create an RC Receiver (Hub-side)
122
+ Use the examples in the `examples/` folder for full, runnable code. Minimal receiver/transmitter snippets:
123
+
124
+ ```python
125
+ from btbricks import RCReceiver, R_STICK_HOR, R_STICK_VER
126
+ from time import sleep_ms
127
+
128
+ # Create RC receiver (advertises as "robot" by default)
129
+ rcv = RCReceiver(name="robot")
130
+
131
+ print("Waiting for RC transmitter to connect...")
132
+ try:
133
+ while True:
134
+ if rcv.is_connected():
135
+ steering = rcv.controller_state(R_STICK_HOR)
136
+ throttle = rcv.controller_state(R_STICK_VER)
137
+ print(f"Steering: {steering}, Throttle: {throttle}")
138
+ sleep_ms(100)
139
+ else:
140
+ sleep_ms(500)
141
+ except KeyboardInterrupt:
142
+ rcv.disconnect()
143
+ ```
144
+
145
+ ```python
146
+ from btbricks import RCTransmitter, L_STICK_HOR, R_STICK_VER
147
+ from time import sleep
148
+
149
+ # Create RC transmitter (central)
150
+ tx = RCTransmitter()
151
+
152
+ if tx.connect(name="robot"):
153
+ try:
154
+ while tx.is_connected():
155
+ # Set stick values in range [-100, 100]
156
+ tx.set_stick(L_STICK_HOR, 0)
157
+ tx.set_stick(R_STICK_VER, 50)
158
+ tx.transmit()
159
+ sleep(0.1)
160
+ except KeyboardInterrupt:
161
+ tx.disconnect()
162
+ ```
163
+
164
+ ### Create a MIDI Controller
165
+
166
+ ```python
167
+ from btbricks import MidiController
168
+ from time import sleep
169
+
170
+ # Create MIDI controller (advertises as "amh-midi" by default)
171
+ midi = MidiController(name="amh-midi")
172
+
173
+ print("MIDI controller started, connect from your DAW...")
174
+
175
+ try:
176
+ while True:
177
+ # Send MIDI note on: middle C (note 60), velocity 100
178
+ midi.note_on(60, 100)
179
+ sleep(0.5)
180
+
181
+ # Send MIDI note off
182
+ midi.note_off(60)
183
+ sleep(0.5)
184
+
185
+ # Or send a chord
186
+ midi.chord_on("C4", velocity=100, style="M") # C major chord
187
+ sleep(1)
188
+ midi.note_off(60) # Stop the chord
189
+
190
+ except KeyboardInterrupt:
191
+ print("MIDI controller stopped")
192
+ ```
193
+
194
+ ## Documentation and API reference
195
+
196
+ See the full documentation and API reference at:
197
+
198
+ https://docs.antonsmindstorms.com/en/latest/Software/btbricks/docs/index.html
199
+
200
+ ### Core Classes
201
+
202
+ - `BLEHandler`: Low-level Bluetooth communication
203
+ - `UARTCentral`: Nordic UART client mode
204
+ - `UARTPeripheral`: Nordic UART server mode
205
+ - `RCReceiver`: Receive RC control signals
206
+ - `RCTransmitter`: Send RC control signals
207
+ - `MidiController`: Send MIDI commands over BLE
208
+ - `BtHub`: High-level hub communication interface
209
+
210
+ ### Control Constants
211
+
212
+ - Sticks: `L_STICK_HOR`, `L_STICK_VER`, `R_STICK_HOR`, `R_STICK_VER`
213
+ - Triggers: `L_TRIGGER`, `R_TRIGGER`
214
+ - Buttons: `BUTTONS`
215
+ - Settings: `SETTING1`, `SETTING2`
216
+
217
+ ## Supported Platforms
218
+
219
+ - **LEGO MINDSTORMS EV3** (with MicroPython firmware)
220
+ - **LEGO SPIKE Prime/Prime Essential** (with MINDSTORMS firmware)
221
+ - **LEGO SPIKE Robot Inventor**
222
+ - **ESP32** with MicroPython
223
+ - Other MicroPython boards with `ubluetooth` support
224
+
225
+
226
+ ## License
227
+
228
+ MIT License
229
+
230
+ ## Author
231
+
232
+ Anton Vanhoucke
@@ -0,0 +1,13 @@
1
+ btbricks/__init__.py,sha256=U1Yxw-A51X4eGmuACmqdfRpb9v_CNs6sgTy4RBKMQZU,1051
2
+ btbricks/bt.py,sha256=JLtXm3RsfpLTbWzGYhrEC1_AMlTpyXHaEoBR1Q5lC7A,44575
3
+ btbricks/bthub.py,sha256=TVvpVo5E9nIzwmASMTAjjLs0lbY4Su4CYFlBU4KZ3dY,7967
4
+ btbricks/ctrl_plus.py,sha256=CAnkG_SZ9Zysv4ZUDJ7UE0HTLMGeZYD0JxPEIIf-o6Y,177
5
+ btbricks-0.2.1.dist-info/licenses/LICENSE,sha256=yVFkYtNY8Mlp5U_xedI4-O8Hxjg_vu-taxJlv8y-xVk,1078
6
+ tests/__init__.py,sha256=CtJ2NCOCvkNNpVgAw3XHsKd_S-C36d7Yuq4QfTPmwgg,34
7
+ tests/test_bthub.py,sha256=9yyBhsydjwN7_yXKzsKTEAxcjW_PqOzwZFcQ2dDv_f0,3298
8
+ tests/test_constants.py,sha256=L0rIn8VsPIPc80qITblsPu9mQltL85pAFLQl2KQUpGY,2023
9
+ tests/test_imports.py,sha256=kHrRnueFPnQxU807Bo9Ddr3x4hsryMroUoWC816N-Nc,2147
10
+ btbricks-0.2.1.dist-info/METADATA,sha256=mdeew3CTC3HfEYMKbHW8AcN2jhrzep9Cf9rUMSE_WkY,7770
11
+ btbricks-0.2.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ btbricks-0.2.1.dist-info/top_level.txt,sha256=nznVmPKoDx79OB6rEM180swD9X5G22V35afi5zon1d8,15
13
+ btbricks-0.2.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 btbricks contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ btbricks
2
+ tests
tests/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Tests for btbricks package."""
tests/test_bthub.py ADDED
@@ -0,0 +1,102 @@
1
+ """Tests for BtHub class."""
2
+
3
+ import pytest
4
+ from unittest.mock import Mock, MagicMock, patch
5
+
6
+
7
+ class TestBtHubInitialization:
8
+ """Test BtHub initialization."""
9
+
10
+ def test_bthub_init_default(self):
11
+ """Test BtHub initialization with default BLEHandler."""
12
+ from btbricks import BtHub
13
+
14
+ with patch("btbricks.bthub.BLEHandler"):
15
+ hub = BtHub()
16
+ assert hub._conn_handle is None
17
+ assert hub.acc_sub is False
18
+ assert hub.gyro_sub is False
19
+ assert hub.tilt_sub is False
20
+ assert isinstance(hub.hub_data, dict)
21
+ assert isinstance(hub.mode_info, dict)
22
+
23
+ def test_bthub_init_with_ble_handler(self):
24
+ """Test BtHub initialization with custom BLEHandler."""
25
+ from btbricks import BtHub
26
+
27
+ mock_ble = Mock()
28
+ hub = BtHub(ble_handler=mock_ble)
29
+ assert hub.ble_handler is mock_ble
30
+ assert hub._conn_handle is None
31
+
32
+ def test_bthub_is_connected_false(self):
33
+ """Test is_connected returns False when not connected."""
34
+ from btbricks import BtHub
35
+
36
+ with patch("btbricks.bthub.BLEHandler"):
37
+ hub = BtHub()
38
+ assert hub.is_connected() is False
39
+
40
+ def test_bthub_is_connected_true(self):
41
+ """Test is_connected returns True when connected."""
42
+ from btbricks import BtHub
43
+
44
+ with patch("btbricks.bthub.BLEHandler"):
45
+ hub = BtHub()
46
+ hub._conn_handle = 1
47
+ assert hub.is_connected() is True
48
+
49
+
50
+ class TestBtHubColorConstants:
51
+ """Test BtHub LED color constants."""
52
+
53
+ def test_color_constants_exist(self):
54
+ """Test that color constants are defined."""
55
+ from btbricks.bthub import OFF, PINK, PURPLE, DARK_BLUE, BLUE, TEAL
56
+ from btbricks.bthub import GREEN, YELLOW, ORANGE, RED, WHITE
57
+
58
+ colors = [OFF, PINK, PURPLE, DARK_BLUE, BLUE, TEAL, GREEN, YELLOW, ORANGE, RED, WHITE]
59
+
60
+ # All should be integers
61
+ assert all(isinstance(c, int) for c in colors)
62
+
63
+ # All should be non-negative
64
+ assert all(c >= 0 for c in colors)
65
+
66
+
67
+ class TestBtHubClampInt:
68
+ """Test clamp_int helper function."""
69
+
70
+ def test_clamp_int_within_range(self):
71
+ """Test clamp_int with value within range."""
72
+ from btbricks.bthub import clamp_int
73
+
74
+ assert clamp_int(50) == 50
75
+ assert clamp_int(0) == 0
76
+
77
+ def test_clamp_int_below_min(self):
78
+ """Test clamp_int with value below minimum."""
79
+ from btbricks.bthub import clamp_int
80
+
81
+ assert clamp_int(-150) == -100
82
+
83
+ def test_clamp_int_above_max(self):
84
+ """Test clamp_int with value above maximum."""
85
+ from btbricks.bthub import clamp_int
86
+
87
+ assert clamp_int(150) == 100
88
+
89
+ def test_clamp_int_custom_range(self):
90
+ """Test clamp_int with custom floor and ceiling."""
91
+ from btbricks.bthub import clamp_int
92
+
93
+ assert clamp_int(5, floor=0, ceiling=10) == 5
94
+ assert clamp_int(-5, floor=0, ceiling=10) == 0
95
+ assert clamp_int(15, floor=0, ceiling=10) == 10
96
+
97
+ def test_clamp_int_rounds(self):
98
+ """Test clamp_int rounds values."""
99
+ from btbricks.bthub import clamp_int
100
+
101
+ assert clamp_int(50.4) == 50
102
+ assert clamp_int(50.5) == 50 or clamp_int(50.5) == 51 # Banker's rounding
@@ -0,0 +1,77 @@
1
+ """Tests for btbricks constants."""
2
+
3
+ import pytest
4
+
5
+
6
+ class TestRCConstants:
7
+ """Test RC control constants."""
8
+
9
+ def test_stick_constants(self):
10
+ """Test that stick constants are defined correctly."""
11
+ from btbricks import (
12
+ L_STICK_HOR,
13
+ L_STICK_VER,
14
+ R_STICK_HOR,
15
+ R_STICK_VER,
16
+ )
17
+
18
+ assert L_STICK_HOR == 0
19
+ assert L_STICK_VER == 1
20
+ assert R_STICK_HOR == 2
21
+ assert R_STICK_VER == 3
22
+
23
+ def test_trigger_constants(self):
24
+ """Test that trigger constants are defined correctly."""
25
+ from btbricks import L_TRIGGER, R_TRIGGER
26
+
27
+ assert L_TRIGGER == 4
28
+ assert R_TRIGGER == 5
29
+
30
+ def test_button_and_setting_constants(self):
31
+ """Test button and setting constants."""
32
+ from btbricks import BUTTONS, SETTING1, SETTING2
33
+
34
+ assert BUTTONS == 8
35
+ assert SETTING1 == 6
36
+ assert SETTING2 == 7
37
+
38
+ def test_constants_are_unique(self):
39
+ """Test that all RC constants are unique."""
40
+ from btbricks import (
41
+ L_STICK_HOR,
42
+ L_STICK_VER,
43
+ R_STICK_HOR,
44
+ R_STICK_VER,
45
+ L_TRIGGER,
46
+ R_TRIGGER,
47
+ SETTING1,
48
+ SETTING2,
49
+ BUTTONS,
50
+ )
51
+
52
+ constants = [
53
+ L_STICK_HOR,
54
+ L_STICK_VER,
55
+ R_STICK_HOR,
56
+ R_STICK_VER,
57
+ L_TRIGGER,
58
+ R_TRIGGER,
59
+ SETTING1,
60
+ SETTING2,
61
+ BUTTONS,
62
+ ]
63
+
64
+ assert len(constants) == len(set(constants))
65
+
66
+
67
+ class TestPortConstants:
68
+ """Test port mapping constants in BtHub."""
69
+
70
+ def test_port_mapping(self):
71
+ """Test that BtHub has correct port mapping."""
72
+ from btbricks import BtHub
73
+
74
+ with __import__("unittest.mock").mock.patch("btbricks.bthub.BLEHandler"):
75
+ hub = BtHub()
76
+ # Check that _BtHub__PORTS exists (name-mangled private attribute)
77
+ assert hasattr(hub, "_BtHub__PORTS")