October 31, 2016 · Arduino Python

Tiny Dashboard (Mini Python + Arduino Project): Part 4 - The Python Client

This is Part 4 of my Arduino dashboard project where I try to display a PC's system utilization stats onto an LCD display via USB.

Here, we talk about the Python client we run on the PC. This component is responsible for reading our PC's system status every second and sending data to our Arduino via USB.

Requirements

Using the Installer

If you are feeling lazy and just want to get the thing to work, here's how you can install the client:

  1. Download the source code here.
  2. Extract the zip file.
  3. In your command line/terminal, go to the extracted directory.
  4. Run python setup.py install
  5. To run the client, type: pc-stats

Plug in your Arduino, the script should automatically attempt to connect as soon as it sees it!

Checking out the code

For those of you who are feeling adventurous and those who would like to get their hands dirty on the Python code, this section is for you. Much like our Arduino code, our Python client can also be found on Github.

Most of the important stuff can be found inside pc_stats/pc_stats.py -- we'll focus on this part.

import re
import time
import serial
from serial.tools import list_ports
import psutil
import click


START_MARKER = 'Clujlusjarr7'
RETRY_INTERVAL = 5


def displayToHardware(s, line1, line2=""):
    line1_str = "%s".ljust(16, ' ') % line1
    line2_str = "%s".ljust(16, ' ') % line2

    s.write(START_MARKER)
    s.write(line1_str + '\n')
    s.write(line2_str + '\n')


def attempt_connection():
    serial_port = None
    ports = list_ports.comports()
    r = re.compile(r'^Arduino*')

    # Guess serial port
    for port in ports:
        if r.match(port.description) is not None:
            print "\nDevice found (%s)! %s" % (port.description, port.device)
            serial_port = port.device

    if serial_port is None:
        raise "Cannot find a device"
    else:
        loop(serial_port)


def loop(serial_port):
    print "Connecting to %s..." % serial_port
    s = serial.Serial(serial_port)

    while(True):
        # CPU Percentage
        cpu_pct = psutil.cpu_percent(interval=1, percpu=False)

        # Memory Percentage
        mem = psutil.virtual_memory()
        mem_pct = mem.percent

        displayToHardware(s, "CPU: %s%%" % cpu_pct, "MEM: %s%%" % mem_pct)
        time.sleep(2)


@click.command()
def cli():
    while True:
        try:
            attempt_connection()
        except KeyboardInterrupt:
            print "\nBye!"
            break
        except:
            print "Connection failed! Retry in %s seconds..." % RETRY_INTERVAL
            time.sleep(RETRY_INTERVAL)


if __name__ == '__main__':
    cli()

Here's a short summary of what this script does:

  1. Automatically looks for and connects to an Arduino device.
  2. Checks and updates CPU and Memory usage statistics every 2 seconds.

Now, for the details.

First, we initialize all libraries we'll use within this script:

import re  
import time  
import serial  
from serial.tools import list_ports  
import psutil  
import click

Important things to note here:

  • serial is a library we use to communicate via USB
  • psutil is the library we use to fetch CPU and Memory usage details.
  • Click is a library I use for command line parameters. (This part is actually unnecessary at this point since we do not have parameters yet, but I set it up for future plans).

Okay, now we have some configuration variables:

START_MARKER = 'Clujlusjarr7'  
RETRY_INTERVAL = 5

Remember the token we specified as a start marker for our messages in the Arduino code? It should match the one we specify in START_MARKER.

RETRY_INTERVAL will control how often we try to look for and connect to an Arduino device.

This next chunk of code is our entrypoint:

@click.command()
def cli():
    while True:
        try:
            attempt_connection()
        except KeyboardInterrupt:
            print "\nBye!"
            break
        except:
            print "Connection failed! Retry in %s seconds..." % RETRY_INTERVAL
            time.sleep(RETRY_INTERVAL)


if __name__ == '__main__':
    cli()

It basically tries to connect to an Arduino, catches any error encountered, then tries to connect again after our RETRY_INTERVAL.

Next, lets take a look at the setup/attempt-connection chunk:

def attempt_connection():
    serial_port = None
    ports = list_ports.comports()
    r = re.compile(r'^Arduino*')

    # Guess serial port
    for port in ports:
        if r.match(port.description) is not None:
            print "\nDevice found (%s)! %s" % (port.description, port.device)
            serial_port = port.device

    if serial_port is None:
        raise "Cannot find a device"
    else:
        loop(serial_port)

It uses the list_ports function from the serial library to list out the names of connected devices. From those devices, try to get one with the name that starts with "Arduino" -- this part can be improved to look for a specific one.

If a valid port is found, we now trigger the main loop with loop(serial_port).

Let's check out loop():

def loop(serial_port):  
    print "Connecting to %s..." % serial_port
    s = serial.Serial(serial_port)

    while(True):
        # CPU Percentage
        cpu_pct = psutil.cpu_percent(interval=1, percpu=False)

        # Memory Percentage
        mem = psutil.virtual_memory()
        mem_pct = mem.percent

        displayToHardware(s, "CPU: %s%%" % cpu_pct, "MEM: %s%%" % mem_pct)
        time.sleep(2)

Here, we can see that we connect to the specified port using the Serial library

We enter the main loop, where we use the psutil library to fetch CPU and memory utilization percentages. We then build the lines we want to display and call our displayToHardware() function to send these lines to our Arduino.

Finally, let's look at how we send messages to the Arduino with the displayToHardware() function:

def displayToHardware(s, line1, line2=""):
    // Make sure the lines are 16 characters wide
    line1_str = "%s".ljust(16, ' ') % line1
    line2_str = "%s".ljust(16, ' ') % line2

    // Send messages via serial
    s.write(START_MARKER)
    s.write(line1_str + '\n')
    s.write(line2_str + '\n')

It is pretty straightforward. We make the two strings exactly 16 characters in length, which is the row length of the LCD display I am using. Then we send the start marker, to tell the Arduino to display the next two lines. Then we write the two lines.

We can run this code: python pc_stats.py. And watch our LED dashboard come to life!

Conclusion

That's it! We have successfully built a dashboard and client that would display CPU and Memory utilization on an LCD Display via a USB

We can stop here, although building a case for it would be nice. Maybe next time?