Escaping the Arduino IDE

This guide also exists for Node.js!

The guide presented here deals primarially with Python. Some of you may prefer to use a language like Node.js. Happily, your kind classmate Billy Kwok has written a great guide about how do do that here!

If you write a guide for a different language I'd love to put it here as well.

In last week’s Thursday lab we discussed making our Arduino programs interactive. The approach outlined there was limited though as we could only perform that interaction from inside of the Arduino IDE. In practice and in your projects we want to escape the IDE and thus increase the amount of digital real estate that our tangible interfaces have access to.

In today’s Thursday bonus lab we will perform this escape act and start communicating with our Arduinos from Python programs. Having done that we will then explore how we can expand the types of user input that we can use to control our Arduino.

Act 1 - The escape

As we saw last week, we can send messages to our Arduino via the IDE’s serial monitor. For example, with the code below loaded onto our Arduino we can send messages to the device which it will send back by opening Tools -> Serial Monitor in the context menu and typing into the top bar.

void setup() {
  Serial.begin(9600);
}

void loop() {
  if (Serial.available()) {
    String message = Serial.readStringUntil('\n');
    Serial.print(": " + message + "\n");
  }
}

An interesting feature of this ‘serial’ style of interaction is that it is neither unique to Arduinos nor the Arduino IDE. We can communicate with our Arduino via this method using essentially whatever language or method we’d like. Let’s take a look at how we might do so using the Python programming language.

Python is a nice choice for this task as its syntax is designed to be relatively human readable even if you don’t know the language and it has an extensive echosystem of libraries to interface with other systems.

Getting set up

Start by following the directions here to install Python if you do not have it installed already. Having done that you ought to be able to open the Terminal / Command Prompt application on macOS / Windows and run python by typing python.

Here is an example python session in my terminal where I ask the language to print hello! to the screen:

$ python3
>>> print('hello!')
hello!
>>> exit()

Having installed python we can install a library to interface with the Arduino’s serial port by running the following command in our terminals:

$ python3 -m pip install pyserial

Interfacing with our Arduino

Having gotten set up, go ahead and upload the “echo” Arduino sketch from the introduction to this section to your Arduino board. Then copy the following python program into a file called pyserial.py on your computer:

import serial

def get_ports():
    """List all of the avaliable ports on the device."""
    import serial.tools.list_ports
    return serial.tools.list_ports.comports()

def prompt_for_and_get_port():
    """Prompts the user to select a port. Returns the selected
    one.

    Input is expected to be a number in [0, num_ports) and is
    read from standard in. An input outside of that range will result
    in re-prompting until a valid input is provided.
    """
    ports = get_ports()
    for index, port in enumerate(ports):
        print("{}) {}\t{}".format(index, port.device, port.description))
    selection = int(input("please input the port number that your port is on: "))
    while selection < 0 or selection >= len(ports):
        print("error: {} is not between 0 and {}".format(selection, len(ports) - 1))
        selection = int(input("please input the port number that your port is on: "))
    return ports[selection]

def prompt_for_and_get_serial(baudrate=9600):
    """Prompts the user to select a port and returns a serial object
    connected to that port.

    By default the returned serial object uses a 9600 baudrate but
    this can be modified by passing one to the function.
    """
    port = prompt_for_and_get_port()
    return serial.Serial(port=port.device, baudrate=baudrate)

serial = prompt_for_and_get_serial()

while True:
    send = input('>> ')
    serial.write(bytes(send, 'utf-8'))
    print(serial.readline().decode('utf-8')[:-1])

If you are using a Mac you can do this by copying the above code and running pbpaste > pyserial.py inside of your Terminal window. If you are on Windows and you are comfortable using a text editor you may create the file like that.

The program that you’re looking at exposes a function called prompt_for_and_get_serial which when run will ask the user what port their Arduino is connected to and open a serial connection to the Arduino there. It will then take input from the user, send that input to the Arduino, and display the Arduino’s response.

Here is an example of an interaction with the program. Note that I run python3 pyserial.py to start the program.

$ python3 pyserial.py
0) /dev/cu.blueberry-SPPDev-11  n/a
1) /dev/cu.blueberry-SPPDev-4   n/a
2) /dev/cu.Bluetooth-Incoming-Port  n/a
3) /dev/cu.iPhone-WirelessiAPv2 n/a
4) /dev/cu.elzekeo-SPPDev   n/a
5) /dev/cu.usbmodem14201    Arduino Uno
please input the port number that your port is on: 5
>> hello
: hello
>> goodbye
: goodbye

Having done that we have successfully escaped the Arduino IDE and are controlling the Arduino from a python program! You can find the complete source code with some documentation for this program here.

Act 2 - Expanding our interaction options

Recall from the Wednesday lab item two for your homework:

Change the code so that you can control the RGB values with multiple key presses. For example, pressing ‘r’ 5 times will set the brightness to 50% (or brightness = 127) and pressing ‘r’ 10 times will set it to 100% (or brightness = 255)

Performing this interaction in the Arduino IDE produces a less than desirable result. In order to send data to the Arduino via the IDE you are required to type your input into the text box and then press enter. This effectively transforms the interaction from ‘press r five times’ to ‘press r followed by enter 5 times.’

An advantage of using our Python script is that we no longer have this constraint. We can send input to the user whenever we would like with serial.write().

In order to do this I’ve provided a python script which exposes a getch function. getch reads a character from input on the terminal without requiring that the user presses enter. You can find the source code here along with instructions on using it at the top of the file.

Here’s an example of a program that sends a number to the Arduino representing the amount of time that has passed between when the user last pressed a key and the current key press. This program uses the functions from the two provided python programs to do this:

def get_time():
    return round(time.time() * 1000)

serial = prompt_for_and_get_serial()
lastPress = get_time()

while True:
    prompt = getch()
    if prompt == '.':
        break

    pressTime = get_time()
    elapsed = 3 * (pressTime - lastPress) // 4

    if elapsed < 255:
        serial.write(bytes(str(255 - elapsed), 'utf-8'))

    # Write a space so that the Arduino can tell the sent numbers
    # apart.
    serial.write(b' ')

    lastPress = pressTime

Your task should you choose to accept it

Use the example code here to communicate with the Arduino. Can you enable an interaction not otherwise possible with just the Arduino IDE?

Some ideas:

  1. Implement the task from homework 2 but without requiring the user to press enter between letters.
  2. Light up a LED on the Arduino according to how quickly the user is pressing buttons.
  3. Write an Arduino program to light up a LED according to numbers being sent over the serial interface (Serial.parseInt() may be helpful here) and then use python to send some information about your computers internet connection to the Arduino.