I extended the code available and described here to also be able to use the Bittle connected to the serial port (as in my bittle box was no bluetooth/wifi adapter). I added some messages on the screen, I thought this might come in handy...
You need to: pip install pyBittle pygame
bittle_xbox_controller.py:
"""This program allow to control Bittle using Xbox controller.
"""
import math
import pyBittle
import pygame
import sys
import time
__initialauthor__ = "EnriqueMoran"
# Define some colors.
BLACK = pygame.Color('black')
WHITE = pygame.Color('white')
BUTTONS_MAP = {
0: pyBittle.Command.BALANCE, # A
1: pyBittle.Command.REST, # B
2: pyBittle.Command.GREETING, # X
3: pyBittle.Command.SIT, # Y
4: pyBittle.Command.STEP, # LB
5: pyBittle.Command.GYRO # RB
}
class TextPrint(object):
def __init__(self):
self.reset()
self.font = pygame.font.Font(None, 20)
def tprint(self, window, textString):
textBitmap = self.font.render(textString, True, BLACK)
window.blit(textBitmap, (self.x, self.y))
self.y += self.line_height
def reset(self):
self.x = 10
self.y = 10
self.line_height = 15
def indent(self):
self.x += 10
def unindent(self):
self.x -= 10
class Controller():
def __init__(self, window_size=(1, 1),
connect_wifi=False, connect_bluetooth=False,
connect_com=False,
ip_addr=None, device_name=None, bt_port=None):
self.window_size = window_size
self.window = None
self.joystick = None
self.n_axes = 0
self.n_buttons = 0
self.n_hats = 0
self.bittle = None
self.connect_wifi = connect_wifi
self.connect_bluetooth = connect_bluetooth
self.connect_com = connect_com
self.ip_addr = ip_addr
self.device_name = device_name
self.bt_port = bt_port
self.direction = pyBittle.Command.BALANCE
def initialize(self):
# os.environ["DISPLAY"] = ":0"
# os.environ["SDL_VIDEODRIVER"] = "dummy" # Hide window
pygame.init()
controller_found = False
self.window = pygame.display.set_mode(self.window_size)
pygame.display.set_caption("Bittle controller")
self.clock = pygame.time.Clock()
pygame.joystick.init()
self.textPrint = TextPrint()
self.bittle = pyBittle.Bittle()
if self.connect_wifi:
self.bittle.wifiManager.ip = self.ip_addr
elif self.connect_bluetooth:
self.isconnected = self.bittle.connect_bluetooth()
elif self.connect_com:
self.isconnected = self.bittle.connect_serial(True)
try:
self.joystick = pygame.joystick.Joystick(0)
self.joystick.init()
self.n_axes = self.joystick.get_numaxes()
self.n_buttons = self.joystick.get_numbuttons()
self.n_hats = self.joystick.get_numhats()
print(f"Joystick found: {self.joystick.get_name()}")
controller_found = True
except:
print("No controller found!")
return controller_found
def read_inputs(self):
new_direction = self.direction
x_axis_value = 0
y_axis_value = 0
for i in range(self.n_axes):
axis_value = self.joystick.get_axis(i)
if i == 0: # Horizontal
x_axis_value = axis_value
elif i == 1: # Vertical
y_axis_value = -axis_value # Set FORWARD as positive value
if abs(x_axis_value) > 0.8 or abs(y_axis_value) > 0.8:
angle = self.get_angle(x_axis_value, y_axis_value)
if angle >= 337.5 or angle < 22.5:
new_direction = pyBittle.Direction.FORWARD
elif angle >= 22.5 and angle < 67.5:
new_direction = pyBittle.Direction.FORWARDRIGHT
elif angle >= 67.5 and angle < 112.5:
new_direction = pyBittle.Direction.FORWARDRIGHT
elif angle >= 112.5 and angle < 157.5:
new_direction = pyBittle.Direction.BACKWARDRIGHT
elif angle >= 157.5 and angle < 202.5:
new_direction = pyBittle.Direction.BACKWARD
elif angle >= 202.5 and angle < 247.5:
new_direction = pyBittle.Direction.BACKWARDLEFT
elif angle >= 247.5 and angle < 292.5:
new_direction = pyBittle.Direction.FORWARDLEFT
elif angle >= 292.5 and angle < 337.5:
new_direction = pyBittle.Direction.FORWARDLEFT
elif abs(x_axis_value) < 0.2 or abs(y_axis_value) < 0.2:
new_direction = pyBittle.Command.BALANCE # Stop
if self.direction != new_direction:
self.direction = new_direction
self.send_direction(self.direction)
self.textPrint.tprint(self.window, "Direction: {}".format(self.direction))
for i in range(self.n_buttons):
button = self.joystick.get_button(i)
if button == 1:
try:
command = BUTTONS_MAP[i]
self.send_command(command)
self.textPrint.tprint(self.window, "Command: {}".format(command))
except Exception as e:
print(e)
for i in range(self.n_hats):
gait = None
hat = self.joystick.get_hat(i)
if hat == (-1, 0): # Left pad
gait = pyBittle.Gait.CRAWL
elif hat == (1, 0): # Right pad
gait = pyBittle.Gait.TROT
elif hat == (0, -1): # Down pad
gait = pyBittle.Gait.WALK
elif hat == (0, 1): # Up pad
gait = pyBittle.Gait.RUN
if gait:
self.bittle.gait = gait
print(f"New gait selected: {gait}")
time.sleep(0.2)
self.textPrint.tprint(self.window, "Gait: {}".format(self.bittle.gait))
def run(self):
initialized = self.initialize()
if initialized:
running = True
clock = pygame.time.Clock()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.JOYBUTTONDOWN:
print("Joystick button pressed.")
elif event.type == pygame.JOYBUTTONUP:
print("Joystick button released.")
self.window.fill(WHITE)
self.textPrint.reset()
if self.connect_wifi:
self.textPrint.tprint(self.window, "WIFI Connnection")
elif self.connect_bluetooth:
self.textPrint.tprint(self.window, "Bluetooth Connnection")
else:
self.textPrint.tprint(self.window, "COM Connnection")
if not self.connect_wifi:
self.textPrint.tprint(self.window, "Connnected: {}".format(self.isconnected))
# Get count of joysticks.
joystick_count = pygame.joystick.get_count()
self.textPrint.tprint(self.window, "Number of joysticks: {}".format(joystick_count))
self.textPrint.indent()
# For each joystick:
for i in range(joystick_count):
joystick = pygame.joystick.Joystick(i)
joystick.init()
try:
jid = joystick.get_instance_id()
except AttributeError:
# get_instance_id() is an SDL2 method
jid = joystick.get_id()
self.textPrint.tprint(self.window, "Joystick {}".format(jid))
self.textPrint.indent()
# Get the name from the OS for the controller/joystick.
name = joystick.get_name()
self.textPrint.tprint(self.window, "Joystick name: {}".format(name))
try:
guid = joystick.get_guid()
except AttributeError:
# get_guid() is an SDL2 method
pass
else:
self.textPrint.tprint(self.window, "GUID: {}".format(guid))
# Usually axis run in pairs, up/down for one, and left/right for
# the other.
axes = joystick.get_numaxes()
self.textPrint.tprint(self.window, "Number of axes: {}".format(axes))
self.textPrint.indent()
for i in range(axes):
axis = joystick.get_axis(i)
self.textPrint.tprint(self.window, "Axis {} value: {:>6.3f}".format(i, axis))
self.textPrint.unindent()
buttons = joystick.get_numbuttons()
self.textPrint.tprint(self.window, "Number of buttons: {}".format(buttons))
self.textPrint.indent()
for i in range(buttons):
button = joystick.get_button(i)
if(button == 1):
self.textPrint.tprint(self.window,
"Button {:>2} value: {}".format(i, button))
self.textPrint.unindent()
hats = joystick.get_numhats()
self.textPrint.tprint(self.window, "Number of hats: {}".format(hats))
self.textPrint.indent()
# Hat position. All or nothing for direction, not a float like
# get_axis(). Position is a tuple of int values (x, y).
for i in range(hats):
hat = joystick.get_hat(i)
self.textPrint.tprint(self.window, "Hat {} value: {}".format(i, str(hat)))
self.textPrint.unindent()
self.textPrint.unindent()
self.read_inputs()
pygame.display.flip()
self.clock.tick(20)
pygame.quit()
def send_command(self, command):
if self.connect_wifi:
if self.bittle.has_wifi_connection():
self.bittle.send_command_wifi(command)
elif self.connect_bluetooth:
self.bittle.send_command_bluetooth(command)
elif self.connect_com:
self.bittle.send_command_serial(command)
print(f"Action: {command} sent")
time.sleep(0.5) # Let Bittle rest to prevent damage
return True
def send_direction(self, direction):
if self.connect_wifi:
if self.bittle.has_wifi_connection():
if direction == pyBittle.Command.BALANCE:
self.bittle.send_command_wifi(direction)
else:
self.bittle.send_movement_wifi(direction)
elif self.connect_bluetooth:
if direction == pyBittle.Command.BALANCE:
self.bittle.send_command_bluetooth(direction)
else:
self.bittle.send_movement_bluetooth(direction)
elif self.connect_com:
if direction == pyBittle.Command.BALANCE:
self.bittle.send_command_serial(direction)
else:
self.bittle.send_movement_serial(direction)
print(f"Direction: {direction} sent")
# Let Bittle rest to prevent damage, modify this under your own risk
time.sleep(0.5)
return True
def get_angle(self, xPercent, yPercent):
"""Returns joystick angle.
"""
angle_deg = 0
if xPercent > 0.9 and yPercent == 0:
angle_deg = 90
elif xPercent < -0.9 and yPercent == 0:
angle_deg = 270
elif xPercent == 0 and yPercent > 0.9:
angle_deg = 1
elif xPercent == 0 and yPercent < -0.9:
angle_deg = 180
elif (xPercent > 0 and yPercent > 0 and (xPercent > 0.2 or
yPercent > 0.2)):
angle_rad = math.atan2(xPercent, yPercent)
angle_deg = angle_rad * 180 / math.pi
elif (xPercent > 0 and yPercent < 0 and (xPercent > 0.2 or
yPercent < -0.2)):
angle_rad = math.atan2(xPercent, yPercent)
angle_deg = angle_rad * 180 / math.pi
elif (xPercent < 0 and yPercent < 0 and (xPercent < -0.2 or
yPercent < -0.2)):
angle_rad = math.atan2(xPercent, yPercent)
angle_deg = angle_rad * 180 / math.pi
angle_deg += 360
elif (xPercent < 0 and yPercent > 0 and (xPercent < -0.2 or
yPercent > 0.2)):
angle_rad = math.atan2(xPercent, yPercent)
angle_deg = angle_rad * 180 / math.pi
angle_deg += 360
return angle_deg
if __name__ == '__main__':
connect_wifi = False # Set to True to connect through WiFi
connect_bluetooth = False # Set to True to connect through Bluetooth
connect_com = True # Set to True to connect through COM port
ip_addr = '192.168.1.138' # Here goes your Bittle's IP address
controller = Controller(connect_wifi=connect_wifi,
connect_bluetooth=connect_bluetooth,
connect_com=connect_com,
window_size=(500, 400),
ip_addr=ip_addr)
if not connect_wifi and not connect_bluetooth and not connect_com:
print("No connection method selected.")
input("Press any key to exit.")
sys.exit()
try:
controller.run()
except KeyboardInterrupt:
if controller.connect_bluetooth:
print("Closing Bluetooth connection with Bittle")
try:
controller.bittle.send_command_bluetooth(pyBittle.Command.REST)
controller.bittle.disconnect_bluetooth()
print("Connection Closed")
except:
pass
input("Press any key to exit.")
sys.exit()
I haven't had much time since pyBittle v1.1.3 was released (which adds the possibility to control Bittle through serial), once I have some free time this week I will update the projects that make uses of pyBittle