Pages

Wednesday, 5 April 2017

Program the Proto-Pic EDGE using O O MicroPython - part 1

Good programmers are Lazy.


According to Larry Wall (a very famous programmer) laziness is a virtue. Lazy programmers come up with labour-saving solutions.

If you’d like to save time for yourself and others by writing MicroPython code that’s easy to re-use, read on.

The article assumes that you are already familiar with the concepts of Object, Class and Composition, and that you know how to write a class definition in Python. If you’re not familiar with Object-Orientation (O O) in Python  you may find the code hard to follow. I’ve given a very brief introduction to O O later in this article.

There are several ways to learn about Python O O. There’s an ‘official’ tutorial here, but I’m not sure a beginner would find it easy to follow. There’s a whole book on the subject from Packt, and it looks good. It represents quite an investment in time and money, but I think it’s worthwhile if you are serious about improving your Python O O skills.

I will probably get round to writing my own introduction, but that’s a major project and will take a while. For now, even if you aren’t familiar with O O, you should be able to follow most of the article and run the example code
.
The O O application uses Proto-Pic’s new super micropixel EDGE add-on for the BBC micro:bit.

(Micro)Python is great for Physical Computing


Python is deservedly a very popular language for Physical Computing Applications.
Python is easy to learn, it’s expressive, and it’s freely available on Linux, Mac OS and Microsoft Windows.

Python gave the Raspberry Pi its name, and a lot of code published for the Pi is implemented in Python.

More recently, MicroPython has brought the language to several popular microcontroller platforms, including the BBC micro:bit.

But...


Most published Python/MicroPython scripts use a procedural style. That’s what most programmers start with, and it’s the style that’s most widely used in tutorials.

When programming in a procedural style you work out what you want the  computer to do, step by step, and your program is a procedure which specifies those steps.

It’s worth exploring other styles, and in this article you’ll see how to develop a simple Python application that makes some use of an O O (Object Oriented) style. In an Object Oriented program you program by creating objects - little packages combining data and code which can carry out simple tasks. The objects collaborate (interact with each other) in order to carry out more complex tasks. You specify the sort of objects your program needs by writing the definitions of classes which specify what a particular type of object knows (data) and what it can do (methods).

You can see the application running here:




The idea is simple. The application displays the current value of a counter using the micro:bit’s build-in display, and you can change that value up and down by using the buttons. Later on you’ll see how this simple idea could be adapted for use in practical projects.

You could implement the demo with a short script, but the script’s constituent parts would not be easy to use in other applications.

Here are a few variations that you might want to implement without having to start from scratch:

  • A display that shows a value using a bar LED display.
  • A display that shows the current light level or temperature.
  • A clock-style display that goes to 0 when the value goes up from 9.
  • A similar display that uses a different type of LED.
  • A display that shows the value by turning on just one LED at a time.
  • A display that shows different values in different colours.

Of course you could do that by creating multiple variants of your original script, but think what would happen if you found a bug in the original script. You’d have to make the same change in every copy!

That’s one reason why experienced programmers try to avoid duplicate copies of code. There’s even an acronym to help you remember: DRY, which stands for “Don’t repeat yourself”.

There’s another good reason to write your code by composing from simple components. Each component will be easy to test on its own. That can help you make to avoid bugs, and to track down and remove any that do creep into your code.

Let’s start to code.


You’ll need code which watches the buttons and tells the counter in your application to count up or down whenever one of the buttons is pressed.

There’s no need to use a class for that.

The application code creates and ‘wires together’ Counter and SimpleDisplay instances so as to achieve the desired result.

max_count = 10
disp = SimpleDisplayer()
counter = Counter(disp, max_count)
while True:
   if button_a.is_pressed():
       counter.up()
   if button_b.is_pressed():
         counter.down()
   sleep(200)

The last part should be self-explanatory.

 The code before it creates a SimpleDisplayer and a Counter. Let's start with the Counter. 


The Counter class

In this application a counter is a simple object which knows its current count, and can count up and down between its initial value of zero and some maximum value. In our case that maximum is 10.

The counter knows about another object which is notified whenever the counter’s value changes. Initially that will be a SimpleDisplayer object which will display the new value on the micro:bit's LEDs.

Here’s the code for the Counter class:

class Counter():
  # count ranges from 0 to max.
  # when the count changes disp (the displayer) is notified
  def __init__(self, disp, max):
      self._disp = disp
      self._max = max
      self._count = 0

  def update(self):
      self._disp.value(self._count)

  def up(self):
      if self._count < self._max:
          self._count += 1
          self.update()

  def down(self):
      if self._count > 0:
          self._count -= 1
          self.update()

The counter sends messages to a displayer.
Here is the code for a SimpleDisplayer which shows a value using the built-in LEDs:

# SimpleDisplayer shows the latest value on the micro:bit's LEDs
class SimpleDisplayer():
      def value(self, num):
          display.show(str(num))  

OOP makes for easy changes

This approach makes it very easy to adapt our application with a minor code change. Suppose that you want to display the current state of the counter using an array of LEDs like those available on the Proto-pic EDGE.

Here's the new version running:


Here’s the code which will drive the revised application:

from microbit import *
import neopixel

...

max_count = 10
leds = neopixel.NeoPixel(pin0, max_count)
disp = BarGraph(leds, BarGraph.YELLOW)
counter = Counter(disp, max_count)
while True:
   if button_a.is_pressed():
       counter.up()
   if button_b.is_pressed():
       counter.down()
   sleep(200)


The last part is unchanged.

The code before it uses two new classes: NeoPixel and BarGraph.

The Neopixel class is part of the micro:bit MicroPython distribution, so we just need to import the neopixel module to use it.

The BarGraph class is the new part of our application; we’ll need to write its definition and insert the code in the listing above.

Here’s the code for the BarGraph class:

# BarGraph uses an array of Neopixels or other similar LEDs
# to show the latest value
class BarGraph():
    # colo(u)r constants.
    # we use low values so the we don't strain the micro:bit's power supply
    WHITE  = (50,50,50)
    RED    = (50,0,0)
    BLUE   = (0,0,50)
    GREEN  = (0,50,0)
    YELLOW = (100,50,0)
    CLEAR  = (0,0,0)
    def __init__(self, leds, on_colour=WHITE):
        self._leds = leds
        self._count = len(leds)
        self._on_colour = on_colour

    def value(self, num):
        for i in range(self._count):
           if i < num:
               self._leds[i] = self._on_colour
           else:
               self._leds[i] = self.CLEAR
           self._leds.show()

The application code (shown earlier) creates and ‘wires together’ the Counter and BarGraph instances so as to achieve the desired result.

Modular code is much easier to change. In the next part of this two-part article we’ll look at changing the input to a light-sensing LDR and adding more colour to the BarGraph display.

We’ll also look at another major benefit of this approach: ease of testing.