MicroPython Tutorial XVI

Ok, lets do something different. Now LEGO has an iOS app that you can use as a remote. It is very good and lets you design your own controls. But to use it you need to be running the LEGO scratch interface. If you’re on MicroPython SD card you need something else.

But wait BEFORE you read on, be warned that the something else in this tutorial needs a Wifi connection. Something like LEGO recommended EDIMAX USB adaptor, without it this tutorial won’t work.

I should say too that is isn’t a tutorial like the others I posted to-date. This is a tutorial for those with quite a bit of experience in coding. All in all it will be around 120+ lines of code.

Here is a video showing what this does too.

It is also not a code base designed to work by itself, but in co-operation with the app remoteCode, that you can download here. OK done that, lets go.

#!/usr/bin/env pybricks-micropython
from pybricks import ev3brick as brick
from pybricks.ev3devices import Motor, UltrasonicSensor, ColorSensor, GyroSensor, InfraredSensor
from pybricks.parameters import Port,Color
from pybricks.robotics import DriveBase
from pybricks.tools import print, wait, StopWatch
from time import sleep
import threading
import sys
import random
import socket
import os
# Section 01hostname = os.popen('hostname -I').read().strip().split(" ")
print("hostname address",hostname[0])
hostIPA = hostname[0]
port = random.randint(50000,50999)
# Section 02left = Motor(Port.C)
right = Motor(Port.B)
# 56 is the diameter of the wheels in mm
# 114 is the distance between them in mm
robot = DriveBase(left, right, 56, 114)
print("host ",hostIPA)
print("port",port)
# Section 03online = Trueai = socket.getaddrinfo(hostIPA,port)
addr = ai[0][-1]
backlog = 5
size = 1024
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(backlog)
# Section 0Etry:
res = s.accept()
while online:
client_s = res[0]
#client_addr = res[1]
req =client_s.recv(1024)
data=req.decode('utf-8')
print("data ",data)
except AssertionError as error:
print("Closing socket",error)
client_s.close()
s.close()

We have imported all the classes we will ultimately need for this code to work to keep this tutorial manageable. Ok, we begin in Section 01 by running a query agains’t the OS class to get the IP address of our robot that we’re running on and a random port number. We need to give both these bits of information to the app so that it can communicate with the robot.

Next in Section 02 we have simply defined the two connected motors declaring them in the process in a drive pair.

In Section 03 we setup the code needed to communicate with the iOS device, and indeed try and make that connection.

In Section 0E we launch the main loop thru which the app remoteCode and our MicroPython code will talk, printing out the conversation that is taking place. Yes, 0E doesn’t follow 03, we dropped a few sections to keep things to get you going.

Download the app remoteCode here and copy and paste this to a python code file on your robot, run it and then run remoteCode on your iOS device. Enter the IP address you’re of your robot into the iOS app and the port and they should start talking to each other.

Assuming you’re connected and it all works, you should see the work “data #:connected” appear on the screen. This is the app talking to your Python app. Now go and select one of the interfaces “Keyboard, Touchpad or Motion”. You should see more text appearing, assuming you choose “Keyboard” for example, it will sa “#:begin” followed by “#:keypad”. Swipe the iPad right and it’ll say “#:end” and then return to the main menu.

Note if you iOS device screen locks during the process, you’ll need to quit the Python and re-run the process. You can quit your connection on the iOS device by shaking it.

And there you have it, the basis of our remote app. But ok, where do you go from here. Here is some more code to add to the mix. These two procedures show subroutines to interpret the #: commands you just saw returned by the app.

# Section 07def actionTrigger(data, client):
global transmit
if data[:5] == "#:end":
stopMotors()
brick.sound.beep()
peerMode = False
if data[:6] == "#:peer":
peerMode = True
if data[:7] == "#:begin":
pass
if data[:8] == "#:keypad":
brick.light(Color.YELLOW)
if data[:10] == "#:touchpad":
brick.light(Color.RED)
if data[:8] == "#:motion":
brick.light(Color.ORANGE)
if data[:5] == "#:con": # connected
brick.sound.beep()
if data[:5] == "#:dis": # disconnect
brick.light(Color.BLACK)
wait(2000)
brick.light(Color.GREEN)
client_s.close()
s.close()
if data[:8] == "#:short":
stopMotors()
brick.sound.beep()
if data[:6] == "#:long":
stopMotors()
brick.sound.beep()
# Section 08def stopMotors():
print("STOP STOP STOP ")
robot.stop()

This code is reasonably self explicit. As you change to different interfaces, the colours of the robot will change in response to the different “#:” conversations the iOS code sends back to it.

We’re almost there. I am going to give you the last section which is the code bases you need for the tracker/motion interfaces and leave the keyboard one as an exercise for you to figure out. You should already have the general gist of the way it works now.

Firstly we add a method to interprete the streams of data that come back if you choose the motion or the tracker interface. Note the code here is a little more complicated since I want to ignore duplicate data packets sent by the app.

lastPitch = 0
lastRoll = 0
# Section 09def joystick(pitch, roll):
global lastPitch
global lastRoll
calcPitch = int(round(pitch / 180 * 800,0)) # needs to be an integer
calcRoll = int(round(roll / 720 * 200,0)) # turns need to be slow and deliberate, this reduces roll by 4
if lastPitch != calcPitch or lastRoll != calcRoll:
print("calc",calcPitch,calcRoll)
robot.drive(calcPitch,calcRoll)
lastPitch = calcPitch
lastRoll = calcRoll

And then I add an else to the if we added early on in the main loop, so after the call to the actionTrigger routine it acts on the data packets prefixed with a “@:” lead.

elif data[:2] == "@:":
mCommands = data.split("\n")
for mCommand in mCommands:
if len(mCommand) != 0:
cords = mCommand[2:]
cord = cords.split(":")
try:
roll = float(cord[0])
pitch = float(cord[1])
joystick(pitch,roll)
except:
pass

When you’re all done, download the script and run it. Try using it with the touchpad or the motion interface and you’re be able to move you’re robot with your iOS device, under MicroPython!

Hint. If you’re in the motion interface, you need to keep the grey circle inside the red one to stop sending data, and indeed tap the circle itself to stop the robot. The touchpad is slightly easier, you just stop touching it.

I may publish a tutorial on getting the keyboard interface running in due time, but for now, its time to play :) A final final note, how do I know all this, well yes I confess I wrote the remoteCode app too. [Obviously it isn’t Python, it is in Swift].

Written by

Coding for 35+ years, enjoying using and learning Swift/iOS development. Writer @ Better Programming, @The StartUp, @Mac O’Clock, Level Up Coding & More

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store