Mastering the Art of Coding - Programming with Python - BeagleBone For Dummies (2015)

BeagleBone For Dummies (2015)

Part IV

Programming with Python

Chapter 11

Mastering the Art of Coding

In This Chapter

Understanding the good practices of programming

Discovering ways to avoid bugs and techniques for debugging

Getting acquainted with binary and data storage

Becoming familiar with BoneScript-specific techniques

Exploring techniques for programming in Python

Just as Salvador Dali’s artistic style was significantly different from Pablo Picasso’s, every programmer has his or her own way of creating a program. Give two programmers the same task and their code will definitely look quite different. Although everyone has an individual style, some good practices are standard throughout the world of programming and can be quite useful.

Following standard programming guidelines isn’t just about organization; it’s also about making your code simple and straightforward so it’s easy for you and others to read it and alter it. It’s about making the program efficient. Most important, though, these practices greatly reduce your chance of getting bugs in your code and making debugging much easier when bugs do occur.

This chapter is about the art of good coding. Even though the information in this chapter may seem to consist of small tips and tricks, when your code starts to grow, these tidbits become extremely helpful. Trust us: Finding that one little bug in a huge script of code is one of the most frustrating endeavors of life. It’s better to avoid bugs from the get-go.

General Programming Tips

This section explains some tricks you can use while programming in any language. They help you keeping your code organized, readable, and less prone to bugs. These tips also make it easier for you to detect bugs in your code.

Following a convention when writing code has the same effect as color-coding your circuit. It helps with readability, debugging, and teamwork.

Variables and function names

Very few programs work without variables, and those that do result in huge messes. We present several variables in earlier chapters of this book. Even though variable names are arbitrary, it helps greatly to use self-explanatory names such as the following:

· led to hold the name of the pin you’re using to light an LED, such as "USR3" or "P9_14"

· state for a variable that holds HIGH or LOW

· b for a BoneScript module object

· button for an input pin to which a button is wired, such as "P8_12"

· dutyCycle for a variable that holds the duty-cycle value of a pulse-width modulation (PWM) output pin

Imagine opening your code two months after you wrote it or handing your code to someone else. Would you or the other person easily understand what each variable represents? Unless you have an exceptional memory, we greatly recommend that you employ this technique in your code.

Additionally, you can use several conventions for variable and function names. You should adopt one convention and use it in all your programs to avoid some pretty annoying bugs. It’s quite common to declare a variable such as dutycycle and then write duty_cycle or dutyCycle somewhere else in your code. JavaScript and Python are case-sensitive languages, so this entry would be an error. Although this type of bug is easy to detect, correcting it is an unnecessary waste of time. Following are the two most widely used conventions for naming variables:

· Camel case: This convention is commonly used with the prebuilt functions of JavaScript, and we also prefer to use it when programming in BoneScript. All words after the first should have uppercase first letters. Using this convention, you’d enter inputPin rather than inputpin.

· Underscores: This convention is used in the prebuilt functions of many programming languages, including Python. The words that compose the variable names are separated by underscores, like so: input_pin.

Some people prefer the underscores convention, the reason usually being that an underscore makes the most sense as a replacement for a space and makes the variable more readable. On the other hand, some people prefer the camel case convention because it’s faster to type (fewer keystrokes) and (in our opinion) looks more elegant. Follow the convention you prefer, or simply use the same one as the prebuilt functions of the language you’re using.

Following are some other conventions for naming variables:

· index for a variable that indicates the index of an array or a list.

· i for loops, j for a loop inside a loop, and k for a loop inside a loop inside a loop. Additionally, these variables are often used as indexes of arrays or lists when the instructions regarding the array or list are inside loops.

· aux, tmp, and temp for auxiliary or temporary variables used to hold a value that will be placed in another variable later — you can’t swap the value of two variables without using a third, for example.

· n and count for variables that count the number of times something happens.

We recommend that you keep variable names short, but don’t shorten them so much that they become unreadable. Using tmp or temp for temporary is justifiable; using iPin rather than inputPin might lead to confusion.

Using names that somewhat explain the variable’s or the function’s task, as well as following conventions, makes changing parts of your code a faster process. You don’t need to define a variable to deal with a pin’s state; you could use "P9_14" all the time instead of defining led = "P9_14". If you decide to change it to pin P8_12 for whatever reason — such as if you notice that P9_14 is already being used for another task — you have to change all the lines of your code instead of just one.

Constants

Constants are variables whose values never change throughout the program. They’re great ways to ensure that altering your script is fast and simple. An example in Python may help you get the idea. The following example illustrates a (incomplete) snippet of code where the speed of several DC (direct current) motors — for an RC (remote control) car, for example — would be proportional to a constant value and the voltage read from some sensor.

motor1_speed = 5*voltage1
motor2_speed = 5*voltage2
motor3_speed = 5*voltage3
motor4_speed = 5*voltage4

When you test your remote-control car, find that you’re not satisfied with the results, and want to change the constant 5, you have no choice but to change it everywhere. You could change it just once if you define a constant like this:

SPEED_CONSTANT = 5

motor1_speed = SPEED_CONSTANT*voltage1
motor2_speed = SPEED_CONSTANT*voltage2
motor3_speed = SPEED_CONSTANT*voltage3
motor4_speed = SPEED_CONSTANT*voltage4

When you define a constant, testing for different values becomes much less tedious.

Constants are regular variables like any others, but they’re defined at the start and never changed through the program. To differentiate constants from other variables, type them in all caps.

You can also define a constant for a message that you’ll be printing many times and don’t want to type repeatedly, as in the following JavaScript script:

var SENSOR_MESSAGE = "The reading from your sensor is: "

(...)

console_output(SENSOR_MESSAGE + temperature_sensor)
console_output(SENSOR_MESSAGE + light_sensor)
console_output(SENSOR_MESSAGE + distance_sensor)

If you’ve programmed in languages such as C and C++ for example, you’ve probably dealt with constants in a similar fashion. From a computational point of view, those constants are quite different from what we did in the previous examples. For those languages, constants are their own data type; in fact, they’re simply replaced with their values everywhere before the code actually runs. In Python and BoneScript, though, from a technical point of view they’re regular variables like any others.

Comments, white space, and indentation

There’s no such thing as a perfect method of programming. Advanced programmers usually develop their own style. There are definitely standards for writing better code, however. If you’re working with a team, contributing to an open-source project, developing a program that will take several days or weeks to complete, or writing something that you may look at again a couple of months later, you should always do the following:

· Comment your code. Typing descriptive comments requires just a slight writing effort that can make an enormous difference in the long run. With proper comments, you never need to figure out what a snippet of code does. The explanation is right there!

· Use white space. Adding extra spaces between your variable names and functions, as well as lines between instructions, makes your code prettier and more readable. Most programming languages ignore extra white space, so there’s absolutely no issue in using it to promote readability.

For example, you can use white space for organization in the form of extra lines between instructions to create blocks of similar code, as in this example:

import Adafruit_BBIO.PWM as PWM #import objects from libraries
import Adafruit_BBIO.ADC as ADC
import Adafruit_BBIO.UART as UART

import math #import libraries
import time
import serial

led = "P9_16" #define variables for pins
sensor = "P9_40"

PWM.start(led, 0) #initialize modules
ADC.setup()
UART.setup("UART1")

· Indent your code. Python forces you to use indentation, but many programming languages don’t. Believe us when we say that being lazy in your programming and not caring about indentation may lead to a lot of frustration if a bug occurs. The bug may simply be a missing or extra closing brace (}), which is easy to detect if your code is indented — and a pain to find if it isn’t.

The origin of the term bug comes from a literal bug in the system. The term was first used in computer science in 1946, when computer pioneer Grace Hooper revealed that the cause of a malfunction in an early electromechanical computer was a moth trapped in a relay.

Debugging

Quite often, your program might not work when you first run it. Sometimes, you come across runtime errors. These errors stop your program and print an error message on the terminal. In the following chunk of code, the closing parenthesis has been left out:

// Load BoneScript module
var b = require('bonescript; #bug is here
// Create a variable called led, which refers to the on-board USR3 LED
var led = "USR3";

That error results in this message:

/var/lib/cloud9/Projects/debug_example.js:9
var led = "USR3";
^^^
SyntaxError: Unexpected token var
at Module._compile (module.js:439:25)
at Object.Module._extensions..js (module.js:474:10)
at Module.load (module.js:356:32)
at Function.Module. load (module.js:312:12)
at Function.Module.runMain (module.js:497:10)
at startup (node.js:119:16)
at node.js:902:3

[Process stopped]

The first line of the error message prints exactly where the trouble first arises: the ninth line of the debug_example.js file. The referenced line is always the first instruction after the line that was badly written, excluding blank and comment lines (because they aren’t instructions). Thus, all you have to do is to check the instruction immediately before line 9.

These bugs are the best kinds of bug you can have. The interpreter notices that something is amiss and tries to help you figure out the issue.

Sometimes, though, you come across bugs that aren’t really errors. Your program runs perfectly but doesn’t do what it’s supposed to do. These bugs are the tricky ones.

A good analogy for this process is to think of the interpreter as a translator. You write the code in a language, and the interpreter translates it into something the computer can understand. Ultimately, the computer is a huge circuit that understands 1 and 0: HIGH and LOW. If the interpreter notices a grammatical error, it stops you and warns you about it. If you’re spouting nonsense that’s grammatically correct, however, the interpreter allows you to proceed. In the end, the interpreter warns that there’s a problem with I is human because it’s not grammatically correct, but it doesn’t warn you about The BeagleBone is a microbanana.

The following script is the one used in Chapter 7 to blink an LED, with a very slight change. The change is almost unnoticeable, really, but it’s enough to make the LED stay off rather than blinking. This is one of those bugs that the interpreter doesn’t warn you about.

If you want to get a glimpse of how frustrating debugging can be, feel free to try to find the bug. Spoilers are after the code snippet.

/*
Blink
Turns an onboard LED on and off continuously,
with intervals of 1 second.
*/
// Load BoneScript module
var b = require('bonescript');
// Create a variable called led, which refers to the onboard USR3 LED
var led = "USR3";
// Initialize the led as an OUTPUT
b.pinMode(led, b.OUTPUT);
// Create a variable called state, which stores the current state of the LED.
var state = b.LOW;
// Set the LED as LOW (off)
b.digitalWrite(led, state);
// Execute the toggle function every one second (1000 milliseconds)
setInterval(toggle, 1000);
// Function that turns the LED either HIGH (on) or LOW (off)
// depending on the parameter state.
function toggle() {
if(state = b.LOW) state = b.HIGH; // if the LED is LOW (off), change the state to HIGH (on)
else state = b.LOW; // otherwise, if the LED is HIGH (on), change the state to LOW (off)
b.digitalWrite(led, state); // write the new state value to the led pin, turning the led on or off
}

The bug is in the following line of code:

if(state = b.LOW) state = b.HIGH; // if the LED is LOW

This mistake — using = instead of == when comparing two variables — is very common. Keep the following in mind:

· = is used to attribute a value to a variable.

· == is used to compare the value of two variables.

This is what happens inside that if statement:

1. state saves the value b.LOW (which is 0).

2. The condition inside the if is evaluated. This condition is merely the value of state, which is 0 (false).

3. The code inside that if — that is, state = b.HIGH — doesn’t execute.

4. state always stays with the value b.LOW.

Had the code been written the other way around — that is, with state = b.HIGH — the if would always execute, and the else would never execute. The LED would just stay on.

Rather than scan the whole code with eagle eyes to find that one little mistake, you can detect which parts of the code are executing and which parts aren’t, as well as finding the value of a variable by printing stuff everywhere. For example, in the following we add three instructions to print information on the terminal.

function toggle() {
if(state = b.LOW){ // if the LED is LOW (off), change the state to HIGH (on)
console.log("This happens 1.");
state = b.HIGH;
}
else { // otherwise, if the LED is HIGH (on), change the state to LOW (off)
console.log("This happens 2.");
state = b.LOW;
}
b.digitalWrite(led, state); // write the new state value to the led pin, turning the led on or off
console.log(state);
}

When you run the code with those changes, you see that only the code below else executes, and that the value of state is stuck on 0. That shows you exactly where you have to look to find the problem with your program.

Another useful technique is to use comments for more than, well, comments. Whenever you want to remove a chunk of code but don’t want to erase it (because you might use it later or because you’re unsure whether removing it is the correct thing to do), you can simply wrap it up in comments: /* code */ in BoneScript or """ code """ in Python.

Despite being aware of several techniques to avoid bugs and to correct them when they do happen, naturally errors still happen frequently. It’s true that searching for a bug is quite frustrating, but figuring it out and correcting it also provides great joy. Figure 11-1 shows the typical state of mind for a programmer over the course of time.

Figure 11-1: Programmer’s happiness chart.

Diving into Binary and Data Storage

For simple applications, you don’t need to know much about binary besides the fact that 1 is True/HIGH/on and 0 is False/LOW/off. A brief introduction to binary is important, however, for three reasons:

· In complex applications, especially (but not only) for mastering communication protocols such as UART, I2C, and SPI, having good knowledge of binary is very important.

· Binary is the foundation of all things regarding computers. Your variables are sets of ones and zeros. Your instructions are sets of ones and zeros. Everything you do on the computer is converted to a set of ones and zeros that command which parts of the computer’s hardware go HIGH and which stay LOW.

· Knowing binary is awesome.

Have you ever wondered why copying things to a CD is often called burning the CD? Burning is what the CD recorder does. It has a laser that precisely burns some areas of the disc according to the data to be saved. The specific areas that the recorder burns and those that it doesn’t are based on the data you’re saving on the CD. The materials darkened by the laser are zeros; the ones left translucent are ones. The CD reader extracts the data by using another, much weaker laser to find all the ones and zeros on the disc. After that, the computer converts the binary code to user-readable data.

Binary

For those who don’t understand it, binary often looks completely alien and complex. In reality, it’s quite simple. It’s like counting as though you had only two fingers in each hand. With ten fingers, here’s what you do:

· 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 and then add another number to the left, starting at 1.

· 10, 11, 12, 13, 14, (…), 19, 20, 21, 22 (…) and then add another number to the left, starting at 1.

· 100, 101, 102 (…), 110, 111, 112 (…).

In binary, though, you have only two fingers, so you proceed in the same way but you are limited to two numbers: 0 and 1 (hence, the name binary). For example,

· 0, 1, and then add another number to the left, starting at 1.

· 10, 11, and then add another number to the left, starting at 1.

· 100, 101, 110, 111 and then add another number to the left, starting at 1.

· 1000, 1001, 1010, 1011, 1100, 1101, 1110, 1111, and then add another number to the left, starting at 1, and so on.

You read these numbers one by one. You read binary number 1001, for example, as “one zero zero one” (not “one thousand and one”). The same applies for hexadecimal (next section).

Converting from binary to decimal is quite straightforward. Look at the following example, which uses the binary code 1 0 1 0 0:

Even though you may not realize it, the process is actually the same one you use for decimal numbers. Look at the decimal number 15634:

Hexadecimal

To avoid having hordes of ones and zeros filling computer screens, many applications use hexadecimal. For organization purposes, values are often displayed in hexadecimal rather than binary. Hexadecimal is another way to represent numbers, going from 0 to 15 in the following succession:

0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, F

In hexadecimal, you count up to 15 (hence the name hexadecimal), as if you had 16 fingers on each hand. The following example shows how to convert a hexadecimal number into a decimal number; it’s a similar approach as the one in the previous section:

Data storage

Data on a computer is stored in bits (binary digits). 0 is a bit. 01 is two bits. Nowadays, most computers are 32-bit or 64-bit, which means that the instructions, variables, and everything else that the computer works with are sets of 32 or 64 binary digits.

Characters on computers are often written based on the ASCII (American Standard Code for Information Interchange) table. This table defines a character for a set of 8 bits, which allows for a total of 28 = 256 characters. The letter A (uppercase) is number 65 on the ASCII table, which is 0100 0001 in binary.

Signed integers require a bit (the first one is called the MSB, or most significant bit) to indicate whether a value is negative or positive. Thus, a signed integer on a 32-bit machine saves a value only up to 231 rather than 232.

There’s a lot more to talk about on the topic of how a computer uses binary to control everything that’s going on inside it. For example, storing an integer value is completely different from storing a number with a decimal point, and negative values are also saved in a different way using a technique known as Two’s complement (generally, as there are other techniques that can be employed). This book doesn’t get into much detail on data storage, but if this section interests you, you should research it online.

Congratulations! You’re now fully qualified to stamp Figure 11-2 on a T-shirt.

Figure 11-2: There are 10 types of people…

BoneScript-Specific Programming Tips

This section tells you a little about two concepts: looping and callbacks. These were briefly introduced in Chapter 7.

Looping, looping, and more looping

In Python, it’s easy to make your code loop indefinitely. You simply use while True:, and the code runs forever until you manually stop it. Here’s an example:

#!/usr/bin/python

import Adafruit_BBIO.GPIO as GPIO
import time

led = "P9_14"
GPIO.setup(led, GPIO.OUT)

while True:
GPIO.output(led, GPIO.HIGH)
time.sleep(1)
GPIO.output(led, GPIO.LOW)
time.sleep(1)

BoneScript is a bit different, and you have a couple of ways to get it into looping. Chapters 7 and 8 cover some of these techniques, but we think that this topic deserves its own section.

Consider the following script to blink an LED:

var b = require('bonescript');
var led = "USR3";
b.pinMode(led, b.OUTPUT);
var state = b.LOW;
b.digitalWrite(led, state);
setInterval(toggle, 1000);

function toggle() {
if(state == b.LOW) state = b.HIGH;
else state = b.LOW;
b.digitalWrite(led, state);
}

The preceding code makes your toggle() function run forever because the setInverval() function calls the toggle() function every second.

Alternatively, you can take advantage of the setTimeout() function to keep recalling your loop() function:

var b = require('bonescript');

var inputPin = "P9_36";
var outputPin = "P9_14";

b.pinMode(outputPin, b.ANALOG_OUTPUT);
loop();

function loop() {
var value = b.analogRead(inputPin);
b.analogWrite(outputPin, value);
setTimeout(loop, 1);
}

The loop() function reads the analog inputPin and writes a value to the outputPin repeatedly until you stop your code. This simplicity is thanks to the setTimeout() function.

Understanding the importance of JavaScript callbacks

When you pass a function to another function as an argument, the process is called a callback. BoneScript projects in Chapters 7 and 8 use callbacks very often. Callbacks are extremely important in JavaScript, which is an asynchronous language, because they enable you to execute functions when an event occurs. In the preceding section, the setInterval() function has a loop as a callback; after 1 millisecond elapses, that function calls the loop() function.

The name callback refers to the fact that the callback is executed only upon completion of the main function.

If you consider that JavaScript was created for web interaction, it makes quite a lot of sense for it to be an asynchronous language. Most of the time, you don’t want things to happen in succession. You want things to happen as responses to whatever the user does on the website.

Python-Specific Programming Tips

This section introduces some Python tips that can help you make your programs more organized, more efficient, and less subject to errors, while also introducing a few techniques for programming in Python.

Creating functions to clear up the mess

In BoneScript, creating functions is necessary because the code isn’t read sequentially, so you need to use callbacks that refer to functions to make the code do what you want it to.

In Python, theoretically — and we place enormous emphasis on theoretically — functions aren’t necessary. But writing a program that consists of several lines of code is extremely hard and frustrating if you don’t resort to functions. The main part of a script should always feature little more than function calls.

Creating time-dependent code

When you work with projects that interact with the real world, managing the speed at which a program runs is important. Most of the Python code featured in Chapters 9 and 10 of this book have the time.sleep() function because you need to settle things down a bit when everything works at such high speeds. (The BeagleBone Black processes data every nanosecond!)

The time.sleep() function halts the program for a set amount of seconds. In some cases, that’s not the kind of time control that you want. Imagine that you want part of your code to run for only a set amount of seconds instead of a set number of iterations. A good example would be a computer game in which the player has to accomplish a task in a limited amount of time. The code would use a new function, time.time(), in the following fashion:

#gets how many seconds have passed since the 1st of January of 1970
t0 = time.time()
t1 = time.time()
while t1 - t0 > 60: #runs this part for 60 seconds
player_position = get_player_position()
if (player_position > finish_line)
success = True
else
success = False
t1 = time.time()

The time.time() function returns how many seconds have elapsed since January 1, 1970 (midnight UTC/GMT), without counting leap seconds. This period is known as the Unix epoch and is basically Second Zero for Unix systems (such as Linux, Mac OS, iOS, and Android).

Most Unix systems store an epoch date as a signed 32-bit integer, which means that these systems can store only up to 231 = 2147483648 seconds (see the “Data storage” section to better understand what a signed integer is). Problems may arise 231 seconds after the Unix epoch on January 19, 2038. Think of this date as the Unix system’s version of the Y2K problem. In fact, this event is often regarded as the Y2038 problem.

The code starts by defining two values that have the same value:

t0 = time.time()
t1 = time.time()

Thus, at the start of the program, t1 - t0 = 0.

Note that the while loop tests for t1 - t0 > 60. At the end of the loop, you determine the time again and save it as t1. You repeat this process over and over until 60 seconds have passed. t0 stays the same throughout the entire while loop, whereas t1 takes in the value of the time at every iteration. Eventually, the difference between these two variables will be greater than 60, which means that 60 seconds will have elapsed.

The time library has plenty of interesting functions. Feel free to check out its documentation at https://docs.python.org/2/library/time.html. You can also check its Python console manual by typing the following at the command line:

python
>>> import time as time
>>> help(time)