Update LEGO Technic 42176 XBOX RC.py

This commit is contained in:
kokofixcomputers 2024-11-12 04:33:25 +00:00
parent ec7caad836
commit f5bc9ad12a

View File

@ -1,275 +1,280 @@
# LEGO Technic Move Hub 88019 (released in LEGO Technic 42176) # LEGO Technic Move Hub 88019 (released in LEGO Technic 42176)
# remote-control with XBOX controller # remote-control with XBOX controller
# Daniele Benedettelli @profbricks - 6 August 2024 # Daniele Benedettelli @profbricks - 6 August 2024
# requires pygame and bleak # requires pygame and bleak
# INSTALLATION: # INSTALLATION:
# pip intall pygame # pip intall pygame
# pip install bleak # pip install bleak
class color: class color:
# ANSI escape codes for colors # ANSI escape codes for colors
red = "\033[31m" # Red text red = "\033[31m" # Red text
green = "\033[32m" # Green text green = "\033[32m" # Green text
yellow = "\033[33m" # Yellow text yellow = "\033[33m" # Yellow text
blue = "\033[34m" # Blue text blue = "\033[34m" # Blue text
magenta = "\033[35m" # Magenta text magenta = "\033[35m" # Magenta text
cyan = "\033[36m" # Cyan text cyan = "\033[36m" # Cyan text
white = "\033[37m" # White text white = "\033[37m" # White text
reset = "\033[0m" # Reset to default color reset = "\033[0m" # Reset to default color
colour = color() colour = color()
import os, sys import os, sys
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1' os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = '1'
# crucial! otherwise Bleak will raise exception # crucial! otherwise Bleak will raise exception
# see https://bleak.readthedocs.io/en/latest/troubleshooting.html#windows-bugs # see https://bleak.readthedocs.io/en/latest/troubleshooting.html#windows-bugs
if sys.platform == "win32": if sys.platform == "win32":
sys.coinit_flags = 0 sys.coinit_flags = 0
import pygame import pygame
import asyncio import asyncio
from bleak import BleakScanner, BleakClient from bleak import BleakScanner, BleakClient
import time import time
start_time = 0 start_time = 0
class TechnicMoveHub: class TechnicMoveHub:
def __init__(self, device_name): def __init__(self, device_name):
self.device_name = device_name self.device_name = device_name
self.service_uuid = "00001623-1212-EFDE-1623-785FEABCD123" self.service_uuid = "00001623-1212-EFDE-1623-785FEABCD123"
self.char_uuid = "00001624-1212-EFDE-1623-785FEABCD123" self.char_uuid = "00001624-1212-EFDE-1623-785FEABCD123"
self.client = None self.client = None
self.LIGHTS_OFF_OFF = 0b100 self.LIGHTS_OFF_OFF = 0b100
self.LIGHTS_OFF_ON = 0b00000001 self.LIGHTS_OFF_ON = 0b00000001
self.LIGHTS_ON_ON = 0b000 self.LIGHTS_ON_ON = 0b000
self.ID_LED = 0x11
def run_discover(self):
try: def run_discover(self):
devices = BleakScanner.discover(timeout=40) try:
return devices devices = BleakScanner.discover(timeout=40)
except Exception as e: return devices
print(f"Discovery failed with error: {e}") except Exception as e:
return None print(f"Discovery failed with error: {e}")
return None
async def scan_and_connect(self):
scanner = BleakScanner() async def scan_and_connect(self):
print(colour.green + f"Searching for Technic Move Hub...\nPlease press the on button to connect." + colour.reset) scanner = BleakScanner()
devices = await scanner.discover(timeout =5) print(colour.green + f"Searching for Technic Move Hub...\nPlease press the on button to connect." + colour.reset)
devices = await scanner.discover(timeout =5)
for device in devices:
if device.name is not None and self.device_name in device.name: for device in devices:
print(colour.green + f"Found device: {device.name} with address: {device.address} Connecting..." + colour.reset) if device.name is not None and self.device_name in device.name:
self.client = BleakClient(device) print(colour.green + f"Found device: {device.name} with address: {device.address} Connecting..." + colour.reset)
self.client = BleakClient(device)
await self.client.connect()
if self.client.is_connected: await self.client.connect()
print(colour.green + f"Connected to {self.device_name}" + colour.reset) if self.client.is_connected:
print(colour.green + f"Connected to {self.device_name}" + colour.reset)
paired = await self.client.pair(protection_level = 2) # this is crucial!!!
if not paired: paired = await self.client.pair(protection_level = 2) # this is crucial!!!
print(colour.red + f"Could Not Pair. Things could go wrong." + colour.reset) if not paired:
return True print(colour.red + f"Could Not Pair. Things could go wrong." + colour.reset)
else: return True
print(colour.red + f"Failed to connect to {self.device_name}" + colour.reset) else:
print(colour.red + f"Device {self.device_name} not found." + colour.reset) print(colour.red + f"Failed to connect to {self.device_name}" + colour.reset)
return False print(colour.red + f"Device {self.device_name} not found." + colour.reset)
return False
async def send_data(self, data):
global start_time async def send_data(self, data):
if self.client is None: global start_time
print("No BLE client connected.") if self.client is None:
return print("No BLE client connected.")
return
try:
# Write the data to the characteristic try:
await self.client.write_gatt_char(self.char_uuid, data) # Write the data to the characteristic
#print(f"Data written to characteristic {self.char_uuid}: {data}") await self.client.write_gatt_char(self.char_uuid, data)
#print(f"Data written to characteristic {self.char_uuid}: {data}")
elapsed_time_ms = (time.time() - start_time) * 1000
#print(f"Timestamp: {elapsed_time_ms:.2f} ms", end=" ") elapsed_time_ms = (time.time() - start_time) * 1000
#print(' '.join(f'{byte:02x}' for byte in data)) #print(f"Timestamp: {elapsed_time_ms:.2f} ms", end=" ")
#print(' '.join(f'{byte:02x}' for byte in data))
except Exception as e:
print(f"Failed to write data: {e}") except Exception as e:
print(f"Failed to write data: {e}")
async def disconnect(self):
if self.client and self.client.is_connected: async def disconnect(self):
await self.client.disconnect() if self.client and self.client.is_connected:
print("Disconnected from the device") await self.client.disconnect()
print("Disconnected from the device")
LED_MODE_COLOR = 0x00
LED_MODE_RGB = 0x01 LED_MODE_COLOR = 0x00
LED_MODE_RGB = 0x01
async def change_led_color(self, colorID):
if self.client and self.client.is_connected: async def change_led_color(self, colorID):
await self.send_data(bytearray([0x08, 0x00, 0x81, self.ID_LED, self.IO_TYPE_RGB_LED, 0x51, self.LED_MODE_COLOR, colorID])) if self.client and self.client.is_connected:
await self.send_data(bytearray([0x08, 0x00, 0x81, self.ID_LED, self.IO_TYPE_RGB_LED, 0x51, self.LED_MODE_COLOR, colorID]))
async def motor_start_power(self, motor, power):
if self.client and self.client.is_connected: async def motor_start_power(self, motor, power):
await self.send_data(bytearray([0x08, 0x00, 0x81, motor&0xFF, self.SC_BUFFER_NO_FEEDBACK, 0x51, self.MOTOR_MODE_POWER, 0xFF&power])) if self.client and self.client.is_connected:
await self.send_data(bytearray([0x08, 0x00, 0x81, motor&0xFF, self.SC_BUFFER_NO_FEEDBACK, 0x51, self.MOTOR_MODE_POWER, 0xFF&power]))
async def motor_stop(self, motor, brake=True):
# motor can be 0x32, 0x33, 0x34 async def motor_stop(self, motor, brake=True):
if self.client and self.client.is_connected: # motor can be 0x32, 0x33, 0x34
await self.send_data(bytearray([0x08, 0x00, 0x81, motor&0xFF, self.SC_BUFFER_NO_FEEDBACK, 0x51, self.MOTOR_MODE_POWER, self.END_STATE_BRAKE if brake else 0x00])) if self.client and self.client.is_connected:
await self.send_data(bytearray([0x08, 0x00, 0x81, motor&0xFF, self.SC_BUFFER_NO_FEEDBACK, 0x51, self.MOTOR_MODE_POWER, self.END_STATE_BRAKE if brake else 0x00]))
async def calibrate_steering(self):
await self.send_data(bytes.fromhex("0d008136115100030000001000")) async def calibrate_steering(self):
#await asyncio.sleep(0.1) await self.send_data(bytes.fromhex("0d008136115100030000001000"))
await self.send_data(bytes.fromhex("0d008136115100030000000800")) #await asyncio.sleep(0.1)
#await asyncio.sleep(0.1) await self.send_data(bytes.fromhex("0d008136115100030000000800"))
#await asyncio.sleep(0.1)
async def drive(self, speed=0, angle=0, lights = 0x00):
await self.send_data(bytearray([0x0d,0x00,0x81,0x36,0x11,0x51,0x00,0x03,0x00, speed&0xFF, angle&0xFF, lights&0xFF,0x00])) async def drive(self, speed=0, angle=0, lights = 0x00):
#await asyncio.sleep(0.1) await self.send_data(bytearray([0x0d,0x00,0x81,0x36,0x11,0x51,0x00,0x03,0x00, speed&0xFF, angle&0xFF, lights&0xFF,0x00]))
#await asyncio.sleep(0.1)
def get_left_joystick(joystick):
x = round(joystick.get_axis(0)*100) def get_left_joystick(joystick):
y = -round(joystick.get_axis(1)*100) x = round(joystick.get_axis(0)*100)
return (x,y) y = -round(joystick.get_axis(1)*100)
return (x,y)
def get_right_joystick(joystick):
x = round(joystick.get_axis(2)*100) def get_right_joystick(joystick):
y = -round(joystick.get_axis(3)*100) x = round(joystick.get_axis(2)*100)
return (x,y) y = -round(joystick.get_axis(3)*100)
return (x,y)
def get_triggers(joystick):
left = round((joystick.get_axis(4)+100)/2) def get_triggers(joystick):
right = round((joystick.get_axis(5)+100)/2) left = round((joystick.get_axis(4)+100)/2)
return (left, right) right = round((joystick.get_axis(5)+100)/2)
return (left, right)
def get_A_button(joystick):
return joystick.get_button(0) def get_A_button(joystick):
return joystick.get_button(0)
def get_B_button(joystick):
return joystick.get_button(1) def get_B_button(joystick):
return joystick.get_button(1)
def get_X_button(joystick):
return joystick.get_button(2) def get_X_button(joystick):
return joystick.get_button(2)
def get_Y_button(joystick):
return joystick.get_button(3) def get_Y_button(joystick):
return joystick.get_button(3)
def get_left_bumper(joystick):
return joystick.get_button(4) def get_left_bumper(joystick):
return joystick.get_button(4)
def get_right_bumper(joystick):
return joystick.get_button(5) def get_right_bumper(joystick):
return joystick.get_button(5)
async def main():
device_name = "Technic Move" # Replace with your BLE device's name async def main():
hub = TechnicMoveHub(device_name) device_name = "Technic Move" # Replace with your BLE device's name
if not await hub.scan_and_connect(): hub = TechnicMoveHub(device_name)
print(colour.red + "Technic hub not found! Exiting... \nPlease make sure the Technic Hub is on then re-run the script." + colour.reset) if not await hub.scan_and_connect():
return print(colour.red + "Technic hub not found! Exiting... \nPlease make sure the Technic Hub is on then re-run the script." + colour.reset)
return
# Initialize Pygame
pygame.init() # Initialize Pygame
pygame.joystick.init() pygame.init()
pygame.joystick.init()
# Check for joystick
if pygame.joystick.get_count() == 0: # Check for joystick
print("No Controller found! Exiting...\nPlease make sure that you have connected a controller then re-run the script.") if pygame.joystick.get_count() == 0:
return print("No Controller found! Exiting...\nPlease make sure that you have connected a controller then re-run the script.")
return
print(colour.green + "Controller Connected!" + colour.reset)
print(colour.green + "Controller Connected!" + colour.reset)
# Initialize the first joystick
joystick = pygame.joystick.Joystick(0) # Initialize the first joystick
joystick.init() joystick = pygame.joystick.Joystick(0)
joystick.init()
print(f"Controller name: {joystick.get_name()}")
print(f"Controller name: {joystick.get_name()}")
await hub.calibrate_steering()
await hub.calibrate_steering()
lights = hub.LIGHTS_ON_ON
toggle_old = False lights = hub.LIGHTS_ON_ON
throttle_old = 0 toggle_old = False
steering_old = 0 throttle_old = 0
lights_old = 0 steering_old = 0
was_brake = False lights_old = 0
start_time = time.time() was_brake = False
slow_mode = False start_time = time.time()
slow_mode_old = False slow_mode = False
slow_mode_old = False
try: change_lights_because_of_slowmode_on = False
while True: change_lights_because_of_slowmode_off = False
# Pump Pygame event loop
pygame.event.pump() # poll joystick try:
while True:
# Print controller inputs # Pump Pygame event loop
throttle = get_right_joystick(joystick)[1] pygame.event.pump() # poll joystick
steering = get_left_joystick(joystick)[0]
#steering = get_right_joystick(joystick)[0] # use only one joystick? # Print controller inputs
throttle = get_right_joystick(joystick)[1]
if abs(throttle)<3: steering = get_left_joystick(joystick)[0]
throttle = 0 #steering = get_right_joystick(joystick)[0] # use only one joystick?
if abs(steering)< 3:
steering = 0 if abs(throttle)<3:
throttle = 0
brake = get_right_bumper(joystick) if abs(steering)< 3:
# toggle lights steering = 0
toggle = get_Y_button(joystick)
if toggle and not toggle_old: brake = get_right_bumper(joystick)
if lights == hub.LIGHTS_OFF_OFF : # toggle lights
print("lights on") toggle = get_Y_button(joystick)
lights = hub.LIGHTS_ON_ON if toggle and not toggle_old:
else: if lights == hub.LIGHTS_OFF_OFF :
print("lights off") print("lights on")
lights = hub.LIGHTS_OFF_OFF lights = hub.LIGHTS_ON_ON
toggle_old = toggle else:
print("lights off")
slow_mode_cont = get_X_button(joystick) lights = hub.LIGHTS_OFF_OFF
if slow_mode_cont and not slow_mode_old: toggle_old = toggle
if slow_mode == True:
print("slowmode off") slow_mode_cont = get_X_button(joystick)
slow_mode = False if slow_mode_cont and not slow_mode_old:
else: if slow_mode == True:
print("slowmode on") print("slowmode off")
slow_mode = True slow_mode = False
#await hub.change_led_color(hub.LIGHTS_OFF_OFF)
else:
if brake and not was_brake: print("slowmode on")
joystick.rumble(0.0, 0.3, 300) slow_mode = True
await hub.drive(0, steering, hub.LIGHTS_OFF_ON) #await hub.change_led_color(hub.LIGHTS_OFF_ON)
await asyncio.sleep(0.4) slow_mode_old = slow_mode_cont
throttle = 0
throttle_old = 0
if brake and not was_brake:
if not brake and was_brake: joystick.rumble(0.0, 0.3, 300)
await hub.drive(throttle, steering, lights) await hub.drive(0, steering, hub.LIGHTS_OFF_ON)
await asyncio.sleep(0.4)
was_brake = brake throttle = 0
throttle_old = 0
if steering != steering_old or throttle != throttle_old or lights != lights_old and not brake:
print("throttle", throttle, "steering", steering) if not brake and was_brake:
if slow_mode == True: await hub.drive(throttle, steering, lights)
throttle = round(throttle / 2)
await hub.drive(throttle, steering, lights) was_brake = brake
else:
await hub.drive(throttle, steering, lights) if steering != steering_old or throttle != throttle_old or lights != lights_old and not brake:
print("throttle", throttle, "steering", steering)
throttle_old = throttle if slow_mode == True:
steering_old = steering throttle = round(throttle / 2)
lights_old = lights await hub.drive(throttle, steering, lights)
slow_mode_old = slow_mode else:
await hub.drive(throttle, steering, lights)
# Flush the output
sys.stdout.flush() throttle_old = throttle
steering_old = steering
asyncio.sleep(0.05) lights_old = lights
except KeyboardInterrupt: # Flush the output
pass sys.stdout.flush()
finally:
pygame.quit() asyncio.sleep(0.05)
if __name__ == "__main__": except KeyboardInterrupt:
asyncio.run(main()) pass
finally:
pygame.quit()
if __name__ == "__main__":
asyncio.run(main())