Thanks to libraries, you can utilise code without fully understanding how it works, with the bonus of keeping your own code clean. That's a big win for complex projects!
It does not take much for a program to become large and unwieldy. So how do we control and maintain large portions of code? Or what if we have a large project and need to break the work up among team members? How can we all work on the code at the same time?
As the complexity of a program increases so does the number of lines of code required. As much as we may try to create an efficient process through loops and functions there comes the point at which the simple navigation of the file becomes a cumbersome and complex task. Often this complexity will require you to work with or even recycle large chunks of code.
THE BROAD OVERVIEW
As you will see in many of the articles we publish here, we use functions in our projects. We aim to keep them as straightforward as possible, but often end up putting all the code into one file. You would have noticed however, that we are regularly using libraries (Arduino/C) and modules (Python3) to enhance functionality, usually around things such as LCDs or keypads.
The simple overview is that it is possible to package up the code and place it outside the main file, and then bring it in only when required.
Setting Up
Libraries or modules can be placed in the directory of the file you are programming or the library’s directory of your chosen programming language. You can refer to www.arduino.cc/en/Guide/Libraries for detailed instructions regarding their installation.
Basic Use
After having placed it in the relevant directory, to use a library you must first import or reference it in your file. For this code, we have a working library; one that prints “hello world” for us already. This library/module is called “hello”.
All languages will start with reference to what it is we are importing, but they do have their differences in the implementation. For instance in Arduino/C, you need to instantiate the library as an object; whereas in Python you can directly address the function you wish to use. We need to call the function we want from the library or module. Both snippets of code below will output “hello world”, provided we have the library or module written (which we have not yet done).
ARDUINO/C:
#include <HELLO.h>
Hello h;
h.world();
PYTHON3:
import hello
hello.world()
Next we’ll split the code examples, and then discuss the principles and how they’re relevant to each other.
Python3
At the simplest level, you may want to include all your functions into one file. This may simply be that there are certain sections of code that are repeatedly reused, or you may be operating in a team environment and therefore need to work on the project concurrently. Using the “hello world” example above, we need to create two files. The first is the module file. There is no special suffix required for these in Python, simply use the “.py” extension. Following is the module “hello.py” and the program file we are working on, which is called “main.py”.
hello.py
def world():
print("Hello, World!")
main.py
import hello
hello.world()
Saving and running the main.py file will result in:
Hello, World!
Because we are importing a function, we need to reference the function that is located in that module. Hence, hello.world(). Where “hello” is the name of the module file and “world” is the name of the function inside it, that we want to run.
Using Variables
In a simple module like this, Python allows us to access and modify variables and functions as if they were located in our current application, provided that we prefix them with the module name. For example, we can declare a variable in our hello.py like so:
def world():
print("Hello, World!")
myVar="Hello again!"
main.py:
import hello
hello.world()
print(hello.myVar)
Hello, World!
Hello again!
Now this may seem very trivial on the surface, but in the context of a maker, you could easily put all your RPi GPIO calls in one module that only deals with that particular subset of the program. This would make chasing down bugs easier, and it would extend the reusability of the code.
Python Classes
We can define and use classes within Python and our modules:
hello.py
def world():
print("Hello, World!")
myVar="Hello again!"
#Dog class
class Dog:
def __init__(self, name, color):
self.color = color
self.name = name
def tell_me_about_this_dog(self):
print("This dog is " + self.color + ".")
print(self.name + " is the dog’s name.")
Using all of the above we can import and run:
main.py
import hello
hello.world()
print(hello.myVar)
#Create our class
rover = hello.Dog("rover", "orange")
rover.tell_me_about_this_dog()
This will result in:
Hello World!
Hello again!
This dog is orange
rover is this dog's name
Arduino
The Arduino implementations are not quite as straightforward as Python’s, because it is not as simple as including the file; but the functions are there. Being underwritten by C++, you must adhere to the standard library pattern (i.e., header file and code file), which is why we use the “.h” extension on the “include” statement. This points towards the header. The library is divided into the header file and the body/cpp file.
ANATOMY OF A LIBRARY (ARDUINO)
At a minimum, the library needs the header and the main function file. However, the standard is to include a keyword, readme and example file.
WHAT'S A HEADER?
Think of the header as a summary of what is in the body/cpp file. Every time we want to use our library, we need to import its header; by doing so, the compiler knows which functions are available.
We are going to veer away slightly from the Python example now, and use the LED that is on pin 13 to give a real world example. We will stick with creating the dog library, but this time we’ll provide the function of wagging its tail, symbolised with an LED turning on and off.
HEADER :
DOG.h
#ifndef DOG_H
#define DOG_H
#include <Arduino.h>
class DOG {
public:
DOG();
~DOG();
void woof();
void quiet();
void wag(int time);
};
#endif
Note: Remember #include
The header follows a standard pattern. The first two and last lines in the header are used to prevent the header from being included twice, and they are quite common in C++. Lines 4 to 10 are necessary if you want to use the standard Arduino functions or constants from within your code. In the header is the class that will represent the function in the code itself. Note that line 5 uses the “public” modifier. This will allow the functions to be visible when the user implements your library.
Why Do They Do It Like This?
The short answer is that when we include our library, the code is compiled into our runtime. But it is not until we instantiate the library as an object, that we can work with it. To create an object in C or C++. we need to have a “class”. The header is the class or blueprint for the object, and it then calls the functions located within the other file.
Next comes the main body/cpp.
#include "DOG.h"
const byte LED_PIN = 13;
DOG::DOG(){
pinMode(LED_PIN, OUTPUT);
}
//<<destructor>>
DOG::~DOG(){/*nothing to destruct*/}
void DOG::woof(){
digitalWrite(LED_PIN,HIGH);
}
void DOG::quiet(){
digitalWrite(LED_PIN,LOW);
}
void DOG::wag(int time){
woof();
delay(time/2);
quiet();
delay(time/2);
}
Note: Notice the “ :: ”, which is called the ‘scope resolution operator.’
The main body of our code started by including the header file. Next, we have a single variable that needs to be set up; in this case, it’s the LED pin, and we will use 13 as it’s the built-in LED. It does not change, so we specify that it is a constant. Next is the constructor “DOG::DOG” and we set up the pin to be an output. We must include the destructor, but in this case, there is nothing to deconstruct.
Library Usage
Using the library is really easy. Place the “include
#include <DOG.h>
DOG dog;
void setup(){/*nothing to setup*/}
void loop(){
dog.wag(2000);
}
Keywords
The Arduino IDE is not one of the most intuitive environments, however it works well at its primary task of allowing us to write and upload to the Arduino. One thing we can do to make our life and those of others much easier, is to introduce a keywords file without a library. Place the following in the keyword.txt file in your library directory:
DOG KEYWORD1
woof KEYWORD2
quiet KEYWORD2
wag KEYWORD2
DIRECTORY STRUCTURE:
DOG
Examples
woof.ino (example of using the library)
DOG.h
DOG.cpp
keyword.txt (Optional)
readme.txt (Optional)
List Of Famous Robot Dogs:
- K9 from Doctor Who
Sorry that’s all I got!
Stay tuned, and next month we’ll explore multiple choice. For example, C, select case (Python is a nested “if” set), and operators (logical and conditional).
What’s OOP (Object Oriented Programming)?
The vast majority of coding we do in the maker world is procedurally based. That is, we define our variables, functions and methods before we set off on our tasks and loops and repeating the tasks in a set order. Now there is nothing wrong with this, as it’s a very traditional methodology of programming; however, when it comes to increasing complex system design, an alternative option is Object Orientated Programming (OOP).
OOP is built on classes. Each class is representative of an object in the world that you define for your program. In the maker-space, software does not often necessitate either complexity of a full OOP design implementation, but as it is supported by both C/C++ (which Arduino’s processing is based upon) and Python3, we need to understand what a class is, in order to fully utilise them in our library’s and modules.
A class can be considered the template for our objects. Let’s say we designed a program about dogs. To do so we would need to have lots of dog objects, as the program has lots of dogs. In order to do this we need to design the template; this template is the class. We identify the defining characteristics (variables) of our dog, and the things our dog can do (functions).
Our dog class contains name and colour. Whilst our behaviours or methods would include: eat(), sleep(), sniff(), bark() and getName().
class DOG {
private:
String color;
String myName;
public:
String getName();
void eat();
void sleep();
void sniff();
void bark();
};
DOG::getName()
{
return this->myName;
}
We could now create 100 dogs using this template or class, and all would inherit the same functions and variables as each other. We could also create an array of dogs and cycle through them as needed.