© Charles Bell 2017

Charles Bell, MicroPython for the Internet of Things, https://doi.org/10.1007/978-1-4842-3123-4_4

4. How to Program in MicroPython

Charles Bell

(1)Warsaw, Virginia, USA

Now that we have a basic understanding of the various MicroPython boards, we can learn more about programming in MicroPython – a very robust and powerful language that you can use to write very powerful applications. Mastering MicroPython is very easy and some may suggest it doesn’t require any formal training to use. This is largely true and thus you should be able to write MicroPython scripts with only a little bit of knowledge about the language.

Given that MicroPython is Python, we can learn the basics of the Python language first through examples on our PC. Thus, this chapter presents a crash course on the basics of Python programming including an explanation about some of the most commonly used language features. As such, this chapter will provide you with the skills you need to understand the Python IOT project examples available on the Internet. The chapter also demonstrates how to program in Python through examples that you can run on your PC or your MicroPython board. So, let’s get started!

Note

I use the term Python to describe programming concepts in this chapter that apply to both MicroPython and Python. Concepts unique to MicroPython use the term MicroPython.

Now let’s learn some of the basic concepts of Python programming. We will begin with the building blocks of the language such as variables, modules, and basic statements, then move into the more complex concepts of flow control and data structures. While the material may seem to come at you in a rush, this tutorial on Python covers only the most fundamental knowledge of the language and how to use it on your PC and MicroPython board. It is intended to get you started writing Python IOT applications.

If you know the basics of Python programming, feel free to skim through this chapter. However, I recommend working through the example projects at the end of the chapter, especially if you’ve not written many Python applications.

The following sections present many of the basic features of Python programming that you will need to know to understand the example projects in this book.

Basic Concepts

Python is a high-level, interpreted, object-oriented scripting language. One of the biggest goals of Python is to have a clear, easy-to-understand syntax that reads as close to English as possible. That is, you should be able to read a Python script and understand it even if you haven’t learned the language. Python also has less punctuation (special symbols) and fewer syntactical machinations than other languages. The following lists a few of the key features of Python.

  • An interpreter processes Python at runtime. No external (separate) compiler is used.

  • Python supports object-oriented programming constructs by way of a class.

  • Python is a great language for the beginner-level programmers and supports the development of a wide range of applications.

  • Python is a scripting language but can be used for a wide range of applications.

  • Python is very popular and used throughout the world, giving it a huge support base.

  • Python has few keywords, simple structure, and a clearly defined syntax. This allows the student to pick up the language quickly.

  • Python code is more clearly defined and visible to the eyes.

Python is available for download (python.org/downloads) for just about every platform that you may encounter or use – even Windows! Python is a very easy language to learn with very few constructs that are even mildly difficult to learn. Rather than toss out a sample application, let’s approach learning the basics of Python in a Python-like way: one step at a time.

Note

If you do not have a MicroPython board and have not installed Python on your PC, you should do so now so that you can run the examples in this chapter.

Code Blocks

The first thing you should learn is that Python does not use a code block demarcated with symbols like other languages. More specifically, code that is local to a construct such as a function or conditional or loop is designated using indentation. Thus, the lines below are indented (by spaces or, gag, tabs) so that the starting characters align for the code body of the construct.

Tip

The following shows this concept in action. Python interpreters will complain and could produce strange results if the indentation is not uniform.

if (expr1):
    print("inside expr1")
    print("still inside expr1")
else:
    print("inside else")
    print("still inside else")
print("in outer level")

Here we see a conditional or if statement. Notice the function call print() is indented. This signals the interpreter that the lines belong to the construct above it. For example, the two print statements that mention expr1 form the code block for the if condition (and executes when the expression evaluates to true). Similarly, the next two print statements form the code block for the else condition. Finally, the non-indented lines are not part of the conditional and thus are executed after either the if or else depending on the expression evaluation.

As you can see, indentation is a key concept to learn when writing Python. Even though it is very simple, making mistakes in indentation can result in code executing that you did not expect or worse errors from the interpreter.

Note

I use ‘program’ and ‘application’ interchangeably with ‘script’ when discussing Python. While technically, Python code saved in a file is a script, we often use it in contexts where ‘program’ or ‘application’ are more appropriate.

There is one special symbol that you will encounter frequently. Notice the use of the colon (:) in the code above. This symbol is used to terminate a construct and signals the interpreter that the declaration is complete and the body of the code block follows. We use this for conditionals, loops, classes, and functions.

Comments

One of the most fundamental concepts in any programming language is the ability to annotate your source code with non-executable text that not only allows you to make notes among the lines of code but also forms a way to document your source code.

To add comments to your source code, use the pound sign (#). Place at least one at the start of the line to create a comment for that line, repeating the # symbols for each subsequent line. This creates what is known as a block comment as shown. Notice I used a comment without any text to create whitespace. This helps with readability and is a common practice for block comments.

#
# MicroPython for the IOT
#
# Example Python application.
#
# Created by Dr. Charles Bell
#

You can also place comments on the same line as the source code. The compiler will ignore anything from the pound sign to the end of the line. For example, the following shows a common style of documenting variables.

zip = 35012                # Zip or postal code
address1= "123 Main St."  # Store the street address

Arithmetic

You can perform many mathematical operations in Python including the usual primitives but also logical operations and operations used to compare values. Rather than discuss these in detail, I provide a quick reference in Table 4-1 that shows the operation and example of how to use the operation.

Table 4-1. Arithmetic, Logical, and Comparison Operators in Python

Type

Operator

Description

Example

Arithmetic

+

Addition

int_var + 1

 

-

Subtraction

int_var - 1

 

*

Multiplication

int_var * 2

 

/

Division

int_var / 3

 

%

Modulus

int_var % 4

 

-

Unary subtraction

-int_var

 

+

Unary addition

+int_var

Logical

&

Bitwise and

var1&var2

 

|

Bitwise or

var1|var2

 

^

Bitwise exclusive or

var1^var2

 

Bitwise complement

∼var1

 

and

Logical and

var1and var2

 

or

Logical or

var1or var2

Comparison

==

Equal

expr1==expr2

 

!=

Not equal

expr1!=expr2

 

<

Less than

expr1<expr2

 

>

Greater than

expr1>expr2

 

<=

Less than or equal

expr1<=expr2

 

>=

Greater than or equal

expr1>=expr2

Bitwise operations produce a result on the values performed on each bit. Logical operators (and, or) produce a value that is either true or false and are often used with expressions or conditions.

Output to Screen

We’ve already seen a few examples of how to print messages to the screen but without any explanation about the statements shown. While it is unlikely that you would print output from your MicroPython board for projects that you deploy, learning Python is much easier when you can display messages to the screen.

Some of the things you may want to print – as we have seen in previous examples – is to communicate what is going on inside your program. This can include simple messages (strings), but can also include the values of variables, expressions, and more.

As we have seen, the built-in print() function is the most common way to display output text contained within single or double quotes. We have also seen some interesting examples using another function named format(). The format() function generates a string for each argument passed. These arguments can be other strings, expressions, variables, etc. The function is used with a special string that contains replacement keys delimited by curly braces { } (called string interpolation 1 ). Each replacement key contains either an index (starting at 0) or a named keyword. The special string is called a format string. Let’s see a few examples to illustrate the concept. You can run these yourself either on your PC or your MicroPython board. I include the output so you can see what each statement does.

>>> a = 42
>>> b = 1.5
>>> c = "seventy"
>>> print("{0} {1} {2} {3}".format(a,b,c,(2+3)))
42 1.5 seventy 5
>>> print("{a_var} {b_var} {c_var} {0}".format((3*3),c_var=c,b_var=b,a_var=a))
42 1.5 seventy 9

Notice I created three variables (we will talk about variables in the next section) assigning them different values with the equal symbol (=). I then printed a message using a format string with four replacement keys labeled using an index. Notice the output of that print statement. Notice I included an expression at the end to show how the format() function evaluates expressions.

The last line is more interesting. Here, I use three named parameters (a_var, b_var, c_var) and used a special argument option in the format() function where I assign the parameter a value. Notice I listed them in a different order. This is the greatest advantage of using named parameters; they can appear in any order but are placed in the format string in the position indicated.

As you can see, it’s just a case of replacing the { } keys with those from the format() function, which converts the arguments to strings. We use this technique anywhere we need a string that contains data gathered from more than one area or type. We can see this in the examples above.

Tip

See https://docs.python.org/3/library/string.html#formatstrings for more information about format strings.

Now let’s look at how we can use variables in our programs (scripts).

Tip

For those who have learned to program in another language like C or C++, Python allows you to terminate a statement with the semicolon (;); however, it is not needed and considered bad form to include it.

Variables

Python is a dynamically typed language, which means the type of the variable (the type of data it can store) is determined by context as it is encountered or used. This contrasts with other language such as C and C++ where you must declare the type before you use the variable.

Variables in Python are simply named memory locations that you can use to store values during execution. We store values by using the equal sign to assign the value. Python variable names can be anything you want, but there are rules and conventions most Python most developers follow. The rules are listed in the Python coding standard.2

However, the general, overriding rule requires variable names that are descriptive, have meaning in context, and can be easily read. That is, you should avoid names with random characters, forced abbreviations, acronyms, and similar obscure names. By convention, your variable names should be longer than a single character (with some acceptable exceptions for loop counting variables) and short enough to avoid overly long code lines.

Thus, there is a lot of flexibility in what you can name your variables. There are additional rules and guidelines in the PEP8 standard and should you wish to bring your project source code up to date with the standards, you should review the PEP8 naming standards for functions, classes, and more. See the PEP8 coding guidelines for Python coding at https://www.python.org/dev/peps/pep-0008 for a complete list of the rules and standards.

The following shows some examples of simple variables and their dynamically determined types.

# floating point number
length = 10.0
# integer
width = 4
# string
box_label = "Tools"
# list
car_makers = ['Ford', 'Chevrolet', 'Dodge']
# tuple
porsche_cars = ('911', 'Cayman', 'Boxster')
# dictionary
address = {"name": "Joe Smith", "Street": "123 Main", "City": "Anytown", "State": "New Happyville"}

So, how did we know the variable width is an integer? Simply because the number 4 is an integer. Likewise, Python will interpret “Tools” as a string . We’ll see more about the last three types and other types supported by Python in the next section.

Tip

For more information about naming conventions governed by the Python coding standard (PEP8), see https://www.python.org/dev/peps/pep-0008/#naming-conventions .

Types

As mentioned, Python does not have a formal type specification mechanism like other languages. However, you can still define variables to store anything you want. In fact, Python permits you to create and use variables based on context and you can use initialization to “set” the data type for the variable. The following shows several examples.

# Numbers
float_value = 9.75
integer_value = 5


# Strings
my_string = "He says, he's already got one."


print("Floating number: {0}".format(float_value))
print("Integer number: {0}".format(integer_value))
print(my_string)

For situations where you need to convert types or want to be sure values are typed a certain way, there are many functions for converting data. Table 4-2 shows a few of the more commonly used type conversion functions. I discuss some of the data structures in a later section.

Table 4-2. Type Conversion in Python

Function

Description

int(x [,base])

Converts x to an integer. Base is optional (e.g., 16 for hex).

long(x [,base])

Converts x to a long integer.

float(x)

Converts x to a floating point.

str(x)

Converts object x to a string.

tuple(t)

Converts t to a tuple.

list(l)

Converts l to a list.

set(s)

Converts s to a set.

dict(d)

Creates a dictionary.

chr(x)

Converts an integer to a character.

hex(x)

Converts an integer to a hexadecimal string.

oct(x)

Converts an integer to an octal string.

However, you should use these conversion functions with care to avoid data loss or rounding. For example, converting a float to an integer can result in truncation. Likewise, printing floating-point numbers can result in rounding.

Now let’s look at some commonly used data structures including this strange thing called a dictionary.

Basic Data Structures

What you have learned so far about Python is enough to write the most basic programs and indeed more than enough to tackle the example projects later in this chapter. However, when you start needing to operate on data – either from the user or from sensors and similar sources – you will need a way to organize and store data as well as perform operations on the data in memory. The following introduces three data structures in order of complexity: lists, tuples, and dictionary.

Lists

Lists are a way to organize data in Python. It is a free-form way to build a collection. That is, the items (or elements) need not be the same data type. Lists also allow you to do some interesting operations such as adding things at the end, beginning, or at a special index. The following demonstrates how to create a list.

# List
my_list = ["abacab", 575, "rex, the wonder dog", 24, 5, 6]
my_list.append("end")
my_list.insert(0,"begin")
for item in my_list:
  print("{0}".format(item))

Here we see I created the list using square brackets ([]). The items in the list definition are separated by commas. Note that you can create an empty list simply by setting a variable equal to []. Since lists, like other data structures, are objects, there are several operations available for lists such as the following.

  • append(x): add x to the end of the list

  • extend(l): add all items to the end of the list

  • insert(pos,item): insert item at a position pos

  • remove(value): remove the first item that matches (==) the value

  • pop([i]): remove and return the item at position i or end of list

  • index(value): return index of first item that matches

  • count(value): count occurrences of value

  • sort(): sort the list (ascending)

  • reverse(): reverse sort the list

Lists are like arrays in other languages and very useful for building dynamic collections of data.

Tuples

Tuples , on the other hand, are a more restrictive type of collection. That is, they are built from a specific set of data and do not allow manipulation like a list. In fact, you cannot change the elements in the tuple. Thus, we can use tuples for data that should not change. The following shows an example of a tuple and how to use it.

# Tuple
my_tuple = (0,1,2,3,4,5,6,7,8,"nine")
for item in my_tuple:
  print("{0}".format(item))
if 7 in my_tuple:
  print("7 is in the list")

Here we see I created the tuple using parenthesis (). The items in the tuple definition are separated by commas. Note that you can create an empty tuple simply by setting a variable equal to (). Since tuples, like other data structures, are objects, there are several operations available such as the following, including operations for sequences such as inclusion, location, etc.

  • x in t: determine if t contains x

  • x not in t: determine if t does not contain x

  • s + t: concatenate tuples

  • s[i]: get element i

  • len(t): length of t (number of elements)

  • min(t): minimal (smallest value)

  • max(t): maximal (largest value)

If you want even more structure with storing data in memory, you can use a special construct (object) called a dictionary.

Dictionaries

A dictionary is a data structure that allows you to store key, value pairs where the data is assessed via the keys. Dictionaries are a very structured way of working with data and the most logical form we will want to use when collecting complex data. The following shows an example of a dictionary.

# Dictionary
my_dictionary = {
  'first_name': "Chuck",
  'last_name': "Bell",
  'age': 36,
  'my_ip': (192,168,1,225),
  42: “What is the meaning of life?”,
}
# Access the keys:
print(my_dictionary.keys())
# Access the items (key, value) pairs
print(my_dictionary.items())
# Access the values
print(my_dictionary.values())
# Create a list of dictionaries
my_addresses = [my_dictionary]

There is a lot going on here! We see a basic dictionary declaration that uses curly braces to create a dictionary . Inside that, we can create as many key, value pairs we want separated by commas. Keys are defined using strings (I use single quotes by convention but double quotes will work) or integers, and values can be any data type we want. For the my_ip attribute, we are also storing a tuple.

Following the dictionary, we see several operations performed on the dictionary from printing the keys, printing all the values, and printing only the values. The following shows the output of executing this code snippet from the Python interpreter.

[42, 'first_name', 'last_name', 'age', 'my_ip']
[(42, 'what is the meaning of life?'), ('first_name', 'Chuck'), ('last_name', 'Bell'), ('age', 36), ('my_ip', (192, 168, 1, 225))]
['what is the meaning of life?', 'Chuck', 'Bell', 36, (192, 168, 1, 225)]
'42': what is the meaning of life?
'first_name': Chuck
'last_name': Bell
'age': 36
'my_ip': (192, 168, 1, 225)

As we have seen in this example, there are several operations (functions or methods) available for dictionaries including the following. Together this list of operations makes dictionaries a very powerful programming tool.

  • len(d): number of items in d

  • d[k]: item of d with key k

  • d[k] = x: assign key k with value x

  • del d[k]: delete item with key k

  • k in d: determine if d has an item with key k

  • d.items(): return a list (view) of the (key, value) pairs in d

  • d.keys(): return a list (view) of the keys in d

  • d.values(): return a list (view) of the values in d

Best of all, objects can be placed inside other objects. For example, you can create a list of dictionaries like I did above, a dictionary that contains lists and tuples, and any combination you need. Thus, lists, tuples, and dictionaries are a powerful way to manage data for your program.

In the next section, we learn how we can control the flow of our programs.

Statements

Now that we know more about the basics of Python, we can discover some of the more complex code concepts you will need to complete your project such as conditional statements and loops.

Conditional Statements

We have also seen some simple conditional statements: statements designed to alter the flow of execution depending on the evaluation of one or more expressions. Conditional statements allow us to direct execution of our programs to sections (blocks) of code based on the evaluation of one or more expressions. The conditional statement in Python is the if statement.

We have seen the if statement in action in our example code. Notice in the example, we can have one or more (optional) else phrases that we execute once the expression for the if conditions evaluate to false. We can chain if / else statements to encompass multiple conditions where the code executed depends on the evaluation of several conditions. The following shows the general structure of the if statement. Notice in the comments how I explain how execution reaches the body of each condition.

if (expr1):
    # execute only if expr1 is true
elif ((expr2) or (expr3)):
    # execute only if expr1 is false *and* either expr2 or expr3 is true
else:
    # execute if both sets of if conditions evaluate to false

While you can chain the statement as much as you want, use some care here because the more elif sections you have, the harder it will become to understand, maintain, and avoid logic errors in your expressions.

There is another form of conditional statement called a ternary operator. Ternary operators are more commonly known as conditional expressions in Python. These operators evaluate something based on a condition being true or not. They became a part of Python in version 2.4. Conditional expressions are a shorthand notation for an if-then-else construct used (typically) in an assignment statement as shown below.

variable = value_if_true if condition else value_if_false

Here we see if the condition is evaluated to true, the value preceding the if is used but if the condition evaluates to false, the value following the else is used. The following shows a short example.

>>> numbers = [1,2,3,4]
>>> for n in numbers:
...   x = 'odd' if n % 2 else 'even'
...   print("{0} is {1}.".format(n, x))
...
1 is odd.
2 is even.
3 is odd.
4 is even.
>>>

Conditional expressions allow you to quickly test a condition instead of a using a multiline conditional statement, which can help make your code a bit easier to read (and shorter).

Loops

Loops are used to control the repetitive execution of a block of code. There are three forms of loops that have slightly different behaviors. All loops use conditional statements to determine whether to repeat execution or not. That is, they repeat as long as the condition is true. The two types of loops are while and for. I explain each with an example.

The while loop has its condition at the “top” or start of the block of code. Thus, while loops only execute the body if and only if the condition evaluates to true on the first pass. The following illustrates the syntax for a while loop. This form of loop is best used when you need to execute code only if some expression(s) evaluate to true. For example, iterating through a collection of things whose number of elements is unknown (loop until we run out of things in the collection).

while (expression):
   # do something here

For loops are sometimes called counting loops because of their unique form. For loops allow you to define a counting variable and a range or list to iterate over. The following illustrates the structure of the for loop. This form of loop is best used for performing an operation in a collection. In this case, Python will automatically place each item in the collection in the variable for each pass of the loop until no more items are available.

for variable_name in list:
  # do something here

You can also do range loops or counting loops. This uses a special function called range() that takes up to three parameters, range([start], stop[, step]), where start is the starting number (an integer), stop is the last number in the series, and step is the increment. So, you can count by 1, 2, 3, etc., through a range of numbers. The following shows a simple example.

for i in range(2,9):
   # do something here

There are other uses for range() that you may encounter. See the documentation on this function and other built-in functions at https://docs.python.org/3/library/functions.html for more information.

Python also provides a mechanism for controlling the flow of the loop (e.g., duration or termination) using a few special keywords as follows.

  • break: exit the loop body immediately

  • continue: skip to next iteration of the loop

  • else: execute code when loop ends (not executed if the loop was stopped with a break statement)

There are some uses for these keywords, particularly break, but it is not the preferred method of terminating and controlling loops. That is, professionals believe the conditional expression or error handling code should behave well enough to not need these options.

Modularization; Modules, Functions, and Classes

The last groups of topics are the most advanced and include modularization (code organization). As we will see, we can use functions to group code, eliminate duplication, and to encapsulate functionality into objects.

Including Modules

Python applications can be built from reusable libraries that are provided by the Python environment. They can also be built from custom modules or libraries that you create yourself or download from a third party. These are often distributed as a set of Python code files (for example, files that have a file extension of .py). When we want to use a library (function, class, etc.) that is included in a module, we use the import keyword and list the name of the module. The following shows some examples.

import os
import sys

The first two lines demonstrate how to import a base or common module provided by Python. In this case, we are using or importing modules for the os and sys modules (operating system and Python system functions).

Tip

It is customary (but not required) to list your imports in alphabetical order with built-in modules first then third-party modules listed next, and finally your own modules.

Functions

Python allows you to use modularization in your code. While it supports object-oriented programming by way of classes (a more advanced feature that you are unlikely to encounter for most Python GPIO examples), on a more fundamental level you can break your code into smaller chunks using functions.

Functions use a special keyword construct (rare in Python) to define a function. We simply use def followed by a name for the function and a comma-separated list of parameters in parentheses. The colon is used to terminate the declaration. The following shows an example.

def print_dictionary(the_dictionary):
    for key, value in the_dictionary.items():
      print("'{0}': {1}".format(key, value))


# define some data
my_dictionary = {
  'name': "Chuck",
  ‘age’: 37,
}

You may be wondering what this strange code does. Notice the loop is assigning two values from the result of the items() function. This is a special function available from the dictionary object.3 The items() function returns the key, value pairs: hence the names of the variables.

The next line prints out the values. The use of formatting strings where the curly braces define the parameter number starting at 0 is common for Python 3 applications. See the Python documentation for more information about formatting strings ( https://docs.python.org/3/library/string.html#format-string-syntax ).

The body of the function is indented. All statements indented under this function declaration belong to the function and are executed when the function is called. We can call functions by name providing any parameters as follows. Notice how I referenced the values in the dictionary by using the key names.

print_dictionary(my_dictionary)
print(my_dictionary['age'])
print(my_dictionary['name'])

This example together with the code above, when executed, generates the following.

$ python3
Python 3.6.0 (v3.6.0:41df79263a11, Dec 22 2016, 17:23:13)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> def print_dictionary(the_dictionary):
...     for key, value in the_dictionary.items():
...       print("'{0}': {1}".format(key, value))
...
>>> # define some data
... my_dictionary = {
...     'name': "Chuck",
...     'age': 41,
... }
>>> print_dictionary(my_dictionary)
'name': Chuck
'age': 41
>>> print(my_dictionary['age'])
41
>>> print(my_dictionary['name'])
Chuck

Now let’s look at the most complex concept in Python – object-oriented programming.

Classes and Objects

You may have heard that Python is an object-oriented programming language . But what does that mean? Simply, Python is a programming language that provides facilities for describing objects (things) and what you can do with the object (operations). Objects are an advanced form of data abstraction where the data is hidden from the caller and only manipulated by the operations (methods) the object provides.

The syntax we use in Python is the class statement, which you can use to help make your projects modular. By modular, we mean the source code is arranged to make it easier to develop and maintain. Typically, we place classes in separate modules (code files), which helps organize the code better. While it is not required, I recommend using this technique of placing a class in its own source file. This makes modifying the class or fixing problems (bugs) easier.

So, what are Python classes? Let’s begin by considering the construct as an organization technique. We can use the class to group data and methods together. The name of the class immediately follows the keyword class followed by a colon. You declare other class methods like any other method except the first argument must be self, which ties the method to the class instance when executed.

Note

I prefer to use terms that have been adopted by the language designers or community of developers. For example, some use ‘function’ but others may use ‘method’. Still others may use subroutine, routine, procedure, etc. It doesn’t matter which term you use, but you should strive to use terms consistently. One example, which can be confusing to some, is I use the term method when discussing object-oriented examples. That is, a class has methods not functions. However, you can use function in place of method and you’d still be correct (mostly).

Accessing the data is done using one or more methods by using the class (creating an instance) and using dot notation to reference the data member or function. Let’s look at an example. Listing 4-1 shows a complete class that describes (models) the most basic characteristics of a vehicle used for transportation. I created a file named vehicle.py to contain this code.

Listing 4-1. Vehicle class
#
# MicroPython for the IOT
#
# Class Example: A generic vehicle
#
# Dr. Charles Bell
#
class Vehicle:
    """Base class for defining vehicles"""
    axles = 0
    doors = 0
    occupants = 0


    def __init__(self, num_axles, num_doors):
        self.axles = num_axles
        self.doors = num_doors


    def get_axles(self):
        return self.axles


    def get_doors(self):
        return self.doors


    def add_occupant(self):
        self.occupants += 1


    def num_occupants(self):
        return self.occupants

Notice a couple of things here. First, there is a method with the name __init__() . This is the constructor and is called when the class instance is created. You place all your initialization code like setting variables in this method. We also have methods for returning the number of axles, doors, and occupants. We have one method in this class: to add occupants.

Also notice we address each of the class attributes (data) using self.<name>. This is how we can ensure we always access the data that is associated with the instance created and not a global variable or other local variable.

Let’s see how this class can be used to define a family sedan. Listing 4-2 shows code that uses this class. We can place this code in a file named sedan.py.

Listing 4-2. Using the Vehicle class
#
# MicroPython for the IOT
#
# Class Example: Using the generic Vehicle class
#
# Dr. Charles Bell
#
from vehicle import Vehicle


sedan = Vehicle(2, 4)
sedan.add_occupant()
sedan.add_occupant()
sedan.add_occupant()
print("The car has {0} occupants.".format(sedan.num_occupants()))

Notice the first line imports the Vehicle class from the vehicle module. Notice I capitalized the class name but not the file name. This is a very common naming scheme. Next in the code, we create an instance of the class. Notice I passed in 2, 4 to the class name. This will cause the __init__() method to be called when the class is instantiated. The variable, sedan, becomes the class instance variable (object) that we can manipulate, and I do so by adding three occupants then printing out the number of occupants using the method in the Vehicle class.

We can run the code on our PC using the following command. As we can see, it tells us there are three occupants in the vehicle when the code is run. Nice.

$ python ./sedan.py
The car has 3 occupants.

Now, let’s see how we can use the vehicle class to demonstrate inheritance. In this case, we will create a new class named PickupTruck that uses the vehicle class but adds specialization to the resulting class . Listing 4-3 shows the new class. I placed this code in a file named pickup_truck.py. As you will see, a pickup truck is a type of vehicle.

Listing 4-3. Pickup Truck class
#
# MicroPython for the IOT
#
# Class Example: Inheriting the Vehicle class to form a
# model of a pickup truck with maximum occupants and maximum
# payload.
#
# Dr. Charles Bell
#
from vehicle import Vehicle


class PickupTruck(Vehicle):
    """This is a pickup truck that has:
    axles = 2,
    doors = 2,
    __max occupants = 3
    The maximum payload is set on instantiation.
    """
    occupants = 0
    payload = 0
    max_payload = 0


    def __init__(self, max_weight):
        super().__init__(2,2)
        self.max_payload = max_weight
        self.__max_occupants = 3


    def add_occupant(self):
        if (self.occupants < self.__max_occupants):
            super().add_occupant()
        else:
            print("Sorry, only 3 occupants are permitted in the truck.")


    def add_payload(self, num_pounds):
        if ((self.payload + num_pounds) < self.max_payload):
            self.payload += num_pounds
        else:
            print("Overloaded!")


    def remove_payload(self, num_pounds):
        if ((self.payload - num_pounds) >= 0):
            self.payload -= num_pounds
        else:
            print("Nothing in the truck.")


    def get_payload(self):
        return self.payload

Notice a few things here. First, notice the class statement: class PickupTruck(Vehicle) :. When we want to inherit from another class, we add the parentheses with the name of the base class. This ensures Python will use the base class allowing the derived class to use all its accessible data and memory. If you want to inherit from more than one class, you can (called multiple inheritance), just list the base (parent) classes with a comma-separated list.

Next, notice the __max_occupants variable. Using two underscores in a class for an attribute or a method makes that, through convention, the item private to the class.4 That is, it should only be accessed from within the class. No caller of the class (via a class variable/instance) can access the private items nor can any class derived from the class. It is always a good practice to hide the attributes (data).

You may be wondering what happened to the occupant methods. Why aren’t they in the new class? They aren’t there because our new class inherited all that behavior from the base class . Not only that, but the code has been modified to limit occupants to exactly three occupants.

I also want to point out the documentation I added to the class. We use documentation strings (strings that use a set of three double quotes before and after) to document the class. You can put documentation here to explain the class and its methods. We’ll see a good use of this a bit later.

Finally, notice the code in the constructor. This demonstrates how to call the base class method, which I do to set the number of axles and doors. We can do the same in other methods if we wanted to call the base class method’s version.

Now, let’s write some code to use this class. Listing 4-4 shows the code we used to test this class. Here, we create a file named pickup.py that creates an instance of the pickup truck, adds occupants, and payload, then prints out the contents of the truck.

Listing 4-4. Using the PickupTruck class
#
# MicroPython for the IOT
#
# Class Example: Exercising the PickupTruck class.
#
# Dr. Charles Bell
#
from pickup_truck import PickupTruck


pickup = PickupTruck(500)
pickup.add_occupant()
pickup.add_occupant()
pickup.add_occupant()
pickup.add_occupant()
pickup.add_payload(100)
pickup.add_payload(300)
print("Number of occupants in truck = {0}.".format(pickup.num_occupants()))
print("Weight in truck = {0}.".format(pickup.get_payload()))
pickup.add_payload(200)
pickup.remove_payload(400)
pickup.remove_payload(10)

Notice I add a couple of calls to the add_occupant() method, which the new class inherits and overrides. I also add calls so that we can test the code in the methods that check for excessive occupants and maximum payload capacity. When we run this code, we will see the results as shown below.

$ python ./pickup.py
Sorry, only 3 occupants are permitted in the truck.
Number of occupants in truck = 3.
Weight in truck = 400.
Overloaded!
Nothing in the truck.

Once again, I ran this code on my PC, but I can run all this code on the MicroPython board and will see the same results.

There is one more thing we should learn about classes: built-in attributes. Recall the __init__() method . Python automatically provides several built-in attributes each starting with __ that you can use to learn more about objects. The following lists a few of the operators available for classes.

  • __dict__: Dictionary containing the class namespace

  • __doc__: Class documentation string

  • __name__: Class name

  • __module__: Module name where the class is defined

  • __bases__: The base class(es) in order of inheritance

The following shows what each of these attributes returns for the PickupTruck class above. I added this code to the pickup.py file.

print("PickupTruck.__doc__:", PickupTruck.__doc__)
print("PickupTruck.__name__:", PickupTruck.__name__)
print("PickupTruck.__module__:", PickupTruck.__module__)
print("PickupTruck.__bases__:", PickupTruck.__bases__)
print("PickupTruck.__dict__:", PickupTruck.__dict__)

When this code is run, we see the following output.

PickupTruck.__doc__: This is a pickup truck that has:
    axles = 2,
    doors = 2,
    max occupants = 3
    The maximum payload is set on instantiation.


PickupTruck.__name__: PickupTruck
PickupTruck.__module__: pickup_truck
PickupTruck.__bases__: (<class 'vehicle.Vehicle'>,)
PickupTruck.__dict__: {'__module__': 'pickup_truck', '__doc__': 'This is a pickup truck that has:     axles = 2,     doors = 2,     max occupants = 3     The maximum payload is set on instantiation.     ', 'occupants': 0, 'payload': 0, 'max_payload': 0, ' _PickupTruck__max_occupants': 3, '__init__': <function PickupTruck.__init__ at 0x1018a1488>, 'add_occupant': <function PickupTruck.add_occupant at 0x1018a17b8>, 'add_payload': <function PickupTruck.add_payload at 0x1018a1840>, 'remove_payload': <function PickupTruck.remove_payload at 0x1018a18c8>, 'get_payload': <function PickupTruck.get_payload at 0x1018a1950>}

You can use the built-in attributes whenever you need more information about a class . Notice the _PickupTruck__max_occupants entry in the dictionary. Recall that we made a pseudo-private variable, __max_occupants. Here, we see how Python refers to the variable by prepending the class name to the variable. Remember, variables that start with two underscores (not one) indicates it should be considered private to the class and only usable from within the class.

Tip

For more information about classes in Python, see https://docs.python.org/3/tutorial/classes.html .

Now, let’s see a few examples of Python programs that we can use to practice. Like the previous examples, you can write and execute these either on your PC or on your MicroPython board.

Learning Python by Example

The best way to learn how to program in any language is practicing with examples. In this section, I present several examples that you can use to practice coding in Python. You can use either your MicroPython board or your PC to run these examples. I present the first two examples using my PC via the Python console and the second two using the MicroPython board via the REPL console.

I explain the code in detail for each example and show example output when you execute the code as well as a challenge for you to try a modification or two of each example on your own. I encourage you to implement these examples and figure out the challenge yourself as practice for the projects later in this book.

Example 1: Using Loops

This example demonstrates how to write loops in Python using the for loop. The problem we are trying to solve is converting integers from decimal to binary, hexadecimal, and octal. Often with IOT projects, we need to see values in one or more of these formats and in some cases the sensors we use (and the associated documentation) uses hexadecimal rather than decimal. Thus, this example can be helpful in the future not only for how to use the for loop but also how to convert integers into different formats.

Write the Code

The example begins with a tuple of integers to convert. Tuples and lists can be iterated through (values read in order) using a for loop. Recall a tuple is read only so in this case since it is input, it is fine but in other cases where you may need to change values, you will want to use a list. Recall, the syntactical difference between a tuple and a list is the tuple uses parentheses and a list uses square brackets.

The for loop demonstrated here is called a “for each” loop. Notice I used the syntax “for value in values,” which tells Python to iterate over the tuple named values fetching (storing) each item into the value variable each iteration through the tuple.

Finally, I use the print() and format() functions to replace two placeholders {0} and {1}, to print out a different format of the integer using the methods bin() for binary, oct() for octal, and hex() for hexadecimal that do the conversion for us. Listing 4-5 shows examples of converting integers to different forms.

Listing 4-5. Converting Integers
#
# MicroPython for the IOT
#
# Example: Convert integer to binary, hex, and octal
#
# Dr. Charles Bell
#


# Create a tuple of integer values
values = (12, 450, 1, 89, 2017, 90125)


# Loop through the values and convert each to binary, hex, and octal
for value in values:
    print("{0} in binary is {1}".format(value, bin(value)))
    print("{0} in octal is {1}".format(value, oct(value)))
    print("{0} in hexadecimal is {1}".format(value, hex(value)))

Execute the Code

You can save this code in a file named conversions.py on your PC and then open a terminal (console window) and run the code with the command, python ./conversions.py (or python3 if you have multiple versions of Python installed). Listing 4-6 shows the output.

Listing 4-6. Conversions Example Output
$ python3 ./conversions.py
12 in binary is 0b1100
12 in octal is 0o14
12 in hexadecimal is 0xc
450 in binary is 0b111000010
450 in octal is 0o702
450 in hexadecimal is 0x1c2
1 in binary is 0b1
1 in octal is 0o1
1 in hexadecimal is 0x1
89 in binary is 0b1011001
89 in octal is 0o131
89 in hexadecimal is 0x59
2017 in binary is 0b11111100001
2017 in octal is 0o3741
2017 in hexadecimal is 0x7e1
90125 in binary is 0b10110000000001101
90125 in octal is 0o260015
90125 in hexadecimal is 0x1600d

Notice all the values in the tuple where converted.

Your Challenge

To make this example better, instead of using a static tuple to contain hard-coded integers, rewrite the example to read the integer from arguments on the command line along with the format. For example, the code would be executed like the following.

$ python3 ./conversions.py 123 hex
123 in hexadecimal is 0x7b

To read arguments from the command line, use the argument parser, argparse ( https://docs.python.org/3/howto/argparse.html ). If you want to read the integer from the command line, you can use the argparse module to add an argument by name as follows.

import argparse

# Setup the argument parser
parser = argparse.ArgumentParser()


# We need two arguments: integer, and conversion
parser.add_argument("original_val")
parser.add_argument("conversion")


# Get the arguments
args = parser.parse_args()

When you use the argument parser (argparse) module, the values of the arguments are all strings, so you will need to convert the value to an integer before you use the bin(), hex(), or oct() method.

You will also need to determine which conversion is requested. I suggest use only hex, bin, and oct for the conversion and use a set of conditions to check the conversion requested. Something like the following would work.

if args.conversion == 'bin':
    # do conversion to binary
elif args.conversion == 'oct':
    # do conversion to octal
elif args.conversion == 'hex':
    # do conversion to hexadecimal
else:
    print("Sorry, I don't understand, {0}.".format(args.conversion))

Notice the last else communicates that the argument was not recognized. This helps to manage user error.

There is one more thing about the argument parser you should know. You can pass in a help string when adding arguments. The argument parser also gets you the help argument (-h) for free. Observe the following. Notice I added a couple of strings using the help= parameter.

# We need two arguments: integer, and conversion
parser.add_argument("original_val", help="Value to convert.")
parser.add_argument("conversion", help="Conversion options: hex, bin, or oct.")

Now when we complete the code and run it with the -h option, we get the following output. Cool, eh?

$ python3 ./conversions.py -h
usage: conversions.py [-h] original_val conversion


positional arguments:
  original_val  Value to convert.
  conversion    Conversion options: hex, bin, or oct.


optional arguments:
  -h, --help    show this help message  and exit

Example 2: Using Complex Data and Files

This example demonstrates how to work with the JavaScript Object Notation5 (JSON) in Python. In short, JSON is a markup language used to exchange data. Not only is it human readable, it can be used directly in your applications to store and retrieve data to and from other applications, servers, and even MySQL. In fact, JSON looks familiar to programmers because it resembles other markup schemes. JSON is also very simple in that it supports only two types of structures: 1) a collection containing (name, value) pairs, and 2) an ordered list (or array). Of course, you can also mix and match the structures in an object. When we create a JSON object, we call it a JSON document.

The problem we are trying to solve is writing and reading data to/from files. In this case, we will use a special JSON encoder and decoder module named json that allows us to easily convert data in files (or other streams) to and from JSON. As you will see, accessing JSON data is easy by simply using the key (sometimes called fields) names to access the data. Thus, this example can be helpful in the future not only for how to use read and write files but also how to work with JSON documents.

Write the Code

This example stores and retrieves data in files. The data is basic information about pets including the name, age, breed, and type. The type is used to determine broad categories like fish, dog, or cat.

We begin by importing the JSON module (named json), which is built into the MicroPython platform. Next, we prepare some initial data by building JSON documents and storing them in a Python list. We use the json.loads() method to pass in a JSON formatted string. The result is a JSON document that we can add to our list. The examples use a very simple form of JSON documents – a collection of (name, value) pairs. The following shows an example of one of the JSON formatted strings used.

{"name":"Violet", "age": 6, "breed":"dachshund", "type":"dog"}

Notice we enclose the string inside curly braces and use a series of key names, a colon, and a value separated by commas. If this looks familiar, it’s because is the same format as a Python dictionary. This demonstrates my comment that JSON syntax looks familiar to programmers.

The JSON method, json.loads(), takes the JSON formatted string then parses the string checking for validity and returns a JSON document. We then store that document in a variable and add it to the list as shown below.

parsed_json = json.loads('{"name":"Violet", "age": 6, "breed":"dachshund", "type":"dog"}')
pets.append(parsed_json)

Once the data is added to the list, we then write the data to a file named my_data.json. To work with files, we first open the file with the open() function, which takes a file name (including a path if you want to put the file in a directory) and an access mode. We use “r” for read, and “w” for write. You can also use “a” for append if you want to open a file and add to the end. Note that the “w” access will overwrite the file when you write to it. If the open() function succeeds, you get a file object that permits you to call additional functions to read or write data. The open() will fail if the file is not present (and you have requested read access) or you do not have permissions to write to the file.

In case you’re curious what other access modes , Table 4-3 shows the list of modes available for the open() function.

Table 4-3. Python File Access Modes

Mode

Description

r

Opens a file for reading only. The file pointer is placed at the beginning of the file. This is the default mode.

rb

Opens a file for reading only in binary format. The file pointer is placed at the beginning of the file. This is the default mode.

r+

Opens a file for both reading and writing. The file pointer is placed at the beginning of the file.

rb+

Opens a file for both reading and writing in binary format. The file pointer is placed at the beginning of the file .

w

Opens a file for writing only. Overwrites the file if the file exists. If the file does not exist, it creates a new file for writing.

wb

Opens a file for writing only in binary format. Overwrites the file if the file exists. If the file does not exist, it creates a new file for writing .

w+

Opens a file for both writing and reading. Overwrites the existing file if the file exists. If the file does not exist, it creates a new file for reading and writing.

wb+

Opens a file for both writing and reading in binary format. Overwrites the existing file if the file exists. If the file does not exist, it creates a new file for reading and writing.

a

Opens a file for appending. The file pointer is at the end of the file if the file exists. That is, the file is in the append mode. If the file does not exist, it creates a new file for writing.

ab

Opens a file for appending in binary format. The file pointer is at the end of the file if the file exists. That is, the file is in the append mode. If the file does not exist, it creates a new file for writing.

a+

Opens a file for both appending and reading. The file pointer is at the end of the file if the file exists. The file opens in the append mode. If the file does not exist, it creates a new file for reading and writing.

ab+

Opens a file for both appending and reading in binary format. The file pointer is at the end of the file if the file exists. The file opens in the append mode. If the file does not exist, it creates a new file for reading and writing .

Once the file is open, we can write the JSON documents to the file by iterating over the list. Iteration means to start at the first element and access the elements in the list one at a time in order (the order they appear in the list). Recall, iteration in Python is very easy. We simply say, “for each item in the list” with the for loop as follows.

for pet in pets:
  // do something with the pet data

To write the JSON document to the file, we use the json.dumps() method , which will produce a JSON formatted string writing that to the file using the file variable and the write() method. Thus, we now see how to build JSON documents from strings and then decode (dump) them to a string.

Once we’ve written data to the file, we then close the file with the close() function then reopen it and read data from the file. In this case, we use another special implementation of the for loop. We use the file variable to read all of the lines in the file with the readlines() method then iterate over them with the following code.

json_file = open("my_data.json", "r")
for pet in json_file.readlines():
  // do something with the pet string

We use the json.loads() method again to read the JSON formatted string as read from the file to convert it to a JSON document, which we add to another list. We then close the file. Now the data has been read back into our program and we can use it. Finally, we iterate over the new list and print out data from the JSON documents using the key names to retrieve the data we want. Listing 4-7 shows the completed code for this example.

Listing 4-7. Writing and Reading JSON Objects to/from Files
#
# MicroPython for the IOT
#
# Example: Storing and retrieving JSON objects in files
#
# Dr. Charles Bell
#


import json

# Prepare a list of JSON documents for pets by converting JSON to a dictionary
pets = []
parsed_json = json.loads('{"name":"Violet", "age": 6, "breed":"dachshund", "type":"dog"}')
pets.append(parsed_json)
parsed_json = json.loads('{"name": "JonJon", "age": 15, "breed":"poodle", "type":"dog"}')
pets.append(parsed_json)
parsed_json = json.loads('{"name": "Mister", "age": 4, "breed":"siberian khatru", "type":"cat"}')
pets.append(parsed_json)
parsed_json = json.loads('{"name": "Spot", "age": 7, "breed":"koi", "type":"fish"}')
pets.append(parsed_json)
parsed_json = json.loads('{"name": "Charlie", "age": 6, "breed":"dachshund", "type":"dog"}')
pets.append(parsed_json)


# Now, write these entries to a file. Note: overwrites the file
json_file = open("my_data.json", "w")
for pet in pets:
    json_file.write(json.dumps(pet))
    json_file.write(" ")
json_file.close()


# Now, let's read the JSON documents then print the name and age for all of the dogs in the list
my_pets = []
json_file = open("my_data.json", "r")
for pet in json_file.readlines():
    parsed_json = json.loads(pet)
    my_pets.append(parsed_json)
json_file.close()


print("Name, Age")
for pet in my_pets:
    if pet['type'] == 'dog':
        print("{0}, {1}".format(pet['name'], pet['age']))

Notice the loop for writing data . We added a second write() method passing in a strange string (it is actually an escaped character). The is a special character called the newline character . This forces the JSON formatted strings to be on separate lines in the file and helps with readability.

Tip

For a more in-depth look at how to work with files in Python, see https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files .

So, what does the file look like? The following is a dump of the file using the more utility, which shows the contents of the file. Notice the file contains the JSON formatted strings just like we had in our code .

$ more my_data.json
{"age": 6, "breed": "dachshund", "type": "dog", "name": "Violet"}
{"age": 15, "breed": "poodle", "type": "dog", "name": "JonJon"}
{"age": 4, "breed": "siberian khatru", "type": "cat", "name": "Mister"}
{"age": 7, "breed": "koi", "type": "fish", "name": "Spot"}
{"age": 6, "breed": "dachshund", "type": "dog", "name": "Charlie"}

Now, let’s see what happens when we run this script.

Execute the Code

You can save this code in a file named rw_json.py on your PC then open a terminal (console window) and run the code with the command, python ./rw_json.py (or python3 if you have multiple versions of Python installed). The following shows the output.

$ python ./rw_json.py
Name, Age
Violet, 6
JonJon, 15
Charlie, 6

While the output may not be very impressive, by completing the example, you’ve learned a great deal about working with files and structured data using JSON documents.

Your Challenge

To make this example more of a challenge , you could modify it to include more information about your pets. I suggest you start with a simple text file and type in the JSON formatted strings for your pets. To increase the complexity, try adding information that is pertinent to the type of pet. For example, you could add some keys for one or more pets, other keys for other pets, and so on. Doing so will show one of the powers of JSON documents; collections of JSON documents do not have to have the same format.

Once you have this file, modify the code to read from the file and print out all the information for each pet by printing the key name and value. Hint: you will need to use special code to print out the key name and the value called “pretty printing.” For example, the following code will print out the JSON document in an easily readable format. Notice we use the sort_keys option to print the keys (fields) and we can control the number of spaces to indent .

for pet in my_pets:
    print(json.dumps(pet, sort_keys=True, indent=4))

When run, the output will look like the following.

{
    "age": 6,
    "breed": "dachshund",
    "name": "Violet",
    "type": "dog"
}
{
    "age": 15,
    "breed": "poodle",
    "name": "JonJon",
    "type": "dog"
}

Example 3: Using Functions

This example demonstrates how to create and use functions. Recall functions are used to help make our code more modular. Functions can also be a key tool in avoiding duplication of code. That is, we can reuse portions of code repeatedly by placing them in a function. Functions are also used to help isolate code for special operations such as mathematical formulae.

The problem we’re exploring in this example is how to create functions to perform calculations. We will also explore a common computer science technique called recursion6 where a function calls itself repeatedly. I will also show you the same function implemented in an iterative manner (typically using a loop). While some would advise avoiding recursion, recursive functions are a bit shorter to write but can be more difficult to debug if something goes wrong. The best advice I can offer is that almost every recursive functions can be written as iterative functions and novice programmers should stick to iterative solutions until they gain confidence using functions.

Write the Code

This example is designed to calculate a Fibonacci series.7 A Fibonacci series is calculated as the sum of the two preceding values in the series. The series begins with 1 followed by 1 (nothing plus 1), then 1 + 1 = 2, and so on. For this example, we will ask the user for an integer then calculate the number of values in the Fibonacci series. If the input is 5, the series is 1, 1, 2, 3, 5.

We will create two functions: one to calculate the Fibonacci series using code that iteratively calculates the series and one to calculate the nth Fibonacci number using a recursive function. Let’s look at the iterative function first.

To define a function, we use the syntax def func_name(<parameters>): where we supply a function name and a list of zero or more parameters followed by a colon. These parameters are then usable inside the function. We pass in data to the function using the parameters. The following shows the iterative version of the Fibonacci series code . We name this function, fibonacci_iterative.

def fibonacci_iterative(count):
    i = 1
    if count == 0:
        fib = []
    elif count == 1:
        fib = [1]
    elif count == 2:
        fib = [1,1]
    elif count > 2:
        fib = [1,1]
        while i < (count - 1):
            fib.append(fib[i] + fib[i-1])
            i += 1
    return fib

This code simply calculates the first N values in the series and returns them in a list. The parameter count is the number of values in the series. The function begins by checking to see if the trivial values are requested: 0, 1, or 2 whose values are known. If the count value is greater than 2, we begin with the know series [1, 1] then use a loop to calculate the next value by adding the two previous values together. Take a moment to notice how I use the list index to get the two previous values in the list (i and i-1). We will use this function and the list returned directly in our code to find a specific value in the series and print it.

Now let’s look at the recursive version of the function. The following shows the code. We name this function fibonacci_recursive.

def fibonacci_recursive(number):
    if number == 0:
        return 0
    elif number == 1:
        return 1
    else:
        # Call our self counting down.
        value = fibonacci_recursive(number-1) + fibonacci_recursive(number-2)
        return value

In this case, we don’t return the entire series; rather, we return the specific value in the series – the nth value. Like the iterative example, we do the same thing regarding the trivial values returning the number requested. Otherwise, we call the same function again for each number . It may take some time to get your mind around how this works, but it does calculate the nth value.

Now, you may be wondering where you place functions in the code. We need to place them at the top of the code. Python will parse the functions and continue to execute statements following the definitions. Thus, we place our “main” code after our functions.

The main code for this example begins with requesting the nth value for the Fibonacci series then uses the recursive function first to calculate the value. We then ask the user if they want to see the entire series and if so, we use the iterative version of the function to get the list and print it out. We print out the nth value and give the option again to see the entire series to show the result is the same using both functions. Listing 4-8 shows the completed code for the example. We will name this code fibonacci.py.

Listing 4-8. Calculating Fibonacci Series
#
# MicroPython for the IOT
#
# Example: Fibonacci series using recursion
#
# Calculate the Fibonacci series based on user input
#
# Dr. Charles Bell
#


# Create a function to calculate Fibonacci series (iterative)
# Returns a list.
def fibonacci_iterative(count):
    i = 1
    if count == 0:
        fib = []
    elif count == 1:
        fib = [1]
    elif count == 2:
        fib = [1,1]
    elif count > 2:
        fib = [1,1]
        while i < (count - 1):
            fib.append(fib[i] + fib[i-1])
            i += 1
    return fib


# Create a function to calculate the nth Fibonacci number (recursive)
# Returns an integer.
def fibonacci_recursive(number):
    if number == 0:
        return 0
    elif number == 1:
        return 1
    else:
        # Call our self counting down.
        value = fibonacci_recursive(number-1) + fibonacci_recursive(number-2)
        return value


# Main code
print("Welcome to my Fibonacci calculator!")
index = int(input("Please enter the number of integers in the series: "))


# Recursive example
print("We calculate the value using a recursive algoritm.")
nth_fibonacci = fibonacci_recursive(index)
print("The {0}{1} fibonacci number is {2}."
      "".format(index, "th" if index > 1 else "st", nth_fibonacci))
see_series = str(input("Do you want to see all of the values in the series? "))
if see_series in ["Y","y"]:
    series = []
    for i in range(1,index+1):
        series.append(fibonacci_recursive(i))
    print("Series: {0}: ".format(series))


# Iterative example
print("We calculate the value using an iterative algoritm.")
series = fibonacci_iterative(index)
print("The {0}{1} fibonacci number is {2}."
      "".format(index, "th" if index > 1 else "st", series[index-1]))
see_series = str(input("Do you want to see all of the values in the series? "))
if see_series in ["Y","y"]:
    print("Series: {0}: ".format(series))


print("bye!")

Take a few moments to read through the code. While the problem being solved is a bit simpler than the previous example, there is a lot more code to read through. When you’re ready, connect your MicroPython board and create the file. You create the file on your PC for this example and name it fibonacci.py. We’ll copy it to our MicroPython board in the next section.

Tip

For a more in-depth look at how to create and use your own functions (methods) in Python, see https://docs.python.org/3/tutorial/controlflow.html#defining-functions .

Now, let’s see what happens when we run this script. Recall, we will be running this code on our MicroPython board so if you’re following along, be sure to set up your board and connect it to your PC.

Execute the Code

Recall from Chapter 3, when we want to move code to our MicroPython board, we need to create the file and then copy it to the MicroPython board and then execute it. I will show you how to do this on macOS in this section. An example of how to do it on Windows 10 is shown in example 4.

When you plug your MicroPython board into a USB port on your PC, the flash drive will appear in your Finder and on your desktop. Figure 4-1 shows an example on macOS.

A447395_1_En_4_Fig1_HTML.jpg
Figure 4-1. MicroPython board Flash Drive Mounted

We now need to copy the fibonacci.py file to the flash drive. On macOS, open the flash drive and then drag the files onto the drive. You should see something like Figure 4-2 when you’ve copied the file.

A447395_1_En_4_Fig2_HTML.jpg
Figure 4-2. Files Copied to the MicroPython board Flash Drive

Notice I annotated the image to show the file . Notice also we see the MicroPython files boot.py and main.py. We will not modify the main.py file to run our example because we don’t want the program to run every time we boot the device. Instead, we will run the code from the REPL console.

Open a REPL console using a terminal window. You should see the welcome message from MicroPython followed by the RELP prompt (>>>). From the REPL prompt, issue the following code statement.

import fibonnaci

This code imports the fibonnaci.py file we just copied and when it does, it executes the code. So, it’s like we ran it on our PC from the Python console. Go ahead and test the program by requesting the twelfth value in the Fibonacci series. You should see output shown in Figure 4-3.

A447395_1_En_4_Fig3_HTML.jpg
Figure 4-3. Output of Fibonacci Example (MicroPython board)

Notice I responded to the query to show the entire series. Notice also that we see the code has exercised both versions of the function we wrote: iterative and recursive. Finally, we see that the values or output of both functions is the same data.

Note

If you do not yet have a MicroPython board, you can run the code from your PC with the command, python ./fibonacci.py. You will see similar output as shown in the figure.

If you run the code again, just press CTRL-D to do a soft reset then issue the import statement again. This will rerun the entire code. Or, if you want to run one of the functions, you can call it again by importing it using the code below. Figure 4-4 shows how this would look when executed on the MicroPython board.

from fibonacci import fibonacci_iterative
fibonacci_iterative(6)
from fibonacci import fibonacci_recursive
fibonacci_recursive(6)
A447395_1_En_4_Fig4_HTML.jpg
Figure 4-4. Experimenting with the Fibonacci Functions

Once imported this way, you can run the functions again. Go ahead and try them with different values to show how they behave. Recall, the functions were implemented differently – the iterative version returns the list and the recursive version returns only the last or nth value.

When you’re done experimenting with the example, remember to close the terminal and eject the drive for the flash before you unplug it.

Your Challenge

To make this example a bit more useful, modify the code to search a Fibonacci series for a specific integer. Ask the user to provide and integer then determine if the value is a valid Fibonacci value. For example, if the user enters 144, the code should tell the user that value is valid and is the twelfth value in the series. While this challenge will require you to rewrite most of the code for the “main” functionality, you must figure out how to use the functions in a new way.

Example 4: Using Classes

This example ramps up the complexity considerably by introducing an object-oriented programming concept: classes. Recall from earlier that classes are another way to modularize our code. Classes are used to model data and behavior on that data. Further, classes are typically placed in their own code module (file) that further modularizes the code. If you need to modify a class, you may need only change the code in the class module.

The problem we’re exploring in this example is how to develop solutions using classes and code modules. We will be creating two files: one for the class and another for the main code.

Write the Code

This example is designed to convert Roman numerals8 to integers . That is, we will enter a value like VIII, which is eight, and expect to see the integer 8. To make things more interesting, we will also take the integer we derive and convert it back to Roman numerals. Roman numerals are formed as a string using the characters I for 1, V for 5, X for 10, L for 50, C for 100, D for 500, and M for 1000. Combinations of other numbers are done by adding the character numerical value together (e.g., 3 = III), or a single, lower character before another character to indicate the representative minus that character (e.g., 4 = IV). The following show some examples of how this works.

3 = III
15 = XV
12 = XII
24 = XXIV
96 = LXLVI
107 = CVII

This may sound like a lot of extra work, but consider this: if we can convert from one format to another, we should be able to convert back without errors. More specifically, we can use the code for one conversion to validate the other. If we get a different value when converting it back, we know we have a problem that needs to be fixed.

To solve the problem, we will place the code for converting Roman numerals into a separate file (code module) and build a class called Roman_Numerals to contain the methods. In this case, the data is a mapping of integers to Roman numerals.

# Private dictionary of roman numerals
__roman_dict = {
    'I': 1,
    'IV': 4,
    'V': 5,
    'IX': 9,
    'X': 10,
    'XL': 40,
    'L': 50,
    'XC': 90,
    'C': 100,
    'CD': 400,
    'D': 500,
    'CM': 900,
    'M': 1000,
}

Notice the two underscores before the name of the dictionary. This is a special notation that marks the dictionary as a private variable in the class. This is a Python aspect for information hiding, which is a recommended technique to use when designing objects; always strive to hide data that is used inside the class.

Notice also that instead of using the basic characters and their values, I used several other values too. I did this to help make the conversion easier (and cheat a bit). In this case, I added the entries that represent the one value previous conversions such as 4 (IV), 9 (IX), etc. This makes the conversion a bit easier (and more accurate).

We will also add two methods; convert_to_int(), which takes a Roman numeral string and converts it to an integer ; and convert_to_roman(), which takes an integer and converts it to a Roman numeral. Rather than explain every line of code in the methods, I leave it to you to read the code to see how it works.

Simply, the convert to integer method takes each character and gets its value from the dictionary summing the values. There is a trick there that requires special handling for the lower value characters appearing before higher values (e.g., IX). The convert to Roman method is a bit easier since we simply divide the value by the highest value in the dictionary until we reach zero. Listing 4-9 shows the code for the class module, which is saved in a file named roman_ numerals .py.

Listing 4-9. Roman Numeral Class
#
# MicroPython for the IOT
#
# Example: Roman numerals class
#
# Convert integers to roman numerals
# Convert roman numerals to integers
#
# Dr. Charles Bell
#


class Roman_Numerals:

    # Private dictionary of roman numerals
    __roman_dict = {
        'I': 1,
        'IV': 4,
        'V': 5,
        'IX': 9,
        'X': 10,
        'XL': 40,
        'L': 50,
        'XC': 90,
        'C': 100,
        'CD': 400,
        'D': 500,
        'CM': 900,
        'M': 1000,
    }


    def convert_to_int(self, roman_num):
        value = 0
        for i in range(len(roman_num)):
            if i > 0 and self.__roman_dict[roman_num[i]] > self.__roman_dict[roman_num[i - 1]]:
                value += self.__roman_dict[roman_num[i]] - 2 * self.__roman_dict[roman_num[i - 1]]
            else:
                value += self.__roman_dict[roman_num[i]]
        return value


    def convert_to_roman(self, int_value):
        # First, get the values of all of entries in the dictionary
        roman_values = list(self.__roman_dict.values())
        roman_keys = list(self.__roman_dict.keys())
        # Prepare the string
        roman_str = ""
        remainder = int_value
        # Loop through the values in reverse
        for i in range(len(roman_values)-1, -1, -1):
            count = int(remainder / roman_values[i])
            if count > 0:
                for j in range(0,count):
                    roman_str += roman_keys[i]
                remainder -= count * roman_values[i]
        return roman_str

If you’re following along with the chapter, go ahead and create a file on your PC for this code and name it roman_numerals.py. We’ll copy it to our MicroPython board in the next section.

Now let’s look at the main code . For this, we simply need to import the new class from the code module as follows. This is a slightly different form of the import directive. In this case, we’re telling Python to include the roman_ numerals class from the file named Roman_Numerals.

from roman_numerals import Roman_Numerals
Note

If the code module were in a subfolder, say roman, we would have written the import statement as from roman import Roman_Numerals where we list the folders using dot notation instead of slashes.

The rest of the code is straightforward. We first ask the user for a valid Roman numeral string then convert it to an integer and use that value to convert back to Roman numerals, printing the result. So, you see having the class in a separate module has simplified our code making it shorter and easier to maintain. Listing 4-10 shows the complete main code saved in a file named simply roman.py.

Listing 4-10. Converting Roman Numerals
#
# MicroPython for the IOT
#
# Example: Convert roman numerals using a class
#
# Convert integers to roman numerals
# Convert roman numerals to integers
#
# Dr. Charles Bell
#


from roman_numerals import Roman_Numerals

roman_str = input("Enter a valid roman numeral: ")
roman_num = Roman_Numerals()


# Convert to roman numberals
value = roman_num.convert_to_int(roman_str)
print("Convert to integer:        {0} = {1}".format(roman_str, value))


# Convert to integer
new_str = roman_num.convert_to_roman(value)
print("Convert to Roman Numerals: {0} = {1}".format(value, new_str))


print("bye!")

If you’re following along with the chapter, go ahead and create a file on your PC for this code and name it roman.py. We’ll copy it to our MicroPython board in the next section.

Tip

For a more in-depth look at how to work with classes in Python, see https://docs.python.org/3/tutorial/classes.html .

Now, let’s see what happens when we run this script. Recall, we will be running this code on our MicroPython board so if you’re following along, be sure to set up your board and connect it to your PC.

Execute the Code

In this example, we will see how to copy the code to our MicroPython board on Windows 10. Like the last example, we will first create the files on our PC, connect the MicroPython board, copy the files, and then execute them.

When you connect your MicroPython board on Windows 10, the drive will show in the file explorer as shown in Figure 4-5.

A447395_1_En_4_Fig5_HTML.jpg
Figure 4-5. MicroPython board Flash Drive Mounted (Windows 10)

If you haven’t created the files, do that now. When ready, we’ll copy them to the MicroPython board. Rather than use the file explorer, I chose to demonstrate how to copy the files from a console (command prompt). Open a console and then change to the drive letter shows in your file explorer . Next, copy the files roman_numerals.py and roman.py to the drive. Figure 4-6 shows an example of copying the files.

A447395_1_En_4_Fig6_HTML.jpg
Figure 4-6. Copying the File to the MicroPython board Flash Drive (Console)

Now, we’re ready to connect to the MicroPython board and run the code. Recall one of the ways to connect to the board is to use PuTTY on Windows. Figure 4-7 shows the PuTTY console. Notice I chose COM3 and serial connection . Recall, you can find the COM port from the Device Manager. When ready, click Open.

PuTTY will open the REPL console when the connection is made. You should see the welcome message from MicroPython followed by the RELP prompt (>>>). From the REPL prompt, issue the following code statement.

import roman

This code imports the roman.py file we just copied and when it does, it executes the code, which if you recall will call the code in the roman_numerals.py code module. Once again, issuing the import is like how we ran it on our PC from the Python console.

A447395_1_En_4_Fig7_HTML.jpg
Figure 4-7. Connecting with PuTTy (Windows 10)

Once you issue the import statement, the code will execute. Go ahead and try it out. Start with the value LXLIV, which is SSS in Roman numerals . Figure 4-8 shows what the output will look like.

A447395_1_En_4_Fig8_HTML.jpg
Figure 4-8. Executing the Roman Numerals Example

You can also experiment with importing the code and executing it again from the terminal or just eject the drive, reset the board, and reconnect to run it again with other values.

When you’re done executing the example, remember to close the terminal and eject the drive for the flash before you unplug it.

Your Challenge

There isn’t much to add for this example to improve it other than perhaps some user friendliness (nicer to use). If you want to improve the code or the class itself, I suggest adding a new method named validate() used to validate a Roman numeral string. This method can take a string and determine if it contains a valid series of characters. Hint: to start, check the string has only the characters in the dictionary.

However, you can use this template to build other classes for converting formats. For example, as an exercise, you could create a new class to convert integers to hexadecimal or even octal. Yes, there are functions that will do this for us, but it can be enlightening and satisfying to build it yourself. Go ahead, give it a go – create a new class to convert integers to other formats. I would suggest doing a hexadecimal to integer function first and when that is working correctly, create the reciprocal to convert integers to hexadecimal.

A more advanced challenge would be to rewrite the class to accept a string in the constructor (when the class variable is created) and use that string to do the conversions instead of passing the string or integer using the convert_to* methods. For example, the class could have a constructor and private member as follows.

__roman_str = ""
...
def __init__(self, name):
        self.name = name

When you create the instance, you will need to pass the string or else you will get an error that a required parameter is missing.

roman_str = input("Enter a valid roman numeral: ")
roman_num = Roman_Numerals(roman_str)

For More Information

Should you require more in-depth knowledge of Python, there are several excellent books on the topic. I list a few of my favorites below. A great resource is the documentation on the Python site: python.org/doc/.

  • Pro Python, 2nd Edition (Apress, 2014) J. Burton Browning, Marty Alchin

  • Learning Python, 5th Edition (O’Reilly Media, 2013) Mark Lutz

  • Automate the Boring Stuff with Python: Practical Programming for Total Beginners (No Starch Press, 2015), Al Sweigart

Summary

Wow! That was a wild ride, wasn’t it? I hope that this short crash course in Python has explained enough about the sample programs shown so far that you now know how they work. This crash course also forms the basis for understanding the other Python examples in this book.

If you are learning how to work with IOT projects and don’t know how to program with Python, learning Python can be fun given its easy-to-understand syntax. While there are many examples on the Internet you can use, very few are documented in such a way as to provide enough information for someone new to Python to understand or much less get started and deploy the sample! But at least the code is easy to read.

This chapter has provided a crash course in Python that covers the basics of the things you will encounter when examining most of the smaller example projects. We discovered the basic syntax and constructs of a Python application including a walkthrough of building a real Python application that blinks an LED. Through that example, we learned how to work with headless applications including how to manage a startup background application.

In the next chapter, we’ll dive deeper into MicroPython programming. We will see more about the special libraries available for use in your IOT projects written for running on MicroPython boards.

Footnotes

3 Yes, dictionaries are objects! So are tuples and lists and many other data structures.

4 Technically, it is called name mangling, which simulates making something private, but can still be accessed if you provide the correct number of underscores. For more information, see https://en.wikipedia.org/wiki/Name_mangling .

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.103.183