Controlling a Raspberry Pi Pico remotely using PySerial

Update: I'm using thie code below in another project, and found that I had not correctly fixed the reported bug. The new version passes automated tests, and I am pretty sure it works OK.

I have changed the name of the class to Talker since it can both send and receive information.

Apologies to all concerned for the bug!

Introduction

You can use a Raspberry Pi Pico as a powerful peripheral to a host - a Raspberry Pi, a Jetson Nano, a laptop or workstation. In this article you'll see how to interact with a Pico running MicroPython or CircuitPython by writing some Python code that runs on the host.

The software is easy to use. It enables you to send a Python statement to the Pico and read the results. The statement can be any valid MicroPython code.

Setting up the host and the Pico

For this article I've used a Raspberry Pi as the host, but any computer running Windows, Linux or Mac OS will do so long as it has Python 3.5 or later installed.

In particular, you can use this technique to connect a Raspberry Pi Pico to a Jetson Nano host or any other member of the Jetson family.

The host needs to have the serial package available, so you need to run 

pip3 install PySerial

on the host.

You'll need to install MicroPython on the Pico. You'll find instructions for MicroPython installation in the official Getting Started Guide which I reviewed recently.

 The £10 Guide is worth buying, but if you can't wait for your copy to arrive you can download a free pdf.

The guide will also tell you how to use the Thonny editor to install the necessary code on your Pico.

Connect the host and the Pico

First, connect the host to the Pico using a USB data lead. Some USB leads only supply power. They will not work.

Install software on the Pico

Next, install the blinker script.

The blinker.py script runs on the Pico, but you should rename it to main.py. Here's the code:

"""
Example for remote control from host via Serial link.

This is the code to run on the Pico.
It sets up the onboard LED and allows you to turn it on or off.
"""
from machine import Pin

# use onboard LED which is controlled by Pin 25
# on a Pico W the onboad lLED is accessed differently,
# so commeent out the line below
# and uncomment the line below that
led = Pin(25, Pin.OUT) # veresion for Pico
# led = Pin('LED', Pin.OUT) # version for Pico W


# Turn the LED on
def on():
    led.value(1)

# Turn the LED off
def off():
    led.value(0)
Install it on the Pico using the Thonny editor.
  1. Open the gist on GitHub.
  2. Copy the code to your clipboard.
  3. Open Thonny and make sure it has connected to the Pico.
  4. Paste the code into the Thonny editor window.
  5. Save it on the Pico as main.py.
  6. Close the Thonny editor.

If you leave the Thonny editor open it will keep the serial port open on the host, and the serial program below will not work!

Since you saved the program as main.py it will run on the Pico automatically.

The talker.py script runs on the host. It uses PySerial to send commands from the host to the Raspberry Pi Pico and read the result.

Here's the talker code:

import serial


class Talker:
TERMINATOR = '\r'.encode('UTF8')

def __init__(self, timeout=1):
self.serial = serial.Serial('/dev/ttyACM0', 115200, timeout=timeout)

def send(self, text: str):
line = '%s\r\f' % text
self.serial.write(line.encode('utf-8'))
reply = self.receive()
reply = reply.replace('>>> ','') # lines after first will be prefixed by a propmt
if reply != text: # the line should be echoed, so the result should match
raise ValueError('expected %s got %s' % (text, reply))

def receive(self) -> str:
line = self.serial.read_until(self.TERMINATOR)
return line.decode('UTF8').strip()

def close(self):
self.serial.close()


On the host

  1. Copy talker.py from this github gist into an editor and save it in a directory of your choice.
  2. In that directory, run python3 to start an interactive session.
  3. Type from talker import Talker
  4. Type t = Talker(). If you are running on Windows, you will need to type t = Talker('COM6') replacing COM6 by whatever port the Pico is visible on.
  5. Type t.send('2 + 2')
  6. Type t.receive(). If all is well, this will print the result 4.
  7. Type t.send('on()')     #  The on-board LED on the Pico should turn on.
  8. Type t.send('off()')    #  The on-board LED on the Pico should turn off.
  9. When you have finished, type t.close().

Of course in a real application you'd normally create the sender, send and receive data using a Python script. In a future post I'll show how to do that to create a simple weather station with the Pico and plot the temperature and light level on the host. Before that I'll explore other ways of connecting and controlling the Pico from a Host.

To keep up to date with this series follow @rareblog on twitter.




Comments

  1. This isn't working for me. First, I had to modify the line that sends the text data:
    line = r'%s\r\f' % text
    to treat it as a raw string, otherwise I'd get '♀n()' being sent instead of 'on()'.
    However, I'm still not seeing the LED turn on or off and receiving 'False' after the timeout.

    Does the pico code (main.py) need to be in a loop?

    ReplyDelete
    Replies
    1. You shouldn't use a raw string. What the program does is to send your string, followed by a carriage return '\r' and a tab '\t'.

      The pico code does not need to be put in a loop. You're sending Python commands from the Pi or Nano to the Pico; the Python REPL will execute them as they are received.

      Delete
  2. What if I didn't wanted the blinker script to run constantly as mail.py, but rather, have the user decides when to toggle the LED by sending a command to the PICO over the serial connection?

    ReplyDelete
    Replies
    1. Then just use a terminal program like screen on the Pi, and type on() to turn the LED on and off() to turn it off. You don't need the sender program at all.

      Delete
  3. I use Thonny (Python 3.7.9) as well as WIN10 command line (Python 3.9):
    I can't get this running.

    Thonny:
    Python 3.7.9 (bundled)
    >>> from sender import Sender
    >>> s = Sender('COM3')
    Traceback (most recent call last):
    File "", line 1, in
    File "D:\Downloads\RaspberryPi\Pico\Projekt\Pico-Kontrolle\sender.py", line 7, in __init__
    selfserial = serial.Serial(device, baud, timeout=timeout)
    AttributeError: module 'serial' has no attribute 'Serial'
    >>>

    CLI:
    Python 3.7.9 (bundled)
    >>> from sender import Sender
    >>> s = Sender('COM3')
    Traceback (most recent call last):
    File "", line 1, in
    File "D:\Downloads\RaspberryPi\Pico\Projekt\Pico-Kontrolle\sender.py", line 7, in __init__
    selfserial = serial.Serial(device, baud, timeout=timeout)
    AttributeError: module 'serial' has no attribute 'Serial'
    >>>

    How to get the missing module 'serial'?
    In Thonny I used the the plugin management to check on PySerial and serial
    pyserial: Version 3.5
    serial: 0.0.97

    What am I missing?

    ReplyDelete
    Replies
    1. You should not install serial; remove it. The serial package that we are using is is part of PySerial.

      Delete
  4. For me this is a very helpful article.
    But there are two little faults in section "On the Host".
    They must be:
    Point 4.: from sender import Sender
    Point 8.: s.send('on()')
    Point 9.: s.send('off()'

    ReplyDelete
  5. I'm seeing this .. any ideas?
    >>> from sender import Sender
    >>> s = Sender()
    >>> s.send('2 + 2')
    False
    >>> s.receive()
    '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0

    ReplyDelete
  6. I should mention that I am using the uart pins.

    ReplyDelete
    Replies
    1. The code uses the USB port, not the . The code would need to be changed to use the uart pins. I'll take a look at what is involved tomorrow.

      Delete
  7. Great stuff, was looking for this :)

    ReplyDelete
  8. Hi, i am very interested in your work. But i do have the same problem as Alan. I did everything as you wrote but all i can see is False output s.send('2+2') >> False and empty string s.receive(). Is there any fix for this or am i missing something? Thank you!

    ReplyDelete
    Replies
    1. I'd need to know more about your environment to find out what's going wrong. What OS are you running, what version of Python, which version of PySerial?

      Delete

Post a Comment

Popular posts from this blog

Five steps to connect Jetson Nano and Arduino

Raspberry Pi Pico project 2 - MCP3008