LEGO MindStorms – Znap with PS4 controller

This is an extended project of LEGO MindStorms – Znap with a remote controller, please refer it for the Dog movement and communication between EV3 Bricks.

Linux /dev/input/event*

Thanks for Anton’s Mindstorms Hacks, evdev and Linux Input drivers v1.0 (c) 1999-2001 Vojtech Pavlik, they gave us good idea how Linux deal with inputs.

Since ev3dev is Linux based, when PS4 controller connected to the EV3 Brick via Bluetooth, we can identify the handler from a directory call /dev/input. As shown below, event2, event3 and event4 are created when the PS4 controller is connected.

When we looked into the device detail from /proc/bus/input/devices, we can get the following details,

  • Handler Event 0 = Input0 – LEGO Mindstorms EV3 Speaker
  • Handler Event 1 = Input1 – EV3 Brick Buttons
  • Handler Event 2 = Input2 – Wireless Controller Touchpad
  • Handler Event 3 = Input3 – Wireless Controller Motion Sensors
  • Handler Event 4 = Input4 – Wireless Controller

We are going to use the Wireless Controller to control the Dog, event4 is the target handler to get all inputs information. Based on the information we reviewed, we learnt that event4 contains 16 bytes of data as shown below,

  • tv_sec (long unsigned value) : Time in seconds since epoch at which event occurred.
  • tv_usec (long unsigned value) : Microsecond portion of the timestamp.
  • ev_type (unsigned short) : Event type
  • code (unsigned short) : Event code
  • value (signed long) : Event value

We are using ev_type, code and value to monitor the PS4 controller inputs. It updates real time for any status change, such as button press, button release, joystick movement. Keep in mind, if a button is being held, it will generate a value ‘1’ for once but no additional update until the button is released, then a value ‘0’ will be generated.

[This part was created by Adam]

PS4 Controller mapping with EV3DEV

# Open the PS4 Controller file at /dev/input/event4 in binary mode
infile_path = "/dev/input/event4"
in_file = open(infile_path, "rb")

# Format of the event contains - unsigned long int, unsigned long int, 
# unsigned short, unsigned short, signed int, i.e. LLHHi
FORMAT = 'LLHHi' 
EVENT_SIZE = struct.calcsize(FORMAT)
.
.
.
# Read from the file and unpack into five variable based on the format
event = in_file.read(EVENT_SIZE)
(tv_sec, tv_usec, ev_type, code, value) = struct.unpack(FORMAT, event)

This part is to open the file event4 to import the PS4 Controller values into 5 variables based on the predefined format – ‘LLHHi’. We use ev_type, code and value to identify the action from PS4 Controller.

  • ev_type : There are two main type, type 1 = buttons, type 3 = joystick, dpad and analog trigger
  • code : Which button or joystick being pressed / moved
  • value : The action done, button pressed = 1, released = 0, or analog value from joystick and analog trigger

So, we wrote a small program to identify each codes and values from the PS4 Controller. The result is shown below,

Key mapping as shown below

Type = 1, code & possible value

L1 - 310 (0,1)
L2 - 312 (0,1)
L3 - 317 (0,1)
R1 - 311 (0,1)
R2 - 313 (0,1)
R3 - 318 (0,1)
Triangle - 307 (0,1)
Square - 308 (0,1)
Cross - 304 (0,1)
Circle - 305 (0,1)
Share - 314 (0,1)
Option - 315 (0,1)
PS - 316 (0,1)

Type = 3, code & possible value

Left Stick Y - 1 (Up 0 - Down 255)
Left Stick X - 0 (Left 0 - Right 255)
Right Stick Y - 4 (Up 0 - Down 255)
Right Stick X - 3 (Left 0 - Right 255)
L2 - 2 (0 - 255)
R2 - 5 (0 - 255)
dpad Up & Down - 17 (-1, 0, 1)
dpad Left & Right- 16 (-1, 0, 1)

How does the program work

def PS4_Controller():
    global pressed
    global trigger_dog
    global speed
    global speed_backwards
    global speed_forwards
    global turning
    global out

    while True:        
        # Read from the file and unpack into five variable based on the format
        event = in_file.read(EVENT_SIZE)
        (tv_sec, tv_usec, ev_type, code, value) = struct.unpack(FORMAT, event)
                    
        if ev_type == 1 and code == 311 and value == 1: 
            trigger_dog =  45                  
            out = 1
        if ev_type == 1 and code == 311 and value == 0:
            trigger_dog = 0        
            out = 0

        if ev_type == 1 and code == 310 and value == 1 and out == 0:
            trigger_dog =  35

        if ev_type == 1 and code == 310 and value == 0 and out == 0:
            trigger_dog =  0
                
        if ev_type == 1 and code == 305 and value == 1:
            pressed = True
            
        if ev_type == 1 and code == 305 and value == 0:
            pressed = False
                    
        if ev_type == 3 and code == 5:
            speed_forwards = value * 6
        
        elif ev_type == 3 and code == 2:
            speed_backwards = value * -6

        if ev_type == 3 and code == 0:
            turning = (value*360-(180*255))/255

PS4_Controller_Thread = threading.Thread(target = PS4_Controller)
PS4_Controller_Thread.setDaemon = True
PS4_Controller_Thread.start()
        
bite = 0
while True:

    speed = speed_forwards + speed_backwards    
    print(speed)
    combo(pressed)
    forward(speed,turning)
    bite = Dog_bite(trigger_dog, bite)
    
in_file.close()

Basic Idea

Since we didn’t want to change the program of ‘the Dog’, we replaced the LEGO Controller part, i.e. receiving LEGO Controller result, with a new function to detect the action from the PS4 Controller buttons and joysticks. When corresponding action done on the PS4 Controller, the function will update the variable for ‘the Dog’ to action.

For example, when we press the circle, the function will detect a value of ev_type = 1 , code = 305 and value = 1. Then, it changes the variable ‘Pressed ‘ to True so that ‘the Dog’ will do combo. If we keep pressing the button, the variable ‘Pressed’ will stay True until we released the button.

Parallel Processing

We made the function parallel processing because it can detect all the status changes from the PS4 Controller. If we use normal function, the function will only read the latest actions from the controller that may result missing some of the actions. Especially, if we want to detect button pressed or released.

Global Variable

When we use global variable, the variable change by the parallel progressing function can be read by the program outside. For the PS4_Controller function detects any actions from the controller, it will change the corresponding variable for ‘the Dog’ to respond, just like the LEGO Controller sending message to ‘the Dog’.

Build instruction and the program

We created the program from scratch without referring any example, you can download from below.

Official Build Instruction from LEGO

The Dog

What’s next?

It may be fun if you use PS4 to control the LEGO Elephant, Snake and even with the Stair Climber!!

Moreover, you may try to learn more about event2 & event3 for how to use the controller touchpad and the motion sensor.

One thought on “LEGO MindStorms – Znap with PS4 controller

Leave a Reply

Your email address will not be published. Required fields are marked *