Getting Started with Pymata4#

Pymata4 is a Python library that allows you to monitor and control Arduino hardware from a host computer. The library uses the Firmata protocol for communicating with the Arduino hardware. Pymata4 supports the StandardFirmata server included with the Arduino IDE, and also StandardFirmataWiFi, and an enhanced server FirmataExpress distributed with Pymata4.

Pymata4 uses concurrent Python threads to manage interaction with the Arduino. The concurrency model enables development of performant and interactive Arduino applications using Python on a host computer. Changes in the status of an Arduino pin can be processed with callbacks. It’s sibling, pymata-express, is available using the Python asyncio package.

Support for common \(I^2C\) devices, including stepper motors, is included in FirmataExpress. Applications using unsupported \(I^2C\) devices may require modifications to the Firmata server sketch.

Useful links:

Hardware Setup and Software Installations#

The Arduino must be attached to the host by USB with either the StandardFirmata or Firmata-express sketch installed using the Arduino IDE. For use with WiFi, install StandardFirmataWiFi.

The Python pymata4 package can be installed with pip.

!pip install pymata4
Requirement already satisfied: pymata4 in /Users/jeff/opt/anaconda3/lib/python3.8/site-packages (1.10)
Requirement already satisfied: pyserial in /Users/jeff/opt/anaconda3/lib/python3.8/site-packages (from pymata4) (3.5)

Basic Usage#

pymata4.Pymata()
board.shutdown()
from pymata4 import pymata4

# create a board instance
board = pymata4.Pymata4()

# remember to shutdown
board.shutdown()
pymata4:  Version 1.10

Copyright (c) 2020 Alan Yorinks All Rights Reserved.

Opening all potential serial ports...
	/dev/cu.usbmodem14301

Waiting 4 seconds(arduino_wait) for Arduino devices to reset...

Searching for an Arduino configured with an arduino_instance = 1
Arduino compatible device found and connected to /dev/cu.usbmodem14301

Retrieving Arduino Firmware ID...
Arduino Firmware ID: 2.5 StandardFirmata.ino

Retrieving analog map...
Auto-discovery complete. Found 30 Digital Pins and 12 Analog Pins

Blinker#

board.digital_write(pin, value)

Pymata4 has two methods for writing a 1 or a 0 to a digital output. digital_write(pin, value) hides details of the Firmata protocol from the user. The user can refer to digital pins just as they would in standard Arduino coding. A second method, digital_pin_write(pin, value) allows writing to multiples at the same time, but requires the user to understand further details of the Firmata protocol.

from pymata4 import pymata4
import time

LED_PIN = 13

board = pymata4.Pymata4()

# set the pin mode
board.set_pin_mode_digital_output(LED_PIN)

for n in range(5):
    print("LED ON")
    board.digital_write(LED_PIN, 1)
    time.sleep(1)
    
    print("LED OFF")
    board.digital_write(LED_PIN, 0)
    time.sleep(1)

board.shutdown()
pymata4:  Version 1.10

Copyright (c) 2020 Alan Yorinks All Rights Reserved.

Opening all potential serial ports...
	/dev/cu.usbmodem14301

Waiting 4 seconds(arduino_wait) for Arduino devices to reset...

Searching for an Arduino configured with an arduino_instance = 1
Arduino compatible device found and connected to /dev/cu.usbmodem14301

Retrieving Arduino Firmware ID...
Arduino Firmware ID: 2.5 StandardFirmata.ino

Retrieving analog map...
Auto-discovery complete. Found 30 Digital Pins and 12 Analog Pins


LED ON
LED OFF
LED ON
LED OFF
LED ON
LED OFF
LED ON
LED OFF
LED ON
LED OFF

Handling a Keyboard Interrupt#

Pymata4 sets up multiple concurrent processes upon opening connection to the Arduino hardware. If Python execution is interrupted, it isimportant to catch the interrupt and shutdown the board before exiting the code. Otherwise the Arduino may continue to stream data requiring the Arduino to be reset.

from pymata4 import pymata4
import time

def blink(board, pin, N=20):
    board.set_pin_mode_digital_output(LED_PIN)
    for n in range(N):
        board.digital_write(LED_PIN, 1)
        time.sleep(0.5)
        board.digital_write(LED_PIN, 0)
        time.sleep(0.5)
    board.shutdown()
    
LED_PIN = 13
board = pymata4.Pymata4() 

try:
    blink(board, LED_PIN)
except KeyboardInterrupt:
    print("Operation interrupted. Shutting down board.")
    board.shutdown()
pymata4:  Version 1.10

Copyright (c) 2020 Alan Yorinks All Rights Reserved.

Opening all potential serial ports...
	/dev/cu.usbmodem14201

Waiting 4 seconds(arduino_wait) for Arduino devices to reset...

Searching for an Arduino configured with an arduino_instance = 1
Arduino compatible device found and connected to /dev/cu.usbmodem14201

Retrieving Arduino Firmware ID...
Arduino Firmware ID: 2.5 StandardFirmata.ino

Retrieving analog map...
Auto-discovery complete. Found 30 Digital Pins and 12 Analog Pins


Operation interrupted. Shutting down board.

Getting Information about the Arduino#

Firmata protocol

from pymata4 import pymata4
import time

board = pymata4.Pymata4()

print("Board Report")
print(f"Firmware version: {board.get_firmware_version()}")
print(f"Protocol version: {board.get_protocol_version()}")
print(f"Pymata version: {board.get_pymata_version()}")

def print_analog_map(board):
    analog_map = board.get_analog_map()
    for pin, apin in enumerate(analog_map):
        if apin < 127:
            print(f"Pin {pin:2d}: analog channel = {apin}")

def print_pin_state_report(board):
    pin_modes = {
        0x00: "INPUT",
        0x01: "OUTPUT",
        0x02: "ANALOG INPUT",
        0x03: "PWM OUTPUT",
        0x04: "SERVO OUTPUT",
        0x06: "I2C",
        0x08: "STEPPER",
        0x0b: "PULLUP",
        0x0c: "SONAR",
        0x0d: "TONE",
    }
    analog_map = board.get_analog_map()
    for pin in range(len(analog_map)):
        state = board.get_pin_state(pin)
        print(f"Pin {pin:2d}: {pin_modes[state[1]]:>15s} = {state[2]}")
    
print_pin_state_report(board)
board.digital_write(13, 1)
print_pin_state_report(board)
print_analog_map(board)

capability_report = board.get_capability_report()

board.shutdown()

# get capability report
print("\nCapability Report")
modes = {
    0x00: "DIN",  # digital input
    0x01: "DO",   # digital output
    0x02: "AIN",  # analog input
    0x03: "PWM",  # pwm output
    0x04: "SRV",  # servo output
    0x05: "SFT",  # shift
    0x06: "I2C",  # I2C
    0x07: "WIR",  # ONEWIRE
    0x08: "STP",  # STEPPER
    0x09: "ENC",  # ENCODER
    0x0A: "SRL",  # SERIAL
    0x0B: "INP",  # INPUT_PULLUP
}

pin_report = {}
pin = 0
k = 0
while k < len(capability_report):
    pin_report[pin] = {}
    while capability_report[k] < 127:
        pin_report[pin][modes[capability_report[k]]] = capability_report[k+1]
        k += 2
    k += 1
    pin += 1

mode_set = set([mode for pin in pin_report.keys() for mode in pin_report[pin].keys()])
print("        " + "".join([f" {mode:>3s} " for mode in sorted(mode_set)]))
for pin in pin_report.keys():
    s = f"Pin {pin:2d}:"
    for mode in sorted(mode_set):
        s += f" {pin_report[pin][mode]:>3d} " if mode in pin_report[pin].keys() else " "*5
    print(s)
pymata4:  Version 1.10

Copyright (c) 2020 Alan Yorinks All Rights Reserved.

Opening all potential serial ports...
	/dev/cu.usbmodem14301

Waiting 4 seconds(arduino_wait) for Arduino devices to reset...

Searching for an Arduino configured with an arduino_instance = 1
Arduino compatible device found and connected to /dev/cu.usbmodem14301

Retrieving Arduino Firmware ID...
Arduino Firmware ID: 2.5 StandardFirmata.ino

Retrieving analog map...
Auto-discovery complete. Found 30 Digital Pins and 12 Analog Pins


Board Report
Firmware version: 2.5 StandardFirmata.ino
Protocol version: 2.5
Pymata version: 1.10
Pin  0:          OUTPUT = 0
Pin  1:          OUTPUT = 0
Pin  2:          OUTPUT = 0
Pin  3:          OUTPUT = 0
Pin  4:          OUTPUT = 0
Pin  5:          OUTPUT = 0
Pin  6:          OUTPUT = 0
Pin  7:          OUTPUT = 0
Pin  8:          OUTPUT = 0
Pin  9:          OUTPUT = 0
Pin 10:          OUTPUT = 0
Pin 11:          OUTPUT = 0
Pin 12:          OUTPUT = 0
Pin 13:          OUTPUT = 0
Pin 14:          OUTPUT = 0
Pin 15:          OUTPUT = 0
Pin 16:          OUTPUT = 0
Pin 17:          OUTPUT = 0
Pin 18:    ANALOG INPUT = 0
Pin 19:    ANALOG INPUT = 0
Pin 20:    ANALOG INPUT = 0
Pin 21:    ANALOG INPUT = 0
Pin 22:    ANALOG INPUT = 0
Pin 23:    ANALOG INPUT = 0
Pin 24:    ANALOG INPUT = 0
Pin 25:    ANALOG INPUT = 0
Pin 26:    ANALOG INPUT = 0
Pin 27:    ANALOG INPUT = 0
Pin 28:    ANALOG INPUT = 0
Pin 29:    ANALOG INPUT = 0
Pin  0:          OUTPUT = 0
Pin  1:          OUTPUT = 0
Pin  2:          OUTPUT = 0
Pin  3:          OUTPUT = 0
Pin  4:          OUTPUT = 0
Pin  5:          OUTPUT = 0
Pin  6:          OUTPUT = 0
Pin  7:          OUTPUT = 0
Pin  8:          OUTPUT = 0
Pin  9:          OUTPUT = 0
Pin 10:          OUTPUT = 0
Pin 11:          OUTPUT = 0
Pin 12:          OUTPUT = 0
Pin 13:          OUTPUT = 1
Pin 14:          OUTPUT = 0
Pin 15:          OUTPUT = 0
Pin 16:          OUTPUT = 0
Pin 17:          OUTPUT = 0
Pin 18:    ANALOG INPUT = 0
Pin 19:    ANALOG INPUT = 0
Pin 20:    ANALOG INPUT = 0
Pin 21:    ANALOG INPUT = 0
Pin 22:    ANALOG INPUT = 0
Pin 23:    ANALOG INPUT = 0
Pin 24:    ANALOG INPUT = 0
Pin 25:    ANALOG INPUT = 0
Pin 26:    ANALOG INPUT = 0
Pin 27:    ANALOG INPUT = 0
Pin 28:    ANALOG INPUT = 0
Pin 29:    ANALOG INPUT = 0
Pin 18: analog channel = 0
Pin 19: analog channel = 1
Pin 20: analog channel = 2
Pin 21: analog channel = 3
Pin 22: analog channel = 4
Pin 23: analog channel = 5
Pin 24: analog channel = 6
Pin 25: analog channel = 7
Pin 26: analog channel = 8
Pin 27: analog channel = 9
Pin 28: analog channel = 10
Pin 29: analog channel = 11

Capability Report
Pin   :AINDINDOI2CINPPWMSRV
Pin  0:                1        1                 1                14 
Pin  1:                1        1                 1                14 
Pin  2:                1        1        1        1                14 
Pin  3:                1        1        1        1        8       14 
Pin  4:                1        1                 1                14 
Pin  5:                1        1                 1        8       14 
Pin  6:                1        1                 1        8       14 
Pin  7:                1        1                 1                14 
Pin  8:                1        1                 1                14 
Pin  9:                1        1                 1        8       14 
Pin 10:                1        1                 1        8       14 
Pin 11:                1        1                 1        8       14 
Pin 12:                1        1                 1                14 
Pin 13:                1        1                 1        8       14 
Pin 14:                1        1                 1                14 
Pin 15:                1        1                 1                14 
Pin 16:                1        1                 1                14 
Pin 17:                1        1                 1                14 
Pin 18:      10        1        1                 1                14 
Pin 19:      10        1        1                 1                14 
Pin 20:      10        1        1                 1                14 
Pin 21:      10        1        1                 1                14 
Pin 22:      10        1        1                 1                14 
Pin 23:      10        1        1                 1                14 
Pin 24:      10        1        1                 1                14 
Pin 25:      10        1        1                 1                14 
Pin 26:      10        1        1                 1                14 
Pin 27:      10        1        1                 1                14 
Pin 28:      10        1        1                 1                14 
Pin 29:      10        1        1                 1                14 
         AIN  DIN   DO  I2C  INP  PWM  SRV 
Pin  0:        1    1         1        14 
Pin  1:        1    1         1        14 
Pin  2:        1    1    1    1        14 
Pin  3:        1    1    1    1    8   14 
Pin  4:        1    1         1        14 
Pin  5:        1    1         1    8   14 
Pin  6:        1    1         1    8   14 
Pin  7:        1    1         1        14 
Pin  8:        1    1         1        14 
Pin  9:        1    1         1    8   14 
Pin 10:        1    1         1    8   14 
Pin 11:        1    1         1    8   14 
Pin 12:        1    1         1        14 
Pin 13:        1    1         1    8   14 
Pin 14:        1    1         1        14 
Pin 15:        1    1         1        14 
Pin 16:        1    1         1        14 
Pin 17:        1    1         1        14 
Pin 18:  10    1    1         1        14 
Pin 19:  10    1    1         1        14 
Pin 20:  10    1    1         1        14 
Pin 21:  10    1    1         1        14 
Pin 22:  10    1    1         1        14 
Pin 23:  10    1    1         1        14 
Pin 24:  10    1    1         1        14 
Pin 25:  10    1    1         1        14 
Pin 26:  10    1    1         1        14 
Pin 27:  10    1    1         1        14 
Pin 28:  10    1    1         1        14 
Pin 29:  10    1    1         1        14 

Temperature Control Lab Shield#

from pymata4 import pymata4
import time

class tclab():
    def __init__(self):
        self.board = pymata4.Pymata4()
        self.LED_PIN = 9
        self.Q1_PIN = 3
        self.Q2_PIN = 5
        self.T1_PIN = 0
        self.T2_PIN = 2
        self.board.set_pin_mode_pwm_output(self.LED_PIN)
        self.board.set_pin_mode_pwm_output(self.Q1_PIN)
        self.board.set_pin_mode_pwm_output(self.Q2_PIN)
        self.board.set_pin_mode_analog_input(self.T1_PIN)
        self.board.set_pin_mode_analog_input(self.T2_PIN)
        self._Q1 = 0
        self._Q2 = 0
        time.sleep(0.1)
        
    def __enter__(self): 
        return self
    
    def __exit__(self, exc_type, exc_value, traceback):
        self.close()
        return
    
    def close(self):
        self.Q1(0)
        self.Q2(0)
        self.board.shutdown()
    
    def read_temperature(self, pin):
        # firmata doesn't provide a means to use the 3.3 volt reference
        adc, ts = self.board.analog_read(pin)
        return round(adc*513/1024 - 50.0, 1)
    
    def Q1(self, val):
        val = int(255*max(0, min(100, val))/100)
        self.board.pwm_write(self.Q1_PIN, val)
        
    def Q2(self, val):
        val = int(255*max(0, min(100, val))/100)
        self.board.pwm_write(self.Q2_PIN, val)
    
    def T1(self):
        return self.read_temperature(self.T1_PIN)
    
    def T2(self):
        return self.read_temperature(self.T2_PIN)
        
    def LED(self, val):
        val = max(0, min(255, int(255*val/100)))
        self.board.pwm_write(self.LED_PIN, val)
        
with tclab() as lab:
    lab.Q1(100)
    lab.Q2(100)
    for n in range(30):
        print(lab.T1(), lab.T2())
        lab.LED(100)
        time.sleep(0.5)
        lab.LED(0)
        time.sleep(0.5)
    lab.Q1(0)
    lab.Q2(0)
pymata4:  Version 1.10

Copyright (c) 2020 Alan Yorinks All Rights Reserved.

Opening all potential serial ports...
	/dev/cu.usbmodem14301

Waiting 4 seconds(arduino_wait) for Arduino devices to reset...

Searching for an Arduino configured with an arduino_instance = 1
Arduino compatible device found and connected to /dev/cu.usbmodem14301

Retrieving Arduino Firmware ID...
Arduino Firmware ID: 2.5 StandardFirmata.ino

Retrieving analog map...
Auto-discovery complete. Found 30 Digital Pins and 12 Analog Pins


22.6 22.1
23.1 22.6
23.1 22.1
23.1 22.1
23.1 22.6
23.1 22.6
22.6 22.6
23.1 22.1
23.1 22.6
23.1 22.1
22.6 22.6
23.1 22.6
23.1 22.6
22.6 22.1
23.1 22.6
23.1 22.6
22.6 22.6
23.1 22.1
22.6 22.6
22.6 22.1
23.1 22.6
23.1 22.6
22.6 22.1
23.1 22.1
23.1 22.6
22.6 22.6
23.1 22.1
23.1 22.6
23.1 22.1
23.1 22.6