Arduino Meets Linux
Arduino Meets Linux
Arduino Meets Linux
EBooks are not transferable. All rights reserved. No part of this publication may be
reproduced, distributed, or transmitted in any form or by any means, including
photocopying, recording, or other electronic or mechanical methods, without the prior
written permission of the publisher, except in the case of brief quotations embodied in
critical reviews and certain other non-commercial uses permitted by copyright law.
Trademarked names, logos, and images may appear in this book. Rather than use a
trademark symbol with every occurrence of a trademarked name, logo, or image, the
names, logos, and images are used only in an editorial fashion and to the benefit of the
trademark owner, with no intention of infringement of the trademark.
Any source code or supplementary materials referenced by the author in this text are
available for readers at www.arduinomeetslinux.com.
Table of Contents
Preface
Getting Started
Programming in Python
You can use the Linux side of the Yn to add faster processing and more-capable
networking to your ATmega32u4 projects writing the main parts of your projects in
Arduino sketches;
Or you can use the ATmega32u4 to add a friendly hardware interface to your Linux-
based projects.
For Arduino users who do not have experience and knowledge of running Linux on
embedded devices and programming in that environment, it can be difficult to learn how
to make use of the extra power.
Whats in this Book?
M y aim in writing Arduino M eets Linux is to show you how you can use the full power of
the Arduino Yn. The chapters and projects in this book guide you through:
To program the ATmega32u4 on the Yn, you can use the Arduino IDE. This book
introduces the libraries and functions that are specific to the Yn and that are built-in to
the Arduino IDE. Every feature of the Yn is included, with sample code to help you
learn to use these features.
To write programs and build projects on the Linux side of the Yn, you can use a variety
of techniques and programming languages. Arduino M eets Linux concentrates on using
the Linux command line and the Python scripting language. If you dont have any
experience of using Linux operating systems or programming in Python, dont worry. You
can find introductions to these subjects in the book.
Because the Yn is extremely capable at communicating over local networks and over
the Internet, I have also included projects that demonstrate:
Hosting your own web sites and web services on the Yn;
Fetching information from third-party web services across the Internet; and
Using Temboo to simplify how you can access many web services.
I hope that you find these projects interesting. But remember, my hope is that you learn
how to make use of the Arduino Yn and develop your own ideas. The techniques and
explanations in my projects are intended to help you build your own. For that reason, I
recommend that you read through all of the chapters in this book and build the projects.
You can learn far more by making and experimenting than I can put into words.
Who Should Read this Book?
If the Yn is your first Arduino, you should take some time to study the beginners
tutorials at http://www.arduino.cc. If you ignore the Atheros AR9331, the Arduino Yn
works almost identically to an Arduino Uno or Arduino Leonardo.
To an extent, Arduino M eets Linux assumes that the Yn is not your first Arduino.
Although I have taken precautions to explain concepts that may be new to you, you
should have a basic grasp of the fundamentals of building projects on the Arduino
platform. This includes: writing sketches in Arduino C in the IDE; and safely building
simple, electronic circuits.
If your main computer is a Windows PC or Apple M ac, and you have never used Linux
before, then this book will help you. Everything you need to know is included. And the
skills that you learn working with Linux on the Arduino Yn will help you if you ever want
to use a Linux operating system on another development board or computer.
Arduino M eets Linux is for Arduino users who want to expand their skill set and learn to
take advantage of the extra power and resources that only the Arduino Yn has.
Online Resources
ArduinoMeetsLinux.com is the companion website for this book. You can contact me
there if theres anything I can help you with, or if you want to let me know about the
great things you build with the Arduino Yn. Ive also put all of the project sketches and
scripts up there for you to download. Itll be worth your while to visit the site regularly
news, updates, and addendum to this book will be posted there first.
To the best of my ability, I have verified the accuracy of all of the information in this
book. And I have tried to ensure that the code samples are robust enough for you to
use, but not so full of optimized programming and error-checking that it is difficult to
understand the code. However, things do change and mistakes do happen. You can
help me to improve future editions, for the benefit of other Arduino fans, by contacting
me at ArduinoMeetsLinux.com if you find any errors, inaccuracies, or places where
information is not as easy to understand as you would have liked.
Conventions Used in this Book
The following table describes the text conventions used in this book.
Convention Meaning
Text that appears in italics refers to file names, variable and function
names, or other code that exists in the project sketch, Python scripts,
Italic
or Arduino libraries. Within the context of giving instruction, italic text
should be typed exactly as shown.
Within the context of giving instruction, items in bold text are user
Bold interface elements, such as key strokes, menu items, or button labels.
In other contexts, words may be emboldened for emphasis.
M onospace A monospace font is used for Arduino C, Python, and Linux command
font line code that you may need to type.
Colored text Items shown with colored text are links to other pages in this book.
Getting Started
The Arduino Yn is a microcontroller-based development board that combines the
flexibility and ease-of-use of the Arduino with a powerful M IPS1 central processing unit
(CPU). It is two computers on one, small board.
The Arduino side is powered by an ATmega32u4 the same chip that is on the
Arduino Leonardo and this is similar to the microcontrollers you have worked with
before if you are an Arduino user.
The two sides of the Yn can communicate with each other, and you use can both in
your projects at the same time.
In This Chapter
Examining the Yn
Updating OpenWRT
Connecting the Arduino Yn to a Network
Changing the Superuser Password
Opening the Advanced Configuration Panel
Uploading Sketches to the Arduino
Examining the Yn
The various features of the Yn are divided between the Arduino side and the Linux
side.
USB controller (for uploading your USB controller for peripheral devices
sketches and communicating with a host such as keyboards, gamepads, and audio
PC). interfaces.
Program with sketches from the Arduino Program with Linux executables, Python,
IDE. shell scripts, or languages that you install.
The Arduino Yn has a similar layout to other Arduino devices, and it is approximately
the same size. The placement of the input/output pins and in-circuit serial programming
(ICSP) header are the same across the Arduino range, and so you can use most shields
with the Yn. However, the vertically-mounted USB socket can make it difficult to use
some shields. You can use an additional set of pin headers to fix this. For an example,
see Project 5 M aking a USB Accelerometer M ouse.
Figure 1. Connectors on the Y n
Description
8 ICSP header.
LED
Description
Label
RX Flashes when the serial port receives data.
Digital output pin 13 links to this LED. When pin 13 is HIGH, the LED lights.
L13
When pin 13 is LOW, the LED is off.
Button Description
32U4 Resets the ATmega32u4. This is the equivalent of the RESET button on
RST other Arduinos. To restart the Arduino sketch, press this button twice.
WLAN Resets the Wi-Fi configuration. For more information, see Resetting the Wi-
RST Fi Connection.
YN
Resets the Atheros A9331 and restarts Linux.
RST
Connecting Power
Unlike many of the other Arduinos, the Arduino Yns power connector is a micro-USB
socket.
To power the Yn from the USB socket of a desktop PC or laptop, you need a USB to
micro-USB data cable:
1. Gently, but firmly, push the micro-USB end of the cable into the micro-USB socket
on the Arduino Yn.
2. Push the full-size USB end of the cable into a USB 2.0 or USB 3.0 socket on your
PC.
The first time that you connect your Arduino to an Apple M ac, M ac OS X may start the
Keyboard Setup Assistant. You can close this window. The Arduino Yns USB
controller creates a USB keyboard on your system. For more information, see Project 6
M aking a Translating Keyboard.
Once connected to your internal network, you can upload files and Arduino sketches to
the Yn over the Ethernet or Wi-Fi connection. You do not need to cable the Yn to a
PC to upload sketches to the board. For more information, see Uploading Sketches to
the Arduino.
M any cellphones use a micro-USB socket for data transfer and charging. You can often
use the charger from smartphones or tablets to power the Yn, but you should check
that the power output of the charger matches the Arduinos requirements. The Yn
requires a 5 V power supply and this should be rated for at least 1 amp (1 A).
1. Gently, but firmly, push the micro-USB end of the cable into the micro-USB socket
on the Arduino Yn.
2. Plug the charger into a wall outlet.
You can also power the Arduino using the Vin pin on the lower row of headers. If you do
this, you must only use a 5 V regulated supply as there is no on-board regulation or
protection when power is applied this way.
Tip: The Linux side of the Yn takes longer to start than other Arduinos. It may
take several moments for the operating system to load, connect to a network, and
run any scripts that you have setup.
To turn off the Yn, unplug it from its power source. Unlike many other Linux devices,
the version of OpenWRT Linux that runs on the Yn does not need to go through a
specific shutdown procedure.
Plug the device (or its cable) into the full-size USB socket on the Arduino Yn.
If a USB device does not work properly when connected to the Arduino, test the device
on a PC or M ac. If the device works correctly then the problem may be one of the
following:
The device requires driver software that is not installed on the Yn;
The Yns power supply is not adequate to power the Yn and the device(s) that
you are connecting. Either use a better power supply, or try connecting the USB
device through a USB hub that has its own power supply.
The socket on the Arduino is not spring-loaded it will not click when you insert a card.
Be aware that the microSD card sticks out from the Yn by roughly 1 cm do not
attempt to force it past this point.
The SD card is accessible on the Linux side as /mnt/sda1/ and through the FileIO class
in the Bridge library. For more information, see Using the Bridge Library.
To remove the microSD card: turn the Arduino Yn off (or make sure that it is not
reading or writing to the card) and then pull the microSD card out from the microSD
socket.
Tip: Although you can save files to the built-in memory of the AR9331, you should
try to use the microSD card to store your information. The AR9331 is limited in the
number of times that it can overwrite the contents of its memory. Although this
limit is very high, once it is reached then the AR9331 may become unusable.
Connecting the Arduino Yn to a Network
On the Arduino Yn, Internet and network connectivity is controlled from the Linux side.
The Atheros AR9331 has built-in support for making network connections over Ethernet
or Wi-Fi.
Use whichever type of connection is most convenient for you. Ethernet is generally
more reliable and easier to setup, but Wi-Fi is often more convenient because you do
not need to run a cable from the Arduino to your network router.
After you connect your Yn to your local network, you can access it from any other
device on the same network. To do this, use the host name arduino.local or its IP
address.
The Yn broadcasts its host name using the multicast domain name system (mDNS),
which helps devices on a network find each other without using a name server. M any
devices and operating systems have built-in support for mDNS; but Windows does not.
If you use Windows then, without additional software, you are unable to access the
Arduino Yn using its host name. However, you can always use its IP address.
If you run Apple iTunes on your computer then you already have the necessary
software to find your Yn. Apples support for mDNS is part of its Bonjour package. If you
want to install Bonjour without installing iTunes, you can download it from
http://support.apple.com/kb/DL999.
To use Bonjour, your computer must be able to communicate with other devices on your
network over UDP port 5353. It is rare for network routers to block this port, but you
should check this on your system. You should also ensure that the software firewall on
your PC allows both incoming and outgoing connections on UDP port 5353.
You only have to install the Arduino IDE on computers that you want to use to create
and upload Arduino sketches to the Yn. The Arduino software expects to find the Yn
either through a USB cable or using its mDNS host name. If you want to upload
sketches to the Arduino Yn, make sure you have enable or installed support for mDNS.
To do this:
It can take several moments for the Arduino to restart and connect to your Wi-Fi
network. When it does, the wireless access point is turned off. Reconnect the device
that you used to access the Arduinos web panel to your Wi-Fi network, and then re-
open the web panel.
If you want to use a static IP address for your Arduino Yn, see Using a Static IP
Address.
If you need to change the wireless network that the Arduino connects to, you can
change the Wi-Fi configuration using the web panel at http://arduino.local
However, if the Yn cannot connect to your network then you will be unable to access
the web panel. To fix this, you can reset the Wi-Fi configuration so that the Arduino
creates a wireless access point instead of connecting to your router.
1. Press and hold the WLAN RST button for 520 seconds.
2. Wait for the Yn to reboot and then follow the instructions in Connecting the
Arduino Yn to a Network.
If you hold the wireless reset button for longer than 30 seconds then this restores the
Arduino Yn back to the initial settings that it came with. This wipes any data (including
network settings, downloaded files, and installed packages) from your Yn.
1. Plug one end of a CAT5 or CAT6 Ethernet cable with RJ45 connectors into the
socket on the Yn.
2. Plug the other end of the cable into an available Ethernet port on your router.
3. Plug the Arduino into a suitable power supply (if it is not connected already).
When the AR9331 chip powers up, it attempts to connect to the network using dynamic
host configuration protocol (DHCP). In this mode, the Yn asks the router for an IP
address and uses the gateway and domain name system (DNS) information that the
router tells it.
If your router does not support DHCP, you can configure the Yn with a static IP
address. You can find more information about using a static IP address in Using a Static
IP Address.
The LED labelled WAN indicates the state of the Ethernet connection.
If you connect your Yn to your network using an Ethernet connection, the Wi-Fi
connection remains as a wireless access point. This can allow other people to connect
to your Arduino, access the administration panels, and take control of your device.
Caution: If you leave the Wi-Fi unconnected, change the Arduinos password!
If you connect to two different networks at the same time, you may have problems
specifying the routes that incoming and outgoing traffic takes.
There are two situations where you might want to connect your Arduino to the same
network using both Wi-Fi and Ethernet:
You connect your Yn to your network using Ethernet, but do not want to leave the
Wi-Fi connection open, change the Arduinos password, or disable the Wi-Fi device;
You want to use two different IP addresses on the Yn. For example, two IP
addresses are useful when hosting websites. For more information, see Hosting
Websites and Services.
To connect to the same network using both Wi-Fi and Ethernet, follow the instructions
for making each type of connection. If either (or both) connections use DHCP, then each
connection has its own IP address. However, if you want to use static IP addresses for
both connections, make sure that you specify two different IP addresses.
Host names are usually up to 15 characters long, and should start with a letter (AZ) or
a number (09). The remaining characters can typically be anything except certain
punctuation marks.
Changing the host name also changes the universal resource locator (URL) for the web
panel. Instead of http://arduino.local, the web panel will be located at
http://<newhostname>.local
Updating OpenWrt-Yun wipes all of your settings and removes any additional software
that you have installed on the Yn.
Sketches on the Arduino (especially ones that use the Bridge library) may interfere with
the update process. You should upload the YunSerialTerminal example sketch before
updating OpenWrt-Yun. For more information, see Uploading Sketches to the Arduino.
To update OpenWrt-Yun, you need to insert a microSD card into your PC or M ac, and
then:
The reset process may take several minutes and the LED labelled WLAN will blink.
When it is complete, OpenWrt-Yun will be up-to-date and you can reconfigure the Yns
network connection.
Changing the Superuser Password
The default password on Arduino Yns is arduino.
If you connect your Arduino Yn to a network where it can receive incoming traffic from
other users (or people across the Internet), then you should change the default
password. Changing the password helps to stop unauthorized people from taking control
of your Arduino and changing settings or files that you do not want them to.
1. On a machine that connects to the same network as the Arduino, open a web
browser.
2. In the address bar, type http://arduino.local
3. Type the current password and then click LOG IN.
4. Click CONFIGURE.
5. Click advanced configuration panel (luci).
Tab Description
Status Shows information about the current status of the Yn, and contains
options for the administration of running processes.
Network Information and configuration options for the Yns network connections.
Each of the main tabs is then split into several other tabs.
If you are unfamiliar with Linux-based operating systems, you should read Introducing
OpenWRT and Linux before changing settings in the advanced configuration panel.
When the Yn restarts, or reconnects to the network, your network router will give it an
IP address. You can use this address to communicate with the Yn, instead of the host
name arduino.local. However, this IP address can change every time the Yn connects
to the network. To prevent the IP address changing, you can use a static IP.
The network configuration options in the Arduino web panel are not detailed enough for
you to use them to setup a static IP address. However, you can do this from the
advanced configuration panel.
If you are unsure what values to type in the IPv4 boxes, look at the network
configuration properties of another machine on your network.
Click the Status tab, and then click the System Log tab.
Click the Status tab, and then click the Kernel Log tab.
Uploading Sketches to the Arduino
The Arduino integrated development environment (IDE) is a program for your PC or
M ac. You can use it to write programs that run on the Arduino side of the Yn (and other
Arduinos). Only IDE versions 1.5.8 BETA or later support the Arduino Yn. The current
version of the Arduino IDE is 1.6.4.
If the Yn is your first Arduino, you need to install the Arduino IDE. If you are currently
using other Arduinos, you should update to the latest version of the IDE.
On the Tools menu, point to Port, and then click COM? (Arduino Yn).
On the Tools menu, point to Port, and then click Arduino at <IP Address> (Arduino
Yn).
To compile the current sketch and upload it to the Arduino using the selected port:
In the Arduino IDE, on the toolbar, click Upload.
Tip: If you cannot make the connection to your Yn, check that your firewall
allows UDP connections on port 5353, and that you have installed support for
Bonjour. For more information, see Installing Support for the Yn on PCs.
If you use the IDE to make a connection to the Arduino Yn over your network, you
cannot send messages to the Serial M onitor using the Serial class. The routines in this
class are designed to send and receive data over the USB serial port only. Instead, you
should use the Console class in the Bridge library. For more information about the
Console, see M aking a Console Connection.
To keep the code samples in this book as easy to understand and work with as
possible, many of them use the Serial class and the Arduino IDEs Serial M onitor. While
you are practicing, you may find it helpful to connect the Yn to your PC using a USB
cable.
You can open the example sketches from the Files menu:
For example, to open the YunSerialTerminal sketch and upload it to the Arduino:
1. On the Files menu, point to Examples, point to Bridge, and then click
YunSerialTerminal.
2. On the toolbar, click Upload.
The table below highlights some of the example sketches and describes what they do.
Sketch Description
Shows how to write files into the Linux file system from the
FileWriteScript
Arduino side.
1
Microprocessor without Interlocked Pipeline Stages (MIPS) is a CPU architecture developed by MIPS Technologies,
Inc.
2
If you have installed an archiving tool on your Mac, the OpenW rt-Y un update image might open in this.
Introducing OpenWRT and Linux
An operating system (OS) is a special type of program that you run on your computer.
Its main jobs are to help you run multiple other programs at the same time, share system
resources between those programs, and manage the file system (how files and
programs are stored).
GNU/Linux (usually referred to as just Linux) is an open source OS. This means that the
files that you need to build it are freely available, and so anyone can make their own
Linux-based OS. A customized OS that is based on Linux is a distribution, or distro.
OpenWRT is a Linux distribution that is designed to run on devices that do not have the
same speed or memory resources as full, desktop machines. It is aimed at standalone
devices, such as network routers, to which you do not usually connect a display or
keyboard.
The Arduino Yn is much more powerful than other Arduinos and so it is able to do
things that other Arduinos cannot. For example, it can play an M P3 file through a USB
audio device, while it is downloading files from the Internet and writing them to an SD
card. By running an OS, the Yn simplifies how you can use this extra power. Without
the OS, using all of the Yns features would be difficult requiring long and detailed
sketches that only experienced programmers would be able to write.
The Atheros AR9331 chip inside the Arduino Yn runs a version of OpenWRT Linux
called OpenWrt-Yun. Unlike the Arduino side of the Yn (to which you upload sketches
using the Arduino IDE), you work with OpenWrt-Yun using a command line. This
involves typing commands and editing text files. And unlike operating systems, such as
Windows and M ac OS X, OpenWrt-Yun does not have a desktop environment that you
control with a mouse.
In This Chapter
You control the command line using a keyboard, by typing in the names of commands
that you want to run. These commands are actually small programs that direct any
messages and output to the console the text-based display. The command line is also
called the shell, or terminal.
Unlike other Linux-based computers, you need to make a connection to the Yns
terminal from another machine on your network. Although you can do this using the
YunSerialTerminal sketch and the Serial M onitor in the Arduino IDE, the Serial M onitor is
quite basic. It is often better to connect to the Yn using the secure shell (SSH) protocol
and a dedicated piece of software.
SSH is an encrypted protocol for executing commands remotely from another machine
on the same network as the Arduino and accessing the command line. When
connected, you can type commands using the keyboard on your computer, and then see
the results.
The Yn has built-in support for SSH, but Windows users have to install an SSH client
on their machine. These are pieces of software that understand how to work with SSH,
and there are many different clients available. One of more popular ones is PuTTY, and
you can download this from http://www.chiark.greenend.org.uk/~sgtatham/putty/
To connect to the Arduinos terminal from Windows, using PuTTY:
1. Locate the file putty.exe that you downloaded, and then double-click it.
2. In the Host Name (or IP address) box, type the host name of the Yn. This is
arduino.local, unless you have changed it.
3. Under Connection Type, click SSH.
4. Click Open.
5. At the login prompt, type root.
6. At the password prompt, type your password and then press Enter. This is arduino
unless you have changed it.
M ac OS X already has an SSH client; you do not need to download a separate client to
connect to your Arduino from a M ac.
5. At the password prompt, type your password. This is arduino unless you have
changed it.
To close the connection, type exit and then press the Enter key.
The secure copy (SCP) protocol is closely-related to SSH. You can use it to download
files from the Arduino to your PC or M ac, and to upload files from your computer to the
Linux file system (or onto the microSD card) on the Yn.
Tip: If you regularly need to upload or download files from the Yn, use SCP. This
way, you do not need to shut down the Yn and remove the microSD card.
On Windows, you need to download and install an SCP client, such as WinSCP
(http://winscp.net/eng/download.php).
1. Start WinSCP.
2. In the left panel, click New Site.
3. In the File Protocol list, click SCP.
4. In the Host name box, type the name of your Arduino. This is arduino.local unless
you have changed it.
5. In the User name box, type root.
6. In the Password box, type your Yns password. This is arduino unless you have
changed it.
7. Click Save As
8. In the Site Name box, type Arduino Yn.
9. Click OK.
10. Click Login.
To upload a file to the Arduino, drag it from a Windows Explorer window and drop it over
the WinSCP window. To download a file from the Arduino, drag it from the WinSCP
window and drop it in a convenient location on your PC.
M ac OS X has an scp command built-in. There are SCP clients available that have a
graphical user interface (GUI) but if you do not want to install one of these then you can
use SCP from the M ac OS X terminal.
5. At the password prompt, type your Yns password. This is arduino unless you
have changed it.
5. At the password prompt, type your Yns password. This is arduino unless you
have changed it.
Looking at the Command Prompt
When you connect to your Arduino over SSH and login, you see the command prompt.
This is a short sequence of characters that ends with a # symbol. Whenever you see
this prompt, you can type a command and then press the Enter key to make Linux run it.
1. The first part shows the name of the user that you are logged in as (usually root).
2. The second part shows the host name of the Arduino.
3. The third part is the current working directory (for more information, see Working with
Files and Directories).
4. The # symbol indicates that you are logged in as the root administrator. Non-
administrator users see a different symbol.
To run a command, type it at the command prompt and then press the Enter key when
you are done.
For example, you can use the echo command to display messages.
Values typed after the command name are called arguments. The echo command
expects one argument, and that is the message to show. In this case, it does not matter
if you do not use quotation marks. However, other commands think that spaces indicate
gaps between arguments. By using quotation marks, you can ensure that the command
treats your text as a single argument.
If you want to run the same command again, press the Up Arrow key and the previous
command appears in the console. You can then make changes to the command. Press
Enter to run the command or the Down Arrow key to return to a blank prompt.
After you have run several commands, you can use the Up and Down Arrow keys to
cycle through your command history.
Every user on a Linux system has their own home directory. This is an area of the file
system where they have permission to create files and directories. On larger Linux
systems, a user may not be able to access anything outside of their home directory.
When you login at the command prompt, Linux places you in a directory. On OpenWrt-
Yun, you login as the root user and so Linux places you in the root users home
directory /root. This is your current working directory. If you do not specify a full file
path then Linux commands assume that you want to place or work with files in the
current working directory.
To change the current working directory and move to the top-level of the file system,
To see a list of all the files and directories that are in the current working directory,
In the top-level directory, there are a lot of subdirectories. M any of these have specific
functions, and are generally the same across different distributions of Linux. On
OpenWrt-Yun, the top-level subdirectories are:
Directory Description
Contains applications that are used by all user accounts, the system, and
bin
the system administrator.
Linux creates file system entries in this folder for hardware devices that
dev
are attached or built-in to the system.
M ostly contains configuration files and settings for the OS and installed
etc
programs.
lost+found Files that are saved during failures are placed here.
mnt Storage devices (such as SD cards) are added to the file system in here.
When the file system is expanded to include space on the microSD card,
overlay
the files in this folder are included in the Linux file system.
When the file system is expanded to include space on the microSD card,
rom this folder refers to the files that are in the Atheros AR9331s built-in
memory.
Contains applications that are only available to the system and the
sbin
system administrator.
This is a virtual directory for accessing information about the current state
sys
of the system.
Temporary files used by the system. The contents of this folder are
tmp
deleted when the Yn starts.
var Variables and temporary files that are created by the user or applications.
Files and scripts for the Arduinos web panels and web-hosting
www
capabilities.
Linux creates entries in the /dev and /mnt folders for input/output devices, memory cards,
and USB peripherals. Because everything is a file, you can often access external
devices by using commands that are designed to work with files. In those cases, writing
to the file actually sends the information to the device.
When creating your own files, try to keep them on the microSD card. This is especially
important if you do not expand the Linux file system to the memory card (see Expanding
the Linux File System onto the microSD Card) as the Atheros AR9331 can only write to
its built-in storage a set number of times.
Tip: If you do expand the file system onto the memory card, the best place to keep
your files is in /root.
Use the cd command followed by the name of the directory. For example, type the
following command and then press Enter:
cd etc
Use the cd command followed by the full path to the directory. For example,
wherever you are in the file system, you can always jump to the SD card by typing
the following command and then pressing Enter:
cd /mnt/sda1/
File names can be up to 255 characters, and usually include a file extension after a dot.
The file extension indicates what type of file it is. But you do not have to use the same
file extensions as everyone else if you do not want to, and in most cases you do not
have to use a file extension at all.
A file path describes the location of a file. There are two types:
Whenever you need to type a file name, you can also include either type of file path.
There are lots of commands that you can use to create, edit, and remove files or
directories:
To Do This
M ove to your Type the following command and then press Enter:
home directory cd ~
Create a Type mkdir followed by a space and then the name of the
directory directory, and then press Enter.
Delete a Type rmdir followed by a space and then the name of the
directory directory, and then press Enter.
Type rm followed by a space and then the name of the file, and
Delete a file
then press Enter.
Type cp followed by a space and then the name (or full file path) of
Copy a file or
the file to be copied. Type another space and then the name (or
directory
full file path) to call the copy. Press Enter.
Rename or Type mv followed by a space and then the name (or full file path)
move a file or of the file to be copied. Type another space and then the name (or
directory full file path) to specify the destination. Press Enter.
Type cat followed by a space and then the name of the file. Then
View a text file
press Enter.
Type nano followed by a space and then the name of the file. Then
Edit a text file press Enter. For more information, see Editing Text Files with GNU
nano.
You can redirect the output of a Linux command to device or file by using pipes.
For example, to redirect the output of the ls command so that the list of files is placed in
a text file instead of appearing in the console,
Create links with the ln command followed by -s, then the full path of the item that you
want to link to, then the file name and file path where you want to save the link. For
example, you can use a link to create an entry in your home directory that points to the
SD card.
In the /root directory (your home directory) you can see an entry named sd. If you list
the contents of this (ls ~/sd) then you actually get a list of the files on the microSD card.
Deleting a symbolic link does not remove the original file. So, to remove the symbolic
link that you just created,
One of the uses for symbolic links on Linux is in the /lib directory. If you list the contents
of this directory then you will see that some files have their version numbers (or build
numbers) in the file name. For those that do, there is usually a matching file that does
not have a version number. This is often a link.
By having a link with name that never changes, it means that the actual file can be
updated to a new version without affecting any program that wants to use the file.
Setting the Access Permissions of Files and Directories
As the root user you will rarely be concerned with the access permissions of files and
directories you always have access to the full system. However, if you create a
project where multiple users access Linux then this information can help you to keep the
system secure.
Access permissions tell Linux which users can access specific files and directories, and
what they can do with them. But because of the way OpenWrt-Yun builds the file
system, there are lots of restrictions on the file access permissions that you can change.
For example, if you need to create a directory that everyone can access then the best
place to do this is in /rootor /www. You have full control of the file permissions in those
directories.
Each file and directory in a Linux system has three sets of permissions:
Set Description
Permissions that apply only to the owner of the file usually the person who
Owner
created it. You can change the owner of a file using the chown command.
Permissions that apply to a defined group of users. You can change the
Group
group of a file or directory with the chgrp command.
All Permissions that apply to everyone who has a login for the system.
In each set, there are three different types of permission that you can give to users:
Permission Description
You can view the permissions that are set for a file or directory by using the ls command
with the -l flag. For example,
Flag Description
_ No special permissions.
The entry has permissions set by setuid or setgid. These permissions are for
s
advanced users and are not covered in this book.
t The entry has sticky permissions. These permissions are for advanced users.
The next three characters show the permissions for the owner of the file or directory. An
r indicates that the owner can read from the file, a w indicates that they can write to it,
and an x indicates that they can execute the file. If a permission type is not set, a - is
used instead.
Following the owner permissions, there are three characters that show the permissions
for groups. And then three characters that show the permissions for all other users.
After the sequence of characters representing the permissions, in the output of ls -l there
is a number1 and then two words. These two words show the owner of the file and
then the group that it belongs to. For a user to obtain the group access permissions, you
must first assign a user to that group.
Tip: OpenWrt-Yun lacks many of the commands used to deal with users and
groups. You can work around this, but these kinds of security measures are less
important on the Arduino than on other Linux-based operating systems.
To give permissions to the owner, use the chmod command followed by u+, and then:
an r to give read permission, w to give write permission, and x to give execute
permission.
For example:
chmod u+rwx test.txt
chmod u+rw- test.txt
chmod u+r-w test.txt
To remove permissions, use the chmod command followed by u-, and then: an r to
remove the read permission, w to remove write permission, or x to remove execute
permission.
For example:
chmod u-rwx test.txt
chmod u--wx test.txt
chmod u-r-x test.txt
You can also change all three sets of permissions with one command. In this case, you
do not use the permission format explained above. Instead, you use a three-digit
number.
The first digit sets the permission for the owner. A zero means no access. r has the
value 4, w has the value 2, and x has the value 1. So to give the owner all three
permission types, add all three values together. The digit you use in the chmod
command is 7 (4+2+1). To only give read and execute permissions, use the digit 5 (4+1).
The second digit sets the permissions for the group. And the third is for all other users.
For example, to change the permissions of test.sh so that only the owner can modify it
(but everyone else can read and execute it),
GNU nano is a small, but useful, text editor for Linux. It is very popular and is included in
many Linux distributions, including OpenWrt-Yun.
At the command line, type the following command and then press Enter:
nano
To edit an existing file, specify the file path and file name after the nano command,
At the command line, type the following command and then press Enter:
nano /www/index.html
To exit nano:
Press Ctrl + X.
If you have made changes to the file, nano asks you whether you want to save the file.
Press the N key if you do not. If you do, press the Y key and then type (or change) the
file name. Press the Enter key when you are done.
Using nano
To move the text cursor, press one of the arrow keys on your keyboard.
Hold the Ctrl key while pressing another key to activate a special function. Some of
these are shown with a ^ symbol at the bottom of the nano editor. For example, ^K is
the same as Ctrl + K. You can also activate these functions by pressing the Esc key
twice and then typing the letter. For example: Esc, Esc, K.
In nano, the Alt key is also known as the meta key. The meta key is another way of
activating certain functions. For example, to move to the first line in a text file:
Press Alt + |
However, if you connect to the Yn over SSH, you may find that the Alt key does not
work. Instead, you can use the Esc key. Press the Esc key once (release it), and then
type the | symbol.
To Do This
M ove to a specific Press Ctrl + _ (underscore), type the line number and then
line number press Enter.
M ove to a specific Press Ctrl + _ (underscore), type the line number, followed by
character position on a comma, then the character position number. Then press
a specific line Enter.
M ove a line Press Ctrl + K. M ove the text cursor and then press Ctrl + U.
Copy and paste a Press Alt + 6, or press Esc and then press 6. M ove the text
line cursor and then press Ctrl + U.
Search the file for a Press Ctrl + W, type the term that you want to search for, and
specific word then press Enter.
Replace all instances Press Ctrl + W, and then press Ctrl + R. Type the term that
of a specific word you want replace and then press Enter. Type the term that
with another you want insert, and then press Enter.
Press Ctrl + O. Then type (or change) the file name and press
Save the file
Enter.
Environment variables
Created by various scripts and configuration files that run when Linux starts up.
Environment variables are accessible from the command line that creates them, and
any other scripts or programs that you start from this command line.
Shell variables
Only accessible from the current command line; not to any scripts or programs that
you start from this command line.
One of the most important environment variables on Linux is PATH. When you type a
command on the command line and press the Enter key, the system searches all of the
directories that are named in the PATH variable and tries to find the command or
program.
To display a variable, use the echo command and prefix the variable name with a $,
You do not have to use capital letters for variable names, but it is common to do so
when setting environment variables. To change a variable of either type, use the same
syntax as when setting a shell variable.
When you connect to the Yn using SSH, Linux starts a new command line (shell) that
sends its output and receives its input over the Yns network connection. The shell
itself is a program, and so you can run multiple copies of it at the same time.
On the Arduino Yn, running multiple shells usually only happens when you make
multiple SSH connections to the Arduino. However, you can start a new shell process
using the sh command. The newly-created shell sends its output back to the shell that
creates it, and usually accepts its input from the information that you specify when you
type the sh command.
4. Now create a shell variable: type the following command and then press Enter:
YOU=Alice
Steps 3 and 6 both start a new shell and tell it to run the echo command. In the first
example, steps 13, both echo commands display Bob. In the second example, steps
46, the first echo command displays Alice but the second does not. This is because
the shell variable YOU is not accessible to the second shell.
In Writing Shell Scripts, you can see how to store sequences of Linux commands in a
text file, and how to run the file and execute the commands. Linux starts these files in a
similar way to how the sh command creates a shell, so you should use environment
variables when you want to access the value from your script.
If a second shell creates its own shell variables, then it deletes them when it closes.
1. On the command line, type the following command and then press Enter:
nano /etc/profile
2. Press the Down Arrow key until the text cursor is on the line that reads export
PS1='\u@\h:w\$ ', and then press Ctrl + E.
3. Press Enter.
4. Type a new variable declaration, for example:
export ARDUINO=Yun
5. Press Ctrl + X.
6. Press Y.
7. Press Enter.
8. Reboot the Arduino
Writing Shell Scripts
A shell script is a text file that contains sequences of Linux commands. This can be
useful in situations where you need to repeatedly run long commands, or perform exactly
the same operation again at a later date.
Scripts are very useful for administrative tasks and file management. But on Linux, a shell
script can also perform complicated procedures, make decisions, and work with hardware
devices. So they are one of the ways that you can use the power of the Linux side of
the Yn in your projects.
To start a new script and save it to the microSD card on the Arduino Yn,
1. At the command line, type the following command and then press Enter:
nano /mnt/sda1/shell1.sh
2. On the first line, type the following text and then press Enter:
#!/bin/sh
5. Press Ctrl + X.
6. Press Y, and then press Enter.
The first line, #!/bin/sh, tells the system how to run the script. It is not always necessary
to include this line, but it is a good practice. The remaining lines in this script are a
sequence of commands that are executed when the script is run. Each command is on
its own line.
The second line uses the echo command to send a text string to the console.
The third line uses the command ls to display the contents of the microSD card.
If you create your script files on your PC or M ac and then transfer them to the Arduino
using SCP, you may need to adjust the file permissions before you can run the script.
For more information about file access permissions, see Setting the Access Permissions
of Files and Directories.
To change the permissions of shell1.sh so that all users can run it,
If you are in the same directory as the script, you can run it without typing the full file
path. Type the following command and then press the Enter key:
./shell1.sh
You can use the same syntax to run one shell script from another. To create a second
script that runs the first,
1. At the command line, type the following command and then press Enter:
nano /mnt/sda1/shell0.sh
2. On the first line, type the following text and then press Enter:
#!/bin/sh
4. Press Ctrl + X.
5. Press Y, and then press Enter.
Leave shell1.sh and shell0.sh on your SD card. You need them in the next section.
By using variables, you can make your shell scripts perform different actions every time
you run the script. For more information about variables on Linux, see Working with
Environment and Shell Variables.
Substitution occurs when you reference a variable in your shell script. For example, when
you run the command echo "The home directory is $HOME", the shell replaces the text
$HOME with the value contained in the variable HOME.
1. At the command line, type the following command and then press Enter:
nano /mnt/sda1/shell2.sh
2. On the first line, type the following text and then press Enter:
#!/bin/sh
5. Press Ctrl + X.
6. Press Y, and then press Enter.
Run this script to test it. You should see the list of directories that are in the top level of
the Linux file system.
1. At the command line, type the following command and then press Enter:
nano /mnt/sda1/shell0.sh
3. Press Ctrl + X.
4. Press Y, and then press Enter.
If you run the shell0.sh script now, it displays an error. The variable SCRIPTNUM is not
defined (it is treated as blank) and so script0.sh tries to execute a file called script.sh.
At the command prompt, type the following command and then press the Enter key:
export SCRIPTNUM=1
In this example, SCRIPTNUM is an environment variable so that all scripts can access it.
If you try to create a shell variable before calling your script, the script is not able to
access that variable.
However, if you declare the shell variable at the same time as you run the script then
the shell creates the variable in the same process as the script file. Then the script can
access it. To do this, specify shell variables before the call to your script, but on the
same line.
Type the following command and then press the Enter key:
SCRIPTNUM=1 /mnt/sda1/script0.sh
You can now delete the three script files from the microSD card2 .
To fix this, you need to run your script on the primary shell process. You can do this
using the source command. For example:
source ./script_file.sh
Or simply:
. ./script_file.sh
Inside the shell script, you can refer to these parameters using a dollar sign and then the
parameter number. $1 is the first parameter, $2 is the second, and so on. When the shell
encounters a parameter number in your script, it replaces it with the information from the
command line.
For example,
1. At the command line, type the following command and then press Enter:
nano /mnt/sda1/shell1.sh
2. On the first line, type the following text and then press Enter:
#!/bin/sh
5. Press Ctrl + X.
6. Press Y, and then press Enter.
Call this script in the usual way, but add on a space and then the file path of a directory:
./shell1.sh /etc
You should see the ls command displays the files from the directory that you specify,
and the preceding text Contents of $1 is replaced by Contents of /etc.
The shell variable always $# shows the number of parameters that you passed into the
script, and the variable $0 always contains the text that is typed on the command line to
start the script. In most cases, this is the file name of the script.
Making Decisions
In shell scripts, you can use the if command to make decisions. With this, you can
execute different commands depending on the values in variables, parameters, and even
the output of other Linux commands.
Tip: When writing if statements, pay attention to the spaces after a [ character
and before a ] character. These spaces are important in a shell script, and it is
an error to miss them out.
If the variable SCRIPTNUM contains the characters v1 then the script runs the
commands between then and fi.
In many situations, it does not matter whether or not you place variable names and
values in quotes. This is usually the case if you compare numbers, but it also applies to
text. For example:
if [ $SCRIPTNUM = v1 ]; then
...
fi
However, you should be aware that the shell uses substitution to handle variables, and
if the variable SCRIPTNUM is empty then the shell tries to run the following command:
if [ = v1 ]; then
This generates an error. If you surround variables and values in quotes then the script is
more robust. Using quotes, if the variable SCRIPTNUM is empty then the shell runs:
if [ = v1 ]; then
If you need to treat multiple words (or character sequences that include punctuation
symbols) as one item, you should wrap them in quotation marks. For example:
if [ $MSG = Hello World! ]; then
Without the quotes around Hello World!, the shell script compares the variable MSG
against the value Hello, and cannot understand how to process World!
The examples so far use = (equals) to test whether one item is the same as another. !=
(not equal to) tests whether the items are different.
You can run a different set of commands when the variable does not contain the correct
value. In the example below, the commands between then and else run if the value is v1;
the commands between else and fi run if the value is not v1.
if [ "$SCRIPTNUM" = "v1" ]; then
...
else
...
fi
With elif, you can make sophisticated decisions based on multiple potential values, and
then use else to state what happens if none of those conditions are met.In addition to
checking whether a variable or parameter contains a specific value, there are a number
of other conditions that you can test for. These conditions are placed inside square
brackets, and include:
Condition Description
-f filename Tests whether the specified file exists and is a regular file.
-r filename Tests whether the specified file is readable by the current user.
-w filename Tests whether the specified file can be written to by the current user.
Tests whether the first file is newer (was modified more recently) than
file1 -nt file2
the second file.
Tests whether the second file is newer (was modified more recently)
file1 -ot file2 than the first file.
string -lt Converts the string to a number and then checks whether it is less than
number the specified number.
string -gt Converts the string to a number and then checks whether it is greater
number than the specified number.
In Using Variables and Substitution, you write a script that calls other scripts depending
on the value of the environment variable SCRIPTNUM. That script causes an error if
SCRIPTNUM is not defined. You can extend the script to avoid this by testing whether
the environment variable is set:
if [ -n "$SCRIPTNUM" ]; then
/mnt/sda1/shell$SCRIPTNUM.sh
else
echo "No script to run."
fi
In the table above, you can see several other conditions that you can use to similar
effect. This includes using [ -e "/mnt/sda1/shell$SCRIPTNUM.sh" ] to check whether a
suitable file can be found, and [ -x "/mnt/sda1/shell$SCRIPTNUM.sh" ] to test whether a
suitable file can be found and make sure it is an executable script.
The final example of decision making in this section also involves an aspect of
substitution that is not referenced in the previous section on that subject: you can
capture the output of a Linux command and use this information in your if statements.
The Linux command id displays information about the user that is currently logged in. On
the Yn, this is usually the root user. At the command prompt, type the following
command and then press the Enter key:
id -u -n
In a shell script, you can test whether the current user is named root by using the
following if statement:
if [ $(id -u -n) = "root" ]; then
fi
This runs the id command and captures the output. The result is substituted into the test
condition and compared with the string root.
If you prefer, you can capture the output of id in a variable and then test that instead.
ME=$(id -u -n)
if [ "$ME" = "root" ]; then
fi
Repeating Commands
There are three basic loops in shell scripting. With these you can repeat a sequence of
commands a predefined number of times, or until a certain condition is met.
In the example above, the while loop runs until COUNT is equal to or greater than 11.
This has the effect of sending the numbers 110 to the console. The while loop does
not automatically increase the COUNT variable, you must do this yourself. At the start of
each phase of the loop, the shell evaluates the test condition. If it is true, the loop runs
again. If it is false, the loop ends and the script continues to run any commands below
the done statement.
Any of the test conditions mentioned so far can be used in a while loop. For example, to
force a script to wait until a certain file no longer exists:
while [ -e "/mnt/sda1/lock.txt" ]; do
sleep 1
done
The sleep command tells the shell to do nothing for the specified number of seconds.
Without it, the code above would check for the file lock.txt as fast as it can. That is a
waste, and working the processor that hard can drain a lot more power than is
necessary.
Tip: It is very easy to accidently write a loop that never ends. If this happens,
press Ctrl + C to terminate the current script.
The until loop works very similarly to the while loop. The difference is that until loops
only run while the test condition is false. So the example above waits until the lock.txt file
exists before exiting the loop and running any commands below the done statement.
The third type of loop is for. You can use the for loop to run a sequence of commands
on each item in a list, or each file in a directory. The example below is a basic version of
the Linux command ls.
for fn in /mnt/sda1/*; do
echo $fn
done
The code above uses a wildcard (asterisk) to match all files in the /mnt/sda1 directory.
During each phase of the loop, the shell copies a file name into the variable fn, and then
the echo command prints this value to the console.
The for loop expects that each item in the sequence is separated by a space, not a line
break. This means that the following example displays the output slightly differently than
you might expect.
for wrd in $(cat $0); do
echo $wrd
done
In Passing Arguments to Shell Scripts, you can see that $0 refers to the name of the
shell script that is running. The cat command sends a text file to the console, but by
using substitution you are able to capture that output and include it in the for loop.
Finally, there are two commands that are only used in the middle of loops: break and
continue.
Command Description
Jumps to the start of a loop, ignoring any other commands that are
continue
underneath the continue command.
You can use break to terminate a loop early. For example, if you use a loop to search
through a text file for the first instance of a particular word then you can use break to
stop when the word is found, rather than needlessly process the rest of the document.
The code above echoes the file name of every file or directory in the /mnt/sda1
directory, except for the one named arduino. The code below is an alternative way of
doing this.
for fn in /mnt/sda1/*; do
if [ "$fn" = "/mnt/sda1/arduino" ]; then
continue
fi
echo $fn
done
When fn equals arduino, the continue command tells the shell to skip the remainder of
the code and start again with the next file name. Because of this, the echo command
does not run on that occasion and the /mnt/sda1/arduino directory does not appear in
the output.
The shell command read takes keyboard input from the user and puts it into a shell
variable. This process continues until the user presses the Enter key.
By default, the read command echoes key presses back to the console so that the user
can see what they are typing. If you ask for sensitive information then you can disable
this feature by attaching the flag -s.
echo "Type your password and then press Enter:"
read -s pwd
The flag -t specifies how long the read command waits for input. You can use this in
combination with other flags. For example:
echo "Type your password and then press Enter within 5 seconds:"
read -s -t 5 pwd
All of the examples so far use the echo command to prompt the user for the information.
Because echo also sends a line break, this causes the input to appear on the next line
in the console. By using the read flag -p, you can prompt the user without sending a line
break.
read -p "What is your name?: " nm
echo "Hello $nm"
Stopping a Shell Script
If your script decides that it must stop (for example, in response to the user typing no in
a confirmation) then you can use the exit command. This stops the script immediately
and makes no further changes to the system. It does not undo any changes that have
already been made.
To use exit, you should also include a number that represents why the script is stopping.
This allows other scripts to decide what action to take.
The exit code zero indicates that the script terminated normally, and that no further
action is needed. For example:
exit 0
Defining Functions
Shell scripts can be quite large and complicated. And you may have long sequences of
commands that are repeated in other places in the same script.
One way of organizing a script and avoiding too much repetition, is to define a function.
Functions are blocks of code that only execute when another part of the script calls
them.
The example below defines a function named confirm. This prompts the user to confirm
an action, and exits the script if they type anything other than yes.
#!/bin/sh
confirm() {
read -p "Are you sure you want to do this? (yes/no): " rsp
if [ "$rsp" != "yes ]; then
echo "OK. Aborting script."
exit 0
fi
}
Note that when defining functions, the function name is followed by parenthesis, and the
body of the function (the commands that it runs) is placed between two braces. You can
run the function at any time by using its name.
You can also pass information into functions. For example, to modify the confirm function
so that you can change the prompt:
confirm() {
read -p "$1" rsp
if [ "$rsp" != "yes" ]; then
echo "OK. Aborting script."
exit 0
fi
}
The token $1 refers to the first piece of information that you pass into the function. $2
refers to the second, and so on.
When declaring variables inside the body of a function, they are still accessible to the
rest of the script. If you want to declare a variable that only your function can use,
include the keyword local. For example:
confirm() {
local pt=$1
read -p "$pt" rsp
if [ "$rsp" != "yes" ]; then
echo "OK. Aborting script."
exit 0
fi
}
Finally, you can make functions return actual values to the piece of the script that calls
them. To do this, use the return command. For example, to modify the confirm function
so that it returns 1 if the user types yes, and 0 if they type anything else (rather than
ending the script):
confirm() {
local pt=$1
read -p "$pt" rsp
if [ "$rsp" != "yes" ]; then
return 0
else
return 1
fi
}
The return value is available using the special variable $? immediately after the function
finishes. You can copy its value to another variable, or use it in an if statement:
echo "This operation will make changes to your Arduino."
confirm "Are you sure you want to do this? (yes/no): "
if [ "$?" = 1 ]; then
echo "OK. Making changes..."
else
echo "OK. I will stop here."
exit 0
fi
It is also possible to configure OpenWrt-Yun so that it runs one your scripts every time
you login to the Yn.
In this short example, you will create a script that defines a new environment variable
and then run this script every time you login.
1. At the command prompt, type the following command and then press Enter:
nano /mnt/sda1/config.sh
3. Press Ctrl + X.
4. Press Y, and then press Enter.
Now add a command to the /etc/profile script so that your config.sh scripts runs every
time you login.
1. At the command prompt, type the following command and then press Enter:
nano /etc/profile
2. On a new line underneath the final export command, add the following command:
source /mnt/sda1/config.sh
3. Press Ctrl + X.
4. Press Y, and then press Enter.
5. To log out of the terminal, type the following command and then press Enter:
exit
Reconnect to the Arduino Yn using SSH, type the following command, and then press
the Enter key:
echo $ME
Because this is only an example, you should remove the reference to the script from
/etc/profile before continuing. It is no longer needed. You can also delete the config.sh
file.
1. At the command prompt, type the following command and then press Enter:
nano /mnt/sda1/config.sh
3. Press Ctrl + X.
4. Press Y, and then press Enter.
Now add a command to the /etc/rc.local script so that your config.sh scripts runs every
time the Arduino starts up.
1. At the command prompt, type the following command and then press Enter:
nano /etc/rc.local
2. On a new line before the exit 0 command, add the following command:
source /mnt/sda1/config.sh
3. Press Ctrl + X.
4. Press Y, and then press Enter.
5. To restart the Arduino, press the YN RST button.
Log back into the Arduino using SSH, type the following command, and then press the
Enter key:
cat /mnt/sda1/list.txt
The difference between running a script from /etc/rc.local and /etc/profile becomes more
obvious if you delete the list.txt file. For example,
When you log back in, the list.txt file does not exist because rc.local only runs your script
when the Arduino is reset.
You can now remove the source /mnt/sda1/config.sh line from your /etc/rc.local file and
delete the script.
Tip: You can also edit the /etc/rc.local file from the Arduinos advanced
configuration panel. In the System tab, click Startup.
To access the advanced panel, and modify the settings for cron:
1. On a machine that connects to the same network as the Arduino, open a web
browser.
2. In the address bar, type http://arduino.local
3. Type the current password3 and then click LOG IN.
4. Click CONFIGURE.
5. Click advanced configuration panel (luci).
6. Click the System tab, and then click Scheduled Tasks.
To schedule a task, you need to add a line to the Scheduled Tasks box. Each task
should appear on its own line and follow a set format that is made up of six parts:
minute hour day month day_of_week command
Part Description
minute The number of minutes past the hour (059) that this task runs at.
day The day of the month (131) that this task runs on.
day_of_week The day of week name (06 or SunSat) that this task runs on.
Each part (with the exception of command) can also be an asterisk. This indicates that
you dont care what the value is.
To Do This
Run a task at 30 minutes past the hour, every hour Define the task with 30 * * * *
For example, to schedule the config.sh file from Configuring OpenWrt-Yun to Run a
Script when It Starts, type the following line into the Scheduled Task box:
0 * * * * source /mnt/sda1/config.sh
Click the Submit button, and then reboot the Yn for these changes to take effect.
Expanding the Linux File System onto the
microSD Card
OpenWrt-Yun takes up most of the storage space on the Atheros AR9331 SoC. This
leaves very little room for any additional files and programs that you may want to install.
To increase the amount of space available to Linux, you can configure the Yn to use
the microSD card for the file system. This divides the microSD card into two partitions
a FAT32 data partition which you can use to transfer files from your PC or M ac to the
Arduino, and a Linux partition that is used by OpenWrt-Yun. M icrosoft Windows and
Apple M ac OS X cannot read the Linux part, and so if you put your microSD card back
into a PC or M ac then it appears to have shrunk in capacity.
Tip: This partitioning is not permanent. If you want to make the whole SD card
accessible to Windows or Mac OS X, you need only delete the two partitions and
create a new primary partition that is set to use all of the space on the card. There
are many applications that you can use to do this, including diskpart (already
installed on Windows) and GParted.
To help you expand the file system, Arduino LLC have a sketch that you can download
from their web site. This runs on the Arduino Yn and does most of the work for you.
This process deletes everything on the microSD card, so check that the card does not
contain any files that you want to keep.
1. If you have any files open on the microSD card, close them. Then close any remote
connections to the Yn.
2. In a web browser, type the following URL into the address bar:
http://arduino.cc/en/uploads/Tutorial/YunDiskSpaceExpander.zip
3. If you are on Windows: find the .zip file you downloaded and right-click it. Point to
Open with and then click Windows Explorer.
4. If you are on M ac OS X: double-click the .zip file that you downloaded and it will
unzip to a new folder4 . In a Finder window, open the newly-unzipped folder.
5. Double-click the .ino file to open it in the Arduino IDE.
6. On the toolbar, click Upload.
7. On the Tools menu, click Serial Monitor.
8. In the No line ending list, click Newline.
9. In the textbox at the top of the Serial Monitor, type yes and then press Enter.
10. Type yes and then press Enter.
11. Type yes and then press Enter.
12. Type a number (in megabytes) for the size of the FAT32 data partition. Then press
Enter.
13. On the Yn, press the YN RST button.
14. Unplug the power supply from the Yn, then reinsert it and wait for OpenWrt-Yun to
load.
You can access the microSD card from the Linux file system using the path /mnt/sda1/.
This applies whether you expand the Linux file system or not.
When you expand the Linux file system onto the microSD card, the
YunDiskSpaceExpander sketch creates an arduino directory (and a www subdirectory) on
the card. It also creates a symbolic link between /www/sd and the
/mnt/sda1/arduino/www directory. This link is useful when you want to run webpages
from the Yn. For more information about hosting websites, see Hosting Websites and
Services.
After expanding the file system, the Yn needs the microSD card to load correctly. If you
remove or change the card then you have to reconfigure the Yn.
Installing Linux Packages
When installing new software on Linux, there are several steps that must be taken:
To assist in this process, Linux distributions use tools called package managers. These
tools try to perform all of the steps above for you. On OpenWRT and OpenWrt-Yun, the
package manager is OPKG.
A repository is an online collection of software that you can download and install by
using a package manager. Each item is called a package and, in most cases, software
in a Linux repository is free. Initially, it can be helpful to think of a repository as a
simplified version of the Apple App Store or Google Play.
On the command line, type the following command then press Enter:
opkg update
You can use the command list to display all of the software that is available in the
repository, or to search for packages by their name.
If a package with that name cannot be found, OPKG displays no information. If the
package is there, OPKG displays a short summary (usually a version number and brief
description) of the software. You can see more information by using the info command,
You can also use wildcards (typed as an asterisk) to search file names. To view only
packages that have a name beginning with the three characters yun,
Or to view a list of packages that contain the characters gcc anywhere in their name,
Installing a Package
Before installing additional software on your Yn, you should expand the Linux file
system so that it can use the microSD card for storage space. Packages, and all of the
software that they depend on, can be quite large and the AR9331 SoC only has a few
megabytes of free space. For more information, see Expanding the Linux File System
onto the microSD Card.
When you have found a package in the repository that you want to install, you can
download and install it with one command: install.
For example, to download the libxml2 package (a library for manipulating XM L files),
This process also downloads and installs any of the dependency software that the new
package needs. In this example libc, libpthread, and zlib are also downloaded if they are
not already on the system.
Tip: When software installs, it is copied to the correct file system folders.
However, each package is different and it may not be obvious how to use the
software. You may have to search the web and find the developers website in
order to learn how to use the package.
To view a list of all of the packages that are installed on your Arduino,
As with the list command, you can also use package names and wildcards with list-
installed.
It is not uncommon for the developers of packages to update them. They usually do this
to fix a bug or problem in the software, or to add new features.
You can use the package manager to update the software on your Yn. To view a list of
any packages that are out of date,
Then to update a single package, you can use the upgrade command. For example,
To uninstall a package and free up space on your Arduino Yn, you can use the remove
command. This does not uninstall any packages that the software depends on. For
example,
To access this:
1. On a machine that connects to the same network as the Arduino, open a web
browser.
2. In the address bar, type http://arduino.local
3. Type the current password and then click LOG IN.
4. Click CONFIGURE.
5. Click advanced configuration panel (luci).
6. Click the System tab, and then click Software.
To download the up to date list of packages that are available in the repository:
To find a package that is either installed on your Arduino or available from the repository:
1. In the Filter box, type any part of the name of the package.
2. Click Find Package.
To install a package:
In either case, you may then have to reconfigure the Yns network connection, install
any additional Linux packages that you need, and expand the Linux file system to the
microSD card.
When you install an upgrade image on the Yn, it overwrites the backup data that the
reset process uses. So resetting OpenWrt-Yun using the WLAN RST button puts the
Linux side of the Yn back into the same state that it was in when you first installed the
upgrade image. It does not return the Yn to the same state that it was in when you first
received the board.
Backing Up the Arduino Yn
As with any computer, there can be times when hardware fails or when you need to
reinstall the operating system. To avoid losing work, you should ensure that you have a
backup of all of your important files and configuration settings.
You can backup network configuration, password settings, and other OpenWrt-Yun
configuration files from the advanced configuration panel;
You can download important files to another computer, using secure copy protocol
(SCP);
If you expand the Linux file system onto the microSD card, you can make a copy of
the entire card.
To create a backup of your configuration files and download them to another computer:
1. On a machine that connects to the same network as the Arduino, open a web
browser.
2. In the address bar, type http://arduino.local
3. Type the current password and then click LOG IN.
4. Click CONFIGURE.
5. Click advanced configuration panel (luci).
6. Click the System tab, and then click Backup / Flash Firmware.
7. Under Backup / Restore, click Generate archive.
The backup file is a .tar.gz archive that contains the contents of the OpenWrt-Yun /etc
directory.
1. On a machine that connects to the same network as the Arduino, open a web
browser.
2. In the address bar, type http://arduino.local
3. Type the current password and then click LOG IN.
4. Click CONFIGURE.
5. Click advanced configuration panel (luci).
6. Click the System tab, and then click Backup / Flash Firmware.
7. Next to Restore backup:, click Browse.
8. Click the .tar.gz file that contains your configuration files, and then click Open.
9. Click Upload archive...
After a reinstall of OpenWrt-Yun, you can restore files by uploading them from your
computer to the Arduino. However, this is not always suitable when transferring Linux
executables and other software that you install on the Yn.
When you expand the Linux file system onto the microSD card, the process divides the
card into two partitions. One of these is a FAT32 data partition that you can use to store
your files. The other contains a copy of the Linux system and any additional software
that you install.
On M ac OS X and Linux, you can use the command dd to create a file that contains an
exact copy of the microSD card. On M icrosoft Windows, you need to install additional
software.
These copies are often called disk images. Disk images that you make from hard drives
and disks contain all of the extra information about the partitions and the permissions of
files.
6. Find the Device in the list. You should see two devices (one for each partition on
the microSD card) with similar names. For example: /dev/sdb1 and /dev/sdb2. One
has the type W95 FAT32 (LBA) (or similar description) and the other has the type
Linux. The parent device of these two devices is /dev/sdb.
7. Type the following command and then press Enter:
sudo dd if=/dev/sdb of=./yun-backup.bin
To restore a backup,
6. Find the device label for the microSD card it contains two partitions. For example,
/dev/disk2.
7. Type the following command and then press Enter:
dd if=/dev/disk2 of=yun-backup.bin
To restore a backup,
Even though Windows cannot recognize the Linux partition and you can only select the
FAT32 partition in Win32DiskImager, the tool reads the entire contents of the card.
To restore a backup:
1
This number shows the number of links to the file. It is not important at this stage.
2
rm /mnt/sda1/*.sh
3
If you have not changed it, the password is arduino.
4
If you have installed an archiving tool on your Mac, the OpenW rt-Y un update image might open with this instead. If
you need help, consult the documentation for your archiving tool.
Programming in Python
Python is a programming language that you can use to control the Atheros AR9331 SoC
on the Arduino Yn. It is similar to the variety of C/C++ that you use to write sketches
on the Arduino, and many of the commands and structures that are in Arduino C are also
in Python.
Unlike C, Python is an interpreted language. This means that you do not compile your
programs as you do with an Arduino sketch. Instead, a piece of software (an interpreter)
runs on OpenWrt-Yun Linux. This software reads your source code and performs the
instructions for you. Version 2.7.3 of the Python interpreter is already installed on your
Arduino Yn.
Python users often encourage programmers to use a specific style of coding, called
Pythonic. Pythonic is a set of style guidelines that promotes writing programs in a way
that highlights the unique features of the language.
This chapter is an introduction to the Python language, and there are certain elements
that are not covered here. It is primarily aimed at readers who are used to Arduino C,
and sometimes uses code samples that are not Pythonic so that they are easier to
understand. When you are more familiar with programming in Python, you should read
the Pythonic style guidelines at http://www.python.org/dev/peps/pep-0008/, and there
are a large number of online resources that can help you learn even more about the
language.
In This Chapter
In interactive mode, the Python interpreter is a command-line interface (CLI) that you
can use to type Python commands one by one.
1. Login to the OpenWrt-Yun command line. For more information, see Connecting to
the Arduino Yn.
2. Type the following command and then press Enter:
python
The Python command prompt is different to the Linux command prompt. You can tell that
you are in the Python environment when the command prompt is >>>.
To run a command, type it on the keyboard and then press the Enter key. For example,
to display a message in the console,
At the Python command prompt, type the following command and then press Enter:
print "Hello from Python."
To close the Python CLI and return to the Linux command line, either
Or press Ctrl + D.
Type each command in a Python script on its own line. And, unlike C, you do not end
the command with a semi-colon.
In the following sections of this chapter, you will learn how to use the Python language
to write programs. For now, create and run a short sample script.
1. At the Linux command prompt, type the following command and then press Enter:
nano /mnt/sda1/test.py
3. Press Ctrl + X.
4. Press Y and then press Enter.
Take care to type in the script accurately, and remember to place four spaces before the
text print name.
Tip: The line beginning with the symbol # is a comment. The Python interpreter
ignores these lines and so you can use them to add extra information to your
program.
At the Linux command prompt, type the following command and then press Enter:
python test.py
In the same way as a shell script can specify the binary program that Linux should use to
interpret it, the first line of a Python script can also include a reference to the Python
interpreter.
2. Add the following text on its own line at the top of the file:
#!/usr/bin/python
3. Press Ctrl + X.
4. Press Y and then press Enter.
With this reference on the first line, you can run the Python script without calling the
Python interpreter yourself. For example, type the following command and then press
the Enter key:
/mnt/sda1/test.py
Performing Basic Arithmetic
There are several different types of number in Python. The two most common are
integers and floating-point numbers (floats).
An integer is a whole number it does not have a decimal point or a fraction. It can be
negative (less than zero) or positive (greater than zero). The numbers 1, -3, and 0 are
examples of integers.
A float is a number that can use a decimal point. The numbers 88.8, -0.0123, and 1.0 are
examples of floats.
You can use mathematical operators to combine numbers in different ways and calculate
the result.
For example,
1. At the Linux command prompt, type the following command and then press Enter:
python
2. At the Python prompt, type the following command and then press Enter:
print 5 + 18.3
The print() function displays a message or value in the console. You can also enclose
the message in parenthesis: print(5 + 18.3)
To Do This
Subtract the second number from the Type the first number, followed by - and
first then the second number.
Divide the first number by the second Type the first number, followed by // and
and return an integer then the second number.
Perform modulo arithmetic Type one number, followed by % and then
another number.
Working with Strings and Variables
As in Arduino C, a string is a sequence of characters that you surround with quotation
marks. The sample script in Running a Python Script, includes the string What is your
name?
Strings can contain any letter or number, and most punctuation marks. For special
characters such as line breaks, quotation marks, or tabs, you can use escape characters
to add them to a string. Escape characters begin with a single backslash, and the Python
interpreter replaces them with the chosen character automatically. For example, you can
include \tto insert a tab, \" to insert a quotation mark, and \n to include a line break. But in
Python, you can also declare triple-quoted strings that include any line breaks and white
space that you type into the script file.
print "Twas the Night Before Christmas"
print "by Clement Moore"
print """
Twas the night before Christmas,
When all through the house,
Not a creature was stirring,
Not even a mouse.
"""
And you can use the * operator to repeat strings a set number of times. In the following
example, the parenthesis ensure that Python only repeats the string !, not the whole
phrase.
print "Hello World" + ("!" * 5)
Tip: In Working with Classes and Objects, you can see other ways of using and
manipulating strings in Python.
Creating Variables
A variable is an area of the computers memory that you give a name to. You can use
variables to hold information until the Python script ends.
To declare a variable in Python: specify a name, followed by the equals sign and then a
value. For example,
result = 1
The name can begin with a letter (az or AZ) or an underscore. Any following
characters can be a letter, an underscore, or a number.
When you use the variable in your program, the Python interpreter tries to decide the
best way of working with the information in the variable. This can sometimes cause
errors.
The following command is valid, and Python converts the number into a string before
appending it to the message.
message = "This is a test (" + 5 + ")."
However, this next command causes an error. Since the number comes first, Python
attempts to convert anything that follows into a number.
message = 5 + "This is a test."
There are two Python functions that you can use to convert a string to a number, and
doing so avoids the error.
Function Description
For example:
v = "6"
message = 5 + int(v)
If the string cannot be converted to a number (for example 6 mm) then the Python
interpreter displays an error. When asking the user to type numbers into your scripts on
the keyboard, there can be a problem if they type letters instead. You can catch these
types of error for more information see Handling the Errors in Scripts.
There are three common types of collection in Python: lists, tuples, and dictionaries.
They appear to be similar to the arrays that you use in Arduino C, but each one has a
slightly different purpose.
Lists package multiple pieces of information together into a single variable. To create a
list, use square brackets. For example, to create an empty list:
mylist = []
Or to specify the values that you want in the list, separate each item with a comma. For
example:
greek = ["Alpha", "Beta", "Gamma", "Delta", "Epsilon", "Eta"]
Each item in the list is accessible by its position number, with the first item being at
position zero. In the example above, position five is Eta. You can change an item (and
fix the deliberate mistake) by using this position number, for example:
greek[5] = "Zeta"
To find the length of a list, use the function len(). The following command outputs the
value 6 because there are six items in the greek list.
print len(greek)
You can also delete items from a list. To do this, use the function del().
del greek[2]
When an item is deleted, all of the items that follow it in the list move up one position.
So deleting Gamma from position two puts Delta into position two, Epsilon into position
three, and Zeta into position four.
In Python, lists are a special type of object, and so they contain several methods that
you can use to manipulate the contents of the list. For more information about objects,
see Working with Classes and Objects.
Method Description
Searches the list for the specified item and returns its position in the
index(object)
list.
insert(position,
Adds an item to the list in the specified position.
object)
To use a method, add a dot after the list name and then specify the method (and any
arguments). For example, to remove the Gamma item:
greek.remove("Gamma")
A tuple is very similar to a list. However, once a tuple is created, you cannot change the
items in it. You cannot add and remove items from a tuple.
Because they are a simpler than a list, the Python interpreter can handle them
significantly faster. This makes them useful when you need to pass a predefined number
of items between different parts of your code. For example, when returning multiple
values from a function.
Like in a list, each item in a tuple is ordered. You can access an individual item by using
square brackets and its position. For example:
print mytuple[0]
And you can find the number of items in a tuple with the len() function:
var1 = len(mytuple)
The third type of collection is a dictionary. Again, these are very similar to Pythons lists.
However, you can give each slot in a dictionary a name (or key). Instead of referring to
items by their position, you can use this key.To create a dictionary, use curly braces. For
example:
character = { "Name":"Legolas", "Class":"Archer", "Race":"Wood Elf", "Age":2931 }
Each item consists of two parts: the name of the key, and a value. Use a colon to
separate these two parts, and a comma to separate each item in the dictionary. The key
is always a string, but the value can be any type of data.
The len() and del() functions work the same way on dictionaries as they do on lists. But
there are also several other methods that you can use.
Method Description
update(dic) Adds the items from the specified dictionary to the current dictionary.
This example uses the raw_input() function to prompt the user for text input on the
keyboard.
The == operator compares two values (the users input and the string y) and returns
True if they are equal, or False if they are different.
There are a few important differences between the if statement in Python and its
equivalent in Arduino C:
You do not surround the code that runs if the statement is True with curly braces;
Put a colon at the end of the first line;
Indent the code that runs if the statement is True. Four spaces is recommended.
Indenting the code is not only to make reading the source code easier. The Python if
statement expects you to indent all of the code that the interpreter should run if the
condition is True. When the interpreter finds code that it is not indented, it treats this as
the end of the if statement.
You can extend the if statement with a block of code that runs if the condition is False.
To do this, use the keyword else.
inp = raw_input("Continue? (y/n): ")
if inp == "y":
print "OK."
print "I shall continue."
else:
print "OK."
print "I shall stop here."
exit
print "This line only runs if the user types y."
And you can also use the if statement to check for several possible values. To do this,
add another condition using the keyword elif.
inp = raw_input("Abort, Retry, Fail?")
if inp == "a":
print "Abort."
elif inp == "r":
print "Retry."
elif inp == "f":
print "Fail."
else:
print "Invalid input."
There are several other operators that you can use in Python. All of these compare two
values and return either True or False.
Operator Description
Returns True if two values are not equal, and False if they are. This is the
!=
opposite of ==.
Returns True if the first number is greater than the second, and False if it
>
is not.
Returns True if the first number is less than the second, and False if it is
<
not.
Returns True if the first number is greater than or equal to the second,
>=
and False if it is not.
Returns True if the first number is less than or equal to the second, and
<=
False if it is not.
To combine two (or more) conditions into one if statement: place each condition in
parenthesis and then use one of the logical operators below:
Operator Description
and Returns True if both conditions are True, and False if either one is False.
For example:
inp = raw_input("Abort, Retry, Fail?")
if (inp == "a") or (inp == "r") or (inp == "f"):
print "Valid input."
else:
print "Invalid input."
The not() function inverts the value of a condition. If the condition is True, then not()
makes it False. If the condition is False then not() makes it True.
To use it, type not and then place your conditions inside parenthesis. In the following
example, if the users input is not a, r, or f then the script prints the message Invalid
input.
inp = raw_input("Abort, Retry, Fail?")
if not( (inp == "a") or (inp == "r") or (inp == "f") ):
print "Invalid input."
else:
print "Valid input."
Tip: If you want to put another if statement inside an if statement, you can. Just
remember to indent the second if statement with four spaces. This means that the
code that runs if the second if statement is True is indented with eight spaces.
Looping your Code
You can use loops to repeat sequences of Python commands a set number of times, or
until something changes.
In Python, the while loop looks quite similar to an if statement. It repeats the indented
code while the condition is True, and stops when the condition becomes False.
while raw_input("Abort, Retry, Fail?") == "r":
pass
exit
Like the if statement, indenting the code is important. When the interpreter reaches code
that is not indented, it treats this as the end of the loop.
The Python code above simulates the infamous Abort, Retry, Fail? error message from
early versions of M icrosoft DOS. Selecting abort or fail causes the program to end, but
selecting retry only repeats the message.
Loops and if statements in Python cannot be empty. However, you can use the pass
command to create loops that do nothing.
Tip: It is quite easy to accidently write an infinite loop in Python a loop that
never ends. To break an infinite loop, press Ctrl + C.
The for loop in Python is a little different from the one in C. It is primarily used for working
with lists, tuples, and dictionaries, and is more similar to the foreach loop in languages
such as PHP and C#.
f4 = ["Reed", "Ben", "Sue", "Johnny"]
for fantastic in f4:
print fantastic
Each time the loop runs, Python copies the next item in the list into the variable
fantastic. When there are no more items, the loop ends.
You can replicate the typical C-style for loop that counts from one number to another. In
C, you create this loop using the code:
for (int count=0; count < 5; count++) {
Serial.println(count);
}
However, replicating the C-style loop is not recommended in the Pythonic style
guidelines. Instead, you can use the range() function to create a special type of list and
then use the for loop.
for count in range(0, 5):
print count
In this example, range(0, 5) creates a list that contains the numbers 0, 1, 2, 3, and 4.
Finally, there are two commands that you can use inside a loop. These are ways to
change how the loop runs.
Command Description
Jumps to the top of the loop, ignoring any other commands that are
continue
below the continue command.
Writing your Own Functions
In the examples so far, you have seen several functions that are built-in to Python.
These include len(), range(), print(), and raw_input().
Breaking your code up into smaller functions helps to make your scripts more readable
and easier to work with. And when you begin working on a new project, you may find
that some functions that you wrote in another script are useful in this one. It is easier to
copy and paste a function from one script to another than it is to copy commands and
variables from one long, continuous sequence of Python commands.
To define a new function, use the def keyword followed by a name, then empty
parenthesis and a colon.
def myfunction():
The name of your function should follow the same rules as variable names they should
begin with a letter or underscore, and then may also include numbers in the characters
that follow.
After the declaration, indent all of the code that you want to run in the function. For
example:
def myfunction():
print "This is my function."
print "There are many like it,"
print "But this one is mine."
When the Python interpreter reaches code that is not indented, it treats this as the end
of the function.
The code in a function does not run until you call it. To call a function, specify its name
followed by empty parenthesis.
myfunction()
When you use functions like len(), range(), and raw_input(), you pass in arguments to
those functions to provide data for them to work on. You can give your own functions
this same ability. To do this, you must define the parameters that your function accepts,
in the functions declaration.The example below defines a function that accepts one
argument:
def inc(val1):
val1 = val1 + 1
The next example accepts two arguments:
def add(val1, val2):
val1 = val1 + val2
To call these functions, you need to include the extra information in parenthesis:
inc(x)
add(y, z)
These examples highlight one important difference between Python and other
languages. When you pass a variable into a function, the Python interpreter actually
sends a reference to that variable. It does not send a copy of the data. This means that
functions like inc() and add() can permanently change the variables that you pass into
them.
Doing this can be confusing; you should avoid changing arguments in your functions.
Instead, you can copy the data to new variables, make the changes, and then return the
new value.
def add(val1, val2):
v1 = val1
v2 = val2
v1 = v1 + v2
return v1
With functions that return values, you can capture the result into a variable:
result = add(5, 7)
Tip: If you write a function that needs to return multiple values, you can return a
tuple. For more information, see Introducing Lists, Tuples, and Dictionaries.
To do this: define both parameters in the function, but then specify a default value for the
second.
def inc(val1, val2 = 1):
return val1 + val2
You can call this function like this:
result = inc(5)
Or like this:
result = inc(5, 7)
If you only send one argument, then the Python interpreter uses the default value (1) for
val2.
Whether you define a function with one, two, three, or more parameters, you can make
parameters optional.
Working with Classes and Objects
Object-oriented programming is a technique where variables and pieces of data are
treated as standalone objects. Simple objects contain the values that you give to them,
but also information about what type of object they are and even functions that only
operate on that object.
The C compiler that translates Arduino sketches into executables that run on the
Arduino, is actually a C++ compiler. C++ is a variant of C that includes features for
working with objects. And so, as an Arduino user you are already using object-oriented
programming.
In an Arduino sketch, this code tells the Serial object to run its println() method:
Serial.println("Hello Serial Monitor");
In Python, string variables are instances of the string class. A class is a blueprint that
specifies what information an object can hold, and what methods it implements. When
you create an instance of a class, you create an object in memory that follows the
structure that is set by that class.The string class contains several methods that you can
use to create new strings by modifying existing ones, and several other methods that
you may find useful.
Method Description
capitalize() Returns a copy of the string, with the first letter in uppercase.
Returns a copy of the string, padded with space characters so that the
center(width)
text appears centered.
Finds the location (starting at zero for the first character) of the
find(seq) specified sequence of characters in the string. Returns -1 if the
sequence is not in the string.
Returns a list of strings each word in the string. The default setting
splits strings whenever there is a space. You can change this action
split() by calling the split() method and passing another string. For example,
to split a string into separate pieces every time there is a comma, call
mystring.split(",")
strip() Returns a copy of the string, without any spaces at the start or end.
You can tell an object to run a method by typing the name of the object, followed by a
dot, and then the name of the method. For example:
mystring = "hello world!"
print mystring.capitalize()
In the following examples, you will create and work with a new class called Movie.
Objects of this type represent an individual movie, and contain multiple pieces of
information about it.
To define a new class, use the keyword class and then a name. Indent the contents of
the class with four spaces. When the Python interpreter finds code that is not indented,
it treats this as the end of the class.
Objects can contain multiple values, and this class definition specifies that objects based
on the Movie class contain three variables. Variables that you create inside an object are
called fields. The keyword None indicates that the field does not have a default value.
To create an instance of the Movie class and get a usable object, add the following line
underneath the class definition (do not indent it):
house = Movie()
To set the information about this film, add the following code underneath the last line:
house.title = "House on Haunted Hill"
house.director = "William Castle"
house.year = 1959
house = Movie()
house.title = "House on Haunted Hill"
house.director = "William Castle"
house.year = 1959
print house
At the command prompt, type the following command and then press Enter:
python /mnt/sda1/movietest.py
Instead of the information you are expecting, you will see an object reference in the
terminal:
<__main__.Movie instance at 0x776daa58>
This is because the print command does not know how to work with your custom object
it expects a string. But you can add a method to the Movie class that returns a
printable string.Underneath the line year = None, add the following code. Remember to
indent this code because you want the method to be part of the Movie class.
def info(self):
return self.title + " (" + str(self.year) + ")"
Run the script again and the output is now House on Haunted Hill (1959).
When defining methods, the first parameter must always be self. The self parameter
allows the methods to refer to variables that are inside the current object. But notice that
when you call the method later, you do not need to pass in anything. The Python
interpreter handles all of this for you.
def info(self):
return self.title + " (" + str(self.year) + ")"
house = Movie()
house.title = "House on Haunted Hill"
house.director = "William Castle"
house.year = 1959
print house.info()
You can also use lists, tuples, dictionaries, and even other objects as fields in a class.
To extend the Movie class with a tuple that holds the names of actors in the movie, add
the following line underneath year = None
starring = None
One of the reasons object-oriented programming is powerful, is that you now have a
single variable house that contains all of the information that you want to hold about the
movie, and also a method for displaying its title. And because objects can be assigned
to variables, they can also be assigned to lists.Remove the line print house.info() and
add the following code to the bottom of the file:
house2 = Movie()
house2.title = "House on Haunted Hill"
house2.director = "William Malone"
house2.year = 1999
house2.starring = ("Geoffrey Rush", "Famke Janssen", "Taye Diggs")
favorites = [house, house2]
The code above creates a second instance of the Movie class, and then adds the
information for the 1999 remake of the first film.
The final line in that code creates a list and adds both objects to it. Because both of the
objects in favorites are instances of Movie, they both contain the method info(). So you
can display all of the titles in the favorites list using a for loop:
for mv in favorites:
print mv.info()
And that code works regardless of how many different Movie objects you add into the
list.
def info(self):
return self.title + " (" + str(self.year) + ")"
house = Movie()
house.title = "House on Haunted Hill"
house.director = "William Castle"
house.year = 1959
house.starring = ("Vincent Price", "Carol Ohmart", "Richard Long")
house2 = Movie()
house2.title = "House on Haunted Hill"
house2.director = "William Malone"
house2.year = 1999
house2.starring = ("Geoffrey Rush", "Famke Janssen", "Taye Diggs")
favorites = [house, house2]
for mv in favorites:
print mv.info()
Underneath the class definition for Movie, define TVShow using the code below:
class TVShow(Movie):
series = None
season = None
episode = None
You tell Python which class to inherit fields and methods from, by specifying its name in
parenthesis after the name of the new class. In the code above, although you have only
declared three fields, all of the fields from the Movie class are also available in TVShow
objects.
In the script, before the line favorites = [house, house2], add the following code:
tv = TVShow()
tv.title = "The Competition Begins"
tv.year = 2011
tv.starring = ("Abby Lee Miller", "Brook Hyland", "Maddie Ziegler-Gisoni")
tv.series = "Dance Moms"
tv.season = 1
tv.episode = 1
Change the line favorites = [house, house2] to read:
favorites = [house, house2, tv]
Then run the script again. Even though the tv object is a different type, the code that
prints the contents of the favorites list does not know this. TVShow objects have the
same info() method and so everything works.
For example, underneath the line episode = None, add the following code:
def info(self):
return self.title + " (TV - " + str(self.year) + ")"
All objects in Python have several methods that you do not write or call explicitly, but
that are used by the Python interpreter at various times. You can override these. For
example, Python uses a built-in method __str__ to try and convert an object to a string
when using the print function.
Instead of having the method info() return a string, you can override __str__.
The table below shows a few of the other built-in methods that you might want to
override.
Method Description
__lt__(self, Should return True if the current object is less than the other object.
other) Or False if it is not.
__le__(self, Should return True if the current object is less than or equal to the
other) other object. Or False if it is not.
__eq__(self, Should return True if the current object is equal to the other object. Or
other) False if it is not.
__ne__(self, Should return True if the current object is not equal to the other object.
other) Or False if it is not.
__gt__(self, Should return True if the current object is greater than the other
other) object. Or False if it is not.
__ge__(self, Should return True if the current object is greater than or equal to the
other) other object. Or False if it is not.
Should return a negative integer if the current object is less than the
__cmp__(self, other object, or a positive number if the current object is greater than
other) the other object. If the two are objects are equal, the __cmp__
method should return 0.
You can override these methods and use them to create complicated objects that you
can compare using the standard Python operators. For example, if you have a field
rating, which holds an integer representing how good the movie or TV show is, you can
use this to compare the objects.
Without overriding the built-in methods, you can do this using an if statement like:
if movie1.rating > movie2.rating:
By overriding the comparison methods, you can define what makes one object greater
than another. This can be based on a combination of fields, not just one.
Tip: If you override one of the comparison methods, it is usually best to override
them all.
In the examples so far, you initialize the fields of an object on individual lines after the
object is created. But you can also initialize fields at the same time as you create the
object. To do this, you need to override the built-in method __init__.
For example, to override __init__ so that you set the title and year of a movie or TV
show at the same time, add the following method override to the Movie class:
def __init__(self, title, year):
self.title = title
self.year = year
Then when you create the object, pass in values for title and year:
house = Movie("House on Haunted Hill", 1999)
You can include as many initializers in the override for __init__ as you want to.
Using Packages and Modules
A module is a collection of classes and functions that you can use in your projects
instead of having to code everything yourself. This is the equivalent of using libraries
and #include statements in Arduino C.
There is large number of packages and modules available that extend the capabilities of
Python on the Yn. Using these can save you many hours of programming time. Some
of these modules work with very complicated Linux libraries that perform advanced
tasks, such as: playing media files; communicating over a network or the Internet; storing
and retrieving data from databases; and many more.
To save space on the Arduino Yn, very few Python packages and modules are
installed. However, you can install others.
To use pip, you must install support for secure sockets layer (SSL) and the easy_install
program. To install all of these programs:
pip works in a very similar way to the command-line package manager OPKG.
You can also use the list command to show whether any of the Python modules on your
Arduino need updating.
To search the Python Package Index, use the search command followed by a keyword.
This command searches all of the titles and descriptions in the package index, and then
shows a list of the ones that contain the keyword you type.
To install a Python module, use the install command. For example, to install the
id3reader module,
When you install Python modules using pip, the software copies all of the files that are
needed into the appropriate Python module directory, and you can use them in your
Python scripts immediately.
Tip: Not all of the modules on the Python Package Index can be used on the
Arduino Yn. Many of them depend on Linux libraries or programs that are not yet
available from the OpenWrt-Yun repository. You can find detailed information
about each Python package on the PyPI website.
Install the id3reader module using the instructions in Downloading and Installing Python
Packages. Then copy an .mp3 file over to the SD card on the Arduino Yn.
Start a new Python script, and then add the following command to the top of the file:
import id3reader
The import command tells the Python interpreter to load a module because your script
uses it.
To actually use the id3reader module, you need to create an instance of the Reader
class and pass in the file name and file path of your M P3 file.
id3 = id3reader.Reader("/mnt/sda1/Track 01.mp3")
To tell the Reader object to return a specific piece of information, use the method
getValue() and pass in the name of the information that you want.
Value Description
album The name of the album that the song appears on.
You can also import modules and give them a different name by using the keyword as.
This changes the way you create an instance of the class:
import id3reader as ID3
myID = ID3.Reader("/mnt/sda1/Track 01.mp3")
And you can be very specific about what you want to import. For example:
from id3reader import Reader as ID3
id3 = ID3("/mnt/sda1/Track 01.mp3")
print id3.getValue("track") + " - " + id3.getValue("title")
print "Artist: " + id3.getValue("performer")
print "Album: " + id3.getValue("album") + " (" + id3.getValue("year") + ")"
The keyword from tells the Python interpreter that you only want to import one specific
class from the id3reader module.
When modules are grouped into packages, you use the from keyword to tell the
interpreter which module you want from the package:
from packagename import modulename
Tip: You will usually need to find the documentation for the package that you
install in order to learn what modules are available and how to use them.
Reading and Writing to Files
On the Arduino Yn, the ability to create, read, and write files is especially important
because it is also how you communicate with many of the Yns features.
Python has built-in support for working files of two types: text files, and binary files. The
difference between the two is that when you open a file as a text file, Python handles
line breaks and character encodings for you. When you open a file as a binary file, you
have raw access to the bytes that it comprises of.
Opening a File
To open a file in Python, use the open() function and pass in the file name (and file path
unless the file is in the current working directory):
myfile = open("/mnt/sda1/test.txt")
By default, Python tries to open the file as a read-only text file. If you want to open the
file for writing, or as a binary file, you need to pass in an additional argument to the
open() function. This is the mode, and it can be one of several values:
Mode Description
Open a text file for writing. This overwrites the existing file or, if it does not
w
exist, creates the file.
Open a binary file for writing. This overwrites the existing file or, if it does not
wb
exist, creates the file.
Open a text file for appending. Anything you write is added to the end. If the
a
file does not exist, Python creates the file.
Open a binary file for appending. Anything you write is added to the end. If
ab
the file does not exist, Python creates the file.
The open() function returns an object that represents the open file. You need to save
this object in a variable so that you can work with the file.
You should always close files. Closing the file ensures that any changes you make are
saved, and allows other programs to access the file. To close a file, call the close()
method of the object:
myfile.close()
To read from a file, you need to open it using one of the modes that support reading
(usually r, or rb).
When working with a text file, Python has several methods that you can use.
Method Description
read() Reads all of the text from the file and returns the information as a string.
Reads a single line (up to a line break) from the file and returns the
readline()
information as a string.
Reads all of the text from the file, and returns a list of strings. Each item
readlines()
in the list is a single line from the text file
These are methods of the file object that you receive from a call to open(). The following
Python code displays the text from the file /etc/passwd in the console:
myfile = open("/etc/passwd", "r")
print myfile.read()
myfile.close()
The read() method also accepts an argument that tells the Python interpreter how many
characters you want to read from the file. When the string returned by read() is empty (or
has a length of zero) then you have reached the end of the file.
Python automatically keeps track of where you are in the file. So you can repeatedly call
read() with an argument, or readline(), to read the file piece by piece.
The example below prints each character from the /etc/passwd file on its own line.
myfile = open("/etc/passwd", "r")
while True:
tx = myfile.read(1)
if len(tx) == 0:
break
else:
print tx
myfile.close()
When working with binary files, you also use the read() function. But it usually does not
make sense to use readline() or readlines() with binary files.
To read a binary file (or a predefined number of bytes from a binary file) into an array, or
buffer, you need to create a special type of object a bytearray. You can then use the
file method readinto()to fill the buffer with the contents of the binary file.
For example, to read the first ten bytes of the /etc/passwd file into a buffer:
myfile = open("/etc/passwd", "rb")
buf = bytearray(10)
myfile.readinto(buf)
myfile.close()
The value returned by readinto() is a number that tells you how many bytes were read
from the file. When this number is zero, you have reached the end of the file. At the end
of this script, buf contains ten bytes. You can access each byte using square brackets
and the number of the byte (starting at zero for the first) that you want to extract:
print buf[0]
Writing to Files
To write to a file, you first need to open it with a mode that supports writing (w, wb, a, or
ab). Then you can call the write() method of the open file, and pass in a string that
contains the text that you want to write to the file. If you open the file using mode a, the
text is written to the end of the file, after any existing content. If you open the file using
mode w then the file is automatically wiped when Python opens it.
Writing strings to binary files can cause unexpected results. The code below does not
write the number 65 to a binary file, it actually writes two bytes to the file: the ASCII
code for the character that looks a 6 (which is 54), and then the ASCII code for the
character that looks like a 5 (which is 53).
myfile.write("65")
If you do want to use a string, but need to ensure that the script writes the correct
number to the file, you can use the escape sequence \x and then include the number in
hexadecimal notation. For example:
myfile.write("\x41")
The preceding command writes the number 65 (which is 41 in hexadecimal) to the file as
a single byte. But if you are working with numbers in your Python script, it is usually
easier and clearer to write to a binary file using a bytearray object. The code below
demonstrates one way to create a bytearray with five values in it and then write it to a
file.
buf = bytearray(5)
for x in range(0, 5):
buf[x] = x
myfile = open("/mnt/sda1/test.dat", "wb")
myfile.write(buf)
myfile.close()
The os module contains many functions for working with files and directories from your
Python script. To use this module, add the following import statement to the top of a
script:
import os
The table below shows some of the functions that you can use. Remember to prefix
them with os and a dot, to call them from your Python scripts.
Method Description
rename(path, Changes the name of, or moves, a directory from the first file
path) path, to the second.
You can stop the interpreter from terminating your program by handling the error yourself.
The basic way of doing this, is to use the try and except statements. Place a try
statement before any code that might cause an error, and then indent the code. Then
use except to specify what happens if an error occurs.
For example:
try:
myfile = open("/mnt/sda1/test.txt", "r")
myfile.close()
except:
print "File does not exist"
If the file /mnt/sda1/test.txtdoes not exist then trying to open it in read-only mode causes
an error. In the example above, the error is caught and the Python interpreter runs the
code indented below except. If there is no error, the interpreter does not run the code
indented below except.
Compiling your Python Programs
The Python interpreter is installed on all Arduino Yns, unless you specifically remove it.
On rare occasions, it may be useful to compile your Python programs so that they can
run on an Arduino Yn without the Python interpreter.
cx_Freeze is a tool that translates Python scripts (and any modules that they rely on)
into native executables. It is available from the Python Package Index. However, the
installation of Python on the Yn is missing several files that cx_Freeze needs, so you
need to add these files to your system before installing the tool.
To install cx_Freeze,
1. At the command prompt, type the following command and then press Enter:
opkg install binutils
Because of the way cx_Freeze works, you need to compile your Python script in a
directory that you have full control over. You can move it again after compiling the script.
If you expand the Linux file system to the SD card, then the best place to put your script
is in /root. If you are not using the SD card this way, put your Python script in /www or
/mnt/sda1/arduino/www.
At the command prompt, type the following command and then press Enter:
cxfreeze hello.py
You can usually ignore any warnings relating to the command ldd.
At the command prompt, type the following command and then press Enter:
dist/hello
cx_Freeze builds the project into a directory named dist. Inside this directory, you can
find a file that has no file extension. This is the executable (hello). The other files in the
dist directory are libraries that you need to distribute with the executable.
If you create a symbolic link in /usr/bin/ that links to the executable, then you can run the
program from anywhere by typing its name. To learn about creating symbolic links, see
Creating and Removing Symbolic Links.
Even though cx_Freeze includes the command line tool, you have more control over the
conversion to an executable if you use a small Python script. For more information about
using cx_Freeze and how to control its output, see the webpage http://cx-
freeze.sourceforge.net/
Using the Bridge Library
The two processors on the Arduino Yn can communicate with each other over a
hardware serial port. To communicate with the AR9331 from the ATmega32u4, you need
to program it with an Arduino sketch that sends the right messages.
As the Wi-Fi and Ethernet adaptor, USB hosting, and SD card facilities are all controlled
by the AR9331, you can use the Bridge library to access these features from the
Arduino side of the Yn.
Using the Bridge library gives control of your project to the ATmega32u4 and your
Arduino sketch. Your sketch initiates communication with the Linux side, and tells it what
to do. To have the Linux side take more control of the project, you can have the Arduino
start the communication but then loop and wait for instructions from the AR9331.
Tip: Many of the examples in this chapter use the Serial class to send messages
to the Arduino IDE. Connect your Yn to your PC using a USB cable to use these
code samples.
In This Chapter
To start the communication between the ATmega32u4 and the Atheros AR9331, you
need to use the Bridge library. And so you need to include it in your Arduino sketch.
Then make a connection to the AR9331 by using the begin() method of the Bridge class.
The most-common place to do this is in your sketchs setup() function.
void setup() {
Bridge.begin()
}
You do not need to create an instance of Bridge to do this, you can call the begin()
method directly.
It usually takes several seconds to make the connection. However, when you first apply
power to the Arduino, this process takes considerably longer as begin() must wait until
the Linux side loads OpenWrt-Yun. None of your code underneath the call to begin()
runs until the connection to the AR9331 completes.
When begin() completes, the connection between the ATmega32u4 and the AR9331 is
ready to use and will remain usable until the Arduino is turned off.
Method Description
You rarely need to use these in your sketches they are for working with Bridge
communications at a very low level. Other classes in the Bridge library rely on these
methods and simplify the processes for you.
However, there are two methods in the Bridge class that you might want to use: put()
and get(). You can use these to store data in the AR9331s memory and then retrieve it
later.
Storing Data in the AR9331s Memory
The ATmega32u4 on the Arduino side only has 2.5 KB of internal RAM for storing your
variables and temporary information. The Atheros AR9331 on the Linux side has 64 M B,
although OpenWrt-Yun uses a lot of this.
If you need more memory on the Arduino side then you can use the Bridge library to
store information in the AR9331s memory instead.
Storing and retrieving information from the AR9331s memory is slower than using the
RAM on the ATmega32u4. And when you turn off the Yn or reboot the AR9331, this
information is lost. Resetting the ATmega32u4 or restarting the sketch does not affect
the values stored on the AR9331.
If you want to find out how much unused memory the Linux side of your Arduino Yn
has, you can do this either from the OpenWrt-Yun command line or from the advanced
configuration panel.
1. Login to the OpenWrt-Yun command line. For more information, see Connecting to
the Arduino Yn.
2. Type the following command and then press Enter:
free
1. Login to the Arduino web panel and open the advanced configuration panel. For
more information, see Opening the Advanced Configuration Panel.
2. Click the Status tab.
3. Click Overview.
4. M ake a note of the values in the Memory section.
There are two methods in the Bridge library that you can use for storing your data in the
AR9331s memory:
Method Description
put(name, Sends a String to the AR9331 and stores it in memory using the
String) specified name.
get(name, Finds the data with the specified name and copies length bytes from
buffer,
length) the AR9331 to the buffer (an array or area of memory).
put() only stores strings, and so if you want to store other types then you must first
convert them to a string. Like variable names, the names that you use with put() should
be unique for each value that you want to store.
Storing Strings
To store a string, pass a String object or char array into put().
For example:
Bridge.put("my_variable", "This is a string!");
Or:
char[] my_string = "This is a string!";
Bridge.put("my_variable", my_string);
To retrieve a string from the AR9331s memory, you need to create a buffer an array or
area of memory in which the get() method can store the data.
key is the name that you gave the value when you added it to the AR9331s
memory using put().
buffer is the name of an array or area of memory in which to store the string.
length is the maximum size of the buffer, or the maximum number of bytes that you
want to fetch from the AR9331.
If you define the buffer as a char array, you can generally treat it as a string.
For example:
#include <Bridge.h>
void setup() {
Serial.begin(9600);
while (!Serial);
Bridge.begin();
void loop() {
char buf[100];
Bridge.get("my_variable", buf, 100);
Serial.println(buf);
delay(5000);
}
Upload this sketch to your Arduino, and then open the Serial M onitor in the Arduino IDE.
Tip: Unlike other Arduinos, opening the Serial Monitor does not restart the
Arduino Yn. The line while (!Serial); forces the Arduino to wait until the Serial
Monitor opens before the remainder of the sketch runs.
To re-run the sketch: close the Serial M onitor, press the 32U4 RST button twice, and
then reopen the Serial M onitor.
Storing Numbers
The char type holds ASCII characters and numbers from 0 through 255. A byte is the
same and you can convert it to a char by including the keyword char in parenthesis
before the name of a byte or byte variable.
byte x = 10;
char y = (char)x;
To store a char type using put(), you can use the String() function to convert it to a
string. For example:
void setup() {
Serial.begin(9600);
while (!Serial);
Bridge.begin();
char x = 10;
Bridge.put("my_variable", String(x));
}
When retrieving the value using get(), you only need the first item (the first byte) in the
buffer array:
char buf[100];
Bridge.get("my_variable", buf, 1);
char x = buf[0];
On the Arduino, other types of number use more than one byte. For example, an integer
comprises two bytes. You can use the String() function to pass these into put(), and
then convert the entire contents of the buffer to an integer using the function atoi().
For example:
#include <Bridge.h>
void setup() {
Serial.begin(9600);
while (!Serial);
Bridge.begin();
int x = -10;
Bridge.put("x", String(x));
}
void loop() {
char buf[100];
Bridge.get("x", buf, 100);
int x = atoi(buf);
Serial.println(x);
delay(5000);
}
To convert the buffer string to a float, use the function atof(). Or to convert the buffer to a
long integer, use atol().
Storing Arrays
Storing arrays is a little more complicated because you cannot use the String() function.
put() expects that the strings that you pass in are null-terminated that the last entry is
zero. This means that if you have any zeroes in an array and you convert it to a string
using String(), the string ends early.
To handle this, you need to convert the array to a string in which all zeroes are changed
to something else. There are many ways of doing this, but in this section you will see
how to convert the array to a base16 string.
Base16 encoding divides each byte of the source item into two halves each half is four
bits. The process then adds on a value to each half so that the result is safe for use in
strings. Each half is then written out as a new byte.This doubles the size of the array,
but it is relatively simple to implement.
In the example below, the code defines a new function called store. This function
creates a new buffer that is twice the size of the one that you pass into it (plus an
additional byte for the final zero).
When you pass arrays into functions, they become more difficult to work with. The
store()function cannot detect how large the array is and so you have to pass this size as
an argument.For each byte in the array, store() first extracts the highest four bits and
adds the character A (65) to that. Then it extracts the lowest four bits and adds A.
Finally it converts the new array to a string and sends it to the AR9331. This process
ensures that all of the values are represented by ASCII letters.
void store(String key, char ar[], int sz) {
char *buf = (char *)malloc((sz * 2)+ 1);
if (buf != NULL) {
int count = 0;
byte b;
for (int i=0; i<sz; i++) {
b = ar[i];
buf[count++] = ((b >> 4) & 0x0F) + 65;
buf[count++] = (b & 0x0F) + 65;
}
buf[count] = 0;
Bridge.put(key, String(buf));
free(buf);
}
}
In order to support arrays of different sizes, this example function uses dynamic memory
management (malloc, and free) to work with the new buffer space.
When using the get() method to retrieve the value from the AR9331, your buffer needs
to be twice as large as the original object that you store. The retrieve() function below is
an example of how to reverse the process and fetch a value from the AR9331.
void retrieve(const char *key, char *dest, int sz) {
char *buf = (char *)malloc(sz * 2);
if (buf != NULL) {
Bridge.get(key, buf, sz * 2);
int count = 0;
for (int i=0; i < sz * 2; i=i+2) {
dest[count++] = ((buf[i] - 65) << 4) | (buf[i+1] - 65);
}
free(buf);
}
}
The next example sketch creates a char array with a zero in the middle. This causes
problems when using the String() function and so the sketch uses store() to encode the
data into base16, and stores the result on the AR9331 as a string. The retrieve() function
reverses this fetching the encoded string from the AR9331 and decoding it back into
an array.
#include <Bridge.h>
void setup() {
Serial.begin(9600);
while (!Serial);
Bridge.begin();
Serial.println("Storing array...");
char myarray[5] = {1, 2, 0, 4, 5};
store("myarray", myarray, 5);
}
void loop() {
Serial.println("Fetching array...");
char buf[5];
Bridge.get("myarray", (char *)buf, 5);
for (int i=0; i<5; i++) {
Serial.println(buf[i], DEC);
}
delay(15000);
}
To store an integer array, you can use exactly the same functions. However, as an
integer is a 16-bit number, if there are five items in the array then the size is 10 bytes.
You need to keep this in mind when passing in values for the sz parameter.
int myarray[5] = {1000, 2000, 3000, 4000, 5000};
store("myarray", (char *)myarray, 10);
retrieve("myarray", (char *)buf, 10);
Storing Structs
Storing a struct is similar to the process for storing an array. In fact, you can use the
same store() and retrieve() functions as the previous example.
#include <Bridge.h>
struct CMD {
char reserved[3];
byte opcode;
int length;
}
void setup() {
Serial.begin(9600);
while (!Serial);
Bridge.begin();
CMD myCmd;
myCmd.reserved[0] = 'C';
myCmd.reserved[1] = 'M';
myCmd.reserved[2] = 'D';
myCmd.opcode = 0;
myCmd.length = 255;
store("myCMD", (char *)&myCmd, sizeof(myCmd));
}
void loop() {
CMD bCmd;
retrieve("myCMD", (char *)&myCmd, sizeof(myCmd));
Serial.println(bCmd.length);
delay(5000);
}
Both arrays and structs are pointers to areas of memory. And so in the call to store() and
the call to retrieve() you only need to tell the C compiler to treat the struct as a suitable
buffer. The & prefix on myCmd fetches the memory address of the structure, and then
(char *) tells the compiler that the content at that address is actually comprised of char
types.
OpenWrt-Yun includes several Python scripts that assist in making the Bridge library
work. The BridgeClient class is able to work with the shared-storage area in much the
same way as the Arduino sketch.
To use the BridgeClient class, you need to add it to Pythons path so that you can
import it:
import sys
sys.path.insert(0, "/usr/lib/python2.7/bridge/")
from bridgeclient import BridgeClient
There are three methods that you can use to store, retrieve, and delete information the
shared-storage area from your Python scripts:
Method Description
put(key, Adds a value to the storage area and identifies it with the specified
value) key.
delete(key) Deletes the item that has the specified string as its key.
To call one of these methods, start with the name of an instance of the BridgeClient
class and then a dot. For example:
client = BridgeClient();
cmd = client.get("myCMD")
And:
client.put("myIDX", 25)
Tip: Architecture differences between the ATmega32u4 and the Atheros AR9331
can make it difficult to store complicated structures and arrays on one processor
and retrieve them on the other. To use the AR9331s memory to pass data
between the two processors, it is usually best to use String objects.
Running Commands and Scripts
The Bridge librarys Process class contains methods for starting Linux commands on the
AR9331 from an Arduino sketch running on the ATmega32u4. You can use this to start
shell scripts and Python programs.
To run a Linux command from an Arduino sketch, you need to create an instance of the
Process class:
Process p;
p.begin("ls");
begin() specifies which command to run. However, the command does not run yet.
On the Linux command line, you send parameters to commands by placing them on the
same line as the command, and separating each parameter with a space. When using
the Process class, add each parameter to the instance using the method
addParameter().
p.addParameter("-l");
p.addParameter("/");
The code so far creates a Process object to run the command ls -l / on the AR9331 and
list the contents of the top-level directory.
To run the command, call the method run(). This method returns the exit code of the
Linux process as an integer. M ost Linux programs return zero if they run without an
error.
However, in most cases, the exit code is not the information that you want. When you
run ls from the command prompt, it displays the information in the console. When you run
ls from a Process object, the information is in a buffer that you need to read from. There
are two methods to help you do this: available() and read().
available() returns true if there is data waiting for you, and false if there is not.
read() fetches one byte from the buffer and returns it as an integer. It returns -1 when
you reach the end.
The example sketch below reads the output of the ls command and writes it to the Serial
M onitor.
#include <Bridge.h>
void setup() {
Serial.begin(9600);
while (!Serial);
Bridge.begin();
Process p;
p.begin("ls");
p.addParameter("-l");
p.addParameter("/");
p.run();
if (p.available()) {
while (p.available())
Serial.write(p.read());
}
Serial.println();
}
}
void loop() {
}
You can run shell scripts and Python programs in a similar way. However, the run()
method is intended to run binary programs. And so to run a script, you need to run the
command that understands those script files:
For a shell script, place sh in the call to Process.begin(), and add the scripts file
path and file name as a parameter;
For Python programs, place python in the call to Process.begin(), and add the
scripts file path and file name as a parameter.
There are several other occasions when you have to use sh in Process.begin() so that
the system can understand your command:
When you want to run multiple commands and redirect the output of one, to another;
and
When you want to use environment and shell variables in your command or
parameters.
The Process class has two additional methods to make using shell command lines
easier: runShellCommand() and runShellCommandAsynchronously(). Both of these
methods accept a string, and this string is the entire command and its parameters. You
do not call begin() or addParameter() when using these two methods.
However, you can use the method runAsynchronously()to tell the Arduino sketch not to
wait. This is useful when:
After you start a command with runAsynchronously(), you can periodically check whether
the command is finished. To do this, use the method running(), which returns true if the
command is still running or false if it is finished. Then you can fetch the exit code using
the exitValue()method.
The example below tells the Linux package manager to download a list of all of the
software that is available on the Arduino Yn repository. This can take several seconds.
While it is happening, the sketch flashes the Yns built-in LED on digital pin 13.
When the update is complete, the sketch checks the exit code and then fetches the
output of the command.
#include <Bridge.h>
void setup() {
Serial.begin(9600);
while (!Serial);
Bridge.begin();
Process p;
p.begin("opkg");
p.addParameter("-l");
p.addParameter("/");
p.runAsynchronously();
while (p.running()) {
digitalWrite(13, HIGH);
delay(100);
digitalWrite(13, LOW);
delay(100);
}
if (p.exitValue() == 0) {
if (p.available()) {
while (p.available()) {
Serial.write(p.read());
}
Serial.println();
}
}
}
void loop() {
}
M any commands (such as this one) send messages to the console while they are in
progress. You do not need to wait until the command is finished before you read the
output.
You can modify the example above so that the sketch picks up messages as quickly as
it can:
void setup() {
Serial.begin(9600);
while (!Serial);
Bridge.begin();
Process p;
p.begin("opkg");
p.addParameter("-l");
p.addParameter("/");
p.runAsynchronously();
while (p.running()) {
digitalWrite(13, HIGH);
delay(100);
digitalWrite(13, LOW);
delay(100);
while (p.available()) {
Serial.write(p.read());
}
}
if (p.exitValue() == 0) {
Serial.println();
}
}
Tip: After starting a command using runAsynchronously(), you can wait as long as
you like before checking whether the command is finished.
When the script runs the read command, it waits for keyboard input from the user. When
you start this script from a Process object in an Arduino sketch, the command waits for
input from the Process object instead. You can send this using the write() method.
To build an Arduino sketch that automates this shell script, start a new sketch in the
Arduino IDE and type in the following code:
#include <Bridge.h>
void setup() {
Serial.begin(9600);
while (!Serial);
Bridge.begin();
Process p;
p.runShellCommandAsynchronously("/mnt/sda1/test.sh");
while (p.running()) {
if (p.available()) {
int x = p.read();
Serial.write(x);
if (x == ':') {
p.write('n');
p.write('o');
p.write((byte)10);
}
}
}
while (p.available()) {
Serial.write(p.read());
}
Serial.println("END.");
}
void loop() {
}
This code reads the output of the shell script and writes it to the Serial M onitor. When it
receives the character :, it answers the question by sending the string no. Because
the Linux read command waits for the Enter key, the script sends a line feed character
(10).
You should note that it is possible for the script to finish before the Arduino has been
able to read all of the scripts output. This is why there is a final while loop in the example
above that reads from the buffer.
Tip: At the time of writing, there are a few bugs in the write() method. It is
currently better to send bytes to the command individually, rather than try to send
strings.
For example:
Process p;
p.runShellCommandAsynchronously("/mnt/sda1/test.sh");
p.close();
Working with Files in the Linux File
System
The File and FileSystem classes in the Bridge library are very similar to the ones that
you use to read and write files on an SD card using shields such as the Arduino
Ethernet shield. On the Arduino Yn, you can use these classes to make changes to
the OpenWrt-Yun Linux file system, or to store information on the microSD card.
The File and FileSystem classes are almost the same as the classes that you use with
the Arduino SD library. However, there are a few small inconsistencies. Do not assume
that code that works for the SD library will always compile when using Bridge.
For more information about the structure of the Linux file system on the Arduino Yn,
see .
Before you can open or create files from an Arduino sketch, you need to initialize the
connection to the Linux file system.
1. At the top of your sketch, include the Bridge and FileIO header files:
#include <Bridge.h>
#include <FileIO.h>
2. In your sketchs setup() function, connect to the Linux file system using the
command:
FileSystem.begin();
The connection to the Linux file system remains open for as long as the sketch runs.
You do not need to close this connection when you finish using it.
There are five methods of the FileSystem class that you can use to work with the file
system:
Method Description
open(filename, Opens a file for reading or writing. For more information, see
mode) Opening or Creating a File.
You do not need to create an instance of the FileSystem class to work with these
methods you can call them directly.
For example, to create a directory named TestDir (and its parent, Test) on the microSD
card:
#include <Bridge.h>
#include <FileIO.h>
void setup() {
Bridge.begin();
FileSystem.begin();
FileSystem.mkdir("/mnt/sda1/Test/TestDir");
}
void loop() {
}
You can delete these directories by logging in to the OpenWrt-Yun command line over
SSH, or by creating an Arduino sketch that calls the rmdir() method twice:
FileSystem.rmdir("/mnt/sda1/Test/TestDir");
FileSystem.rmdir("/mnt/sda1/Test");
filename
The full path to the file that you want to open or create. To read from a file, you should
make sure that it exists before calling open().
mode
Can be either FILE_READ or FILE_WRITE. If mode is not specified then open() opens
the file for reading.
The open() method returns an instance of the File class. If the Arduino cannot find, open,
or create the file, this instance evaluates to false in an if statement. To check only
whether a file exists, use the method FileSystem.exists(), which returns true if the file is
there or false if the file cannot be found.
void setup() {
Serial.begin(9600);
while (!Serial);
Bridge.begin();
FileSystem.begin();
You should close files as soon as you are finished with them. Closing a file ensures that
any changes that you have made are saved, and allows other programs to open the file.
To close a file, call the close() method of the File object that you receive from
FileSystem.open().
If you try to open a file with mode set to FILE_WRITE and the file does not exist, the
open() method creates it. If the file does exist, open() opens the file so that the next
piece of information that you write is added to the end of the file.
If you do not pass any arguments, a call to read() returns the next byte from the file. The
library keeps track of your position in the file, so that the next call to read() does not
return the same value. The method available() returns the number of bytes left in the file
that you have not yet read, and you can use this to detect the end of the file. When your
position is at the end of the file, there are no bytes available.
To read the entire file, you can loop until available() returns zero, reading and printing
bytes one-by-one:
#include <Bridge.h>
#include <FileIO.h>
void setup() {
Serial.begin(9600);
while (!Serial);
Bridge.begin();
FileSystem.begin();
Serial.println();
} else {
Serial.println("NOT FOUND!");
}
}
void loop() {
}
peek() can be useful occasionally, but it is not used by the examples in this book. It
reads the next byte from the file in the same way as read(), but it does not advance your
position in the file.
An alternate form of read() reads multiple bytes into an area of memory often called a
buffer including the memory occupied by an array or a struct, or dynamically-allocated
with malloc(). When used in this way, read() accepts two arguments and returns an
integer value indicating how many bytes were read from the file:
int read( void *buf, uint16_t nbyte );
buf is a pointer to an area of memory. The name of an array is also a pointer to an area
of memory and can be used as this argument.
nbyte specifies the number of bytes that should be read from the file.
When working with arrays, keep the size of the data types in mind. An array of integer
types defined as int buf[100] allocates memory that is 200 bytes long, because integer
types on the Arduino are 16-bit values. You can calculate the actual size in bytes of any
type (including arrays and structs) with the sizeof() operator.
Writing to a File
When a file is opened with FILE_WRITE and it already exists, it opens at the end so that
any data that you write is added after the existing content. If you want to overwrite a file
then the easiest way is usually to delete the existing file before the call to
FileSystem.open().
You use the instance of the File class that is returned by a successful call to
FileSystem.open() to write to the file, calling the method write(). Like read(), write() also
has two forms the first accepts a single byte and writes it to the file. The second form
accepts a pointer to the area of memory that contains the data to be written to the file,
and an integer number specifying how many bytes to write. The name of an array is
actually a pointer and so this can be used when calling write().
Tip: There is no guarantee that the data will be written to the file immediately it
may only be saved when the file is closed. To ensure that the data is written, you
can call the method flush().
The following code sample creates a new file on the SD card, and then writes the
number 65 to it. When the file is opened with a text editor, such as nano, this number
appears as A.
The code then writes an array of numbers, which appears in a text editor as BCDEF.
Finally, this sample demonstrates how you can use calls to write() to store entire
structures with one command.
To run this sketch, upload it to your Arduino and then open the Serial M onitor.
#include <Bridge.h>
#include <FileIO.h>
struct TS {
byte G;
byte H;
byte I;
};
void setup() {
Serial.begin(9600);
while (!Serial);
Bridge.begin();
FileSystem.begin();
if (FileSystem.exists("/mnt/sda1/test.txt")) {
FileSystem.remove("/mnt/sda1/test.txt");
}
//Write a byte
test.write(65);
Serial.println("Finished!");
}
void loop() {
}
To open the text file and look at the data that this sketch writes:
1. Login to the OpenWrt-Yun command line.
2. At the command prompt, type the following command and then press Enter:
nano /mnt/sda1/test.txt
3. Press Ctrl + X.
4. To delete the file, type the following command and then press Enter:
rm /mnt/sda1/test.txt
Opening Directories
You can use the methods in the FileSystem class to create and remove directories. To
open a directory, use the FileSystem.open() method in the same way as you open a file
for reading.
When you open a directory, the File object for it has three useful methods for working
with the contents of the directory.
Method Description
Opens the first (or next) file or directory inside the current
directory, without specifying a file name. It then returns a new File
openNextFile()
object. The library remembers the last item that you open, and so
the next call to openNextFile() opens the next file in the directory.
For an example of opening directories and listing their contents, see the functions
countMP3s() and getMP3Filename()in Project 3 M aking an M P3 Jukebox.
Making a Console Connection
If you upload sketches from the Arduino IDE to the Yn over Wi-Fi or Ethernet, you
cannot use the Serial class to send messages to the Serial M onitor. For debugging
purposes, you can use the Console class in the Bridge library instead.
The Console is also one of the simpler ways of giving the Linux side of the Yn control
over the Arduino side. And you can still use the Console even if you connect your Yn
to your PC using the USB cable.
Using the Console class is similar to working with the Arduinos serial port or connecting
to the Yn over SSH. However, there are two important notes:
As when receiving data that you send from the Serial M onitor, you need to create
an Arduino sketch that listens and responds to messages. There is no built-in
functionality;
The connection is made from software running on the AR9331, not from another
computer.
And then call the Consoles begin() method from your sketchs setup() function:
Console.begin();
It can take a few moments to make the connection. So it is usually a good idea to wait
until the connection is setup before continuing. To do this, add the following code
underneath the call to Console.begin():
while (!Console);
This creates an empty loop that runs until the Console is ready.
To send information from an Arduino sketch to the Console, you can use the methods
print(), println(), and write(). These work the same as the versions in the Serial class.
print() sends information to the Console as ASCII text. It accepts numbers, characters,
and strings. If you pass a number (for example, an integer, byte, or float) then the
method converts the number to a series of ASCII characters that show the value in a
human-readable form.
For example:
Console.print(65);
This call to print() converts the number 65 into two ASCII characters 6 and 5.
You can use an optional second parameter, format, to convert the number in different
ways. The second parameter can be:
Format Description
BIN Converts the number to a string that shows its binary representation.
Converts the number to a decimal (base 10) form. This is the default if you
DEC
do not specify the format.
HEX Converts the number to a string that shows its hexadecimal representation.
With numbers of the float type those that include decimal points you can specify a
number as the second parameter. This number tells the method how many decimal
places you want to show.
The println() method acts the same as print() and accepts the same inputs. However, it
also adds a line break to the end of the string.Because these two methods translate
numbers into human-readable text strings, if you send messages to other programs in
this way then they have to convert the string back into a number. For precise control of
how you send data through the Console class, you can use the method write(). write()
does not translate numbers into human-readable text. For example:
Console.write(65);
This call does not send the characters 6, and 5. Instead, it sends the number as a
single byte which appears in text editors and the Linux console as the character A
because 65 is the ASCII code for the character A.
write() also accepts strings, and arrays of bytes or characters. When using arrays, you
have to specify a second parameter that states how many bytes you want to send. For
example:
byte buf[] = {66, 67, 68, 69, 70};
Console.write(buf, 5);
Using the Console as a Replacement for the Serial Class
When debugging or testing code, it is a common practice to send messages to the
Arduino IDEs Serial M onitor using the Serial class. If you upload sketches to your Yn
over a network connection then you cannot use the Serial class to do this. Instead, you
can use the Console class to send messages to the Linux command shell.
In Running Commands and Scripts on page 113, you can see how to start the ls
command from an Arduino sketch and send the results to the Serial M onitor. The code
for this is below.
#include <Bridge.h>
void setup() {
Serial.begin(9600);
while (!Serial);
Bridge.begin();
Process p;
p.begin("ls");
p.addParameter("-l");
p.addParameter("/");
p.run();
if (p.available()) {
while (p.available())
Serial.write(p.read());
}
Serial.println();
}
}
void loop() {
}
To rewrite this sketch so that it sends the result of the ls command to the Console, you
need to remove the references to the Serial class and replace them with calls to the
Console class.
#include <Bridge.h>
#include <Console.h>
void setup() {
Bridge.begin();
Console.begin();
while (!Console);
Process p;
p.begin("ls");
p.addParameter("-l");
p.addParameter("/");
p.run();
if (p.available()) {
while (p.available())
Console.write(p.read());
}
Console.println();
}
}
void loop() {
}
1. Login to the OpenWrt-Yun command line. For more information, see Connecting to
the Arduino Yn.
2. At the command prompt, type the following command and then press Enter:
telnet localhost 6571
To test this sketch, you can use the Linux command telnet. The two arguments that you
pass into telnet tell the program which machine on the network to connect to, and which
port to use. In this example, you tell the Arduino to connect to itself on port 6571. This
port is for use with the Console class.
To disconnect from the Console, and return to the Linux command prompt:
1. Press Ctrl + C.
2. Press E.
To restart the ATmega32u4 and rerun the sketch, press the 32u4 RST button twice.
The Console class only accepts connections from the Linux side of the Yn. You cannot
make a telnet connection directly to it from another PC, you must use SSH.
The Console class has seven methods that you can use, and it also inherits the print()
and println() methods from the Stream class.
Method Description
Returns an integer that specifies how many bytes the AR9331 has
available()
sent, but that the Arduino sketch has not yet read.
Reads the next byte from the connection, but does not increment
peek() your position in the buffer. The next call to peek() or read() fetches
the same byte.
Ensures that any bytes that you have written with calls to write(),
flush()
print(), or println() are sent to the AR9331 immediately.
Reads from the Console connection until the method receives the
specified character, the connection ends, or the method receives
readBytesUntil()
no data. Then the method returns all of the data as an array of
bytes.
Reads from the Console connection until the method receives the
readStringUntil() specified character, the connection ends, or the method receives
no data. Then the method returns all of the data as a string.
The following example code is an Arduino sketch that gives control of the Arduinos
digital pins to Linux. It opens the Console connection and waits for information from the
Linux side of the Yn.
When the sketch receives data, it looks for a three-character message. The first
character is either H, L, O, or I. A two digit number follows and this specifies the number
of a digital pin on the Arduino.
Character Description
H Writes a HIGH to the specified digital pin.
L Writes a LOW to the specified digital pin.
char buf[3];
int pin;
void setup() {
Bridge.begin();
Console.begin();
while (!Console);
}
void loop() {
int count = 0;
while (count <= 3) {
if (Console.available() > 0) {
if ((Console.peek() != '\n') && (count == 3)) {
Console.read();
}
else {
buf[count++] = Console.read();
}
}
}
buf[3] = 0;
pin = atoi((char *)&buf[1]);
switch (buf[0]) {
case 'H':
digitalWrite(pin, HIGH);
break;
case 'L':
digitalWrite(pin, LOW);
break;
case 'O':
pinMode(pin, OUTPUT);
break;
case 'I':
pinMode(pin, INPUT);
break;
}
}
In many cases, you can simply read until you have the number of characters that you
need. However, because of the way you are going to test this sketch, the code has to
wait until it receives a line break.
The while loop in the sketchs loop() function ensures that the sketch waits until it has
the three bytes for the command. At that point, the loop ignores any other characters
until it reads a line break.
Tip: If the AR9331 drops the connection, this code loops because there is no data
available. When the AR9331 reestablishes the connection, the Console class
automatically handles this and Console.available() is again able to detect data.
This code uses a small array, buf, to hold the characters that it reads from the Console
connection. The sketch reads bytes in groups of three (since all commands are
expected to be three characters long) but buf contains space for four bytes. It sets the
final byte to zero.
The sketch does this so that it can convert the two ASCII characters that represent the
pin number to an actual integer. To do this, it uses the function atoi() which accepts a
null-terminated string. However, the data read from the Console connection starts with
H, L, O, or I. To exclude this character from the conversion, the sketch passes in the
memory address of the second character in the buffer as the start of the string.
Telnet is not a raw connection and it only sends information when you press the Enter
key. This is why the example in this section must wait for the line break character.
To test the sketch and control the L13 LED on the Yn:
4. To light the LED, type the following text and then press Enter:
H13
5. To turn off the LED, type the following text and then press Enter:
L13
6. Press Ctrl + C.
7. Press E.
If you want to write a shell script to programmatically send and receive data to the
Arduino sketch, you can use Netcat. Netcat is a very similar program to telnet, but it is
easier to use from scripts. It creates a connection to an IP address and port, and then
allows other commands to redirect their input and output to this connection.
For example, to send the data H13 to the Arduino, you can use the command:
echo "H13" | nc 127.0.0.1 6571
Netcat only accepts IP addresses. In the example above, 127.0.0.1 is the IP address for
the host name localhost, which always points to the same machine that opens the
connection. After the echo command is done, netcat closes the connection to the
Console.
You can run multiple commands, one after the other, by placing them in parenthesis and
separating each with a semi-colon:
(echo "H13"; sleep 2; echo "L13") | nc 127.0.0.1 6571
To write a shell script, you can redirect the input and output of every command in the
script by executing the entire script with one call to netcat.
For example, to write a shell script on the microSD card that works with the Arduino
sketch in the previous example and blinks the L13 LED:
1. At the Linux command prompt, type the following command and then press Enter:
nano /mnt/sda1/blink.sh
3. Press Ctrl + X.
4. Press Y.
To run this script from the command prompt and direct its output to the ATmega32u4,
type the following command and then press the Enter key:
/mnt/sda1/blink.sh | nc 127.0.0.1 6571
One of the ways to do this is to open a raw socket to the localhost on port 6571. A
socket is a type of connection to another machine on your network or the Internet, and a
raw socket specifies that you are not using any predefined protocols (such as FTP or
HTTP). Instead, you send and receive characters through a socket in a similar way to
how you work with a serial port.
To use raw sockets in a Python script, you must include the socket module in your
script.
Start a new Python script file and save it to the microSD card of your Arduino Yn.
This imports the socket module and the time module. This example script uses the
sleep() function in the time module to wait for a few seconds before changing the state of
the Arduinos LED.
And specify a time-out value in seconds. The ATmega32u4 needs sufficient time to
acknowledge the connection, so a good value for this is 5.
s.settimeout(5)
The connect() method accepts a tuple that specifies the host name or IP address of a
computer to connect to, and the port number. If the script cannot connect to the Arduino,
the try and except statements catch the error and the script displays a message. If the
script successfully makes the connection, it displays the message Connected.
M ake sure that digital pin 13 is set to an output by sending the command O13:
s.send("O13\n")
The send() method writes values to the socket. Because the Arduino sketch requires
that commands end with a line break, the string you pass into the method also includes
\n, which the Python interpreter replaces with a new line character. This marks the end of
the command in the same way as pressing the Enter key when you test the Arduino
sketch from the Linux command prompt.
To create the infinite loop that blinks the LED, add the following code to the file:
while 1:
s.send("H13\n")
time.sleep(2)
s.send("L13\n")
time.sleep(2)
Because this loop is infinite, the Python interpreter does not run any statements that are
not inside the loop. Even so, the full example script includes a line at the end to
demonstrate how to call the close() method.
s.close()
The close() method ends the current connection. You should close sockets when you
do not need them anymore. However, if you forget, Python will usually close the
connection for you when the script ends or when it decides that the object s is not
needed.
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
try:
s.connect(("localhost", 6571))
except:
print "Unable to connect to the Arduino Console."
exit()
print "Connected."
s.send("O13\n")
while 1:
s.send("H13\n")
time.sleep(2)
s.send("L13\n")
time.sleep(2)
s.close()
Using Mailbox and Sending Messages
The Mailbox class is another way of sending information from the AR9331 to the
ATmega32u4. You can also use it to send information from other machines on your
network, and have the AR9331 receive and store them until the Arduino sketch reads
them.
At the time of writing, sending messages from the ATmega32u4 to the AR9331 is not
fully supported by the M ailbox.
M ailbox uses short text strings as messages. You can send these to the Arduino using
a universal resource locator (URL) and the hypertext transfer protocol (HTTP). The built-
in web server in OpenWrt-Yun Linux does the work of processing the HTTP request for
you.
In the Arduino IDE, the example sketch MailboxReadMessage demonstrates how to read
messages from an Arduino sketch. To upload this example to your Arduino from the
Arduino IDE:
1. On the File menu, point to Examples, then point to Bridge, and then click
MailboxReadMessage.
2. On the toolbar, click Upload.
3. On the Tools menu, click Serial Monitor.
1. Open a web browser and type the following URL into the address bar:
http://arduino.local/mailbox/Test
2. Press Enter:
3. In the username box, type root.
4. In the password box, type arduino.2
5. Click OK.
Everything that you type after http://arduino.local/mailbox/ is a message. When you send
a message from the web browser, the response that you receive is a blank page.
If you want to disable the password protection and allow any machines on your network
to send messages to the Arduino without specifying the username and password:
1. On a machine that connects to the same network as the Arduino, open a web
browser.
2. In the address bar, type http://arduino.local
3. Press Enter.
4. In the username box, type root.
5. In the password box, type arduino.3
6. Click LOG IN.
7. Click CONFIGURE.
8. Scroll to the bottom of the page and, under REST API ACCESS, click the Open box.
You do not need to restart the Yn to protect or unprotect the M ailbox interface.
void setup() {
pinMode(13, OUTPUT);
digitalWrite(13, LOW);
// Initialize Bridge and Mailbox
Bridge.begin();
Mailbox.begin();
digitalWrite(13, HIGH);
// Initialize Serial
Serial.begin(9600);
void loop() {
String message;
// if there is a message in the Mailbox
if (Mailbox.messageAvailable()) {
// read all the messages present in the queue
while (Mailbox.messageAvailable()) {
Mailbox.readMessage(message);
Serial.println(message);
}
Serial.println("Waiting 10 seconds before checking the Mailbox again");
}
// wait 10 seconds
delay(10000);
}
In the sketchs loop() function, you call the begin() method of the Mailbox class to
initialize the connection to the M ailbox on the Atheros AR9331.
The method messageAvailable() returns true if there are any messages stored on the
AR9331 that you have not yet read.
To read a message, you use the method readMessage(). readM essage() accepts two
arguments:
Argument Description
The Linux command curl makes an HTTP request to a specified URL. So to send a
simple message to the MailboxReadMessage example sketch:
The IP address 127.0.0.1 is a special address that always points to the same machine
that the request is made from.
You can use curl in a shell script in exactly the same way. Remember, even if you use a
variable to store the message, you need to URL encode special characters (such as
spaces) so that curl sends them to the M ailbox.
For example:
#!/bin/sh
MSG="This%20is%20a%20test!"
curl -u root:arduino http://127.0.0.1/mailbox/$MSG
Or if you turn off the password protection for the REST API in the Arduino web panel:
#!/bin/sh
MSG="This%20is%20a%20test!"
curl http://127.0.0.1/mailbox/$MSG
To make a URL request from a Python script, you can use the urllib module. To do this,
first import the module at the top of your script and then open the URL using the function
urllib.urlopen(). For example:
import urllib
urllib.urlopen("http://root:[email protected]/mailbox/Hello from Python!")
You can automatically send the username and password to access the REST API by
including those pieces of information before an @ symbol. If you turn off password
protection for the REST API in the Arduino web panel, you can simply call:
urllib.urlopen("http://127.0.0.1/mailbox/Hello from Python!")
Unlike curl, urlopen() automatically URL encodes special characters (such as spaces and
punctuation marks) so that the M ailbox can receive them.
There is also a special Python module, BridgeClient, which you can use to work with the
M ailbox from scripts on the AR9331. This avoids passing the information in a URL. To
use this module, you need to add it to Pythons path so that you can import it:
import sys
sys.path.insert(0, "/usr/lib/python2.7/bridge/")
from bridgeclient import BridgeClient
Then you can create an instance of BridgeClient and use the method mailbox() to send a
string to the M ailbox for the Arduino sketch to pick up.
client = BridgeClient()
client.mailbox("Hello from Python!")
Communicating over Local Networks and
the Internet
On the Yn, the Atheros AR9331 provides network connectivity to the ATmega32u4 in a
similar way to how an Ethernet Shield or Wi-Fi Shield provides network connectivity to
other Arduinos.
There are three classes in the Bridge library for working with network and Internet
connections.
Class Description
You can use the two client classes (HttpClient and YunClient) to make connections with
servers on your local network, the Internet, and the AR9331. And you can use the
YunServer class to receive connections from external machines, and from the Linux side
of the Yn.
In , you can see how these classes work and how to use them to create devices that
communicate over Internet protocols.
Choosing a Connection Method
In this chapter, you can see six ways of exchanging information between the
ATmega32u4 on the Arduino side of the Yn and the Atheros AR9331 on the Linux side:
There are no fixed rules as to when you should use each. However, the summary notes
below may help you.
Using the shared-memory area works when passing values between an Arduino
sketch and a Python script. It relies on both the sketch and the Python program
knowing the key names of each item of data, and understanding who can change
this information and when. It is most useful when you have a long-running program
on one processor that needs up to date information from the other (for example, a
temperature reading), but that itself never needs to change the information. The
shared-memory area works best with simple numbers and strings. For other types,
you may find it more useful to work with files, the Console class, or network
connections.
Running Linux commands is helpful when the Arduino needs to modify the Linux
system, run programs that do not take long to complete, or start long-running
programs that communicate with the ATmega32u4 through a different method. You
can easily pass information into the program at the start, but it is more difficult to
pass information to programs that are already running.
You can use the file system to exchange very large pieces information between the
two processors. However, you first need to work out how each processor knows
that data is ready, how it knows when all of the relevant data has been transferred,
and how it knows that it is safe to open that information without blocking the other
processor. This works best in combination with another method for example,
sending a M ailbox message to the ATmega32u4 with a file name to tell the Arduino
sketch that it can open the file.
The M ailbox is a simple and convenient way of sending short strings from the Linux
side of the Yn to the Arduino side. However, it does not work well when trying to
send messages in the other direction, or when trying to exchange non-text-based
information.
You can use the Console class when you want to make a continuous connection
between the ATmega32u4 and the AR9331. Once the connection is made, either
side of the Yn can send and receive data when it wants.
The three networking classes of the Bridge library (HttpClient, YunClient, and
YunServer) are a good choice when: you are already using network protocols for
some other aspect of your project; when you want to accept or make connections
to and from other devices; and when you want to pass information to Linux
programs that normally communicate with web-based systems.
1
It is possible for a connection to be closed after data is sent, but before you read it from the buffer.
2
If you change the password on your Y n, type the new password here.
3
If you have not changed it, the password is arduino.
Hosting Websites and Services
Web servers wait for incoming connections from clients, usually web browsers. They
process requests for information, and then send the information or an error message
indicating why the server could not complete the request. When hosting websites, the
information that a web browser requests is usually a page. After examining the page, the
browser then makes requests for all the images, JavaScript files, stylesheets, and other
files that the page needs.
If you work with shields, such as the Arduino Ethernet or Wi-Fi shields, then you may be
familiar with how to accept an incoming connection in an Arduino sketch, and how to
send back pages and information to web browsers. On the Arduino Yn, you can use
the same techniques that you use with those shields. In Project 2 Controlling an LED
M atrix through a Web API you can see how to accept connections using the YunServer
and YunClient classes in the Bridge library.
However, the Atheros AR9331 is much faster and has more memory than the
ATmega32u4. And so the Linux side of the Yn is a much more capable web server than
the Arduino side. OpenWrt-Yun has a built-in software package called uHTTPd. This is
the software that runs the Yns two web-based administration panels, and you can use
it to host your own websites and web services.
In This Chapter
When a web browser requests a file from the Arduino Yn, uHTTPd looks in this
directory for it. If the browser specifies a sub-directory as part of the web address then
uHTTPd looks for the directory inside /www. If the browser does not specify which page
it is looking for, uHTTPd sends back the default webpage. This default is usually one of
the following file names: index.html, index.htm, default.html, or default.htm.
OpenWrt-Yun takes up most of the available space on the Atheros AR9331. To host
your own files, you need to store them on the microSD card.
If you expand the Linux file system onto the microSD card then inside /www there is a
symbolic link, /www/sd. This links to the /mnt/sda1/arduino/www directory on the microSD
card. For example, if you place a hypertext markup language (HTM L) file called
index.html inside the /mnt/sda1/arduino/www directory then you can access this file
using the universal resource locators (URLs):
http://arduino.local/sd; or
http://arduino.local/sd/index.html
If you do not expand the Linux file system onto the microSD card, but want to add files
to the Yns default website in the way described, you have to create this link yourself.
For example, to link /www/sd to a directory called www in the top-level of the microSD
card, type the following command at the OpenWrt-Yun command prompt and then press
the Enter key:
ln -s /mnt/sda/www /www/sd
Placing your files in the Yns default website is often convenient. But any configuration
changes that you make to this site also apply to the two administration panels.
You can run more than one website on the Yn. However, uHTTPd has a few limitations
that affect how you do this. Web clients make connections to web servers over
transmission control protocol (TCP), and they usually do this over port 80. uHTTPd can
only run one website for each TCP port.1
To change how uHTTPd runs, you can make changes to the file /etc/config/uhttpd. Open
this file in nano to see how to structure information in this file:
The file comprises several sections, each one starting with a config declaration. At the
top of the file, you can see the section beginning with config uhttpd main. This
configuration section controls the default website on the Yn including the two
administration panels. To create a new website, you need to add a new configuration
section.
To create a new website, first create a directory on the microSD card to contain all of
your files. For example:
To configure uHTTPd with a new website that runs on TCP port 81,
2. Press the Down Arrow key until you reach the lines:
# Certificate defaults for px5g key generator
config cert px5g
4. Press Ctrl + X.
5. Press Y and then press Enter.
Use the Tab key to indent the lines. The spacing in this file is very important to uHTTPd.
Lines that begin with # are comments, and uHTTPd ignores these. Place as many
comments in the file as you need to help you remember why you made certain settings.
site2 is the name of the new website; you can set this to anything you want. The
configuration section in this example is very minimal, and there are only two important
lines to be aware of:
The line list listen_http 0.0.0.0:81 tells uHTTPd to listen on TCP port 81 on all IP
addresses. You can specify an IP address by replacing 0.0.0.0 with the IP address
that you want uHTTPd to listen on.
The line option home /mnt/sda1/www2 specifies where uHTTPd looks for the files
for this website.
Before these changes take effect, you have to restart uHTTPd. To do this,
At the command prompt, type the following command and then press Enter:
/etc/init.d/uhttpd restart
1. At the command prompt, type the following command and then press Enter:
nano /mnt/sda1/www2/index.html
3. Press Ctrl + X.
4. Press Y and then press Enter.
To test this website, you need to open a web browser on a machine that is on the same
network as your Arduino. To specify the TCP port that the browser should use, you add
a colon and then the port number to the end of the host name or domain name.
Tip: The TCP port numbers for your websites can be a number from 0 through
65535. However, many of these ports are already in use by other programs.
Numbers 8090 are usually a safe choice.
You can change this by specifying the index_page option in the /etc/config/uhttpd
configuration section for your site. For example:
option index_page home.html
Introducing Port Forwarding and Dynamic DNS
On a network, computers make connections to other computers using IP addresses.
These addresses tell one machine where it can find the other.
However, when your computers connect to the Internet through a router, your Internet
service provider (ISP) assigns an additional IP address to the router. To the outside
world, all devices on your local network appear to have the same IP address the one
set by the ISP. The local network addresses are not used by clients connecting through
the Internet.
This creates two problems: how can your router know which machine on your network is
supposed to respond to web requests, and how do clients on the Internet find your
router?
Port forwarding is a configuration setting that you can use to tell your router which device
on your local area network (LAN) should receive the connection from the outside world.
The exact method of setting this up is different for each router.
As a general guide:
1. Login to your routers administration panel. For many home routers, this is done by
visiting the URL http://192.168.0.1 in a web browser on your PC.
2. Look for an option or page that allows you to control inbound connections. This may
be named Port Forwarding, Firewall Rules, Services, or something similar.
3. Create rules that says the HTTP service is allowed on a specific port (for example,
80 and 81), and that requests should be sent to IP address of the Arduino Yn.
Because the IP address of the Yn changes every time it connects to the router, you
should connect the Yn to your network using a static IP address. For more information,
see Using a Static IP Address.
When you visit a website, you use its domain name (for example, google.com) as part of
the web address. Your web browser looks up the domain name to find the associated IP
address, using the domain name system (DNS). But unless you ask them to, your ISP
does not allocate a fixed (or static) IP address to your router. Instead, the IP address
will change every time your router connects to the Internet. If the connection drops or
the router restarts, this address changes.
Dynamic DNS (DDNS) is a method of automatically updating DNS records when the
router receives a new IP address from an ISP. This means that any web clients trying to
find your server using a domain name will always receive the up-to-date IP address.
Although there are many DDNS services out there, they all tend to work the same way:
when your router connects to the Internet then it (or a machine on your network)
contacts the DDNS servers and tells them the new IP address. You can usually choose
whether to use a domain name given to you by the DDNS service, or buy your own
domain name and use that.
When hosting multiple websites on the Yn, there are additional problems.Unless you
use multiple IP addresses for the Yn, only one of your websites can use TCP port 80.
If the user (or a link that they click on) does not specify the port number, then the
browser uses TCP port 80. With the default configuration, this opens up the Arduino
Yns administration panel to the world. If you allow incoming connections from across
the Internet, you should change the password for your Arduino Yn.If you want to run
your second website on TCP port 80, so that it is the default site, then you have to
change the website configuration for the administration panels in /etc/config/uhttpd so
that they use a different port. You can then configure your router to only allow access on
TCP port 80, and the administration panels become inaccessible outside of your local
network.
3. In the configuration section labelled main, change the line that reads list listen_http
0.0.0.0:80 to
list listen_http 0.0.0.0:81
4. In the configuration section for your new website, change the line that starts list
listen_http to
list listen_http 0.0.0.0:80
5. Press Ctrl + X.
6. Press Y and then press Enter.
7. Type the following command and then press Enter:
/etc/init.d/uhttpd restart
You need to create an httpd.confconfiguration file for this. httpd.conf is a file name, but it
also describes a type of file. The file names that you use for your configuration files can
be different. You use this file to set additional configuration properties for the site. /etc is
a good place to keep these files. In this example, you will password-protect the website
that is created in Setting Up a Website on the Arduino Yn. The configuration file is
called /etc/site2.conf.
4. Press Ctrl + X.
5. Press Y and then press Enter.
The line that you add to the file contains three pieces of information separated by
colons: the path2 ; the username that visitors must use; and the password.
To add multiple users, add multiple lines of this type.To enable basic authentication on
the website in uHTTPd,
1. At the command prompt, type the following command and then press Enter:
nano /etc/config/uhttpd
2. Find the configuration section for the website that you want to protect.
3. The realm name is usually displayed to the user. It can be anything that you want
(but only one word). To add a realm name to the configuration, add the line:
option realm Site2
4. To specify the location of the httpd.conf file that you created earlier, add the line:
option config /etc/site2.conf
5. Press Ctrl + X.
6. Press Y and then press Enter.
7. Type the following command and then press Enter:
/etc/init.d/uhttpd restart
Using an additional website configuration file is another way that you can create links
between files and directories. Unlike symbolic links, these links only work when you
access the files through the web server. You need to create an httpd.conf configuration
file for this. This file allows you set additional configuration properties for the site. /etc is
a good place to keep these files.For example, you can have all of your images stored in
the directory data/images/ and create a virtual directory that allows you to access these
files from the URL http://arduino.local/images/.
1. At the command prompt, type the following command and then press Enter:
nano /etc/site2.conf
3. Press Ctrl + X.
4. Press Y and then press Enter.
1. At the command prompt, type the following command and then press Enter:
nano /etc/config/uhttpd
2. Find the configuration section for the website that you want to apply these extra
options to.
3. To specify the location of the httpd.conf file that you created earlier, add the line:
option config /etc/site2.conf
4. Press Ctrl + X.
5. Press Y and then press Enter.
6. Type the following command and then press Enter:
/etc/init.d/uhttpd restart
In the next section of this chapter, you can see how to write programs that dynamically
create content for your website. You may choose to put these programs in a special
folder named cgi-bin. However, by using virtual files and directories, you can make
these programs appear anywhere in your website.
Server-Side Scripting with CGI and Python
When you host HTM L pages and files on the Arduino and send them to web browsers,
this is called static content. The files that uHTTPd sends to the clients, and what the
web browsers display, only change when you update the files on the Yn.
This can be done with client-side scripting, or server-side scripting. Client-side scripting
uses languages, such as JavaScript, that run in the web browser. The server sends the
same information every time, but the browser runs a script to change how it uses that
information.
With server-side scripting, a program changes the information before the web server
sends it to the web browser.
On web servers, you can use languages such as active server pages (ASP), java
server pages (JSP), and PHP: hypertext processor (PHP) for server-side scripting.
OpenWrt-Yun does not have built-in support for these languages, but it is sometimes
possible to install them.
On the Yn, you can use the common gateway interface (CGI) to write dynamic content.
CGI programs and scripts send their output to the standard output device this is the
command-line shell when you run a program from the command prompt, but when you
run the same program from uHTTPd then it sends the output to the web browser.
Because the Arduino Yn has built-in support for the Python language, this chapter
focusses on writing Python scripts for server-side scripting. In Programming in Python,
you can learn more about writing Python scripts.Before you can run Python scripts that
send information to web browsers, you need to change the configuration of uHTTPd to
allow this. If you do not already have an additional web site on your Yn, follow the
instructions in Setting Up a Website on the Arduino Yn to create one.There are two
ways of running Python scripts from a website: using a cgi-bin directory; and using a
script interpreter.
1. At the command prompt, type the following command and then press Enter:
nano /etc/config/uhttpd
2. Find the configuration section for the website that you want to apply these extra
options to.
3. Add the following lines to the sites configuration:
option cgi_prefix /cgi-bin
option script_timeout 60
4. Press Ctrl + X.
5. Press Y and then press Enter.
6. Create a directory inside the websites home directory, and call it cgi-bin.
7. Type the following command and then press Enter:
/etc/init.d/uhttpd restart
3. Open a web browser on a machine that is connected to the same network as your
Arduino.
4. In the address bar, type the URL of the website followed by /cgi-bin/test.py and then
press Enter.
1. At the command prompt, type the following command and then press Enter:
nano /etc/config/uhttpd
2. Find the configuration section for the website that you want to apply these extra
options to.
3. Add the following lines to the sites configuration:
list interpreter ".py=/usr/bin/python"
option script_timeout 60
4. Press Ctrl + X.
5. Press Y and then press Enter.
6. Type the following command and then press Enter:
/etc/init.d/uhttpd restart
1. Create a file, for example test.py, inside the websites home directory.
2. Add the following text to the file:
#!/usr/bin/python
print "Content-type: text/plain"
print
print "Hello from Python"
3. Open a web browser on a machine that is connected to the same network as your
Arduino.
4. In the address bar, type the URL of the website followed by /test.py, and then press
Enter.
If you are working on a project that allows users to upload files then you should be
careful using an interpreter declaration. If you allow Python scripts to run anywhere on
the site, not just in a cgi-bin, you need to ensure that users cannot upload their own
script and then run it.
The first line of the test script tells the web server which command to use to run the
script.
When a server sends a file to a web browser, it also sends additional information that
describes the file and the server from which it came. These pieces of information are
HTTP response headers. On the Yn, uHTTPd does most of the work of generating
and sending the HTTP headers. The only one that you must send yourself is Content-
Type.
Web browsers use the Content-Type field in an HTTP response header to decide what
to do with the file this serves the same purpose as the file extension on M icrosoft
Windows.
The values of the Content-Type field are called M IM E types because they are derived
from the multipurpose Internet mail extensions (M IM E) standard, which is used to allow
email messages to contain attachments and different types of text.
Some of the most common M IM E types used on the web are:
File Media
Description
Extension Type
.CSS text/css Specifies that the file is a cascading style sheet (CSS).
To send an HTTP header, you print its name followed by a semi-colon, and then a
value. For example:
print "Content-type: text/plain"
If you want to send other HTTP header fields in your scripts, you can do it in exactly the
same way.
After sending all of the HTTP headers, there is an empty line between them and the
actual web page information.
uHTTPd places this information in environment variables, and you can access it from a
Python script by using the os module.
The example below shows how to write a server-side Python script that shows the
name (and similar information) of the web browser.
#!/usr/bin/python
import os
The most-common CGI environment variables that you can access using the os.environ
collection are:
Variable Description
Some of this information comes from the web browser, and some of it is extra
information from uHTTPd.
Accessing Query String and Form Data using the Python CGI Module
Web browsers can send information to the web server in several ways: through a query
string; with a form submission; or in cookie variables.
The query string is a collection of key and value pairs that the browser sends as part of
the request URL. Each pair comprises a key (like a variable name) and a value, and
these are separated by an equals sign. Each pair is separated from the others using an
ampersand. A question mark in the URL indicates the start of the query string.
For example:
http://arduino.local/cgi-bin/file.py?key1=value1&key2=value2&key3=value3
When uHTTPd receives this request, it puts everything after the question mark into the
CGI environment variable QUERY_STRING. You can process the query string using
Pythons string functions. However, there is a simpler way.
Tip: When the browser makes a form submission using the request method POST,
it creates a very similar key-value string. However, it sends the string after the
HTTP request header, not as part of the request URL.
The Python CGI module simplifies how you can access query string and form data. You
can use it from scripts in a cgi-bin directory, or from Python scripts that run through a
script interpreter.To use the module, you need to import it into your Python script. For
example:
#!/usr/bin/python
import cgi
The CGI module interprets the CGI environment variables and makes it easier for you to
extract the information that you want. Whether the browser passes values into a script
using a query string or it sends them with a form submission, you can use the
FieldStorage class to access the values.
Then you can access a query string value using the getvalue() method and the key
name. For example, to send back the value of the query string parameter key1, place this
script into your cgi-bin directory:
#!/usr/bin/python
import cgi
form = cgi.FieldStorage()
If the web browser does not send a key-value pair with the name that you request,
getvalue() returns an empty string.
Tip: If the web browser sends multiple entries with the same key (for example,
multiple form fields on an HTML page with the same name) then the entries in
form are lists of FieldStorage or MiniFieldStorage objects instead. If you need to
process all of the entries, use the getlist() method instead of getvalue().
However, the process is slightly different if you make an HTM L form that allows the user
to upload a file. The HTM L code below is a short example of how to create a form that
prompts the user to upload a file:
<html>
<head><title>Form Upload Example</title></head>
<body>
<form enctype="multipart/form-data" action="/cgi-bin/save.py" method="post">
<p>File: <input type="file" name="uFile" /></p>
<p><input type="submit" value="Upload" /></p>
</form>
</body>
</html>
If the CGI module receives a file then it places the file name into the filename property of
the object that represents the form field. To retrieve the file name, you need to access
the form field using the same name as it has in the HTM L.
To create a Python script to display the file name of an uploaded file: create the script
/cgi-bin/save.py4 and type in the following code:
#!/usr/bin/python
import cgi
form = cgi.FieldStorage()
print "Content-type: text/plain"
print
try:
print "File: " + form["uFile"].filename
except:
print "Error."
If the web browser requests the /cgi-bin/save.py script without sending the form data
then the Python interpreter raises an exception. The try and except statements stop the
script from ending early and not sending back a response to the web browser.
To store the uploaded file somewhere, you need to create a new file on the file system
(ideally on the microSD card) and write the file information to it. The files raw data is
accessible through the file property of the form object.
form = cgi.FieldStorage()
try:
fn = "/mnt/sda1/" + form["uFile"].filename
myfile = open(fn, "wb")
myfile.write(form["uFile"].file.read())
myfile.close()
print "Thank you."
except:
print "Error writing file, or no file uploaded."
The read() method of the file property returns all of the information from the file that the
browser uploads. By passing this into the write() method of the open file, you can store
the entire file with one line of code.
Project 1 Building a Web-Based
Temperature Monitor
This project uses the Arduino Yn with an Adafruit M CP9808
(http://www.adafruit.com/product/1782) to create a web-based temperature monitor.
Anyone on your local network (or across the Internet if you allow incoming connections
on your router) can open the temperature monitor website to see the current room
temperature.
The built-in web server on the Linux side of the Yn accepts connections from web
browsers, and sends the HTM L page, cascading style sheet (CSS) file, and images to
the client. An Arduino sketch on the ATmega32u4 reads from the M CP9808 sensor and
stores the current temperature in the AR9331s memory.
If you do not have access to an M CP9808, there are many other I2C temperature
sensors that you can use for this project. If you use a different board, consult the
documentation for your sensor carefully. The connection diagrams and instructions that
follow may be specific to the M CP9808.
Caution: You can only do this when the device that you are using has very small
power requirements. Drawing too much current from the Arduinos pins can
damage the Arduino. If you are unsure about a device, do not connect it this way.
This project uses the analog input pins on the Arduino as digital input/output pins. Pins
A1 and A2 on the M CP9808 are not used in this project, and they have built-in, pull-
down resistors so you can leave them unconnected.
Pin Description
Gnd Ground.
Alert A pin that can warn you if the temperature changes by a set amount.
I2C address. The default address is 0x18. If the Arduino brings this pin high
A0
then it adds 1 onto the I2C address.
I2C address. The default address is 0x18. If this pin is high then it adds 2 onto
A1
the I2C address.
A2 I2C address. The default address is 0x18. If this pin is high then it adds 4 onto
the I2C address.
1. If you use Windows: open Windows Explorer, and browse to the Arduino IDEs
installation directory.6 Then double-click Libraries.
2. If you use M ac OS X: open Finder, browse to the Arduino IDEs installation
directory, and then double-click Libraries.
3. M ake a new directory and call it SoftI2CMaster.
4. Download the SoftI2CMaster.h file from the following URL, and save it to the
SoftI2CMaster directory: http://arduinomeetslinux.com/download/SoftI2CM aster.h
5. Download the SoftI2CMaster.cpp file from the following URL, and save it to the
SoftI2CMaster directory: http://arduinomeetslinux.com/download/SoftI2CM aster.cpp
6. Restart, or open, the Arduino IDE.
In the Arduino IDE, start a new sketch. In the setup() function, add the following code to
use two analog pins as a power supply for the M CP9808.
pinMode(A0, OUTPUT); // create Vdd power.
digitalWrite(A0, HIGH);
pinMode(A1, OUTPUT); // create GND.
digitalWrite(A1, LOW);
To use the SoftI2CM aster library, you need to import the library. Add the following code
to the top of your sketch:
#include <SoftI2CMaster.h>
The example code below uses the Console class in the Bridge library to display
messages. If you upload sketches to your Yn using a USB cable then you can use the
Serial class and the Serial M onitor instead.
Add these lines to the top of your sketch, underneath the line #include
<SoftI2CMaster.h>:
#include <Bridge.h>
#include <Console.h>
To test the communication between the Arduino and the M CP9808, add the following
code to the end of your sketchs setup() function:
Bridge.begin();
Console.begin();
while (!Console);
Console.print("Testing I2C connection... ");
i2c.beginTransmission(0x18);
i2c.send(0x07);
i2c.endTransmission();
i2c.requestFrom(0x18);
uint16_t v = i2c.receive();
v <<= 8;
v |= i2c.receive();
Console.println(v, HEX);
You should see the text 400 (0x0400), which is the correct device ID for the Adafruit
M CP9808.
The sketch fetches the device ID by sending the command 0x07 to the I2C address of
the M CP9808 (0x18). It waits for a response from the same address and then receives
the ID as two bytes, which it combines into a single 16-bit value.
i2c.requestFrom(0x18);
uint16_t v = i2c.receive();
v <<= 8;
v |= i2c.receive();
return v;
}
The return statement in the setup() function prevents the sketch from entering the loop()
function if the M CP9808 is not available.
i2c.requestFrom(0x18);
uint16_t v = i2c.receive();
v <<= 8;
v |= i2c.receive();
This function fetches the temperature in the same way as the getDeviceId() function
fetches the device ID. To get a meaningful value, getTemperature() converts the value
from the M CP9808 into a float that represents the temperature in degrees Celsius.7 In
the sketchs loop() function, add the following code:
float temp = getTemperature();
Console.print("Temperature: ");
Console.print(temp, 1);
Console.println("C");
delay(1000);
Run the sketch and then reopen the Console connection from the Linux command
prompt. You should see the temperature updates every second.
To finish the Arduino sketch, you can store the temperature in the Atheros AR9331s
memory so that your website can read the value and display it.
To make sure that the sketch runs even if you do not open a Console connection to
view the debugging messages, remove the line that reads while(!Console); from the
setup() function.
You can find the full source code for this sketch in Source Code Sketch.
To begin, you need to create a new website in the configuration file for uHTTPd. For
more information, see Setting Up a Website on the Arduino Yn.Add the following
configuration options to the website in /etc/config/uhttpd:
option index_page index.py
option script_timeout 60
list interpreter ".py=/usr/bin/python"
This project uses an interpreter setting to run the Python code, because you want the
websites default page to be a script.
At the command prompt, change your working directory to the location of the website.
For example, if your website is in the folder /mnt/sda1/www2, type the following
command and then press the Enter key:
cd /mnt/sda1/www2
Then download the sample website for this project and decompress it.
There are three directories and one Python script in the sample website:
css/
Contains a cascading style sheet file that makes the HTM L look like a wall-mounted
temperature monitor.
fonts/
This website uses a web font to simulate the appearance of text on a liquid crystal
display (LCD). The files for this web font are in the fonts directory.
images/
Contains an image to use as the background for the monitor.
index.py
A server-side Python script that fetches the temperature from the shared-storage area
of the AR9331s memory and sends an HTM L page to web browsers. The source
code for this file is in Source Code Python.
The script that generates the HTM L content for this project is fairly basic.
In Accessing Stored Data from Python Scripts, you can see how to import the
BridgeClient class into a Python script, and how to read the values from the memory
area that the ATmega32u4 can access.
index.py first reads the temperature that the Arduino sketch places in the AR9331s
memory. It converts the temperature string to a float, so that later it can convert the
temperature to Fahrenheit and round it to a single decimal place.
client = BridgeClient()
temp = float(client.get("MCP9808_Temperature"))
Then it uses the os module to check whether the query string is empty. If it is, the script
sets the default unit to Fahrenheit.
unit = os.environ["QUERY_STRING"]
if unit == "":
unit = "F"
The query string is usually empty until the user clicks the button labelled C / F on the
webpage.
Next, the script finishes the HTTP response header by setting the Content-Type to
text/html so that the web browser knows to expect an HTM L page.
Using triple-quoted strings, the script sends HTM L code to the web browser. When it
needs to place a calculated value (such as the temperature) in that part of the page, it
ends the triple-quoted string and prints the value separately.
For example, to add the current time to the HTM L output, you can use the code:
print """
</p>
<p id="time" class="lcd">
"""
print time.strftime("%H:%M")
print """
</p>
...
The text below shows the actual HTM L page that this script outputs. The markup code
on this page structures the information; it is the CSS file that makes the paragraphs of
text look like a wall-mounted panel.
<!DOCTYPE html>
<html>
<head>
<title>Project 1 - Building a Web-Based Temperature Monitor</title>
<link rel="stylesheet" href="/css/normal.css" type="text/css" />
<meta http-equiv="refresh" content="10">
<meta charset="UTF-8">
</head>
<body>
<div id="panel">
<p id="temperature" class="lcd">
54.9
</p>
<p id="unit" class="lcd">
F
</p>
<p id="date" class="lcd">
01/17/2015
</p>
<p id="time" class="lcd">
12:34
</p>
<input type="button" id="btnUnit"
onclick="javascript:location.href='/?C'" value="Unit" />
<input type="button" id="btnRefresh"
onclick="location.reload();" value="Refresh" />
</div>
</body>
</html>
uHTTPd handles all of the other files that the web browser requests the CSS file, the
web font, and the background image.
After 10 seconds, the refresh directive in the HTM L code tells the web browser to
reload the web page. Or you can click the button labelled REFRESH to reload the page
immediately.
Figure 9. The Arduino temperature monitor
uint16_t getDeviceId() {
i2c.beginTransmission(0x18);
i2c.send(0x07);
i2c.endTransmission();
i2c.requestFrom(0x18);
uint16_t v = i2c.receive();
v <<= 8;
v |= i2c.receive();
return v;
}
float getTemperature() {
i2c.beginTransmission(0x18);
i2c.send(0x05);
i2c.endTransmission();
i2c.requestFrom(0x18);
uint16_t v = i2c.receive();
v <<= 8;
v |= i2c.receive();
void setup() {
pinMode(A0, OUTPUT);
digitalWrite(A0, HIGH);
pinMode(A1, OUTPUT);
digitalWrite(A1, LOW);
Bridge.begin();
Console.begin();
Console.print("Testing I2C setup... ");
if (getDeviceId() == 0x0400) {
Console.println("OK.");
}
else {
Console.println("Error. Could not find Adafruit MCP9808.");
return;
}
}
void loop() {
float temp = getTemperature();
Console.print("Temperature: ");
Console.print(temp, 1);
Console.println("C");
Bridge.put("MCP9808_Temperature", String(temp));
delay(1000);
}
client = BridgeClient()
temp = float(client.get("MCP9808_Temperature"))
unit = os.environ["QUERY_STRING"]
if unit == "":
unit = "F"
if unit == "F":
temp = (temp * (9/5)) + 32
print ("%.1f" % temp)
print """
</p>
<p id="unit" class="lcd">
"""
print unit
print """
</p>
<p id="date" class="lcd">
"""
print time.strftime("%m/%d/%Y")
print """
</p>
<p id="time" class="lcd">
"""
print time.strftime("%H:%M")
print """
</p>
<input type="button" id="btnUnit"
"""
if unit == "F":
print "onclick=\"javascript:location.href='/?C'\""
else:
print "onclick=\"javascript:location.href='/?F'\""
print """
value="Unit" />
<input type="button" id="btnRefresh" onclick="location.reload();" value="Refresh" />
</div>
</body>
</html>
"""
Making Web Services and APIs
In programming terms, an application programming interface (API) is a collection of
functions or classes. One program can use anothers API to communicate with the
second program.
Web services and web APIs are two very similar techniques for using this type of
communication over the Internet. Instead of requesting a webpage, programs that call
your API specify the function that they want to run. And instead of sending back a web
page, your server runs the function and sends back the result. The result is usually not
something that you can display on a page it is in a format that the client program can
understand.
You do not need a different type of web server to run web services or web APIs you
can use any server that supports CGI or some form of scripting language.
Web services usually use an accompanying file that describes all of the functions that
the client can call. This file is written (or automatically generated) in web service
definition language (WSDL). Several development environments (such as M icrosoft
Visual Studio) can automatically generate all of the code needed to use a web service
from your program, just by analyzing this WSDL file.
Full web services can be difficult to process on the ATmega32u4, and so you should
use the Atheros AR9331 and OpenWrt-Yun Linux to do as much of the work as
possible.
The process for running a SOAP web service on the Yn comprises the following steps:
You can often see RESTful APIs used when JavaScript code in a web browser wants to
make an API call on a web server. M any web APIs use JavaScript object notation
(JSON) as a way of describing the requests and data objects in a way that web
browsers can easily work with.
JSON represents objects and collections of objects in a structured text format typically
organized into key/value pairs, but each value can also be another collection of
key/value pairs. The JavaScript interpreter in web browsers can create a data object
directly from JSON, and this has helped make it extremely popular for use in web APIs.
When evaluated by the JSON parser, this data creates an object with two properties:
result (which contains the text string ok), and values (an array of numbers, 19).
Because they are usually a little simpler, RESTful APIs can be run from either an Arduino
sketch or from server-side Python scripts on OpenWrt-Yun. However, the AR9331 has
much more power and resources than the ATmega32u4, and you should generally try to
use this advantage in your projects.
The process for running a JSON-based web API on the Arduino Yn is very similar to
that of running a web service:
This project uses an Adafruit 8x8 LED matrix and a custom website. If you open the site
in a web browser, you can click the LED buttons on the webpage to turn the actual
LEDs in the matrix on or off. Both versions of this project create a JavaScript object
notation (JSON) API.
Pin Description
D I2C data pin (SDA). Connect this to digital pin 2 on the Arduino.
C I2C clock pin (SCL). Connect this to digital pin 3 on the Arduino.
Close nano, and then type the following command and press the Enter key:
cd /www/sd
To download the sample website for this project and put it on your microSD card,
There are three directories and one HTM L file in the sample website:
css/
Contains a cascading style sheet file that makes the HTM L look like an Adafruit 8x8
LED matrix.
images/
Contains a background and button images.
scripts/
This project uses jQuery (a library that extends JavaScript). The jQuery library is in
here.
index.html
Contains HTM L and JavaScript code to display the matrix in the web browser. The
JavaScript code creates the LED buttons the page, and sends click information to
the Arduino sketch.
Tip: It is beyond the scope of this book to fully explain JavaScript and jQuery. But
you can see how jQuery sends data to the Arduino in the next section of this
chapter.
LEDs on the Adafruit matrix are arranged like a table. There are eight columns, and each
column has eight rows.To turn on an LED, the script makes a request to
http://arduino.local/arduino/on/X/Y, where
X
Is a number from 0 through 7 and states which column of the matrix contains the LED
that the sketch should turn on.
Y
Is a number from 0 through 7 and states which row of the matrix contains the LED that
the sketch should turn on.
The jQuery code that sends these commands looks like this:
var jq = $.getJSON(call)
.done(function (json) {
if (json.result) {
console.log("OK.")
} else {
console.log("API Error: Invalid LED coordinates.");
}
})
.fail(function (jqxhr, textStatus, error) {
var err = textStatus + ", " + error;
console.log("API Error: " + err);
});
The variable call contains the URL and command that you want to send to the Arduino
sketch, and the $.getJSON() function makes the HTTP request.
This script sets up two functions that run depending on the result of the HTTP request. If
the server receives the request and responds, the script runs the .done()function. If the
request fails, the script runs the .fail() function.
.done() expects the server to send back a response in JSON format. The response
should contain at least one variable, result. If result is zero then the script assumes that
the Arduino sketch rejected the command. Note: this script takes no action if errors
occur the .done() and .fail() functions are here only as an example.
When the web browser first displays the web page, the script makes a different request
to the Arduino sketch. It makes a request to the URL http://arduino.local/arduino/get, and
the sketch sends back a multidimensional array of 1s and 0s. This array matches the
layout of the LED matrix. A 1 indicates that the LED is lit, and a 0 indicates that the LED
is not lit. This means that if you reload the webpage, the script can make the interface
look like the actual LED matrix.
Tip: You can also use the Mailbox to receive this information from the web
browser. The Mailbox is explained in Using M ailbox and Sending M essages.
However, the Mailbox is less useful for sending back responses to the browser
and so this project uses YunServer and YunClient.
For the sample website to work, you need to set the Arduinos REST API to allow
connections without a username and password. To change the REST API setting:
1. On a machine that connects to the same network as the Arduino, open a web
browser.
2. In the address bar, type http://arduino.local9 and then press Enter.
3. Type the current password10 and then click LOG IN.
4. Click CONFIGURE.
5. Scroll to the bottom of the page, and under REST API ACCESS, click the Open box.
There are two advantages to using the /arduino script instead of pointing the web
browser directly to http://arduino.local:5555/:
1. The /arduino script removes all of the HTTP headers so that you do not have to
process them before you can access the data from the webpage.
2. Non-standard TCP ports (ports that are not 80, or 443) are often blocked by public
Internet connections, such as in Internet cafs and offices. If you run the Arduinos
administration panels on TCP port 80 and allow incoming connections on your
router, everyone can access the /arduino script.
To build the Arduino sketch, start a new sketch in the Arduino IDE and add the following
code:
#include <Bridge.h>
#include <YunServer.h>
#include <YunClient.h>
YunServer srv;
void setup() {
Bridge.begin();
srv.listenOnLocalhost();
srv.begin();
}
void loop() {
YunClient yc = srv.accept();
if (yc) {
yc.stop();
}
}
The line that reads YunServer srv; creates an instance of the YunServer class. You can
use this class when you want to receive incoming connections across the network.
Method Description
Then all the setup() function needs to do is tell the server to start listening, using the
begin() method.
Code in the sketchs loop() function continually checks if there is an incoming request. If
there is, it accepts the connection and obtains an instance of the YunClient class. The
code in the loop() function then checks if it is a valid connection and immediately closes
it using the stop() method.
The instance of the YunClient class that the sketch obtains from YunServers accept()
method, represents an open connection to a web browser (or other client). It contains
methods for reading information from the client, and sending back responses.
Tip: You can also use YunClient to initiate connections to servers. To do this:
create a new instance of YunClient; call the connect() method, and pass in the IP
address or domain name of the server and the TCP port to use.
Method Description
If the client sends data to the Arduino, this method returns the
available()
number of bytes of data that you have not yet read.
Ensures that any bytes that you have written with calls to
flush()
write(), are sent to the web browser immediately.
Reads a byte from the client but does not remove it from the
buffer. The next call to peek(), read(), readBytes(),
peek()
readBytesUntil(), readString() or readStringUntil() returns the
same byte.
Reads from the client until the connection ends or the method
readBytes() receives no data. Then the method returns all of the data as an
array of bytes.
Reads from the client until the method receives the specified
character, the connection ends, or the method receives no
readBytesUntil(char)
data. Then the method returns all of the data as an array of
bytes.
Reads from the client until the connection ends or the method
readString() receives no data. Then the method returns all of the data as a
string.
Reads from the client until the method receives the specified
readStringUntil(char) character, the connection ends, or the method receives no
data. Then the method returns all of the data as a string.
Before you can respond to incoming requests in this project, add the following line
underneath the line that reads YunServer srv;:
byte matrix[8][8];
This creates a multidimensional array in the sketch. This array stores information about
which LEDs are lit, and which LEDs are not lit.
ctoi() converts a single ASCII character to a number. It only supports the values 0
through 7, because the LED matrix only has eight rows and eight columns. For example,
ctoi('0') converts the ASCII character that looks a zero (but is actually the number 48) to
an actual zero.
When your Arduino sketch receives an incoming connection from the /arduino script, you
need to read the commands that the web browser sends. You can do this using the
readString() method.
Before the line that reads yc.stop();, add the following line:
String cmd = yc.readString();
You can then check which command the browser sends by using the startsWith()
method of the string object. Add the following code underneath the previous line:
if (cmd.startsWith(F("on/"))) {
}
else if (cmd.startsWith(F("off/"))) {
}
else if (cmd.startsWith(F("get"))) {
}
Tip: Using strings to process data can take up a lot of memory. If you surround a
string literal with F() then you can ensure that the Arduino access the string from
program ROM, and does not use the RAM area.
If the browser sends the on or off command, the sketch needs to convert the x and y
values from the request into usable numbers.
Add these lines into the code that runs if the command starts with on/:
int x = ctoi(cmd[3]);
int y = ctoi(cmd[5]);
if ( (x > -1) && (y > -1) ) {
matrix[x][y] = 1;
yc.print(F("{\"result\":1}"));
}
else {
yc.print(F("{\"result\":0}"));
}
You can use square brackets to access the individual characters that make up a string.
The first character is at position 0. So the code above extracts the fourth and sixth
characters from the string on/X/Y and converts each value to a number. It then checks
that the numbers are valid.
If both x and y are valid, then the sketch sets the matching value in the matrix array to 1.
It then sends back a JSON string with the result 1. If either x or y are invalid, the sketch
sends back a JSON string with the result 0.
The process for handling off commands is largely the same. Add the following lines to
the code that runs if the command starts with off/:
int x = ctoi(cmd[4]);
int y = ctoi(cmd[6]);
if ( (x > -1) && (y > -1) ) {
matrix[x][y] = 0;
yc.print(F("{\"result\":1}"));
}
else {
yc.print(F("{\"result\":0}"));
}
To handle the get command, you need to send back a JSON string that describes the
state of each LED in the matrix. A valid response for this looks like the following
example:
{ "result": 1,
"LEDs": [
[1, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 1]
]
}
To create this kind of response in the Arduino sketch, you can paste the code below
into the code that runs if cmd starts with get:
yc.print(F("{\"result\":1,\"LEDs\":["));
for (int x=0; x<8; x++) {
yc.print('['];
for (int y=0; y<8; y++) {
if (matrix[x][y]) {
yc.print('1');
}
else {
yc.print('0');
}
if (y < 7) {
yc.print(',');
}
}
yc.print(']');
if (x < 7) {
yc.print(',');
}
}
yc.print(']');
yc.print('}');
The full source code for the sketch so far is shown below. Upload the sketch to your
Yn, and then open a web browser that is on the same network as your Arduino. Open
the webpage, and then click a few LED buttons to turn them on.
#include <Bridge.h>
#include <YunServer.h>
#include <YunClient.h>
YunServer srv;
byte matrix[8][8];
void setup() {
Bridge.begin();
srv.listenOnLocalhost();
srv.begin();
}
void loop() {
YunClient yc = srv.accept();
if (yc) {
String cmd = yc.readString();
if (cmd.startsWith(F("on/"))) {
int x = ctoi(cmd[3]);
int y = ctoi(cmd[5]);
if ( (x > -1) && (y > -1) ) {
matrix[x][y] = 1;
yc.print(F("{\"result\":1}"));
}
else {
yc.print(F("{\"result\":0}"));
}
}
else if (cmd.startsWith(F("off/"))) {
int x = ctoi(cmd[4]);
int y = ctoi(cmd[6]);
if ( (x > -1) && (y > -1) ) {
matrix[x][y] = 0;
yc.print(F("{\"result\":1}"));
}
else {
yc.print(F("{\"result\":0}"));
}
}
else if (cmd.startsWith(F("get"))) {
yc.print(F("{\"result\":1,\"LEDs\":["));
for (int x=0; x<8; x++) {
yc.print('['];
for (int y=0; y<8; y++) {
if (matrix[x][y]) {
yc.print('1');
}
else {
yc.print('0');
}
if (y < 7) {
yc.print(',');
}
}
yc.print(']');
if (x < 7) {
yc.print(',');
}
}
yc.print(']');
yc.print('}');
}
yc.stop();
}
}
int ctoi(char a) {
if ( (a >= '0') && (a <= '7') ) {
return a-48;
} else {
return -1;
}
}
If everything is working correctly then you should be able to reload the webpage (or
open it on another computer) and see the same pattern. In the next section, you will use
this functionality to control the LEDs on the Adafruit matrix.
1. If you use Windows: open Windows Explorer, and browse to the Arduino IDEs
installation directory.12 Then double-click Libraries.
2. If you use M ac OS X: open Finder, browse to the Arduino IDEs installation
directory, and then double-click Libraries.
3. M ake a new directory and call it SoftI2CMaster.
4. Download the SoftI2CMaster.h file from the following URL, and save it to the
SoftI2CMaster directory: http://arduinomeetslinux.com/download/SoftI2CM aster.h
5. Download the SoftI2CMaster.cpp file from the following URL, and save it to the
SoftI2CMaster directory: http://arduinomeetslinux.com/download/SoftI2CM aster.cpp
6. Restart the Arduino IDE.
7. Reload your sketch.
Include the SoftI2CM aster library in your sketch by adding the following line to the top of
your code:
#include <SoftI2CMaster.h>
Then underneath the line that reads YunServer srv;, add the following line:
SoftI2CMaster i2c = SoftI2CMaster(2, 3);
To create an instance of SoftI2CMaster, you need to pass in the pins that you are using
for the SDA and SCL lines. The code above declares an instance of the SoftI2CMaster
class, and tells it to use digital pin 2 as the SDA line and digital pin 3 as the SCL line.
To make the LED matrix work in this sketch, you need to perform a few short
initialization routines. Add these two functions to the sketch:
void setBlinkRate(uint8_t b) {
if (b > 3) {
b = 3;
}
i2c.beginTransmission(0x70);
i2c.send((byte)(0x81 | (b << 1)));
i2c.endTransmission();
}
void setBrightness(uint8_t b) {
if (b > 15) {
b = 15;
}
i2c.beginTransmission(0x70);
i2c.send((byte)(0xE0 | b));
i2c.endTransmission();
}
The first I2C transmission makes the LED matrix active, but the process must be
completed by setting the blink rate to 0. The setBrightness() function accepts an integer
from 0 through 15, and it controls the brightness of all the LEDs (with 15 being very
bright).
To update the LED display, you need to make an I2C transmission that sends all of the
information from the matrix array. However, the Adafruit LED matrix uses a different
method of representing the state of each LED.
matrix uses a byte for each LED with 1 meaning that the LED is on, and 0 meaning
that the LED is off. The HT16K33 driver chip on the Adafruit board uses a 16-bit number
to represent an entire row of LEDs. Eight bits of that number represent the eight different
LEDs in that row. If a bit is set, the LED is on. If a bit is clear, the LED is off.
The least-significant bit (bit 0) does not control the first LED in the row. It controls the
second LED. The first LED is actually bit 7.
To handle all this in the Arduino sketch, create the function below:
void reDisplay() {
i2c.beginTransmission(0x70);
i2c.send(0x00);
for (byte y=0; y<8; y++) {
byte tmp = 0;
for (byte x=0; x<8; x++) {
if (matrix[y][x]) {
if (x == 0) {
tmp |= 0x80;
}
else {
tmp |= 1 << (x-1);
}
}
}
i2c.send(tmp);
i2c.send(0x00);
}
i2c.endTransmission();
}
The first value that the function sends to the LED matrix is a zero. This tells the driver
chip that it should start at its first memory location and then store everything that follows.
It automatically moves to the next memory address every time it receives a 16-bit
number.
The reDisplay() function loops through the matrix array in a different way than the other
parts of the sketch. For each row, it assigns a zero to the variable tmp. Then it looks at
each column in that row. If the value is 1, it sets the appropriate bit in tmp. For the first
LED, this is bit 7. For the other LEDs, the bit number is one less than the position of the
LED in matrix.
The reDisplay() function does not have to do anything if an LED is not lit, because it
initializes tmp to zero (all LEDs off) and only turns on LEDs that are 1 in matrix.
When the value for tmp is calculated, the function sends it to the LED matrix. Since the
HT16K33 driver expects a 16-bit number, the function also sends an additional byte
(zero).
To complete this part of the project, add a call to reDisplay() to the end of the Arduino
sketchs loop() function. The full source code for this sketch is in Source Code Sketch.
Upload the sketch to the Arduino, and test the project from a web browser.
There can be a short delay between clicking on an LED in the web browser and the
display changing on the LED matrix. Opening connections to web servers can take
several seconds, and this project is inefficient in that it makes a very short request every
time the user clicks an LED.
This project uses the /arduino script that is part of the Arduino Yns administration
panels, and stores the website files in the main sites directory. Whatever port the site
uses has to be accessible to the computers that want to load the webpage.
If you want to run this project from a new website (and move the administration panels
to a different port), then you can do this in the /etc/config/uhttpd file. For more information
about using a different TCP port for the administration panels, see Changing the TCP
Port Number of the Administration Panels.
The site that you create for this project should have the following setting:
option index_page index.html
However, if you move the Yns default website to a different port then you also have to
change the JavaScript code to tell web browsers where to find the /arduino script.
For example, if the default website is on TCP port 81, edit the file index.html and change
the line that reads api = "/arduino/"; to
api = "http://arduino.local:81/arduino/";
Accessing the /arduino script this way fails if the web browser cannot connect on TCP
port 81. The visitors Internet connection may not allow them to make connections on
that port, or your router configuration may not allow incoming connections on that port.
To make the /arduino script available to your custom website, you can write a short
server-side Python script that receives the information from the web browser and then
forwards this to the /arduino script on the correct port. This script also has to retrieve the
response from /arduino and send this back to the web browser.
Add the following settings to your website in /etc/config/uhttpd and then restart the
server:
option script_timeout 60
list interpreter ".py=/usr/bin/python"
Then create the following Python script in your sites directory, and name it api.py.
#!/usr/bin/python
import os, urllib
u = os.environ["QUERY_STRING"]
print "Content-type: application/json"
print
h = urllib.urlopen("http://127.0.0.1:81/arduino/" + u).read();
print h
If your Yns administration panels are not on TCP port 81, replace 81 with the port
number that they use.
Edit the file index.html and change the line that reads api = "/arduino/"; to
api = "api.py?";
YunServer srv;
SoftI2CMaster i2c = SoftI2CMaster(2, 3);
byte matrix[8][8];
int ctoi(char a) {
if ( (a >= '0') && (a <= '7') ) {
return a-48;
} else {
return -1;
}
}
void reDisplay() {
i2c.beginTransmission(0x70);
i2c.send(0x00);
for (byte y=0; y<8; y++) {
byte tmp = 0;
for (byte x=0; x<8; x++) {
if (matrix[y][x]) {
if (x == 0) {
tmp |= 0x80;
}
else {
tmp |= 1 << (x-1);
}
}
}
i2c.send(tmp);
i2c.send(0x00);
}
i2c.endTransmission();
}
void setBlinkRate(uint8_t b) {
if (b > 3) {
b = 3;
}
i2c.beginTransmission(0x70);
i2c.send((byte)(0x81 | (b << 1)));
i2c.endTransmission();
}
void setBrightness(uint8_t b) {
if (b > 15) {
b = 15;
}
i2c.beginTransmission(0x70);
i2c.send((byte)(0xE0 | b));
i2c.endTransmission();
}
void setup() {
i2c.beginTransmission(0x70);
i2c.send(0x21);
i2c.endTransmission();
setBlinkRate(0);
setBrightness(3);
Bridge.begin();
srv.listenOnLocalhost();
srv.begin();
}
void loop() {
YunClient yc = srv.accept();
if (yc) {
String cmd = yc.readString();
if (cmd.startsWith(F("on/"))) {
int x = ctoi(cmd[3]);
int y = ctoi(cmd[5]);
if ( (x > -1) && (y > -1) ) {
matrix[x][y] = 1;
yc.print(F("{\"result\":1}"));
}
else {
yc.print(F("{\"result\":0}"));
}
}
else if (cmd.startsWith(F("off/"))) {
int x = ctoi(cmd[4]);
int y = ctoi(cmd[6]);
if ( (x > -1) && (y > -1) ) {
matrix[x][y] = 0;
yc.print(F("{\"result\":1}"));
}
else {
yc.print(F("{\"result\":0}"));
}
}
else if (cmd.startsWith(F("get"))) {
yc.print(F("{\"result\":1,\"LEDs\":["));
for (int x=0; x<8; x++) {
yc.print('['];
for (int y=0; y<8; y++) {
if (matrix[x][y]) {
yc.print('1');
}
else {
yc.print('0');
}
if (y < 7) {
yc.print(',');
}
}
yc.print(']');
if (x < 7) {
yc.print(',');
}
}
yc.print(']');
yc.print('}');
}
yc.stop();
}
reDisplay();
}
1. If you use a Python script to forward the information from the web browser to the
/arduino script, it causes an additional delay.
2. The /arduinoscript only passes on information that the web browser sends in the
query string. Full SOAP web services and web APIs can send complicated objects
in the body of their HTTP request. /arduino cannot accept this and so you cannot
use it to build full APIs.
You can use a server-side Python script (as in the previous section) to forward
information from the web browser, directly to the ATmega32u4 over port 5555. However,
you then have to do a lot of work to process the HTTP request and perform additional
API tasks that are not directly related to turning on LEDs. This approach can use a lot of
memory on the ATmega32u4 and can take a lot of program code.
When building projects for the Arduino Yn, try to use the Linux side as much as
possible. It has a lot more power and resources than the Arduino side.
In the following sections, you can see how to revise Project 2 to run a JSON API from
Python on the Arduino Yn. In this example, the AR9331 sends information to the
ATmega32u4 using the Console.
Tip: If you want to download the modified website files and completed Python
script, you can do so from http://www.arduinomeetslinux.com/download/project2-
b.tar.gz
To begin, create a new website and, in /etc/config/uhttpd, set the following options:
option cgi_prefix /api
option max_requests 200
option script_timeout 60
URLs for APIs usually do not use file extensions. By using a cgi-bin, you can write
server-side Python scripts that reside in the cgi-bin and have no file extension.
If you click the LED buttons on the webpage rapidly, the JavaScript code sends
multiple HTTP requests to the Arduino at the same time. If max_requests is lower than
the number of requests that the browser sends to your Arduino, then uHTTPd may
ignore some of them.
M ake an api directory inside the directory on the microSD card that contains the files for
this web site.
You can also find the Python script in Source Code Python. But if you want to create
the file yourself: inside the api directory, start a new Python script call it leds and do
not give it a file extension.
def get():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("localhost", 6571))
s.send("get\00")
res["result"] = 1
matrix = [[0 for x in range(8)] for y in range(8)]
for y in range(0, 8):
for x in range(0, 8):
matrix[x][y] = s.recv(1)
res["LEDs"] = matrix
s.close()
The script contains three functions that you want to make available to the web browser:
on(), off(), and get(). The first two functions open a Console connection to ATmega32u4,
send a command, and then close the connection.The third function, get(), does not close
the Console connection immediately because it needs a response from the Arduino
sketch. It builds a two dimensional array and reads the LED status information from the
Console connection. Finally, it adds the array to the res object that the script sends back
to the browser.Each function adds a null (0) to the end of the command string. This helps
the Arduino sketch process the commands.The Python module json contains functions
to help you convert Python objects to JSON strings, and JSON strings to Python
objects. This example script creates a dictionary, and sends it to the browser as the
result of the API call. The method dumps() converts the dictionary to a JSON string that
the JavaScript code on the webpage can understand. dumps() accepts most types of
Python object.
Tip: JSON APIs put the name of the function that they call in the URL. For large
projects, you can place code for each method in its own Python script inside the
api directory.
To complete the script, you need to work out which of the three functions you should
run. In the next section, you can see how to modify the JavaScript code in the web page
to send JSON requests using the HTTP POST method. POST is a more appropriate
request method when you send large or complicated information to the web server.
Instead of putting the information in the query string, the web browser sends the data
after the HTTP request headers.
To retrieve the POST data from a Python CGI script, you need to read from the sys.stdin
object. The CGI environment variable CONTENT_LENGTH contains the numbers of
bytes of data that the browser sends after its HTTP request headers. Since the
environment variable is a string, you need to convert it to an integer.
If there is an error processing the request, the try and except statements ensure that the
script does not end with an error message, and that it still sends back the res object with
the result property in its default state (0).
The loads() method of the json module converts the JSON string from the web browser
into a usable Python object usually a dictionary.
In this example, the web browser sends a JSON object that has up to three properties:
cmd, x, and y. cmd is always present, but the browser only sends x and y if it calls the
on() or off() functions. To run the appropriate method, add the following code before the
except statement:
if js["cmd"] == "on":
on(js["x"], js["y"])
elif js["cmd"] == "off":
off(js["x"], js["y"])
elif js["cmd"] == "get":
get()
Tip: There are various frameworks available for Python that handle this logic for
you automatically calling methods depending on the information that the web
browser sends. The most common of these is Flask. However, Flask runs as a
separate web server and so you can have problems using both uHTTPd and Flask
at the same time.
To use this Python script as your API, you need to make a few small changes the
Arduino sketch and then modify the JavaScript code in the webpage.
To keep the amount of changes to a minimum, the Python script sends commands in the
same format as the webpage in the previous example. In your future projects, you may
have to implement better methods of sending data through the Console. And you should
perform more error checking than this example sketch does.
There is one issue to be aware of. When the web browser (or multiple web browsers)
sends many HTTP requests to uHTTPd at the same time, uHTTPd starts multiple
copies of the Python script. All of these scripts then access the same Console
connection.
If you use the Console.readString()method then you can sometimes find that there are
multiple commands in one string. This does not happen in the previous sketch because,
when using YunClient and YunServer, you only read data from one connection at a time.
The Python script adds a null (0) to the end of each command, so that the Arduino
sketch can separate the commands if they appear as one string. Instead of using
Console.readString(), you can use the Console.readStringUntil()method and pass in zero.
This means that the method stops when it reaches the null, and so the sketch reads
each command one at a time.
6. Remove the existing loop() function, and then add the following code:
void loop() {
String cmd = Console.readStringUntil(0);
if (cmd.startsWith(F("on/"))) {
int x = ctoi(cmd[3]);
int y = ctoi(cmd[5]);
if ((x > -1) && (y > -1)) {
matrix[x][y] = 1;
}
}
else if (cmd.startsWith(F("off/"))) {
int x = ctoi(cmd[4]);
int y = ctoi(cmd[6]);
if ((x > -1) && (y > -1)) {
matrix[x][y] = 0;
}
}
else if (cmd.startsWith(F("get"))) {
for (int y=0; y<8; y++) {
for (int x=0; x<8; x++) {
if (matrix[x][y]) {
Console.write('1');
}
else {
Console.write('0');
}
}
}
}
reDisplay();
}
The full source code for this sketch is in Source Code Sketch.
The other difference between this sketch and the previous one is that to handle a get
command, the sketch dumps the contents of the matrix array out to the Console as a
sequence of the characters 1 or 0. The Python script reads each character and builds
an array from this information.
Inside the api directory of the sample files, you can find the Python script for the Python
JSON API.
This project only sends small pieces of information to the Arduino. But by using JSON in
the web browser and the json module in a Python-based API, you can exchange very
large and very complicated objects between the client and server.
int ctoi(char a) {
if ( (a >= '0') && (a <= '7') ) {
return a-48;
} else {
return -1;
}
}
void reDisplay() {
i2c.beginTransmission(0x70);
i2c.send(0x00);
for (byte y=0; y<8; y++) {
byte tmp = 0;
for (byte x=0; x<8; x++) {
if (matrix[y][x]) {
if (x == 0) {
tmp |= 0x80;
}
else {
tmp |= 1 << (x-1);
}
}
}
i2c.send(tmp);
i2c.send(0x00);
}
i2c.endTransmission();
}
void setBlinkRate(uint8_t b) {
if (b > 3) {
b = 3;
}
i2c.beginTransmission(0x70);
i2c.send((byte)(0x81 | (b << 1)));
i2c.endTransmission();
}
void setBrightness(uint8_t b) {
if (b > 15) {
b = 15;
}
i2c.beginTransmission(0x70);
i2c.send((byte)(0xE0 | b));
i2c.endTransmission();
}
void setup() {
Bridge.begin();
Console.begin();
i2c.beginTransmission(0x70);
i2c.send(0x21);
i2c.endTransmission();
setBlinkRate(0);
setBrightness(3);
}
void loop() {
String cmd = Console.readStringUntil(0);
if (cmd.startsWith(F("on/"))) {
int x = ctoi(cmd[3]);
int y = ctoi(cmd[5]);
if ((x > -1) && (y > -1)) {
matrix[x][y] = 1;
}
}
else if (cmd.startsWith(F("off/"))) {
int x = ctoi(cmd[4]);
int y = ctoi(cmd[6]);
if ((x > -1) && (y > -1)) {
matrix[x][y] = 0;
}
}
else if (cmd.startsWith(F("get"))) {
for (int y=0; y<8; y++) {
for (int x=0; x<8; x++) {
if (matrix[x][y]) {
Console.write('1');
}
else {
Console.write('0');
}
}
}
}
reDisplay();
}
def get():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("localhost", 6571))
s.send("get\00")
res["result"] = 1
matrix = [[0 for x in range(8)] for y in range(8)]
for y in range(0, 8):
for x in range(0, 8):
matrix[x][y] = s.recv(1)
res["LEDs"] = matrix
s.close()
try:
if os.environ["REQUEST_METHOD"] == "POST":
data = sys.stdin.read(int(os.environ["CONTENT_LENGTH"]))
js = json.loads(data)
if js["cmd"] == "on":
on(js["x"], js["y"])
elif js["cmd"] == "off":
off(js["x"], js["y"])
elif js["cmd"] == "get":
get()
except:
pass
However, it is more common for web developers to use PHP: hypertext processor
(PHP) to add dynamic content to their websites. In the two projects in this chapter, you
can see how Python scripts send HTM L code to web browsers by printing them to the
standard output stream. In PHP, you use the opposite approach: writing script code
inside HTM L documents.
For example:
<html>
<head><title>Hello from PHP</title></head>
<body>
<p>
<?php
echo "Hello from PHP.";
?>
</p>
</body>
</html>
Put your PHP code in between <?php and ?> tags. You can use as many of these script
blocks as you need.
Tip: If you are not familiar with programming in PHP and would like to learn more,
there is a comprehensive (and free) tutorial available at
http://www.w3schools.com/php/
To install PHP version 5 and the program that adapts PHP for use with CGI:
2. Find the options for website that you want to enable PHP on, and add the following
line:
list interpreter ".php=/usr/bin/php-cgi"
3. Press Ctrl + X.
4. Press Y, and then press Enter.
5. Type the following command and then press Enter:
/etc/init.d/uhttpd restart
To test that you successfully installed PHP and added the script interpreter, you can
write a short script that uses the PHP function phpinfo() to display information about the
system.
1. Use the Linux command cd to move into your websites content directory.
2. Type the following command and then press Enter:
nano phpinfo.php
4. Press Ctrl + X.
5. Press Y, and then press Enter.
6. In a web browser on a machine that connects to the same network as your Arduino,
open the phpinfo.php page.
The default installation of PHP does not always work with custom websites in uHTTPd.
If you only see the message No input file specified on the webpage, you need to edit
PHPs .ini file.
3. Press Ctrl + X.
4. Press Y, and then press Enter.
5. Reload the webpage in your web browser.
The version of Python in OpenWrt-Yun already has a module for working with the Yns
Bridge library; the version of PHP on the Arduino Yn has a very similar extension. This
extension is bridgeclient.class.php, and it is in /usr/lib/php/bridge.
To make sure that you can use all of the features of the Bridge extension on your Yn,
install (or update) a few extra files that the script works with.
1. At the OpenWrt-Yun command prompt, type the following command and then press
Enter:
opkg update
To use the extension, add the following line to the top of your PHP script files:
<?php require("/usr/lib/php/bridge/bridgeclient.class.php") ?>
Then, like the Python version, you need to create an instance of the class:
$client = new bridgeclient();
The PHP version of the Bridge library contains the same methods as the Python
version. These methods are summarized in the following table, and you can find
examples of these methods in Accessing Stored Data from Python Scripts and Sending
M essages from a Python Script. You can also find example PHP code in the file
/usr/lib/php/bridge/example.php.
To call one of these methods in PHP, write -> between the name of your instance and
the method name. For example:
$client->put("PHP", "Yes");
Method Description
1
If you connect your Arduino to your network using both W i-Fi and Ethernet, the Y n can have three IP addresses
(including 127.0.0.1, which always refers to the current device). uHTTPd supports one website for each TCP port on
the specified IP address. So you can have three websites on TCP port 80, if you use a different IP address for each
one.
2
'/' means that the entire website is protected. But you can also use the names of directories or files.
3
If you have enabled basic authentication on this site then you already enabled an httpd.conf file.
4
To use a cgi-bin directory, check that you have set the cgi_prefix to /cgi-bin in /etc/config/uhttpd.
5
http://todbot.com/blog/
6
This is usually C:\Program Files\Arduino\ or similar.
7
W hen the webpage in this project displays this value, it has the option to convert the temperature to Fahrenheit.
8
Or any other directory on the microSD card if you create this symbolic link yourself.
9
Or http://arduino.local:<PORT> if your administration panel does not use TCP port 80.
10
If you have not changed it, the password is arduino.
11
http://todbot.com/blog/
12
This is usually C:\Program Files\Arduino\ or similar.
Project 3 Making an MP3 Jukebox
In this project, you will build an M P3 player, or jukebox-style device. The project
comprises an Arduino sketch, a Python script, and a prototype circuit that contains: an
LCD screen on which to display track information; two buttons for browsing through the
M P3 files on the microSD card; and a button to start playing a track.
To build this kind of project on other Arduinos, you usually need a shield (such as the
Wave Shield or M P3 Player Shield). However, OpenWrt-Yun on the Arduino Yn can
use small, cheap, USB audio devices to connect the Yn to speakers or headphones.
The Arduino sketch handles button presses and sending text to the LCD display. The
Python script (and OpenWrt-Yuns built-in support for audio devices) handles playing
M P3 files through the audio interface.
In This Chapter
USB audio interfaces range from a few dollars, to several hundred dollars. For this
project, you want a very simple device with a 3.5 mm headphone or speaker output
socket. You can usually use small USB audio interface, such as the one shown in Figure
12, with no drivers or additional software requirements.
Connect your speakers to the headphone or speaker output socket, and then plug the
USB device into the vertically-mounted USB socket on your Arduino Yn.
Figure 13 shows a full-size, solderless breadboard for clarity. However, you can fit all of
the connections on a half-size breadboard.
The pins for the LCD are numbered 116 (or 114 on LCDs without a backlight), from left
to right. Follow the diagram in Figure 13, or the table below, to connect the LCD.
LCD
Description Connect To
Pin
The potentiometer, as
3 Contrast adjustment.
shown in Figure 13.
5 Read/write. This project only writes to the LCD. Arduino GND pin.
This project uses an LED to indicate when a track is playing. If the LED is lit, the Linux
side of the Yn is not currently playing music, and the user is able to press the play
button to start a track. If the LED is not lit, the Yn is playing a track and the user cannot
start another.
1. Connect the anode (positive, long-leg) end of the LED to Arduino digital pin 13.
2. Connect one end of a 220 resistor to the cathode (the other leg of the LED).
3. Connect the other end of the resistor to ground.
The three buttons in this circuit are: previous, play, and next. You can use the previous
and next buttons to browse through the .mp3 files on the microSD card. If the LED is lit,
you can press the play button to start the track that is displayed on the LCD.
To connect the buttons:
Button Connect To
In the circuit shown in Figure 13, the Arduino digital pins connect to ground through the
10 k resistors. The 5 V power inputs go nowhere. This causes the pin to read LOW.
When you press the button, it connects the 5 V power line to the Arduino digital pin and
this causes the pin to read HIGH in the Arduino sketch. Both the 5 V and Arduino digital
pin lines now connect to ground through the resistor. However, the value of the resistor
is high so that enough voltage still moves into the digital pin for it to read as HIGH.
Tip: You can make a simpler circuit by using the Arduinos internal pull-up and
pull-down resistors and connecting a button or switch between the digital pin and
ground. But if you set the digital pin as a HIGH output and press the switch then
you have a short circuit, which may damage your Arduino.
Playing MP3 Files from Python Scripts
This project uses the Linux command-line program madplay to play music.
Before you can install madplay and call it from your Python script, you need to install a
library that it depends on.
Test madplay by copying an M P3 file to your microSD card, or by uploading one to your
Arduino over the network. Then, at the command line, change to the directory where the
.mp3 is and run the following command:
madplay music_file.mp3
The Python script in this project performs two functions depending on the commands
that the Arduino sketch sends to it:
1. Send back the title, artist, and album information from the ID3 tag of the file name
that the Arduino sketch specifies.
2. Play an M P3 file that the Arduino sketch specifies.
To write the player script, you need the Python id3reader module. To install this, see
Downloading and Installing Python Packages. The id3reader module is an example in
that section of the chapter.
The player script looks at the command line arguments that the Arduino sketch uses to
start it. There are two arguments: command, and then file.
command
Can be -info or -play.
file
The file name and file path of the M P3 file.
Create a directory on your microSD card for this project. For example, create the
directory /mnt/sda1/P3. Then create another directory inside that one, called "M P3", and
transfer a few .mp3 files into it.
Start a new Python script in the project directory (call it player.py) and type the following
code at the beginning.
#!/usr/bin/python
import id3reader, os, sys
The variable mp3opt contains extra command-line parameters that the script passes to
madplay. The -a option adjusts the volume of playback. To see the other options that
you can specify, run the following command from the OpenWrt-Yun command line:
madplay --help
You can fetch the command and file arguments from the command line using the array
sys.argv[]. The first item in this array is always the name of the script. If there are any
other arguments, they are in the array in the order that they are typed on the command
line.
To test this script from the OpenWrt-Yun command line, type the following command and
then press the Enter key:
./player.py -info "<music file.mp3>"
By putting the file name in quotes, you can ensure that the shell passes the entire file
name as a single argument to player.py. Without the quotes, spaces in the file name
cause the shell to split the file name into several arguments.
Handling Button Presses and Starting the
Music
The Python script in the previous section takes its input from the command line. After
each task, the script ends. The Process class in the Bridge library is a good choice for
controlling Linux programs that only need input from the command line, and that end
quickly.
The first thing that the Arduino sketch needs to do, is work out how many M P3 files are
on the microSD card. This is so that the sketch does not try to access files that dont
exist if the user presses the next or previous buttons too many times.
Tip: You can handle of all the file routines from Python on the Linux side of the
Yn. However, this project uses the File and FileSystem classes from the Bridge
library to demonstrate how to access files on the SD card from an Arduino sketch.
To use the LiquidCrystal library, you need to create an instance of it and specify the
digital pins that you connect the LCD to. To do this, add the following line underneath
the include directives:
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);
Then define the Arduino pins that connect to your input buttons and output LED:
int prev = 8;
int play = 9;
int next = 10;
int LED = 13;
In your sketchs setup() function, set the three buttons to INPUT, and the LED to
OUTPUT. Then start the Bridge library and the lcd instance.
void setup() {
pinMode(next, INPUT);
pinMode(play, INPUT);
pinMode(prev, INPUT);
pinMode(LED, OUTPUT);
Bridge.begin();
lcd.begin(16, 2);
}
The arguments that you pass into lcd.begin() specify the size of your LCD. In this
example, you use a 16x2 display and so the arguments are 16 and 2.
Counting MP3s
You can use Linux commands to count the number of files in the M P3 directory on the
microSD card. However, you can also do this from an Arduino sketch by using the File
and FileSystem classes from the Bridge library.
To test everything so far, add the following code underneath the line lcd.begin(16, 2); in
your sketchs setup() function:
countMP3s();
lcd.clear();
lcd.home();
lcd.print(_max);
Upload this sketch to your arduino. When the sketch starts, the LCD displays the
number of .mp3 files that are on the microSD card.
When you finish testing, remove these three lines from your sketchs setup() function:
lcd.clear();
lcd.home();
lcd.print(_max);
To keep track of which M P3 is on the LCD, create two new global variables at the top of
the sketch:
int _track = 1;
String _mp3 = "";
To fetch the ID3 tag information, you can use the Python script that you created in
Playing M P3 Files from Python Scripts. This script expects the Arduino sketch to pass in
the file name of an M P3.
It would take a lot of memory to store the file names of all of the M P3 files. Instead, you
only want to store the file name of the M P3 that is currently shown on the LCD.
This function loops through the directory in the same way as countMP3s(). However,
when it reaches the file that _track indicates, it fetches the file name from the File object.
You can now access ID3 tags by starting the player.py script and passing in the
contents of the _mp3 variable.
Add the following function to the sketch:
void showMP3Info() {
Process p;
p.runShellCommand("/mnt/sda1/P3/player.py -info \"" + _mp3 + "\"");
String t1 = p.readStringUntil('\n');
String t2 = p.readStringUntil('\n');
p.close();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(t1);
lcd.setCursor(0, 1);
lcd.print(t2);
}
When you ask the player.py script for the ID3 tag information, it returns two lines.
showMP3Info() reads these two lines into strings. It then uses three methods of the
LiquidCrystal class to show the information on the LCD.
Method Description
print() Displays a string on the LCD, starting at the current cursor position.
There are several other methods in the LiquidCrystal class that you can use in your
projects:
Method Description
Turns on the graphic that shows the LCD cursor position on the
cursor()
display.
noCursor() Turns off the graphic that shows the position of the LCD cursor.
If the LCD cursor is turned on, blink() makes it flash on and off
blink()
repeatedly.
noDisplay() Turns off the LCD, but does not clear its contents.
Turns on the LCD, and re-displays the text that was previously
display()
visible.
scrollDisplayLeft() Scrolls each line of the LCD one character to the left.
scrollDisplayRight() Scrolls each line of the LCD one character to the right.
To continue, add the following code underneath the line that reads countMP3s();:
getMP3Filename();
showMP3Info();
digitalWrite(LED, HIGH);
Upload the sketch to your Arduino. The LCD shows the track name and artist information
of the first M P3 file on the microSD card. And the LED is lit, to indicate that the Arduino
is ready to play a file.
When you press a button or close a switch, it actually makes and breaks contacts
several times in a very short amount of time. For the Arduino sketch to work, you need
to debounce the buttons. Debouncing means checking the state of the button twice,
very quickly, to check that a good contact is made.
One way to debounce a button, is to wait for it to go HIGH. Then wait a few milliseconds
and check the button again. For example:
if (digitalRead(next) == HIGH) {
delay(50);
if (digitalRead(next) == HIGH) {
// button press
}
}
In this project, when you detect that the next button is HIGH, you need to:
Upload this sketch to your Arduino and check that you can now browse through the
contents of the M P3 directory on the microSD card.
Because jukeboxes normally play a track through to the end, you only want to detect the
play button if the Arduino is not already playing an M P3. In the next section, Stopping an
M P3, you can see how to change this behavior.
Then add the following code to your sketchs loop() function, underneath the code for the
next and prev buttons:
if (!playing.running()) {
digitalWrite(LED, HIGH);
if (digitalRead(play) == HIGH) {
delay(50);
if (digitalRead(play) == HIGH) {
digitalWrite(LED, LOW);
playing.runShellCommandAsynchronously("/mnt/sda1/P3/player.py "
+ "-play \"" + _mp3 + "\"");
delay(500);
}
}
}
If the Arduino is not playing an M P3, the code keeps the LED on.
If this code detects that someone presses the play button, it turns off the LED and then
starts the Python script on the AR9331. It passes -play and the file name of the M P3
into the script, so that player.py tells madplay to play the file. playing.running() then
returns true, and so this part of loop() does not run again until the M P3 ends.When the
M P3 ends, playing.running() returns false, and so the code lights the LED and is able to
detect the state of the play button.Using this structure in loop() lets you accept input
from the next and prev buttons while the Arduino is playing a track.
Stopping an MP3
To build an M P3 player that stops the current M P3 before starting another, you need to
make a few small changes to the project. These modifications are not included in the
following Source Code sections of this chapter.
Every time Linux runs a program or command, it assigns the program a process identifier
(PID). With this number, you can stop the process from Python using the kill() method of
the os module.
3. Add the following elif statement to the if statement that checks which command the
Arduino sketch sends:
elif sys.argv[1] == "-stop":
pid = os.popen("pgrep madplay").read()
os.kill(int(pid), signal.SIGKILL)
The Linux command pgrep searches the list of running processes for the keyword that
you specify. If it finds a process containing that keyword, pgrep returns the PID. In this
example, the Python script runs the command using os.popen() so that it can read the
output of the command into a variable. os.popen() performs the same function as
os.system(). However, the way that you access the output information is different.
The os.kill() method accepts an integer PID and an argument that describes the type of
quit message that you want to send. There are many different types of quit message on
Linux systems. SIGKILL is one that cannot be ignored.
Tip: You can find a list of POSIX signal names, and descriptions, on Wikipedia at
http://en.wikipedia.org/wiki/Unix_signal#POSIX_signals
To complete the modifications to this project, you need to change the Arduino sketch so
that it responds to the play button even if it already playing a track.
if (!playing.running()) {
digitalWrite(LED, HIGH);
}
}
When you press the play button, loop() checks whether the Arduino is playing a track. If
it is, it calls player.py -stop to stop the M P3. Then it starts the new track.
The additional if statement at the end of loop() keeps the LED turned on when the
Arduino is not playing a file.
Source Code Sketch
#include <Bridge.h>
#include <FileIO.h>
#include <LiquidCrystal.h>
int prev = 8;
int play = 9;
int next = 10;
int LED = 13;
int _max = 0;
int _track = 1;
String _mp3 = ;
Process playing;
void countMP3s() {
_max = 0;
File mp3s = FileSystem.open("/mnt/sda1/P3/MP3");
mp3s.rewindDirectory();
while (true) {
File f = mp3s.openNextFile();
if (f) {
if (String(f.name()).endsWith(".mp3") ) {
_max++;
}
f.close();
}
else {
break;
}
}
mp3s.close();
}
void getMP3Filename() {
_mp3 = "";
File mp3s = FileSystem.open("/mnt/sda1/P3/MP3");
mp3s.rewindDirectory();
for (int t=1; t<=_track; t++) {
File f = mp3s.openNextFile();
if (f) {
if (t == _track) {
_mp3 = f.name();
}
f.close();
}
}
mp3s.close();
}
void showMP3Info() {
Process p;
p.runShellCommand("/mnt/sda1/P3/player.py -info \"" + _mp3 + "\"");
String t1 = p.readStringUntil('\n');
String t2 = p.readStringUntil('\n');
p.close();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(t1);
lcd.setCursor(0, 1);
lcd.print(t2);
}
void setup() {
pinMode(next, INPUT);
pinMode(play, INPUT);
pinMode(prev, INPUT);
pinMode(LED, OUTPUT);
Bridge.begin();
lcd.begin(16, 2);
countMP3s();
getMP3Filename();
showMP3Info();
digitalWrite(LED, HIGH);
}
void loop() {
if (digitalRead(next) == HIGH) {
delay(50);
if (digitalRead(next) == HIGH) {
_track++;
if (_track > _max) {
_track = 1;
}
getMP3Filename();
showMP3Info();
delay(500);
}
}
else if (digitalRead(prev) == HIGH) {
delay(50);
if (digitalRead(prev) == HIGH) {
_track--;
if (_track < 1) {
_track = _max;
}
getMP3Filename();
showMP3Info();
delay(500);
}
}
else if (digitalRead(play) == HIGH) {
delay(50);
if (digitalRead(play) == HIGH) {
digitalWrite(LED, LOW);
if (playing.running()) {
Process p;
p.runShellCommand("/mnt/sda1/P3/player.py -stop");
p.close();
}
playing.runShellCommandAsynchronously("/mnt/sda1/P3/player.py "
+ "-play \"" + _mp3 + "\"");
delay(500);
}
}
if (!playing.running()) {
digitalWrite(LED, HIGH);
}
}
Source Code Python
#!/usr/bin/python
import id3reader, os, signal, sys
if len(sys.argv) > 1:
if sys.argv[1] == "-info":
try:
id3 = id3reader.Reader(sys.argv[2])
print id3.getValue("title")
print id3.getValue("performer") + " - " + id3.getValue("album")
except IOError:
print
print
elif sys.argv[1] == "-play":
os.system("madplay " + mp3opt + " \"" + sys.argv[2] + "\" &>/dev/null")
elif sys.argv[1] == "-stop":
pid = os.popen("pgrep madplay").read()
os.kill(int(pid), signal.SIGKILL)
1
W hen you finish the project, you can disconnect the Arduino from your network.
2
Or tactile switches.
Project 4 Hosting a USB Game
Controller
OpenWrt-Yun Linux has built-in drivers for most types of USB device, and you can use
many devices from shell scripts and Python programs without writing support for them
yourself.
However, OpenWrt-Yun does not automatically support every USB device there are
some that do not work correctly on Linux, and some that OpenWrt-Yun does not know
about at all. In these situations, you have three options:
1. Check if there is a piece of software for another version of Linux that you can make
work on OpenWrt-Yun;
2. Write a Linux device driver; or
3. Write code into a single project to use the USB device.
A device driver is a program that the operating system loads when it detects a device.
These drivers provide a way for other programs on the system to use the hardware,
without communicating with it at a low level. Usually only experienced programmers write
device drivers because it requires a lot of technical knowledge. For more information
about writing drivers, you may wish to read Linux Device Drivers from OReilly M edia
Inc.1
Writing code into your project to use the device is often the simplest approach.
However, only your project can use the USB device no other programs on the Yn are
able to access the device.
In this project, you can see how to take full control of USB devices using your own
Python scripts. This chapter provides an introduction to how USB devices work; a short
tutorial on how to figure out what you need to do to receive input from USB devices; and
example code that reads input from a USB game controller and sends it to an Arduino
sketch to control a servo motor and some LEDs.
In This Chapter
To connect this many high-current devices to the Arduino Yn, you need more power
than most PC USB sockets supply. The project may not work correctly unless you use
a good quality power supply.
This chapter contains instructions and information to help you host and use one of the
following game controllers:
If you do not have one of these controllers, you may be able to make other USB
controllers work using the information in the next section, Spying On USB Devices. If
you buy a controller for use with this project, it is advisable to get an Xbox 360 or
PlayStation controller because PC game controllers can sometimes be quite different
from each other.
Tip: This project does not cover using Bluetooth controllers (such as PS3 or
PS4 controllers without the USB cable), or radio frequency (RF) controllers that
use an additional receiver dongle. RF receivers often need more current than the
maximum 500 mA that the Arduino Yn supplies to the USB socket.
USB is a serial protocol it sends information one bit at a time (using HIGH and LOW
electrical signals to represent 1 and 0). The USB specification defines how you should
structure and transmit information.
Unlike many other types of serial connection, a USB message contains many layers of
information. Each layer has a slightly different purpose. For example, when sending a
message to a peripheral, the message includes additional information that tells the USB
hub which device the message is for. Each layer is more device-specific and function-
specific than the last. Until, in the final layer, there is the actual data.
Tip: These layers of messages are similar to how network protocols work. Or
imagine putting a message in an envelope, and then putting that envelope inside
another envelope, and so on. Each recipient processes and removes one envelope
and then sends the message on to the next recipient.
When writing Python scripts to control USB peripherals on the Arduino Yn, you do not
need to construct all of the data for these layers yourself. However, it is useful to know
that these layers exist when you try to find out how a specific USB peripheral works.
There are two main types of USB communication: enumeration and application. During
enumeration, the host operating system talks to its USB bus and hub devices to
discover any USB peripherals that are in your computer. If it finds a device, it sends
special messages to learn what kind of device it is and what features it has.
Application communications cover a wide range of different messages. These are often
specific to the particular device that you write software to control. However, USB
peripherals of the HID class are relatively simple devices, and they are often very similar
to each other.
All application communications travel to and from endpoints. It may be helpful to think of
an endpoint as the USB equivalent of a TCP or UDP port different endpoints are
programmed to respond to different types of message. Every USB device has at least
one endpoint this is the control endpoint and it is always bi-directional. Other endpoints
can be inputs (IN for sending information from the device to the computer) or outputs
(OUT for sending information to the device, from the computer).
The USB game controllers in this project all have: a control (or setup) endpoint; at least
one IN endpoint that sends the state of the buttons and analog sticks; and an OUT
endpoint that the computer can use to activate LEDs and rumble effects.
The USB 2.0 specification defines four different ways that computers and USB
peripherals can send messages:
Control
The first endpoint (endpoint 0) in a USB device is always a control. This is used for
configuration messages, but it can also be a general-purpose way of sending
information from one device to another.
Bulk
Bulk transfers are for sending large amounts of data. Devices use this method when it
is acceptable for the delivery of the message to be delayed a short time. However, a
bulk transfer is usually quicker than sending a large number of small messages.
Interrupt
Interrupt transfers are useful for sending a small amount of data that must be received
and processed quickly. For example, computers need to receive messages from a
keyboard quickly, or the user may see the delay between pressing a key and the
character appearing on their screen. However, this is not like the interrupts used by
microprocessors and microcontrollers the USB controller still asks the peripheral if it
has any data to send; peripherals do not spontaneously send data.
Isochronous
Isochronous transfers are a continual stream of data. This is very fast. However, using
this mode disables error checking and automatic retransmission of data if there is a
problem.
The manufacturer of the USB device chooses how their device communicates when
they build the product. HID peripherals, like game controllers, usually send their
information to the host computer using control or interrupt transfers.
Tip: The USB specification also includes interfaces and configurations that the
device can use to support multiple ways of communicating. However, you do not
need to understand these in order to work with most HID peripherals.
Spying On USB Devices
M ost device manufacturers do not publish all of the technical information about their
products. If you want to write a script to control a USB device that OpenWrt-Yun does
not understand, you may have to try and discover how the device works yourself.There
are two main ways of doing this:
Look at source code for driver software that is available on other computers; or
Install the device on a system that you can obtain driver software for. Then look at
the messages that the computer sends to the device and the messages that the
device sends to the computer.
The second option is often called spying, snooping, or sniffing. It is often simplest to
do this on a Windows PC because the tools that you need are free, and there are
Windows drivers available for almost all of the USB devices that you can buy.
Sniffing USB devices and writing a Python script to replicate them can take time. It is
often trial and error. The more complicated the device, the longer it usually takes to
discover how it works.
You do not need to work through this section to complete the game controller project. If
you want to skip this, you can turn to Installing and Using PyUSB. The remainder of this
section provides a brief introduction to sniffing USB devices and discovering how they
work.
To use a USB peripheral in a Python script on the Arduino, you need to know its vendor
and product identifiers. These are 32-bit numbers, and the combination of the two is
unique to a specific device. However, manufacturers sometimes use the vendor and
product identifiers from products that their peripheral is compatible with. For example,
most game controllers that work on the Sony PlayStation 4 games console use the
identifiers that are assigned to Sonys official DualShock 4 controller.
To find the vendor and product identifiers for a USB device on Windows 8/7/Vista/XP:
You can usually find HID peripherals under Human Interface Devices, Mice and other
pointing devices, Sound video and game controllers, or Other devices.
When you find your device:
For example, the Windows Device M anager shows the M icrosoft Xbox 360 controller
with the hardware identifiers:
HID\VID_045E&PID_028E&IG_00
The two numbers are in hexadecimal notation. The four characters after VID_ are the
vendor identifier. The four characters after PID_ are the product identifier.
To intercept the messages that travel between your computer and the USB device, you
need additional pieces of software. There are many different USB sniffing programs
available for Windows computers. M any of these are commercial products, but USBPcap
(http://desowin.org/usbpcap/) and Wireshark (http://www.wireshark.org) are free.
USBPcap is a command line tool for logging USB messages. It does not have a
graphical user interface to make it easy for you to look at messages. However, you can
send the results of USBPcap to Wireshark.
6. In the list of devices, find your peripheral and make a note of the USBPcap device
identifier. For example: \\.\USBPcap6.
7. Type q and then press Enter.
8. Type the following command on one line, and then press Enter:4
"c:\program files\usbpcap\usbpcapcmd.exe" -d \\.\USBPcap6 -o - | "c:\program files\wireshark\wireshark.exe" -
k -i -
Tip: If you do not have appropriate driver software for the USB peripheral then
you may not be able to see all of the messages that you need.
Wireshark displays incoming and outgoing messages for the USB peripheral in the top
panel. The remainder of this section is an example of working with a M icrosoft Xbox
360 controller, and you need to press a button on that device before you see any
messages.
The source and destination columns show which device sent the message and which
device received it. If the value in the destination column is host then the controller sent
the message to your PC.
To view more information about a message, click it. The Wireshark application displays
the contents of the message in the middle panel.
Click the box next to USB URB to expand that section. USB request blocks (URBs)
contain information about the USB message. Of particular interest are the lines:
Endpoint: 0x81, Direction: IN
URB transfer type: URB_INTERRUPT (0x01)
Packet Data Length: 20
This information tells you that the Xbox 360 controller sends an interrupt message from
endpoint 0x81 whenever you press a button, and that the message is 20 bytes long.
Click Leftover Capture Data in the middle panel to highlight part of the USB message in
the bottom panel. The highlighted part of the message in Figure 14 is the actual data that
the controller sends; all of the bytes before this help the USB protocol deliver and
understand the message.
The controller data is 20-bytes long (just as the Packet Data Length entry says it is) and
looks like this:
00 14 00 00 00 00 ce 11 a2 f7 79 f8 bd 01 00 00 00 00 00 00
This is the button input report. By viewing different messages, you can see that the
second byte in this part of the message is usually 14. 14 is the hexadecimal version of
20. So you can guess, with reasonable certainty, that the second byte in this 20-byte
button report is the length of the message.
Looking through the different messages, you can see that when you press the A button,
the controller sends a message with the fourth byte in the input report as 10. When you
press the X button, this byte is 40. If you release a button then this byte is 00. If you
press A and X together, the value is 50. The binary representations of these
hexadecimal numbers are:
Hexadecimal Binary
00 00000000
10 00010000
40 01000000
50 01010000
From looking at the binary representations, you can see that the Xbox 360 controller
uses one bit to represent the state of each of the main buttons. Bit 6 represents the X
button, and bit 4 represents the A button. If you press the Xbox Guide, LB, or RB
buttons then they change the same byte in the report. Other buttons change bits in
other bytes.
Using this method of pressing buttons and comparing the messages, you can start to
interpret the controllers report. In Working with M icrosoft Xbox 360 Controllers, you
can see more information about the message format of the Xbox 360 controller.
When you plug a M icrosoft Xbox 360 controller into a Windows PC, the USB drivers
send various messages to the controller. Sometimes you need to know what these
messages are and to replicate them in your Python scripts. For example, the USB driver
tells the Xbox controller to light an LED to show the controller or player number. If you
want to control the LEDs from your Python script, you need to know how to send this
message.
But because the device is already running when you start USBPcap and Wireshark, you
cannot see those messages. To see all of the messages that the host sends to the
device, you need to have USBPcap and Wireshark running before you connect the
controller to your system:
When you connect the device to the system in the same USB socket, it will usually use
the same USBPcap device identifier.
M any of these are standard USB messages that you do need to understand. For some
devices, the SET CONFIGURATION and SET INTERFACE messages are important and
you may have to replicate them in your Python scripts. By trial and error, you can
discover if the device works without them. In this case, the Xbox controller does not
actually need to receive those messages.
In the example above, the USB_INTERRUPT out messages are interesting. In the USB
URB view, you can see that these are labelled vendor specific, which means that they
do something specific to that controller and are not one of the usual HID peripheral
messages. If you have software on your PC that lets you send different LED messages
to the controller, you would find a USB_INTERRUPT outmessage every time you change
the LED pattern. The first byte is always 01, and the second byte is always 03. But the
third byte changes depending on the LEDs that you want to set.
This is how the set LED commands in Setting the LEDs were discovered.
The same process applies to working out how to activate the rumble feature. You need
to trigger the rumble from software and then find the messages sent from your computer
to the controller. When you have a message (and its endpoint information) that you think
might control the rumble, you can try it from your own script on the Arduino.
Installing and Using PyUSB
On Linux, you can use the libusb library to read from and write to USB devices.
However, this library is for C programmers. Python users have the module PyUSB.
PyUSB is a wrapper for libusb, and it contains functions to access USB devices at a low
level so that you can communicate with a device even if OpenWrt-Yun does not know
how.
You need to download and build PyUSB from its source code. To do this, you need the
GNU GCC package and some supporting tools from the Arduino Yn repository. These
packages are quite large, and so you should make sure that you expand Linux file
system onto the microSD card. For more information, see Expanding the Linux File
System onto the microSD Card.
To install PyUSB:
You can now delete the pyusb-master directory, and the master.zip archive.
In this project, the Arduino sketch starts a Python script that reads input from the USB
controller and then sends it to the ATmega32u4. Any of the methods of exchanging
information between the Atheros AR9331 and the ATmega32u4 work, but this example
script writes to the standard output device. Because the Arduino sketch starts the
Python script, it can read the output data through the Process class in the Bridge library.
M ake a new directory on the microSD card for your project files. For example, at the
command prompt, type the following command and then press the Enter key:
mkdir /mnt/sda1/P4 && cd /mnt/sda1/P4
To use PyUSB on the Arduino Yn, you need to specify the location of version 1.0 of
the libusb library and create an instance of the correct backend. To do this, add the lines:
backend = usb.backend.libusb1.get_backend(
find_library=lambda x: "/usr/lib/libusb-1.0.so"
)
Before you can make a connection to a controller, you need to find it. You can do this by
calling the find() method in usb.core and passing in the vendor identifier and product
identifier of the controller that you have. The table below shows the ID numbers (in
hexadecimal format) for the controllers that this project supports.
Tip: To check the hardware identifiers of any USB devices that you plug into the
Yn, type lsusb at the OpenWrt-Yun command line and then press the Enter key.
Add the following line to your Python script to create an object that connects to your
controller. Replace the hexadecimal values in this line with the ones that refer to the
controller you use.
dev = usb.core.find(idVendor=0x054C, idProduct=0x05C4)
If find() cannot locate the type of controller that you specify, it returns None. You can
check whether a suitable device is available by adding the following lines:
if dev is None:
sys.stdout.write(chr(0))
sys.stdout.flush()
sys.exit(0)
else:
sys.stdout.write(chr(1))
sys.stdout.flush()
If PyUSB does not find the controller, the script sends 0 (null) to the standard output
device, and then the call to sys.exit() terminates the script. If the script finds the
controller, it sends 1.
Tip: This code uses sys.stdout.write() instead of print() because write() method
does not send a line break. To ensure that it only sends one byte (one character),
the code also uses the chr() method.
The call to sys.stdout.flush() ensures that the Python interpreter sends the information to
the Arduino as soon as possible.
When you connect a USB device to the Arduino Yn, OpenWrt-Yun tries to load an
appropriate driver for it. If it succeeds, you are not able to control the device from
PyUSB.
The PyUSB module contains methods that you can use to find out if Linux is using the
device and, if necessary, tell Linux to release its control and allow PyUSB to use the
USB device.
Before you can run this script, you should make sure that it exits cleanly and releases
the USB device back to Linux.
Near the top of the script, underneath the import directives, add the following code:
def clean_getaway(sig, frame):
usb.util.release_interface(dev, 0)
sys.exit(0)
signal.signal(signal.SIGINT, clean_getaway)
This code states that the Python interpreter should run the function clean_getaway()
when the script receives the SIGINT signal. When you press Ctrl + C at the command
line to cancel the Python script, Linux sends SIGINT to it. Ordinarily, the Python
interpreter closes the script immediately, but now it calls clean_getaway() instead.
The next part of the script is different, depending on which game controller you use.
To use an Xbox 360 controller, see Working with M icrosoft Xbox 360
Controllers.
To use a PlayStation controller, see Working with Sony PlayStation DualShock
4 Controllers or Working with Sony PlayStation DualShock 3 and SIXAXIS
Controllers.
To use a Thrustmaster Firestorm Dual Power controller, see Working with PC
USB Controllers.
When first powered, the controller enters its default configuration. In this mode, the
controller sends the state of every button on the device whenever you press one of the
buttons or move a stick. And you can set the pattern of LEDs, or activate the rumble
motors, by sending control messages.
To read from the controller, you can use the read() method of the PyUSB device
instance. This method accepts two arguments the endpoint address, and the maximum
number of bytes to read. The IN endpoint from which the controller sends button press
information is at address 0x81. And the maximum length of a valid input report is 20
bytes.Replace the line in the while loop that reads pass with the following code, and
then indent it:
try:
inp = dev.read(0x81, 20)
print inp
except usb.core.USBError:
pass
There are several small errors that can occur during USB transmission. The try and
except statements ensure that if one of these occurs, the script will continue to try and
read from the controller instead of ending with an error message.
Connect the controller to the vertically-mounted USB socket on your Yn (if it is not
connected), and then run this script from the command line. Press a few buttons on the
controller. The script sends an array of bytes to the Linux console, and each byte
represents a button or analog stick position on the controller.
The button press report from the M icrosoft Xbox 360 controller consists of 20 bytes.
The controller may sometimes send shorter messages than this, however, those reports
are not button presses and you can ignore them. The first byte of a button press report
is always 0. The second byte contains the value 20 the length of the report.
The table that follows show a description of the most-commonly used bytes. This
information starts with the third byte, which is at position 2 of the inp array.
Byte
Description
No.
Represents the state of the d-pad buttons, BACK, START, LS, and RS. Each
bit represents a different button. If a bit is set, the button is pressed. If a bit is
2 clear, the button is not pressed. From most-significant to least-significant: Bit 7
RS Bit 6 LS Bit 5 back Bit 4 start Bit 3 right Bit 2 left Bit 1 down
Bit 0 up
Represents the state of the A, X, Y, B, LB, RB, and Xbox Guide buttons.
Each bit represents a different button. If a bit is set, the button is pressed. If a
3 bit is clear, the button is not pressed. From most-significant to least-
significant: Bit 7 Y Bit 6 X Bit 5 B Bit 4 A Bit 3 Bit 2 Xbox Guide Bit
1 RB Bit 0 LB
The state of LT. This is a value in the range 0255 and represents how far
4
the player pulls the left trigger.
The state of RT. This is a value in the range 0255 and represents how far
5
the player pulls the right trigger.
The x-axis position of the left analog stick. This is a signed byte in the range -
7
128127 (left to right) with the midpoint at 0.
The y-axis position of the left analog stick. This is a signed byte in the range -
9
128127 (down to up) with the midpoint at 0.
The x-axis position of the right analog stick. This is a signed byte in the range
11 -128127 (left to right) with the midpoint at 0.
The y-axis position of the right analog stick. This is a signed byte in the range
13
-128127 (down to up) with the midpoint at 0.
Note: The controller.py script does not display the negative numbers correctly yet. You
can fix this later.
Four light-emitting diodes (LEDs) surround the Xbox Guide button. The LEDs usually
indicate the number of the controller when your Xbox 360 has more than one controller.
There are also several predefined animations.
To set the state of the LEDs, you can send a short message to the controllers OUT
endpoint at address 0x01. The data that you need to send is an array of three bytes. For
example:
[0x01, 0x03, 0x0A]
The first byte in this sequence is the message type; this is always 1 when setting the
LEDs. The next byte is the length of the message; this is 3 because there is a total of
three bytes in the data. The third byte specifies the LED pattern, and this can be one of
the following values:
Value Description
Blinks any LEDs that are currently on, and then reverts to the previous
0x0B
setting.
Slowly blinks any LEDs that are currently on, and then reverts to the
0x0C
previous setting.
Alternates between two pairs of LEDs for a short time and then reverts to
0x0D
the previous setting.
To send a message to the controller that tells it to start the rotating pattern, add the
following line above the while loop:
dev.write(0x01, [0x01, 0x03, 0x0A])
The write() method accepts two arguments the endpoint address (in this case, 0x01 for
the output endpoint) and an array of bytes that are the message to the controller.
Turning On Rumble
The M icrosoft Xbox 360 controller contains two small motors that you can use to
rumble or vibrate the device. The first motor creates a low-frequency, deep (or big)
rumble, and the second motor creates a higher-frequency rumble. To control the speed
or strength of each motor, you can send a value in the range 0255 to the controller. To
the send a rumble message, you need an array of eight bytes. For example:
[0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
The second byte is the length of the message, and this is always 0x08 for rumble
messages. The fourth byte controls the deep rumble and the fifth controls the shallow
rumble. To turn both motors on to the maximum strength, you can use the following code
and send the rumble message to the controllers OUT endpoint at address 0x01.
dev.write(0x01, [0x00, 0x08, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00])
After you start the rumble feature, you have to turn it off yourself. If you are testing the
rumble in controller.py, you can add this line to the top of the clean_getaway() function.
dev.write(0x01, [0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
This ensures that the rumble ends when the script does.
The sketch in Building a Test Circuit reads input from the OpenWrt-Yun shells standard
output device. If you use the Python print() function to send these numbers then it may
convert them to text. Instead, you can use the sys.stdout.write() method.
The eighth byte of the input report (position 7 in the array inp) is a signed byte that
specifies the x position of the left analog stick. However, Python is not great at handling
signed 8-bit numbers, and it loses the sign when it reads the position from the USB
device. Since the lowest eight bits of the number are not changed, you can restore the
sign.
And then replace the line that reads print inp, with the following code:
if len(inp) == 20:
sys.stdout.write(chr(ctypes.c_byte(inp[7]).value + 128))
sys.stdout.write(chr(inp[3] >> 4))
sys.stdout.flush()
The ctypes module contains functions and data types that closely correspond to the
data types used in C. c_byte is an 8-bit signed byte. The code above first converts
inp[7] to a c_byte, which preserves negative values. It then gets a Python integer from
this, using the value property. The result is a signed Python integer in the range -128
127. However, the Arduino sketch expects the analog stick position to be in the range
0255. By adding 128 to the value, you convert -128127 numbers to 0255 numbers.
The fourth byte (position 3 in the inp array) of the input report represents the state of the
A, X, Y, B, and several other buttons. To keep the Arduino sketch the same (regardless
of which controller you use with this project), the sketch only looks for four buttons. And
it expects that the buttons are the least-significant four bits of the number. To send only
the state of the A, X, Y, and B buttons, you can right-shift the value from the Xbox 360
controller by four places.
The call to sys.stdout.flush() ensures that the Python interpreter sends the information to
the Arduino as soon as possible.
Without sending any messages to control the LEDs or rumble, your Python script should
now look like this:
#!/usr/bin/python
import ctypes
import signal
import sys
import usb.backend.libusb1
import usb.core
import usb.util
signal.signal(signal.SIGINT, clean_getaway)
backend = usb.backend.libusb1.get_backend(
find_library=lambda x: "/usr/lib/libusb-1.0.so"
)
dev = usb.core.find(idVendor=0x045E, idProduct=0x028E)
if dev is None:
sys.stdout.write(chr(0))
sys.stdout.flush()
sys.exit(0)
else:
sys.stdout.write(chr(1))
sys.stdout.flush()
if dev.is_kernel_driver_active(0):
dev.detach_kernel_driver(0)
usb.util.claim_interface(dev, 0)
while (1):
try:
inp = dev.read(0x81, 20)
if len(inp) == 20:
sys.stdout.write(chr(ctypes.c_byte(inp[7]).value + 128))
sys.stdout.write(chr(inp[3] >> 4))
sys.stdout.flush()
except usb.core.USBError:
pass
The Sony PlayStation DualShock 4 controller is a wireless gamepad for the PS4. It
normally communicates with the console over Bluetooth. However, you can connect it to
the Arduino Yn using a USB to micro-USB cable. When in wired mode, the controller
works in the same way as most other USB controllers.
The DualShock 4 contains more endpoints and configuration options than other USB
controllers, because it supports additional hardware such as headsets. The DualShock
4 continually reports the state of all buttons, analog sticks, and accelerometer/gyroscope
sensors.
To read from the controller, you can use the read() method of the PyUSB device
instance. This method accepts two arguments the endpoint address, and the maximum
number of bytes to read. The IN endpoint from which the controller sends button press
information is at address 0x84. And the maximum length of a valid input report is 64
bytes.
Replace the line in the while loop (which reads pass) with the following code, and then
indent it:
try:
inp = dev.read(0x84, 64)
print inp
except usb.core.USBError:
pass
There are several small errors that can occur during USB transmission. The try and
except statements ensure that if one of these occur, the script continues to read from
the controller.
Connect the controller to the vertically-mounted USB socket on your Yn (if it is not
connected). Then run this script from the command line, and press a few buttons on the
controller. The script sends an array of bytes to the Linux console, and each byte
represents a button, analog stick position, or sensor value on the controller.
Tip: When working with DualShock 4 controllers, there are a lot of values to
process. In the following sections, you will see how to extract the individual
buttons and pieces of information that you want to use.
The button press report from PS4 controllers consists of 64 bytes. The table that
follows shows a description of the most-commonly used bytes starting with the
second byte, which is at position 1 of the inp array.
The first byte (byte 0) specifies the report type. This is always 1 for button input reports.
PS4 controllers may sometimes send shorter reports of a different type.
Byte
Description
No.
The x-axis position of the left analog stick. This is an unsigned byte in the
1
range 0255 (left to right) with the midpoint at 128.
The y-axis position of the left analog stick. This is an unsigned byte in the
2
range 0255 (up to down) with the midpoint at 128.
The x-axis position of the right analog stick. This is an unsigned byte in the
3
range 0255 (left to right) with the midpoint at 128.
The y-axis position of the right analog stick. This is an unsigned byte in the
4 range 0255 (up to down) with the midpoint at 128.
Represents the state of the triangle, circle, cross, and square buttons, and the
d-pad. For the d-pad, the least-significant four bits can be one of the following
values: 0b0111 up and left 0b0110 left 0b0101 down and left 0b0100
down 0b0011 down and right 0b0010 right 0b0001 up and right 0b0000
5
up 0b1000 no button pressed For the other buttons, the most-significant four
bits each represent the state of one button. If a bit is set, the button is
pressed. If a bit is not set, the button is not pressed. From most-significant to
least-significant: Bit 7 triangle Bit 6 circle Bit 5 cross Bit 4 square
Represents the state of the L1, L2, L3, R1, R2, R3, OPTIONS, and SHARE
buttons. If a bit is set, the button is pressed. If a bit is not set, the button is
6
not pressed. From most-significant to least-significant: Bit 7 R3 Bit 6 L3 Bit
5 options Bit 4 share Bit 3 R2 Bit 2 L2 Bit 1 R1 Bit 0 L1
The eighth byte of the input report contains a number that automatically
increments every time the controller sends a report. However, the least-
significant two bits of this value represents the state of the trackpad button
7
and PlayStation Home button. If a bit is not set, the button is not pressed.
From most-significant to least-significant: Bit 1 trackpad button Bit 0
PlayStation home
Note: The DualShock 4 does not have the pressure-sensitive buttons that the
DualShock 3 does.
You can also find the trackpad, accelerometer and gyroscope data in the same input
report. However, these features are not used in this project.
The DualShock 4s light bar is a 24-bit color device, made up of three channels red
(R), green (G), and blue (B). You can set each channel to a value in the range 0255.
These values indicate the brightness or strength of each of the primary colors. 0 is off
(or black), and 255 is the maximum brightness.
The light bar blends these three values to create colors. For example R:255 G:0 B:0 is a
bright red. R:255 G:0 B:255 is a bright purple. And R:255 G:255 B:255 is white.
To set the color of the light bar from your Python script, you can send three bytes (one
for each RGB channel) as part of a special message to endpoint 0x03. In the same
message, you can also tell the DualShock 4 to flash the light bar continuously.
As well as the light bar, the controller contains two small motors that you can use to
rumble or vibrate the device. The first motor creates a low-frequency, deep (or big)
rumble, and the second motor creates a higher-frequency rumble. To control the speed
or strength of each motor, you can send a value in the range 0255 in the same
message that you use to control the light bar.
The actual length of the message that you send can vary. However, 11 bytes is enough
to set the color of the light bar, the timing of the flashes, and the strength of the rumble.
For example:
[0x05, 0xFF, 0x00, 0x00, SMALLR, BIGR, RED, GREEN, BLUE, FLASHON, FLASHOFF]
The first byte, 0x05, specifies the message type that you are sending. This is always the
same when you send a message to control the light bar and rumble. The next byte is
always 0xFF (255). Bytes 3 and 4 should be zero. The remaining bytes are:
Option Description
A value in the range 0255 that specifies the strength/speed of the big,
BIGR
low-frequency rumble motor. 0 is off. 255 is the maximum.
A value in the range 0255 that specifies the length of time that the light
FLASHON
bar stays on.
A value in the range 0255 that specifies the length of time that the light
FLASHOFF
bar stays off.
The controller uses the FLASHON and FLASHOFF options together to create a
repeating flash. If both options are 0 then the light bar does not flash.
If you activate the rumble feature, the controller automatically turns it off after
approximately 3 seconds.
To send a message to the DualShock 4 to control these features, you need to write it
to endpoint 0x03. For example, to activate both rumble motors and set the light bar to a
flashing purple:
dev.write(0x03, [0x05,0xFF,0x00,0x00,0xFF,0xFF,0xFF,0x00,0xFF,0x80,0x80])
To complete the controller.py script, send the x position of the left analog stick and a
single byte that represents the state of four buttons to the Arduino sketch. The
ATmega32u4 struggles to read the full 64-byte report quickly enough, so it is more
efficient to only send the data that the sketch needs.
The sketch in Building a Test Circuit reads input from the OpenWrt-Yun shells standard
output device. If you use the Python print() function to send these numbers then it may
convert them to text. Instead, you can use the sys.stdout.write() method.
Replace the line that reads print inp, with the following code:
if len(inp) == 64:
sys.stdout.write(chr(inp[1]))
sys.stdout.write(chr(inp[5] >> 4))
sys.stdout.flush()
The sixth byte (position 5 in the inp array) of the input report represents the state of the
triangle, circle, cross, and square buttons, as well as the d-pad. To keep the Arduino
sketch the same (regardless of which controller you use with this project), the sketch
only looks for four buttons. And it expects that the buttons are the least-significant four
bits of the number. This means that you must right-shift the value from the PS4
controller by four places.
The call to sys.stdout.flush() ensures that the Python interpreter sends the information to
the Arduino as soon as possible.
Without sending any messages to control the DualShock 4s light bar or rumble, your
Python script should now look like this:
#!/usr/bin/python
import signal
import sys
import usb.backend.libusb1
import usb.core
import usb.util
signal.signal(signal.SIGINT, clean_getaway)
backend = usb.backend.libusb1.get_backend(
find_library=lambda x: "/usr/lib/libusb-1.0.so"
)
dev = usb.core.find(idVendor=0x054C, idProduct=0x05C4)
if dev is None:
sys.stdout.write(chr(0))
sys.stdout.flush()
sys.exit(0)
else:
sys.stdout.write(chr(1))
sys.stdout.flush()
if dev.is_kernel_driver_active(0):
dev.detach_kernel_driver(0)
usb.util.claim_interface(dev, 0)
while (1):
try:
inp = dev.read(0x84, 64)
if len(inp) == 64:
sys.stdout.write(chr(inp[1]))
sys.stdout.write(chr(inp[5] >> 4))
sys.stdout.flush()
except usb.core.USBError:
pass
Before you read from one of these controllers, you need to tell the controller to send a
report that contains the status of the buttons. Add the following code above the line that
reads while (1):
dev.ctrl_transfer(0x21, 0x09, 0x03F4, 0x00, [0x42, 0x0C, 0x00, 0x00])
To read from the controller, you can use the read() method of the PyUSB device
instance. This method accepts two arguments the endpoint address, and the maximum
number of bytes to read. The IN endpoint from which the controller sends button press
information is at address 0x81. And the maximum length of a valid input report is 49
bytes.
Replace the line in the while loop that reads pass with the following code, and then
indent it:
try:
inp = dev.read(0x81, 49)
print inp
except usb.core.USBError:
pass
There are several small errors that can occur during USB transmission. The try and
except statements ensure that if one of these occurs, the script continues to read from
the controller.
Connect the controller to the vertically-mounted USB socket on your Yn (if it is not
connected). Then run this script from the command line, and press a few buttons on the
controller. The script sends an array of bytes to the Linux console, and each byte
represents a button, analog stick position, or accelerometer value on the controller.
The first byte (byte 0) specifies the report type. This is always 1 for button input reports.
PS3 controllers may sometimes send shorter reports of a different type. These do not
usually contain button information, and you can ignore them.
Bytes 1, 5, 10, 11, 12, 13, 26, 27, 28, 32, 33, 34, 35, 36, 37, 38, 39, and 40 are reserved
and you can ignore them.The main values are:
Byte
Description
No.
Represents the state of the d-pad buttons, SELECT, START, L3, and R3.
Each bit represents a different button. If a bit is set, the button is pressed. If a
2 bit is not set, the button is not pressed. From most-significant to least-
significant: Bit 7 left Bit 6 down Bit 5 right Bit 4 up Bit 3 start Bit 2
R3 Bit 1 L3 Bit 0 SELECT
Represents the state of the cross, circle, triangle and square buttons on the
face of the controller, and the L1, L2, R1, and R2 shoulder buttons. Each bit
represents a different button. If a bit is set, the button is pressed. If a bit is not
3
set, the button is not pressed. From most-significant to least-significant: Bit 7
square Bit 6 cross Bit 5 circle Bit 4 triangle Bit 3 R1 Bit 2 L1 Bit 1
R2 Bit 0 L2
If the PS button is pressed, this value is 1. If the button is not pressed, this
4
value is 0.
The x axis of the left analog stick. This is an unsigned value in the range 0
6
255 (left to right) with 128 as the midpoint.
The y axis of the left analog stick. This is an unsigned value in the range 0
7
255 (up to down) with 128 as the midpoint.
The x axis of the right analog stick. This is an unsigned value in the range 0
8 255 (left to right) with 128 as the midpoint.
The y axis of the right analog stick. This is an unsigned value in the range 0
9
255 (up to down) with 128 as the midpoint.
As well as reporting whether each button is pressed, the DualShock 3 and SIXAXIS
controllers also report how much pressure the player presses the buttons with. The
pressure values are a single byte in the range 0255, and you can find them at the
following positions in the report:
14 up (d-pad)
15 right (d-pad)
16 down (d-pad)
17 left (d-pad)
18 L2
19 R2
20 L1
21 R1
22 triangle
23 circle
24 cross
25 square
You can also find the accelerometer and gyroscope data in the same input report.
However, these features are not used in this project.
The DualShock 3 and SIXAXIS have four LED port indicatorsthat the PS3 console
can light to tell players which controller number they are. This is useful when you
connect multiple controllers to the console.
Tip: Most PS3 controllers will automatically turn off if you do not set the
controller number. It usually a good idea to do this after you send the control
message to turn on the button report from the controller.
To set a port indicator, you need to send a message to the controller. This message
consists of 48 bytes, and many of the values in this report are predefined. The tenth
byte is the port indicator, and it can be one of the following values:
Value Description
0x00 Turns off all LEDs and tells the controller that the console is not using it.
You can also turn on combinations of LEDs by adding the values together. For example,
to turn on LEDs 2 and 4, you can use the value 0x12 (0x02 + 0x10).
Then, after the line that reads dev.ctrl_transfer(0x21, 0x09, 0x03F4, 0x00, [0x42, 0x0C,
0x00, 0x00]), add the following statement:
setLED(0x02)
The DualShock 3 controller also contains two small motors that you can use to
rumble or vibrate the device. The first motor creates a low-frequency deep (or big)
rumble, and the second motor creates a higher-frequency rumble. You can use four
bytes to control the strength of the motors and the duration of time that they stay on for
two bytes for each motor.
Tip: Because of a legal dispute between Sony Computer Entertainment Inc. and
the Immersion Corporation, PS3 controllers without the DUALSHOCK3 mark
do not support rumble. And not all DualShock 3 controllers support rumble when
you connect them over a mini-USB cable.
The control message that you send to the controller is the same as the one that you
use to set the port indicator LEDs. In that 48-byte message, bytes 2 and 3 control the
right motor and bytes 4 and 5 control the left motor.
The first byte in each pair is a duration. 0xFF tells the controller to rumble forever (or until
you manually set the strength to 0). M ost drivers for DualShock 3 and SIXAXIS
gamepads control the rumble this way, and they turn it off manually. The second byte in
each pair is the strength of the rumble. The left motor accepts values in the range 0255.
However, the setting for the right motor can only be 0 or 1.
For example, the following function extends the setLED() function with two additional
parameters that activate and deactivate the rumble. This project does not use the
rumble features of PS3 controllers, but you can include this function in the controller.py
Python script if you want to.
def setRumble(ledn, left, righ):
dev.ctrl_transfer(0x21, 0x09, 0x0201, 0 [
0x00, 0xFF, righ, 0xFF, left, 0x00, 0x00, 0x00,
0x00, ledn, 0xFF, 0x27, 0x10, 0x00, 0x32, 0xFF,
0x27, 0x10, 0x00, 0x32, 0xFF, 0x27, 0x10, 0x00,
0x32, 0xFF, 0x27, 0x10, 0x00, 0x32, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
])
To complete the controller.py script, send the x position of the left analog stick and a
single byte that represents the state of four buttons to the Arduino sketch. The
ATmega32u4 can struggle to read a full-length report quickly enough, so it is more
efficient to only send the data that the sketch needs.
The sketch in Building a Test Circuit reads input from the OpenWrt-Yun shells standard
output device. If you use the Python print() function to send these numbers then it may
convert them to text. Instead, you can use the sys.stdout.write() method.
Replace the line that reads print inp, with the following code:
if len(inp) == 49:
sys.stdout.write(chr(inp[6]))
sys.stdout.write(chr(inp[3] >> 4))
sys.stdout.flush()
The fourth byte (position 3 in the inp array) of the input report represents the state of the
triangle, circle, cross, square, and trigger buttons. To keep the Arduino sketch the same
(regardless of which controller you use with this project), the sketch only looks for four
buttons. And it expects that the buttons are the least-significant four bits of the number.
To use the triangle, circle, cross and square buttons with this project, you can right-shift
the value from the PS3 controller by four places.
The call to sys.stdout.flush() ensures that the Python interpreter sends the information to
the Arduino as soon as possible.
except:
pass
sys.exit(0)
signal.signal(signal.SIGINT, clean_getaway)
def setLED(ledn):
dev.ctrl_transfer(0x21, 0x09, 0x0201, 0 [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, ledn, 0xFF, 0x27, 0x10, 0x00, 0x32, 0xFF,
0x27, 0x10, 0x00, 0x32, 0xFF, 0x27, 0x10, 0x00,
0x32, 0xFF, 0x27, 0x10, 0x00, 0x32, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
])
backend = usb.backend.libusb1.get_backend(
find_library=lambda x: "/usr/lib/libusb-1.0.so"
)
dev = usb.core.find(idVendor=0x054C, idProduct=0x0268)
if dev is None:
sys.stdout.write(chr(0))
sys.stdout.flush()
sys.exit(0)
else:
sys.stdout.write(chr(1))
sys.stdout.flush()
if dev.is_kernel_driver_active(0):
dev.detach_kernel_driver(0)
usb.util.claim_interface(dev, 0)
USB game controllers for the PC come in many different shapes and sizes, with a wide-
variety of buttons. Because of this, it is not possible to provide detailed instructions for
all of the different controllers. This section is an example of using a Thrustmaster
Firestorm Dual Power gamepad. However, most PC gamepads are fairly similar.
In its default configuration, the Firestorm Dual Power reports the state of all buttons and
the positions of the analog sticks continuously. You only need to read from the input
endpoint.
To read from the controller, you can use the read() method of the PyUSB device
instance. This method accepts two arguments the endpoint address, and the maximum
number of bytes to read. The IN endpoint from which the controller sends button press
information is at address 0x81. And the maximum length (in bytes) of a valid input report
is seven.
Replace the line in the while loop that reads pass with the following code, and then
indent it:
try:
inp = dev.read(0x81, 7)
print inp
except usb.core.USBError:
pass
There are several small errors that can occur during USB transmission. The try and
except statements ensure that if one of these occurs, the script will continue to try and
read from the controller instead of ending with an error message.
Connect the controller to the vertically-mounted USB socket on your Yn (if it is not
connected). Then run this script from the command line, and press a few buttons on the
controller. The script sends an array of bytes to the Linux console, and each byte
represents a button, analog stick position, or accelerometer value on the controller.
The input report consists of seven bytes. The following table shows a description of
each byte starting with the first byte, which is at position 0 of the inp array.
Byte
No. Description
Represents the state of the four buttons on the face of the controller, and the
four shoulder buttons. Each bit represents a different button. If a bit is set, the
button is pressed. If a bit is not set, the button is not pressed. From most-
0 significant to least-significant: Bit 7 bottom-right shoulder button Bit 6 top-
right shoulder button Bit 5 bottom-left shoulder button Bit 4 top-left
shoulder button Bit 3 button 4 Bit 2 button 3 Bit 1 button 2 Bit 0 button
1
Represents the state of the two trigger buttons and the analog stick buttons.
Each bit represents a different button. If a bit is set, the button is pressed. If a
1 bit is not set, the button is not pressed. From most-significant to least-
significant: Bit 3 right analog stick button Bit 2 left analog stick button Bit 1
right trigger Bit 0 left trigger
If digital mode is enabled, this value is always 224. If dual analog mode is
enabled then this is value is 240 unless the d-pad is pressed. If the d-pad is
2 pressed, this byte can have the following values: 0 up 16 up and right 32
right 48 down and right 64 down 80 down and left 96 left 112 up and
left
In digital mode, this value is 0 unless the left or right d-pad button is pressed.
If the value is 127 then the right d-pad is pressed; if this value is -128 then the
3 left d-pad is pressed. In dual analog mode, this is a value that represents the
x axis of the left analog stick. It is a signed 8-bit number, in the range -128
127 (left to right) with 0 as the midpoint.
In digital mode, this value is 0 unless the up or down d-pad buttons are
pressed. If the value is 127 then the down d-pad is pressed; if this value is -
4 128 then the up d-pad is pressed. In dual analog mode, this is a value that
represents the y axis of the left analog stick. It is a signed 8-bit number, in the
range -128127 (up to down) with 0 as the midpoint.
The x axis of the right analog stick. This is a signed value in the range -128
5
127 (left to right) with 0 as the midpoint.
The y axis of the right analog stick. This is a value in the range 0255 (up to
6
down), with 128 as the midpoint.
Note: The controller.py script does not display the negative numbers correctly yet. You
can fix this later.
Turning On Rumble
The Thrustmaster Firestorm Dual Power controller contains two small motors that you
can use to rumble or vibrate the device. The first motor creates a high-frequency
rumble, and the second motor creates a low-frequency rumble. To control the speed or
strength of each motor, you can send a value in the range 0255 to the controller.
To the send a rumble message, you need an array of four bytes. For example:
[0xFF, 0xFF, 0x00, 0x00]
The first byte is the strength of the fast (high-frequency) rumble. The second byte is
the strength of the slow (low-frequency) rumble.
To send this information to the controller, you can use the following code:
dev.write(0x21, 0x09, 0x0200, 0, [0xFF, 0xFF, 0x00, 0x00])
After you start the rumble feature, you have to turn it off yourself. If you are testing the
rumble in controller.py, you can add this line to the top of the clean_getaway() function.
dev.write(0x21, 0x09, 0x0200, 0, [0x00, 0x00, 0x00, 0x00])
This ensures that the rumble ends when the script does.
To complete the controller.py script, send the x position of the left analog stick and a
single byte that represents the state of four buttons to the Arduino sketch. The
ATmega32u4 can struggle to read a full-length report quickly enough, so it is more
efficient to only send the data that the sketch needs.
The sketch in Building a Test Circuit reads input from the OpenWrt-Yun shells standard
output device. If you use the Python print() function to send these numbers then it may
convert them to text. Instead, you can use the sys.stdout.write() method.
And then replace the line that reads print inp, with the following code:
if len(inp) == 7:
sys.stdout.write(chr(ctypes.c_byte(inp[3]).value + 128))
sys.stdout.write(chr(inp[0]))
sys.stdout.flush()
The fourth byte of the input report (position 3 in the array inp) is a signed byte that
specifies the x position of the left analog stick. However, Python is not adept at handling
signed 8-bit numbers, and it loses the sign when it reads the position from the USB
device. Since the lowest eight bits of the number are not changed, you can restore the
sign.
The ctypes module contains functions and data types that closely correspond to the
data types used in C. c_byte is an 8-bit signed byte. The code above first converts
inp[7] to a c_byte, which preserves negative values. It then gets a Python integer from
this, using the value property. The result is a signed Python integer in the range -128
127. However, the Arduino sketch expects the analog stick position to be in the range
0255. By adding 128 to the value, you convert -128127 numbers to 0255
numbers.The call to sys.stdout.flush() ensures that the Python interpreter sends the
information to the Arduino as soon as possible.Your Python script should now look like
this:
#!/usr/bin/python
import ctypes
import signal
import sys
import usb.backend.libusb1
import usb.core
import usb.util
signal.signal(signal.SIGINT, clean_getaway)
backend = usb.backend.libusb1.get_backend(
find_library=lambda x: "/usr/lib/libusb-1.0.so"
)
dev = usb.core.find(idVendor=0x044F, idProduct=0xB304)
if dev is None:
sys.stdout.write(chr(0))
sys.stdout.flush()
sys.exit(0)
else:
sys.stdout.write(chr(1))
sys.stdout.flush()
if dev.is_kernel_driver_active(0):
dev.detach_kernel_driver(0)
usb.util.claim_interface(dev, 0)
while (1):
try:
inp = dev.read(0x81, 7)
if len(inp) == 7:
sys.stdout.write(chr(ctypes.c_byte(inp[3]).value + 128))
sys.stdout.write(chr(inp[0]))
sys.stdout.flush()
except usb.core.USBError:
pass
Building a Test Circuit
You can build the circuit for this project on a standard, half-size breadboard. If you want
to expand the project by adding more LEDs, you can do so by connecting them in the
same way that the following diagram shows.
If you want to add additional servo motors, you should be aware that servos can draw
significant amounts of current. The Arduino is not a suitable power source if you are
using more than one or two servos. In those situations, you should power the servos
from an external battery, or DC adapter and voltage regulator.
Disconnect the Yn from its power supply (or unplug the USB cable), and then build the
circuit as shown in Figure 16.
On this diagram, the longer, left leg of each LED is the anode and you should connect
these to an Arduino digital pin. One for each LED. The right leg of each LED is the
cathode; connect these to ground through 220 resistors.
Reconnect the power to the Arduino Yn. Then start the Arduino IDE and add the
following line to the top of a new sketch:
#include <Bridge.h>
Then set all of the LEDs HIGH, to show that the sketch runs.
digitalWrite(LED1, HIGH);
digitalWrite(LED2, HIGH);
digitalWrite(LED3, HIGH);
digitalWrite(LED4, HIGH);
When it starts, the controller.py script tries to detect a game controller. If it finds one, the
script sends 1 to the ATmega32u4. If it does not find the controller, it sends 0 and then
ends.
To communicate with the Python script, the Arduino sketch needs to: start the Bridge
library; start the Python script; and then read the information from the script process.
In Running Commands and Scripts, you can see how to control Linux processes and run
commands through the Arduino Bridge library. This project reuses one instance of the
Process class, and so you should declare it as a global variable, underneath the #define
statements.
Process p;
Then add the following code to the sketchs setup() function, underneath the calls to
digitalWrite():
Bridge.begin();
p.runShellCommand("killall controller.py");
p.runShellCommandAsynchronously("/mnt/sda1/P4/controller.py");
while (p.available() == 0);
if (p.read() != 1) {
while (1) {
digitalWrite(LED1, LOW);
digitalWrite(LED2, LOW);
digitalWrite(LED3, LOW);
digitalWrite(LED4, LOW);
delay(500);
digitalWrite(LED1, HIGH);
digitalWrite(LED2, HIGH);
digitalWrite(LED3, HIGH);
digitalWrite(LED4, HIGH);
delay(500);
}
}
else {
digitalWrite(LED1, LOW);
digitalWrite(LED2, LOW);
digitalWrite(LED3, LOW);
digitalWrite(LED4, LOW);
}
Change the file name and file path in the call to runShellCommandAsynchronously() to
specify the location of your controller.py Python script.
If you run one copy of your script and then start another, the second process is unable
to access the controller safely. Since the sketch on the ATmega32u4 starts the Python
script on the AR9331, this can happen if the ATmega32u4 resets but the AR9331 does
not.
To prevent this, the code uses the Linux command killall to terminate the older script
before the sketch starts a new one.
p.runShellCommand("killall controller.py");
If the script does not find the correct USB peripheral, the Arduino sketch enters an
infinite loop and flashes the LEDs until you reset the Arduino. This prevents the sketch
from moving into its loop() function.
If the Python script does find the controller, the Arduino sketch turns off all of the LEDs
and moves into the loop() function.
The Python script sends a 2-byte message. The first byte is the horizontal position of an
analog stick; the second byte contains the status of four buttons.
The least-significant four bits of this number represent the state of the buttons. To
detect an individual button press, you need to use bitwise operations. Since arrays on
the Arduino start at position 0, you can access the button information using the code
buf[1]. To check each button and set the LEDs, add the following code underneath the
for loop:
if (buf[1] & 0b00000001) {
digitalWrite(LED1, HIGH);
}
else {
digitalWrite(LED1, LOW);
}
Each of these comparisons works the same way the code ands a number to the
button input. Each number only has one bit set. The result of this operation is 0 if the
selected bit is clear in buf[1], or a different number if the bit is set in buf[1]. When used in
an if statement, 0 evaluates as false and anything else evaluates as true.
The Arduino IDE comes with a library for controlling servos Servo.h. Include this library
at the top of the sketch:
#include <Servo.h>
In this project, the pulse line of the servo connects to Arduino digital pin 6, and the servo
library uses this line to move the motor. Add these two lines underneath the existing
#define lines:
#define SRV1 6
Servo servo1;
To prepare the servo for use, you call the attach() method from the Servo class. Then
use the method write() to move the servo to its center position it may have moved
while connecting the wires, or could be out of position if the sketch has run previously.
Add the following lines to the setup() function in your sketch, before the call to
Bridge.begin():
servo1.attach(SRV1);
servo1.write(90);
The controller.py Python script sends one unsigned byte to represent the horizontal
position of the analog stick. An unsigned byte contains values in the range 0255, with
128 as the midpoint or center position. However, the servo only accepts values in the
range 0180, with 90 as the center position.
To make the servo move correctly, you can translate the position of the analog stick into
a signed byte in the range -9090. If you subtract this number from the servo center
point (90) then the sketch can move the servo in the appropriate direction.
You can use the Arduino map() function to translate the analog stick position into the
range -9090. This function takes five arguments:
Argument Description
For example:
map(buf[0], 0, 255, -90, 90)
To move the servo in response to the position of the analog stick, add this line to the
bottom your sketchs loop() function:
servo1.write(90 - map((unsigned char)buf[0], 0, 255, -90, 90));
Note that (unsigned char) forces the Arduino C compiler to treat buf[0] as an unsigned
byte. This is a precaution in case the compiler thinks that buf[0] is a signed number.
If your servo turns in a different direction, or has a wider range of motion than standard
RC servo motors, then you may need to adjust this code.
Source Code Sketch
#include <Servo.h>
#include <Bridge.h>
#define LED1 2
#define LED2 3
#define LED3 4
#define LED4 5
#define SRV1 6
Servo servo1;
Process p;
void setup() {
pinMode(LED1, OUTPUT);
pinMode(LED2, OUTPUT);
pinMode(LED3, OUTPUT);
pinMode(LED4, OUTPUT);
digitalWrite(LED1, HIGH);
digitalWrite(LED2, HIGH);
digitalWrite(LED3, HIGH);
digitalWrite(LED4, HIGH);
servo1.attach(SRV1);
servo1.write(90);
Bridge.begin();
p.runShellCommand("killall controller.py");
p.runShellCommandAsynchronously("/mnt/sda1/P4/controller.py");
while (p.available() == 0);
if (p.read() != 1) {
while (1) {
digitalWrite(LED1, LOW);
digitalWrite(LED2, LOW);
digitalWrite(LED3, LOW);
digitalWrite(LED4, LOW);
delay(500);
digitalWrite(LED1, HIGH);
digitalWrite(LED2, HIGH);
digitalWrite(LED3, HIGH);
digitalWrite(LED4, HIGH);
delay(500);
}
}
else {
digitalWrite(LED1, LOW);
digitalWrite(LED2, LOW);
digitalWrite(LED3, LOW);
digitalWrite(LED4, LOW);
}
}
void loop() {
char buf[2];
for (int i=0; i<2; i++) {
while (p.available() == 0);
buf[i] = p.read();
}
1
Rubini, Alessandro, and Jonathan Corbet. 2005. Linux Device Drivers. Sebastopol: OReilly Media Inc.
2
W hen you finish coding the project, you can disconnect the Arduino from your network and still use the circuit.
3
Axelson, Jan. 2009. USB Complete. Madison, W is: Lakeview Research LLC.
4
Replace \\.\USBPcap6 with the device that you want to spy on.
5
Replace \\.\USBPcap6 with the device that you want to spy on.
Project 5 Making a USB Accelerometer
Mouse
When you connect an Arduino Yn to a computer using a micro-USB cable, the
computer identifies the Yn as a serial port. This allows the Arduino IDE to upload
sketches, and you can communicate with the Yn from any terminal software that
supports serial connections.
M any Arduinos (and other development boards) use a dedicated chip to tell the
computer that the device is a USB serial port, and to exchange data. However, the
ATmega32u4 on the Arduino Yn, Arduino Leonardo, and Arduino M icro has a built-in
USB connection. These Arduinos do not need an additional chip.
Because the ATmega32u4 is programmable, you can make the Arduino Yn appear as
different types of USB device. The Arduino IDE compiles sketches so that they include
the option of using the Yn as a mouse or keyboard.
This project reads values from an accelerometer sensor module and uses this
information to move the mouse pointer it creates a prototype controller that you use
by tilting the Arduino.To complete this project, you need:
1. An Arduino Yn.
2. A USB to micro-USB cable.
3. An M PU-6050 triple-axis accelerometer and gyroscope breakout board.
4. Two 10 k resistors.
5. Two momentary push buttons 1 .
6. A mini breadboard from an Arduino ProtoShield.
7. Jumper wires.
In This Chapter
A gyroscope has a tiny, spinning disc inside. When you tilt or turn a spinning disc, it
pushes back in the opposite direction to try and remain in the same position. This is the
gyroscopic effect, and a gyroscope uses this to determine the orientation of the sensor.
Over time, and with large movements, gyroscopes become less accurate.
To avoid the limitations that each type of sensor has if used on its own, manufacturers
create devices that have both types of sensor in one module. The M PU-6050 is a
single-chip sensor that combines a triple-axis gyroscope with a triple-axis accelerometer.
You communicate with this device using I2C.
M any different companies produce breakout boards that contain an M PU-6050. The list
below shows a few links to appropriate boards, but you can find others by searching the
web for M PU-6050 breakout board.
http://www.sainsmart.com/sainsmart-mpu-6050-3-axis-gyroscope-module.html
http://www.sparkfun.com/products/11028
http://www.geeetech.com/triple-axis-accelerometergyro-breakout-mpu6050-p-
539.html
http://www.robotshop.com/en/6-dof-gyro-accelerometer-imu-mpu6050.html
You can use most M PU-6050 breakout boards with both 3 V and 5 V Arduinos. For this
project, make sure your sensor is able to work with 5 V.
If your breakout board does not have header pins, you need to solder some onto the
board. The code in this project assumes that you solder the pins so that the breakout
board fits into the breadboard as shown in Figure 17. If you solder the pins so that the
breakout board fits in upside down, the readings from the sensor change. This is fixable
in the sketch code.
Remove the power from your Yn and connect the circuit as shown in the following
diagram.
Figure 17. Connecting an MPU-6050 to an Arduino
The mini breadboard from an Arduino ProtoShield fits on top of the Arduino Yn. You do
not need to stick the breadboard to the Arduino for this project.
Your M PU-6050 breakout board may have a different number of pins than the one in
Figure 17. You should connect:
VCC 5V.
GND GND.
Connect one leg of each button to ground. Connect the other leg of each button to a 10
k resistor. One resistor connects to Arduino digital pin 4, and the other connects to
Arduino digital pin 5.
Electricity should not flow between the two legs of each button, unless you press the
switch.
If you want to use the full Arduino ProtoShield, you can use extra-long pin headers to
increase the space between the Yn and the ProtoShield. Without these headers, the
ProtoShields pins do not fully-connect with the Arduino and the shield may not work
correctly.
The M PU-6050 library is a very comprehensive and large collection of code. You may
want to read the librarys source code to learn about the other features that it has.
You can start the M PU-6050 by calling the initialize() method from the MPU6050 class.
This method does not return a result, but you can check whether you wired the breakout
board correctly by using the method testConnection().
Connect the Arduino to your PC using the micro-USB cable. Upload the sketch to your
Arduino, and then open the Serial M onitor in the Arduino IDE.
If you see the text Testing M PU-6050 connection OK. then the sketch
communicates with the breakout board successfully and you can continue.
Tip: If you see the text Testing MPU-6050 connection Not connected!, or if
Testing MPU-6050 connection remains on the display for a long period, check
your circuit matches the one in Connecting to an Accelerometer M odule.
The MPU6050 method getMotion6() reads from the M PU-6050 and returns six values.
The first three values represent the three axis values from the accelerometers. The last
three values represent the three axis values from the gyroscope.
To call this method, you need to pass in the address of six variables. getMotion6() writes
the information to these memory addresses.
The int16_t type ensures that each variable is a signed 16-bit integer number.
In this code, the ampersand is the address of operator. This tells the compiler to pass in
the memory address of the variable, not the value of the variable.
Upload this sketch and then open the Serial M onitor. If you move and tilt the Arduino,
the numbers change. Even though accelerometers usually do not maintain a value when
they stop moving, the M PU-6050 and the accompanying library ensure that ax, ay, and
az are always useful.
ax and ay contain all of the data you need. These numbers are usually in the range -
18000 through 18000. Using the Arduino map() function, you can scale the data to a
more workable range.
This maps the values to angles in the range 90 through -90, with zero as the midpoint.
If you tilt the Arduino up and down, the Y value changes. In the next section of this
chapter, this controls the vertical position of the mouse pointer.
If you twist the Arduino, the X value changes. This controls the horizontal position of the
mouse pointer.
Figure 19. Twisting and turning the Arduino
In the next section, you can see how to use these values to move the mouse pointer
on your computer. If the mouse pointer moves in the wrong direction if it moves up
when it should move down, or left when it should move right you can change the range
in the calls to map().
For example:
int vx = map(ax, -18000, 18000, -90, 90);
int vy = map(ay, -18000, 18000, -90, 90);
Using the Mouse Class
Before continuing, remove all references to the Serial class. You do not need them.
When you compile an Arduino sketch and upload it to the ATmega32u4, the Arduino IDE
combines your sketch with some standard code. On the Arduino Yn, part of this code
describes the Arduino to the USB control devices in a PC.
The code tells the PC that the Yn contains three USB devices a serial port, a mouse,
and a keyboard. To send a mouse message from the Yn, you only need to call
methods in the Mouse class.
Tip: To keep compatibility with other mouse libraries and example code, the Yns
Mouse class includes the methods begin() and end(). However, these methods are
empty on the Yn; you do not need to use them.
Method Description
Sends a mouse button down event to the computer to tell it that one
of the mouse buttons is pressed. Without any arguments, this is the left
press()
button. Or you can pass in one of the following values: MOUSE_LEFT,
MOUSE_RIGHT, or MOUSE_MIDDLE.
To modify your sketch so that it moves the mouse pointer, you need to translate the ax
and ay angles into distances. The approach taken in this project is to move the mouse
pointer by a predefined amount depending on the orientation of the Arduino. The more
you tilt the Arduino, the more the sketch moves the mouse pointer.
There are many ways of doing this. The example below is not elegant, but it is easy to
modify to suit your preferences.
The combination of the values returned by angleToDistance() and the value in the call to
delay() determine how responsive the mouse pointer is, and how fast it moves. Too fast
or responsive and the pointer is difficult to control. To slow or unresponsive and the
mouse is frustrating to use. You should adjust these values until you find a setting that
is comfortable for you.
Upload the sketch to your Arduino. After the sketch begins, moving the Arduino should
move the mouse pointer on your PC.
When a sketch sends mouse events to the PC, you may find it difficult to upload new
sketches over a USB cable. If this happens, either:
Press the 32U4 RST button twice, and then click Upload in the Arduino IDE; or
Connect your Yn to your local network, and then upload the sketch over TCP/IP.
Add the following code underneath the #include directives in your sketch:
#define LBUT 4
#define RBUT 5
The wiring in this circuit connects the buttons to ground when you press them. This
reads as LOW in the Arduino sketch.
When a button is not pressed, it connects to an Arduino digital pin at one end, but
nothing at the other. To stop the pin floating, you can enable the Arduinos internal pull-
up resistors. This ensures that when the button is not pressed, it always reads HIGH in
the sketch.
Add the following lines to the top of your sketchs setup() function:
pinMode(LBUT, INPUT);
digitalWrite(LBUT, HIGH);
pinMode(RBUT, INPUT);
digitalWrite(RBUT, HIGH);
Writing a HIGH to an INPUT pin enables the pull-up resistor.
Before the line that reads delay(20); in your sketchs loop() function, add the following
code:
if (digitalRead(LBUT) == LOW) {
if (!Mouse.isPressed(MOUSE_LEFT)) {
Mouse.press(MOUSE_LEFT);
}
}
else {
if (Mouse.isPressed(MOUSE_LEFT)) {
Mouse.release(MOUSE_LEFT);
}
}
if (digitalRead(RBUT) == LOW) {
if (!Mouse.isPressed(MOUSE_RIGHT)) {
Mouse.press(MOUSE_RIGHT);
}
}
else {
if (Mouse.isPressed(MOUSE_RIGHT)) {
Mouse.release(MOUSE_RIGHT);
}
}
If the sketch reads that a button is LOW, it only needs to send a mouse event message
to the PC if the state of the button is different from the last time that it sent a message.
The Mouse class stores the last state of the mouse buttons, so you can use isPressed()
to check whether things change.
If the push button is down but isPressed() returns false, the code calls press() to tell the
PC that the mouse button is down. If the push button is up (not pressed) but isPressed()
returns true, the code calls release() to tell the PC that the mouse button is not down
anymore.
You can find the source code for the completed sketch in the following section.
Source Code Sketch
#include <Wire.h>
#include <I2Cdev.h>
#include <MPU6050.h>
#define LBUT 4
#define RBUT 5
MPU6050 mpu;
int16_t ax, ay, az, gx, gy, gz;
int angleToDistance(int a) {
if (a < -80) {
return -40;
}
else if (a < -65) {
return -20;
}
else if (a < -50) {
return -10;
}
else if (a < -15) {
return -5;
}
else if (a < -5) {
return -1;
}
else if (a > 80) {
return 40;
}
else if (a > 65) {
return 20;
}
else if (a > 50) {
return 10;
}
else if (a > 15) {
return 5;
}
else if (a > 5) {
return 1;
}
else {
return 0;
}
}
void setup() {
pinMode(LBUT, INPUT);
digitalWrite(LBUT, HIGH);
pinMode(RBUT, INPUT);
digitalWrite(RBUT, HIGH);
Wire.begin();
mpu.initialize();
if (!mpu.testConnection()) {
while (1);
}
}
void loop() {
mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
int vx = map(ax, -18000, 18000, 90, -90);
int vy = map(ay, -18000, 18000, 90, -90);
Mouse.move(angleToDistance(vx), angleToDistance(vy));
if (digitalRead(LBUT) == LOW) {
if (!Mouse.isPressed(MOUSE_LEFT)) {
Mouse.press(MOUSE_LEFT);
}
}
else {
if (Mouse.isPressed(MOUSE_LEFT)) {
Mouse.release(MOUSE_LEFT);
}
}
if (digitalRead(RBUT) == LOW) {
if (!Mouse.isPressed(MOUSE_RIGHT)) {
Mouse.press(MOUSE_RIGHT);
}
}
else {
if (Mouse.isPressed(MOUSE_RIGHT)) {
Mouse.release(MOUSE_RIGHT);
}
}
delay(20);
}
1
Or tactile switches.
Project 6 Making a Translating Keyboard
The Arduino Yn has the ability to act as a USB keyboard. This project builds a proof-of-
concept device that: reads from a USB keyboard that you plug into the Yn; sends key
presses to the PC; and translates sentences and paragraphs from English to one of
three foreign languages.
To translate text, you can use M icrosofts Translator web service. However, accessing
this service is complicated on the Yn. Temboo is a web service that simplifies how you
can work with other, more-complicated web services.
In This Chapter
1. At the OpenWrt-Yun command prompt, type the following command and then press
Enter:
opkg update
To begin this project, connect the Arduino to your PC using a micro-USB cable. Then
plug a USB keyboard into the vertically-mounted USB socket on the Arduino Yn.
If the USB keyboard on the Yn is working, you should see the device in the list.
Type the following command and then press the Enter key:
ls /dev/input/
You should see a file called event1. If your keyboard contains extra buttons, such as
multimedia controls (play, stop, and so on), then you may also see an additional entry
called event2. /dev/input/event1 usually reports the key presses from the standard
keys, and /dev/input/event2 reports the key presses of the additional buttons.
When you connect a USB keyboard to the Arduino Yn, OpenWrt-Yun creates virtual
files in /dev/input/. These files communicate directly with keyboards and other input
devices, and you can access them using the File class in the Arduinos Bridge library.
Reading from the keyboard using the Bridge library is not always reliable over a long
period of time. Instead, this project includes a Python script that uses the Python module
evdev to read from the keyboard. The script passes this information to the ATmega32u4
using the Console. For more information about passing information between the Yns
processors, see M aking a Console Connection.
To install evdev,
1. At the command prompt, type the following command and then press Enter:
cd /usr/lib/python2.7/
2. Type the following command and then press Enter:
wget http://www.arduinomeetslinux.com/download/evdev.tar.gz
M ake a new directory on the microSD card for this project, and move inside it. For
example:
mkdir /mnt/sda1/P6 && cd /mnt/sda1/P6
Start a new Python file, call it kbd.py and save it into the project directory. Add the
following code to the file:
#!/usr/bin/python
import socket, struct
from evdev import InputDevice
dev = InputDevice("/dev/input/event1")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("localhost", 6571))
The socket module makes the Console connection between the AR9331 and the
ATmega32u4.
evdev contains methods for working with Linux input events, those sent through
/dev/input/. To use it with a keyboard, you need to create an instance of the InputDevice
class and point it at the devices virtual file.
The method read_loop() continually reads from the input device and returns an
InputEvent object. InputEvent is a Python version of the Linux object input_event:
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
8
time The date and time when the event occurs.
bytes
2 An unsigned 16-bit number that describes the event. Events from
type
bytes a keyboard have the value 0x0100.
2 For keyboard events, this is the Linux key code that describes
code
bytes which key is pressed or released on the keyboard.
For this project, you need to pass the events type, code, and value properties to the
Arduino sketch. To do this, you can send them to the Console as a group of bytes.
The struct module contains methods for working with C-style struct data types. The
pack() method converts a sequence of variables into an array of bytes. It accepts a
string that describes the format of the output, followed by as many variables as you
need to send.
Each character in the format string describes the type of variable that you want to pack
into the output. In the script code, HHI puts two unsigned 16-bit (short) numbers and
one signed 32-bit number into the output.
The Arduino sketch can read this information into a similar structure.
To receive the information that kbd.py sends, you can use a struct. Add the following
code to the sketch:
struct input_event {
uint16_t type;
uint16_t code;
int32_t value;
};
struct input_event event;
Now declare the Process object that the sketch uses to start the Python script:
Process p;
When the kbd.py script starts, it continues running until the AR9331 resets. If the sketch
resets but the AR9331 does not, the existing script still runs. To ensure only one copy
of the script runs at a time, this code starts the Linux command killall to terminate any
existing copies of kbd.py.
The code then waits for the Python script to connect to the Console.
In your sketchs loop()function, you need to wait until the Python script sends an input
event. Then you can read the data into the event object.
The ampersand in this example is the address of operator. It tells the C compiler to send
the memory address of a variable, not the contents of a variable.
When the Consoles buffer contains enough bytes to fill the event object, the code reads
the bytes from the Console and overwrites the memory that contains event.
Now event.type contains the type of event, event.value contains whether a key is
pressed or released, and event.code contains a Linux key code.
Before you can send this key press information to the PC, you need to convert the
Linux key code in event.code to an ASCII character (or one of the special keys defined
in the Arduinos standard library). The key codes from /dev/input/event1 are different to
the values that you send through the Arduino Yns Keyboard methods.
To convert the values, you can use a combination of a lookup table and a switch
statement. Add the following global arrays to your sketch, below the line that reads
Process p;:
const uint8_t rq[] = {'q','w','e','r','t','y','u','i','o','p','[',']'};
const uint8_t ra[] = {'a','s','d','f','g','h','j','k','l',';','\'','`'};
const uint8_t rz[] = {'z','x','c','v','b','n','m',',','.','/'};
Tip: If you do not use a standard US keyboard, you may have to adjust this key
map.
In your sketchs loop() function, add the following code on a new line underneath the line
that reads Console.readBytes((char *)&event, sizeof(event));:
if (event.type == EV_KEY) {
uint8_t k = convertKey(event.code);
if ( (event.value == EV_PRESSED) && (k != 0) ) {
Keyboard.press(k);
}
else if ( (event.value == EV_RELEASED) && (k != 0) ) {
Keyboard.release(k);
}
}
When you compile the Arduino sketch and upload it to the ATmega32u4, the Arduino
IDE combines your sketch with some standard code. On the Arduino Yn, part of this
code enables you to pass key press messages to the PC, as if the Yn is a keyboard.
The methods for doing this are in the Keyboard class. Keyboard contains the following
methods:
Method Description
Sends a message to press the specified key. The parameter for this
press()
method is an ASCII character.
Sends an ASCII character or string to the PC, and then sends a carriage
println()
return.
Sends a message to release the specified key. The parameter for this
release()
method is an ASCII character.
Tip: To keep compatibility with other keyboard libraries and example code, the
Yns Keyboard class includes the methods begin() and end(). However, these
methods are empty on the Yn; you do not need to use them.
In general, the Keyboard class can only send key presses and messages that are made
from characters that are on a standard QWERTY keyboard.
Upload the sketch to your Arduino and connect the Yn to your computer using the
micro-USB cable. After OpenWrt-Yun loads, press keys on the USB keyboard and they
should appear on your PC.
Translating with Temboo and Microsoft
Translator
Temboo is a web service that you can connect to using your Arduino Yn. It simplifies
the code that you need to write to connect to powerful web services from companies
such as Google, M icrosoft, Twitter, Facebook, and others.
Temboos service is free for up to 250 calls per month. Or you can upgrade your
account, starting at $7 per month for a M aker account that supports 25,000 calls per
month.
If you are not familiar with using web services, the Translator API seems complicated.
With Temboo, you can access the API from an Arduino sketch, using simple classes
and methods. The Temboo library is included with the Arduino IDE, and it contains code
that handles the most-complicated aspects of working with web services and APIs.
If you have a M icrosoft Xbox, or are a Windows 8 user, you probably already have a
M icrosoft account. If you do not:
To use the M icrosoft Translator, you need to add it to your M icrosoft account:
1. Visit http://datamarket.azure.com/dataset/bing/microsofttranslator/
2. Next to 2,000,000 Characters/month, click SIGN UP.
3. Sign in to your M icrosoft account.
4. Click the box next to I have read and agree to the above publisher's Offer Terms
and Privacy Policy.
5. Click SIGN UP.
6. Click My Account.
7. In the my account menu, click DEVELOPERS.
Before you can use the Translator from an Arduino sketch, you need to register your
project as an application:
1. Click Register.
2. In the Client ID box, type YunKeyboardTranslator followed by some random
characters. You need to send this value from your sketch to use the service.
3. In the Name box, type Yun Keyboard Translator.
4. In the Redirect URI box, type in any web address. For example,
https://datamarket.azure.com.
5. Write down the Client secret, or copy and paste it somewhere for later. You need
to send this value from your sketch to use the service.
6. Click CREATE.
The identifier that you type into the Client ID box has to be unique. If anyone else tries
to use the same identifier, M icrosofts system rejects the entry.
The sketch uses the buffer array to hold key presses. The array needs to be long
enough to store a reasonable number of characters, but not too long. The ATmega32u4
does not have a huge amount of memory.
buffer_len points to the next free slot in the array. When the sketch starts, the buffer
array is empty and so buffer_len is zero.
When you hold the Ctrl key, Windows logo key, or Apple key, and then you press
another key, the keyboard sends two key presses. The key press that occurs while
holding these keys is usually a command, and so you do not want to store it in buffer.
You use the variable ctrl_held to keep track of whether to store the key press in buffer.
language contains the ISO language code for the language that you want to translate
text into. You use this in the next section of this chapter. If the sketch sets language to
an empty string, loop() should not store key presses or translate text.
In your sketchs loop() function, before the line that reads Keyboard.press(k);, add the
following code:
if (language != "") {
if (k == KEY_RETURN) {
// add code to translate text buffer here.
}
else if ( (k == KEY_LEFT_CTRL) || (k == KEY_RIGHT_CTRL) || (k == KEY_LEFT_GUI) ) {
ctrl_held = 1;
}
}
This sets ctrl_held to 1 when you press either the left Ctrl key, the right Ctrl key, the
Windows logo key, or the Apple key.
After the line that reads Keyboard.press(k);, add the following code:
if (language != "") {
if (!ctrl_held) {
if ( (k >= 32) && (k <= 126) ) {
if (buffer_len <= 510) {
buffer[buffer_len] = k;
buffer_len++;
}
Keyboard.release(k);
}
else if (k == KEY_BACKSPACE) {
buffer[buffer_len] = 0;
buffer_len--;
if (buffer_len < 0) {
buffer_len = 0;
}
}
}
}
To detect when you release either Ctrl key, and modify the value of ctrl_held, add the
following code after the line that reads else if ( (event.value == EV_RELEASED) && (k =
0) ) {:
if ( (language != "") && ( (k == KEY_LEFT_CTRL) || (k == KEY_RIGHT_CTRL) || (k == KEY_LEFT_GUI) ) ) {
ctrl_held = 0;
}
If a Ctrl key, Windows logo key, or Apple key is not pressed, the sketch checks whether
the key press refers to a text character. If it does, the code adds the character to the
buffer array and then increments the value in buffer_len.
If you press the Backspace key, the sketch deletes the last character in buffer by
setting it to null (0). It also decrements the value in buffer_len so that the next key press
replaces the previous one.
Tip: The code has to make sure that buffer_len is always a value in the range 0
through 510. buffer only has space for 512 items, so a value outside of this range
causes an error in the sketch. The final entry (511) in the array may need to be a
null character (0) so it cannot contain a key press character.
In the code above, the call to Keyboard.release() is important. When you hold a key on
the keyboard, the device sends an EV_REPEAT message after a set number of
milliseconds. The sketch ignores these messages. However, because the key is still
down, the operating system can decide to trigger a repeat message of its own, and it
may insert the same character again. If you do not stop this, the text on your PC screen
can be different from the text in buffer, and this makes the translation function unusable.
When you press the Enter key (KEY_RETURN), the sketch should run the translation
and replace the on-screen text with the translated version. To do this, it needs to
simulate Backspace key presses to delete the existing text. Then it translates the text
using Temboo, and sends the new text to the PC.
Replace the comment // add code to translate text here with the following code:
if (buffer_len > 0) {
for (int i = 0; i < buffer_len; i++) {
Keyboard.write(KEY_BACKSPACE);
}
buffer[buffer_len] = 0;
translate();
buffer_len = 0;
}
This code deletes the existing text from your PCs screen. It then ensures that the final
character after the key press information is null (0). M any of the Arduinos string
functions rely on character sequences ending with a null.
If you upload this sketch to your Arduino and run it, the device passes key press
information to the PC. When you press the Enter key, it deletes the text. In the next
section of this chapter, you expand the translate()function to translate the words in buffer
to another language, and then send this information to the PC.
Although the project does not translate text yet, it is now possible to see some of its
limitations.
The device assumes that you are typing continuous paragraphs for example, in a word
processor or email. If you reposition the text cursor (using the cursor keys or mouse)
and continue typing, the contents of buffer do not match what is on the screen. This
problem also occurs if you change applications, or start a new document without
pressing the Enter key.
To be reliable and easy to use, a commercial device would have to use custom driver
software to respond to these situations.
When you connect to Temboo, you need to send your Temboo and M icrosoft account
information. Underneath the #define directives in your sketch, add the following
statements:
#define TEMBOO_ACCOUNT F("Your Temboo username")
#define TEMBOO_APP_KEY_NAME F("YunKeyboardTranslator")
#define TEMBOO_APP_KEY F("Your registered application key")
#define MICROSOFT_ID F("Your Microsoft Client ID")
#define MICROSOFT_KEY F("Your Microsoft Azure application key")
Replace Your Temboo username and Your registered application key with the values that
you receive from the Temboo website.
Replace Your Microsoft Client ID and Your Microsoft Azure application key with the
values that you set on the M icrosoft Azure website.
Unlike a variable, a #define statement can contain code. Because the sketch uses a lot
of memory, the statements above define data using the F() function. This ensures that
the Arduino reads the strings directly from the read-only, program code space, instead of
creating the strings in the limited amount of variable space on the ATmega32u4.
Each web service that you can use is called a choreo. You can find the details of these
choreos (and example code that uses them) on the Temboo website.
1. Login to http://www.temboo.com
2. Click LIBRARY.
3. Under CHOREOS, click the arrow next to Microsoft.
4. Click Translator.
To use a choreo from an Arduino sketch, you need to create an instance of the
TembooChoreo class.
To use the M icrosoft Translator service, you need to tell the choreo to use it and then
send your input parameters. Now, add the following code directly underneath the
preceding lines:
TranslateChoreo.setChoreo(F("/Library/Microsoft/Translator/Translate"));
TranslateChoreo.addInput(F("ClientID"), MICROSOFT_ID);
TranslateChoreo.addInput(F("ClientSecret"), MICROSOFT_KEY);
TranslateChoreo.addInput(F("From"), F("en"));
TranslateChoreo.addInput(F("To"), language);
TranslateChoreo.addInput(F("Text"), buffer);
In addition to your M icrosoft Azure account information, you need to send three
parameters to control the translation:
Parameter Description
An ISO-631 language code that specifies the language that your text is
From
in.
An ISO-631 language code that specifies the language that you want to
To
translate your text to.
When the Temboo request finishes, you can read the result like you do when reading
from files or network connections. However, the result of a call to the M icrosoft
Translator returns several lines of information. For example:
ExpiresIn
600
NewAccessToken
http://schemas.xmlsoap.org/ws/2005/...
TranslatedText
Salut din arduinomeetslinux
HTTP_CODE
200
This project only captures the line after TranslatedText. To do this, the following code
reads characters from the TranslateChoreo object and stores them in a string. The string
only needs to be 14 characters in length, which is enough to hold the sequence
TranslatedText.
Each time it reads a character, the code checks whether the string is 14 characters long.
If it is, it removes the first character and continues. This means that the string only holds
the last 14 characters of the choreos response. Then, if the string has the value
TranslatedText, the code skips the next two characters because they represent the
line break. Then it extracts all of the characters on the next line, which is the translated
text.
Tip: uint8_t is a data type that treats all numbers as unsigned 8-bit numbers.
Certain foreign characters appear as negative numbers if you use a char.
You can upload this sketch to your Arduino and test it. However, there is a problem to
fix before the code is usable.
The ASCII character set uses one byte for each character, and it contains the English
alphabet, numbers, and standard punctuation marks. However, many other languages
use characters and symbols that are not in the ASCII character set. For these
languages, the output from the M icrosoft Translator tool is in Unicode not ASCII.
Unicode characters are actually a sequence of values there are multiple bytes for each
character.
However, the Keyboard class can only send ASCII keys to the PC.
On most operating systems, you can type alternate characters by holding the Alt key
and then pressing a combination of other keys. But when you want to type a Unicode
character, the process is slightly different.
To type Unicode hex strings on M icrosoft Windows, you need to make a small change
to the system registry:
1. Hold Alt.
2. On the numeric keypad, type +.
3. Type a 4-character, hexadecimal point code. For example: c380.
4. Release Alt.
To type Unicode hex strings on M ac OS X, you need to enable the Unicode keyboard
input in the System Preferences, then change the input source selector to Unicode Hex
Input whenever you want to use this project. To set all of this up:
M ost Linux distributions already support entering Unicode characters from the keyboard.
To type a Unicode character on Linux:
In order to send foreign characters to the PC, the Arduino sketch needs to simulate the
key combinations that your operating system uses.
The first step is to determine how many bytes are in the Unicode sequence. This project
only supports languages that use a 2-byte or 3-byte sequence. If the first byte of a
Unicode sequence is in the range 0xC20xDF, it is a 2-byte sequence and so there is
one more byte to read. If the first byte is 0xE00xEF, it is 3-byte sequence and there
are two more bytes to read.
Find the line in translate() that reads Keyboard.write((char)c); and replace it with:
if ( (c >= 0xC2) && (c < 0xE0) ) {
writeUTF8PointCode(c, TranslateChoreo.read(), 0);
}
else if ( (c >= 0xE0) && (c < 0xF0) ) {
uint8_t c2 = TranslateChoreo.read();
uint8_t c3 = TranslateChoreo.read();
writeUTF8PointCode(c, c2, c3);
}
else {
Keyboard.write((char)c);
}
The writeUTF8PointCode() function does not exist yet. It accepts the two or three bytes
that form the Unicode sequence, and then converts that information into a key
combination.
Later, you may want to redevelop this project and convert the sequences in the Arduino
sketch instead of using Python. Calling the Python script for each character slows down
the speed at which the Arduino sends translated text to the PC.
5. Press Ctrl + X.
6. Press Y and then press Enter.
The script reads parameters from the command line. It converts each parameter to a
number, combines them into a string, and then converts the string.
The result of the unicode() function is another Unicode string. You have to convert this
to a number before you can print the result back to the command line as a hexadecimal
character sequence.
To start the utf8.py script, writeUTF8PointCode() needs a Process object. Add the
following line to the function:
Process cnv;
If you use M icrosoft Windows, add the following code underneath the line that reads
Process cnv;:
Keyboard.press(KEY_LEFT_ALT);
Keyboard.press(223);
Keyboard.release(223);
If you use M ac OS X, add the following code underneath the line that reads Process
cnv;:
Keyboard.press(KEY_LEFT_ALT);
If you use Linux, add the following code underneath the line that reads Process cnv;:
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press(KEY_LEFT_SHIFT);
keyboard.write('u');
Tip: Keyboard.write() cannot send keys from the numeric keypad. These keys are
in the range 8499. To send these codes through the Keyboard class, you need to
add 136.
Because utf8.py sends back the result as an ASCII character string, you can read each
character from the cnv object and then call Keyboard.write(). The Python script sends at
least one additional character (a carriage return), but the sketch must only read the first
four characters. Add these lines:
for (int i = 0; i < 4; i++) {
while (!cnv.available());
Keyboard.write(cnv.read());
}
cnv.close();
Finally, writeUTF8PointCode() needs to release the held keys. This is different for each
operating system.
The source code for this sketch is in Source Code Sketch. To complete the project,
you can add an RGB LED (to indicate the status and activity of the device) and a rotary
switch (to choose between three different languages).
Building the Circuit and Finishing the
Sketch
This project uses a 4-way rotary switch. When you turn a rotary switch, it makes a
connection between a pole (a pin) and one of four other pins. You can also buy rotary
switches in versions that have multiple poles.
For example: a 3-pole, 4-way rotary switch has three sets of contacts. They commonly
have the poles arranged in an inner ring, and they are labelled A, B, and C. An outer ring
has twelve pins, numbered 1 through 12. In the first position: A connects to pin 1; B
connects to pin 5; and C connects to pin 9. In the second position: A connects to pin 2;
B connects to pin 6; and C connects to pin 10.
Figure 20. Connecting the switches and LED
Unplug the power from your Arduino, and then build the circuit in Figure 20. Then
reconnect the Arduino to your PC using a micro-USB cable.
In this project, the first position of the rotary switch turns off the automatic translation.
The three other positions select the language that you want to translate to.
Each position of the rotary switch acts like a button. To prevent a short circuit, connect a
10 k resistor between the switchs pin A and ground.
Connect another 10 k resistor between the other leg of the push button and ground.
A common-cathode RGB LED contains three LEDs one red, one green, and one blue.
It usually has four legs. The longest leg is usually the second one, and this is the
cathode. Each LED in a common-cathode RGB LED shares the same connection to
ground.
When using RGB LEDs, you need to add a current-limiting resistor to your circuit the
same as you do with normal LEDs. Connect a 330 resistor between the cathode of
the RGB LED and ground.
To control the brightness and color of the LED, you can use pulse-width modulation
(PWM ) to set the individual LEDs in the RGB LED. Depending on how bright each LED
is, your eyes see a blend of the colors.
The Arduino sketch for this project uses the RGB LED to indicate the status of the
sketch:
Color Description
At the top of your setup() function, add the following code to set the Arduinos digital pins
to OUTPUT and ensure the LED is off:
pinMode(LED_R, OUTPUT);
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);
digitalWrite(LED_R, LOW);
digitalWrite(LED_G, LOW);
digitalWrite(LED_B, LOW);
This flashes the RGB LED red while the ATmega32u4 waits for the kbd.py script to open
the Console. If the light continues to flash red for a long time, it indicates that kbd.py is
unable to find the USB keyboard. Try pressing the 32U4 RST button and running the
lsusb command again.
To set an LED in the RGB LED to maximum brightness, you can use digitalWrite().
However, when you want to blend colors and reduce the brightness of the LED, you
need to use analogWrite().
Tip: If the switch is backwards when you test the sketch, you can reverse the pin
numbers in these #define statements set TRANS_OFF to 5, and TRANS_3 to 2.
In your sketchs setup() function, add the following code:
pinMode(TRANS_OFF, INPUT);
digitalWrite(TRANS_OFF, HIGH);
pinMode(TRANS_1, INPUT);
digitalWrite(TRANS_1, HIGH);
pinMode(TRANS_2, INPUT);
digitalWrite(TRANS_2, HIGH);
pinMode(TRANS_3, INPUT);
digitalWrite(TRANS_3, HIGH);
The wiring of the circuit (see Building the Circuit and Finishing the Sketch) means that
one of the digital pins connects to ground (LOW) when you select a position on the
rotary switch. To ensure that the Arduinos digital pins never float, you enable the
internal pull-up resistors using digitalWrite().
When you read the position of the rotary switch, you need to change the color of the
LED and the language variable that the translate() function uses.
The loop() function in the sketch is quite large, so add the code that checks the rotary
switch to a new function:
void checkSwitches() {
if (digitalRead(TRANS_OFF) == LOW) {
language = "";
buffer_len = 0;
analogWrite(LED_R, 0);
analogWrite(LED_G, 5);
analogWrite(LED_B, 0);
}
else if (digitalRead(TRANS_1) == LOW) {
language = "fr";
analogWrite(LED_R, 175);
analogWrite(LED_G, 0);
analogWrite(LED_B, 128);
}
else if (digitalRead(TRANS_2) == LOW) {
language = "ko";
analogWrite(LED_R, 0);
analogWrite(LED_G, 0);
analogWrite(LED_B, 255);
}
else if (digitalRead(TRANS_3) == LOW) {
language = "fi";
analogWrite(LED_R, 175);
analogWrite(LED_G, 255);
analogWrite(LED_B, 0);
}
}
The language options for this project are described in Setting the Language Options.
The code sets the color of the RGB LED to one of four settings depending on the
position of the rotary switch. The values for each LED should be in the range 0255.
Tip: When choosing values for the RGB LED, setting colors to maximum strength
(255) can make it difficult to blend them. Experiment to find the colors that you
want.
checkSwitches() also resets the buffer (by setting buffer_len to zero) when you turn the
rotary switch into the off position.
Add a call to checkSwitches() at the start of your sketchs loop() function. For example:
void loop() {
checkSwitches();
if (Console.available >= sizeof(event)) {
When translate() runs, it should change the RGB LED to green. To do this, add the
following code before the TranslateChoreo.begin(); in the translate() function:
digitalWrite(LED_R, LOW);
digitalWrite(LED_G, HIGH);
digitalWrite(LED_B, LOW);
The push button in this project is intended for situations where you want to translate
text, but do not want to press the Enter key.
Because the translate() function takes time to complete, there is no need to debounce
the button.
Arabic ar
Bosnian bs-Latn
Bulgarian bg
Catalan ca
Chinese zh-CHS
Croatian hr
Czech cz
Danish da
Dutch nl
English en
Estonian et
Finnish fi
French fr
German de
Greek el
Haitian ht
Hebrew he
Hindi hi
Hungarian hu
Indonesian id
Italian it
Japanese ja
Korean ko
Latvian lv
Lithuanian lt
M altese mt
Norwegian no
Polish pl
Portuguese pt
Romanian ro
Russian ru
Serbian sr-Latn
Slovak sk
Slovene sl
Spanish es
Swedish sv
Thai th
Turkish tr
Ukrainian uk
Urdu ur
Vietnamese vi
Welsh cy
Source Code Sketch
This code includes all of the lines that are specific to the different operating systems. At
the top of the source code, there is a #define instruction:
#define WINDOWS
You should only define one of the following values: WINDOWS, LINUX, or MAC_OS_X.
In the code for the writeUTF8PointCode() function, there are #ifdef and #endif
statements. For example:
#ifdef WINDOWS
Keyboard.press(KEY_LEFT_ALT);
Keyboard.press(223);
Keyboard.release(223);
#endif
An #ifdef instruction tells the Arduino C compiler to check whether a value is defined
using the #define directive. If the value is not defined, the compiler removes lines of
code from the file until it sees an #endif instruction.
Tip: This is not the same as using an if statement. An if statement runs code if a
condition is true. The actual code block is uploaded to the Arduino, but does not
run if the condition is false. With an #ifdef statement, if the value is not defined
then the C compiler does not compile that part of the code, and it does not upload
it to the Arduino.
#include <Bridge.h>
#include <Console.h>
#include <Temboo.h>
#define WINDOWS
#define LED_R 11
#define LED_G 10
#define LED_B 9
#define TRANS_OFF 2
#define TRANS_1 3
#define TRANS_2 4
#define TRANS_3 5
#define BTN_1 6
struct input_event {
uint16_t type;
uint16_t code;
int32_t value;
};
struct input_event event;
Process p;
const uint8_t rq[] = {'q','w','e','r','t','y','u','i','o','p','[',']'};
const uint8_t ra[] = {'a','s','d','f','g','h','j','k','l',';','\'','`'};
const uint8_t rz[] = {'z', 'x', 'c', 'v', 'b', 'n', 'm',',','.','/'};
char buffer[512];
int buffer_len = 0;
int ctrl_held = 0;
String language = "ro";
void checkSwitches() {
if (digitalRead(TRANS_OFF) == LOW) {
language = "";
buffer_len = 0;
analogWrite(LED_R, 0);
analogWrite(LED_G, 5);
analogWrite(LED_B, 0);
}
else if (digitalRead(TRANS_1) == LOW) {
language = "fr";
analogWrite(LED_R, 175);
analogWrite(LED_G, 0);
analogWrite(LED_B, 128);
}
else if (digitalRead(TRANS_2) == LOW) {
language = "ko";
analogWrite(LED_R, 0);
analogWrite(LED_G, 0);
analogWrite(LED_B, 255);
}
else if (digitalRead(TRANS_3) == LOW) {
language = "fi";
analogWrite(LED_R, 175);
analogWrite(LED_G, 255);
analogWrite(LED_B, 0);
}
if (digitalRead(BTN_1) == LOW) {
if (buffer_len > 0) {
for (int i=0; i<buffer_len; i++) {
Keyboard.write(KEY_BACKSPACE);
}
buffer[buffer_len] = 0;
translate();
buffer_len = 0;
}
}
}
uint8_t convertKey(uint16_t k) {
uint8_t key = (k & 0xFF00) >> 8;
switch (key) {
case 1:
return KEY_ESC;
case 12:
return '-';
case 13:
return '=';
case 14:
return KEY_BACKSPACE;
case 15:
return KEY_TAB;
case 28:
return KEY_RETURN;
case 29:
return KEY_LEFT_CTRL;
case 42:
return KEY_LEFT_SHIFT;
case 43:
return '\\';
case 54:
return KEY_RIGHT_SHIFT;
case 56:
return KEY_LEFT_ALT;
case 57:
return ' ';
case 58:
return KEY_CAPS_LOCK;
case 87:
return KEY_F11;
case 88:
return KEY_F12;
case 96:
return KEY_RETURN;
case 97:
return KEY_RIGHT_CTRL;
case 100:
return KEY_RIGHT_ALT;
case 102:
return KEY_HOME;
case 103:
return KEY_UP_ARROW;
case 104:
return KEY_PAGE_UP;
case 105:
return KEY_LEFT_ARROW;
case 106:
return KEY_RIGHT_ARROW;
case 107:
return KEY_END;
case 108:
return KEY_DOWN_ARROW;
case 109:
return KEY_PAGE_DOWN;
case 110:
return KEY_INSERT;
case 111:
return KEY_DELETE;
case 125:
return KEY_LEFT_GUI;
case 127:
return KEY_RIGHT_GUI;
default:
if ( (key >= 59) && (key <= 68) ) {
return KEY_F1 + (key - 59);
}
else if ( (key >= 2) && (key <= 10) ) {
return 49 + (key - 2);
}
else if (key == 11) {
return '0';
}
else if ( (key >= 16) && (key <= 27) ) {
return rq[key - 16];
}
else if ( (key >= 30) && (key <= 41) ) {
return ra[key - 30];
}
else if ( (key >= 44) && (key <= 53) ) {
return rz[key - 44];
}
else {
return 0;
}
}
}
void translate() {
TembooChoreo TranslateChoreo;
digitalWrite(LED_R, LOW);
digitalWrite(LED_G, HIGH);
digitalWrite(LED_B, LOW);
TranslateChoreo.begin();
TranslateChoreo.setAccountName(TEMBOO_ACCOUNT);
TranslateChoreo.setAppKeyName(TEMBOO_APP_KEY_NAME);
TranslateChoreo.setAppKey(TEMBOO_APP_KEY);
TranslateChoreo.setChoreo(F("/Library/Microsoft/Translator/Translate"));
TranslateChoreo.addInput(F("ClientID"), MICROSOFT_ID);
TranslateChoreo.addInput(F("ClientSecret"), MICROSOFT_KEY);
TranslateChoreo.addInput(F("To"), language);
TranslateChoreo.addInput(F("From"), F("en"));
TranslateChoreo.addInput(F("Text"), buffer);
TranslateChoreo.run();
String response;
int rLen = 0;
while (TranslateChoreo.available()) {
response.concat((char)TranslateChoreo.read());
rLen++;
if (rLen == 14) {
if (response == F("TranslatedText")) {
TranslateChoreo.read();
TranslateChoreo.read();
while (TranslateChoreo.available()) {
uint8_t c = TranslateChoreo.read();
if ( (c == '\n') || (c == '\r') ) {
break;
}
else {
if ( (c >= 0xC2) && (c < 0xE0) ) {
writeUTF8PointCode(c, TranslateChoreo.read(), 0);
}
else if ( ( c >= 0xE0) && (c < 0xF0) ) {
uint8_t c2 = TranslateChoreo.read();
uint8_t c3 = TranslateChoreo.read();
writeUTF8PointCode(c, c2, c3);
}
else {
Keyboard.write((char)c);
}
}
}
break;
}
response = response.substring(1);
rLen = 13;
}
}
TranslateChoreo.close();
}
#ifdef WINDOWS
Keyboard.press(KEY_LEFT_ALT);
Keyboard.press(223);
Keyboard.release(223)
#endif
#ifdef LINUX
Keyboard.press(KEY_LEFT_CTRL);
Keyboard.press(KEY_LEFT_SHIFT);
Keyboard.write('u');
#endif
#ifdef MAC_OS_X
Keyboard.press(KEY_LEFT_ALT);
#endif
cnv.begin("/mnt/sda1/P6/utf8.py");
cnv.addParameter(String(c1));
cnv.addParameter(String(c2));
if ( (c1 >= 0xE0) && (c1 < 0xF0) ) {
cnv.addParameter(String(c3));
}
cnv.run();
for (int i=0; i<4; i++) {
while (!cnv.available());
Keyboard.write(cnv.read());
}
cnv.close();
#ifdef WINDOWS
Keyboard.release(KEY_LEFT_ALT);
#endif
#ifdef LINUX
Keyboard.release(KEY_LEFT_SHIFT);
Keyboard.release(KEY_LEFT_CTRL);
#endif
#ifdef MAC_OS_X
Keyboard.release(KEY_LEFT_ALT);
#endif
}
void setup() {
pinMode(LED_R, OUTPUT);
pinMode(LED_G, OUTPUT);
pinMode(LED_B, OUTPUT);
digitalWrite(LED_R, LOW);
digitalWrite(LED_B, LOW);
digitalWrite(LED_G, LOW);
pinMode(TRANS_OFF, INPUT);
digitalWrite(TRANS_OFF, HIGH);
pinMode(TRANS_1, INPUT);
digitalWrite(TRANS_1, HIGH);
pinMode(TRANS_2, INPUT);
digitalWrite(TRANS_2, HIGH);
pinMode(TRANS_3, INPUT);
digitalWrite(TRANS_3, HIGH);
pinMode(BTN_1, INPUT);
digitalWrite(BTN_1, HIGH);
Bridge.begin();
p.runShellCommand(F("killall kbd.py"));
p.runShellCommandAsynchronously(F("/mnt/sda1/P6/kbd.py"));
Console.begin();
while (!Console) {
digitalWrite(LED_R, HIGH);
delay(200);
digitalWrite(LED_R, LOW);
delay(200);
}
}
void loop() {
checkSwitches();
if (Console.available() >= sizeof(event)) {
Console.readBytes((char *)&event, sizeof(event));
if (event.type == EV_KEY) {
uint8_t k = convertKey(event.code);
if ( (event.value == EV_PRESSED) && (k != 0) ) {
if (language != "") {
if (k == KEY_RETURN) {
if (buffer_len > 0) {
for (int i=0; i<buffer_len; i++) {
Keyboard.write(KEY_BACKSPACE);
}
buffer[buffer_len] = 0;
translate();
buffer_len = 0;
}
}
else if ( (k == KEY_LEFT_CTRL) || (k == KEY_RIGHT_CTRL) || (k == KEY_LEFT_GUI) ) {
ctrl_held = 1;
}
}
Keyboard.press(k);
if (language != "") {
if (!ctrl_held) {
if ( (k >= 32) && (k <= 126) ) {
Keyboard.release(k);
if (buffer_len <= 510) {
buffer[buffer_len] = k;
buffer_len++;
}
}
else if (k == KEY_BACKSPACE) {
buffer[buffer_len] = 0;
buffer_len--;
if (buffer_len < 0) {
buffer_len = 0;
}
}
}
}
}
else if ( (event.value == EV_RELEASED) && (k != 0) ) {
if ( (language != "") &&
( (k == KEY_LEFT_CTRL) || (k == KEY_RIGHT_CTRL) || (k == KEY_LEFT_GUI) )
) {
ctrl_held = 0;
}
Keyboard.release(k);
}
}
}
}
Source Code Python
There are two Python scripts in this project. Store both of them on the microSD card. For
example, in /mnt/sda1/P6.
kbd.py
#!/usr/bin/python
import socket, struct
from evdev import InputDevice
dev = InputDevice("/dev/input/event1")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("localhost", 6571))
utf8.py
#!/usr/bin/python
import sys
args = []
for a in range(1, len(sys.argv)):
args.append(int(sys.argv[a]))
try:
print format(ord(v), '04x')
except:
print "0000"
1
W ith the default settings, the input source selector in Mac OS X shows the national flag of the current input language.
Project 7 Controlling your Arduino
Projects with Voice Commands
Voice recognition and voice control applications are increasing in popularity. Features
like Apples Siri, M icrosofts Cortana, and OK Google are changing the way people
interact with their phones, games consoles, and multimedia devices.
Where a full set of buttons and switches is not practical (or not desired), voice control
often provides a solution to the problem of making a user interface. In some cases,
voice control is a significant benefit to people who are unable to use regular user
interfaces.
This project demonstrates the steps for adding voice control to your Arduino Yn
projects. It comprises an Arduino sketch that responds to a button and handles the
digital pins, and a Python script that: records speech from a microphone; sends the
audio to a web service that translates the voice into text; parses the text to decide what
to do; and sends commands to the Arduino sketch.
The voice control aspect of this project has the nickname Yuri.
This project uses the audio interface from Project 3 M aking an M P3 Jukebox. If you do
not have a USB audio interface that has a 3.5 mm microphone socket, you may need to
buy a different interface. You can usually use small USB audio interface, such as the one
shown in Figure 21.
Figure 12. A basic USB audio interface
In This Chapter
You may wish to add a light-emitting diode (LED) to the circuit. If you want to use an
RGB LED, you can refer to the information in Project 6 M aking a Translating Keyboard.
For example, Building the Circuit and Finishing the Sketch includes diagrams and code for
controlling an RGB LED.
To begin:
The 10 k resistor protects your Arduino from accidental damage. You can connect the
button directly to the Arduinos GND pin. But if you do this and accidently set digital pin 8
to a HIGH output, then pressing the button creates a short circuit that could damage the
Arduino.
In the Arduino sketch, you enable the Arduinos internal pull-up resistors on digital pin 8.
This means that reading from the pin with digitalRead() returns LOW when you press the
button, and HIGH at all other times.
To work with the USB audio interface, you need to install additional files if you did not
complete Project 3:
1. Login to the OpenWrt-Yun command line.
2. Type the following command and then press Enter:
opkg update
This project uses a program called madplay to play sound effects. To download and
install madplay,
To begin writing the Python script that performs most of the work in this project,
4. Press Ctrl + X.
5. Press Y and then press Enter.
6. Type the following command and then press Enter:
wget http://www.arduinomeetslinux.com/download/yuri-off.mp3
9. To test the script, type the following command and then press Enter:
./yuri.py
You need to specify the full file path and file names of the M P3s in the Python script.
When you run the script from the Arduino sketch, the current working directory is not
your project folder.
And then in your sketchs setup() function, add the following code to configure the digital
pin for the button and to make a connection to the Bridge library:
pinMode(BTN_1, INPUT);
digitalWrite(BTN_1, HIGH);
Bridge.begin();
Upload this sketch to your Arduino and then hold down the button for about 1 second.
The Arduino starts the Python script and plays the sound files.
Tip: The runShellCommand() method waits for the Python script to finish. For
more information about starting Linux commands and Python scripts from an
Arduino sketch, see .
Recording from a Microphone
For this project, you record sound from the microphone to a wave file (.wav). However,
you also need to analyze the data as you fetch it from the microphone to determine
when the user stops speaking.
Python has a module called PyAudio, which contains classes and methods that you
can use for controlling audio devices. PyAudio relies on a library called PortAudio. This
library is not available on the Arduino Yn OPKG repository.
You can download a version of PyAudio that does not rely on an external PortAudio
library from this books companion website.
1. At the command prompt, type the following command and then press Enter:
wget http://www.arduinomeetslinux.com/download/pyaudio_0.2.8_ar71xx.ipk
In the next section of this chapter, you will see how to send this audio information to a
web service. The web service that you use in this project to convert speech to text,
supports the following audio formats:
Open the yuri.py script and add the following import directives:
import pyaudio
import wave
To read audio data from the microphone, you need to open the device and specify the
format in which you want to receive sound.
The open() method opens the input stream of the audio device. Its parameters are
described in the table below:
Parameter Description
A mono sound file has one channel. A stereo signal has two
channels
channels.
Reading from the microphone takes time. To help you make high-
quality recordings, audio interfaces and software libraries use
frames_per_buffer buffers. These buffers hold multiple frames (or samples) and only
return data to your Python program when the buffer is full. This is
much faster than trying to read each sample yourself.
The frames list holds the sound data from the microphone. To fill this list, a for loop
reads from the microphone a set number of times. If the sample rate is 8 kHz, the loop
needs to read 8000 samples for each second of audio. The number of times the loop
runs is the sample rate divided by the buffer size (since the buffer holds multiple
samples), and then multiplied by the number of seconds of audio that you want to
record.
To read from the microphone, the code uses the read() method and passes in the size
of the buffer. It then adds the data to the frames list.
This code closes the microphone input and the audio interface.
The wave module contains classes and methods for reading and writing .wav files. After
audio.terminate(), add the following lines:
wf = wave.open("/mnt/sda1/P7/data.wav", 'wb')
wf.setnchannels(1)
wf.setsampwidth(audio.get_sample_size(pyaudio.paInt16))
wf.setframerate(8000)
wf.writeframes(b''.join(frames))
wf.close()
This code open a new .wav file and then sets the format to match the format of the audio
data from the microphone. The writeframes() method stores data in the file, and the call
to join() is just to convert the frames list into a binary array.
If you want to play the data.wav file, you need to install aplay from the alsa-utils package.
If aplay is noisy or crackles, run the command again. aplays limitations and bugs are not
a problem in the next stages of this project.
5 seconds is not long enough to contain enough words for this project. To extend the
script to record up to 1 minute of audio,
Replace the line that reads for i in range(0, int(8000 / 1024 * 5)): with the following
code:
for i in range(0, int(8000 / 1024 * 60)):
There are many ways to detect when a user stops speaking. This project uses a very
basic method. This technique is not perfect, but it is a starting point from which you can
develop more effective ways of detecting the end of the users speech.
The approach works by checking the volume level of the sound samples. If the volume
level falls below a set level, there is silence and the script should break out of the loop.
The Python module audioop contains methods that you can use to perform calculations
on sound samples. Add it to the script:
import audioop
A deque collection is a type of list. When it reaches full capacity, if you add a new item to
the end of the list then it removes the first item to make space. You can use this
structure to store information about the last few seconds (or milliseconds) of audio from
the microphone.
Below the line that reads frames = [], add the following code:
q = deque(maxlen=int(8000 / 1024))
This creates a deque that holds enough data for the last 1 second of audio.
Then, underneath the line that reads frames.append(data), add the following code and
indent it:
q.append(audioop.rms(data, 2))
if i > int(8000 / 1024 * 2):
if sum([x > threshold for x in q]) <= 0:
break
The append() method of a deque collection adds an item to the list (and removes the
first item, if needed). In this example, you add the result of a call to audioop.rms() to the
collection.
The rms() method calculates the root mean square (RM S) of the audio fragment. When
working with audio and speakers, RM S is a measure of the signals overall strength or
volume. The second argument, 2, specifies that each sample in data is 2-bytes long (a
16-bit number).
You can find the full documentation for the audioop module at
http://docs.python.org/2/library/audioop.html
The code adds together every value in q that is greater than threshold. If the result is
less than or equal to zero, then the script detects the silence and breaks out of the loop.
This is one way of checking that every value in the last second of audio is above the
threshold.
Tip: To prevent the script detecting silence when it first starts, the code checks
that it records at least 2 seconds of audio before checking for silence.
Your Python script now records from the microphone until you stop speaking, or until the
.wav file is 60-seconds long.
Translating Speech to Text
To translate the audio information into a string of text, you can use a web service from
AT&T. The AT&T Speech API includes methods for converting voice recordings into text
strings, and also methods for converting a text string into a voice recording.
The AT&T APIs are not free. However, you can build this project with Sandbox Access
and not pay anything. This plan allows you to build a device for your own testing, but
you may not use for devices that are in production.
To sign up for the AT&T developer program and use the Speech API:
Before your script can send audio data to the API and receive a text response, the
script needs to login to the AT&T API and collect an access token. The script then
sends this token with all other calls to the API.
OAuth access tokens expire, and so this script fetches a new one every time that it
runs.
To communicate with the AT&T server, your script needs to use secure sockets layer
(SSL). To do this, install the Python SSL module.
1. At the OpenWrt-Yun command prompt, type the following command and then press
Enter:
opkg update
The AT&T Speech API returns information in JavaScript object notation (JSON) format.
JSON is a technique for structuring your data into collections and objects and sending
this information over the Internet. The Python json module contains methods for
converting a JSON object into a Python object.
The urllib2 module contains methods for fetching information from the web over
hypertext transfer protocol (HTTP). However, there are a few useful methods in an older
version of urllib, so you need to import that one too.
In the APP_KEY string, type (or paste) in your App Key from the AT&T website. In the
APP_SECRET string, type (or paste) in your App Secret from the website.
To get an access token from the AT&T Speech API, you need to send your App Key
and App Secret to the API method /oauth/v4/token.
To use urllib2, you can create a Request object. This way, you can set all of the options
and additional HTTP header information before you call urlopen() to send the request to
the web server.
M ost of the options for this are set by AT&T. The add_header() method adds an HTTP
header with the specified name and value. In the next section, you can see an
alternative way of adding some HTTP headers to your Request object.
You must convert the params dictionary into a format that you can send over HTTP.
The urlencode() method converts a Python object to a percent-encoded string.
When you send information over local networks and the Internet, there are many
reasons why the request can fail. If one of these errors occurs, urlopen() throws an error
message and this ends the Python script.
To stop the script from ending, you can use try and except statements. If the code
indented underneath try generates an error, then the Python interpreter runs the code
indented underneath except. If there is no error, the except code does not run.
In this project, if you cannot fetch the access token then the script plays a sound file and
then does nothing until the script ends.
To convert the JSON string from the AT&T API into a dictionary, use the method
json.loads(). The result is an dictionary that usually has the following items:
Item Description
An access code that you can use to call methods from the AT&T
access_token
APIs.
Specifies an additional access code that you can use to refresh the
refresh_token
access_token. This project does not reuse access tokens.
You can access these items as you do with any Python dictionary. For example, to print
the access_token:
print token["access_token"]
On an un-indented line underneath the pass statement, add the following code:
if "access_token" in token:
print token["access_token"]
os.system("madplay /mnt/sda1/P7/yuri-ok.mp3")
If the previous code fails to retrieve an access token from the AT&T API then token is
empty and does not have an item named access_token. If it does have an item named
access_token, the script plays another M P3 to signal to the user that it is now
processing the audio.
Before testing the script, you may want to add the following line to the end of the file:
os.remove("/mnt/sda1/P7/data.wav")
When the script ends, this deletes the audio file that you recorded.
audio = pyaudio.PyAudio()
os.system("madplay /mnt/sda1/P7/yuri-on.mp3")
mic.stop_stream()
mic.close()
audio.terminate()
wf = wave.open("/mnt/sda1/P7/data.wav", 'wb')
wf.setnchannels(1)
wf.setsampwidth(audio.get_sample_size(pyaudio.paInt16))
wf.setframerate(8000)
wf.writeframes(b''.join(frames))
wf.close()
token = {}
params = {"client_id" : APP_KEY,
"client_secret" : APP_SECRET,
"grant_type" : "client_credentials",
"scope" : "SPEECH"}
tReq = urllib2.Request("https://api.att.com/oauth/v4/token", urllib.urlencode(params))
tReq.add_header("Accept", "application/json")
tReq.add_header("Content-Type", "application/x-www-form-urlencoded")
try:
response = urllib2.urlopen(tReq)
token = json.loads(response.read())
except:
os.system("madplay /mnt/sda1/P7/yuri-off.mp3")
pass
if "access_token" in token:
print token["access_token"]
os.system("madplay /mnt/sda1/P7/yuri-ok.mp3")
os.remove("/mnt/sda1/P7/data.wav")
Test the script to make sure that it receives and prints an access token, and that it plays
the appropriate sound files.
To send the audio information to the AT&T Speech API, you need to:
In this code, you first define the HTTP headers in a Python dictionary. Then you read
the entire contents of the .wav file into a variable. When you create the Request object,
you can pass in both of these Python variables in and set up the request with one call.
Save the script and run it from the OpenWrt-Yun command line:
./yuri.py
Speak into the microphone and then stop. After a short delay, you should see the
response text.
Replace the line print response.read() with:
vData = json.loads(response.read())
This converts the JSON response into a Python dictionary. The only item is
Recognition. This too is a Python dictionary and there are lot of values in the
Recognition dictionary. The table below shows the first level of objects inside the
Recognition dictionary. Some of these items contain another dictionary or list.
Item Description
Contains information about the APIs best guess at the words that are
NBest
spoken in the audio file.
You can access these items using the syntax shown below:
print vData["Recognition"]["Status"]
For information about all of the values and other dictionaries in a Recognition dictionary,
see the AT&T Speech API documentation at
http://developer.att.com/apis/speech/docs#resources-speech-to-text
To extract the text version of the audio file, you need to process the NBest dictionary.
Because of the format of the JSON response, NBest is a list that contains only one item
another dictionary. This contains:
Item Description
A string that indicates how confident the AT&T API is that it accurately
translated the speech to text. Can be: accept the Hypothesis value is
Grade probably ok; confirm the Hypothesis value may not be accurate; reject
the Hypothesis value is not accurate. This project ignores the Grade
value.
The ISO 639 language code and ISO 3166 country code that the API
LanguageId used to translate the spoken words to text.
A list of values that indicates how confident the AT&T API is in the
WordScores
accuracy of each word.
Tip: While you write the code in the next section of this chapter, it can be useful
to add print vData["Recognition"]["NBest"][0]["ResultText"] to your script. Then
you can run the yuri.py script from the command line and see what the AT&T
Speech API thinks that you say.
When the script receives one of these voice commands, it sends a message to the
ATmega32u4 through the M ailbox. For example, when it receives turn on thirteen then
it sends the M ailbox message n13. When it receives turn off three then it sends the
M ailbox message f3.
Depending on your accent, the basic Speech API is often unable to work out the
difference between words like eight and ate, or two and too. To implement voice
control, you may have to convert common inaccuracies.
Tip: To improve the quality of the Speech APIs transcription, you can use the
Speech To Text Custom methods of the API. Because of its complexity, this is not
covered in this project. The Custom methods accept a file that describes grammar
suggestions and other parameters that can resolve problems identifying the
differences between words.
Add the following dictionary to the yuri.py script, underneath the APP_SECRET.
pins = {"one" : "1",
"two" : "2",
"to" : "2",
"too" : "2",
"three" : "3",
"four" : "4",
"for" : "4",
"five" : "5",
"six" : "6",
"sex" : "6",
"seven" : "7",
"eight" : "8",
"ate" : "8",
"nine" : "9",
"ten" : "10",
"tan" : "10",
"teen" : "10",
"eleven" : "11",
"twelve" : "12",
"thirteen" : "13"}
To use the M ailbox from Python, you need to import the BridgeClient module into your
script. Underneath the line that reads import wave, add the following code:
import sys
sys.path.insert(0, "/usr/lib/python2.7/bridge/")
from bridgeclient import BridgeClient
For more information about exchanging information between the Atheros AR9331 and
the ATmega32u4 using the M ailbox, see Using M ailbox and Sending M essages.
Underneath the line that reads vData = json.loads(response.read()), add the following
code:
if vData["Recognition"]["Status"] == "OK":
processCommand(vData["Recognition"]["NBest"][0]["Hypothesis"].split(' '))
else:
os.system("madplay /mnt/sda1/P7/yuri-off.mp3")
M ake sure you indent this code so that it is inside the try block.
Although Words is a list of the words in the response from the Speech API, it may also
include punctuation marks and capital letters that you do not want for this project.
Instead, the code splits Hypothesis into a list so that each word after a space is a new
entry.
To make the voice control work, your Arduino sketch needs to read the messages from
the M ailbox, and then change a digital pin depending on the content of the message.
In the Arduino IDE, underneath the line that reads #include <Bridge.h>, add:
#include <Mailbox.h>
In your sketchs loop() function, add the following code to the end of the loop:
if (Mailbox.messageAvailable()) {
String message;
Mailbox.readMessage(message);
if (message.length() >= 2) {
int pin = atoi(&message[1]);
pinMode(pin, OUTPUT);
if (message[0] == 'n') {
digitalWrite(pin, HIGH);
}
else if (message[0] == 'f') {
digitalWrite(pin, LOW);
}
}
}
Tip: atoi() is a C function that converts a string to an integer. The string must
contain a number represented by ASCII characters. For example, 123. Because
the first character in message is either n or f, you pass in the memory address
of the second character in message, so that atoi() does not see the first character.
Upload the sketch to your Arduino and then press (and hold) the button. After the sound
effect, say turn on thirteen. If the Speech API and Python script file recognize your
command, the sketch turns on the Arduino Yns built-in LED. If the Speech API and
Python script do not recognize your command, the script plays another sound.
To extend this project, you have to write code into yuri.py to understand new commands
and pass these to the ATmega32u4. Then modify your sketch to respond to the new
commands.
Translating Text to Speech
Because the project only supports turning Arduino digital pins on or off, the results are
easy to see. For more complicated voice control, it can be useful to make the script
speak back to the user.
The AT&T Speech API has a method for doing this. However, you can also do this
using a Linux program that performs the translation without sending information across
the Internet. The program that you need to install is called espeak.
If you have not installed the GCC compiler, you need to do so now.
1. At the OpenWrt-Yun command prompt, type the following command and then press
Enter:
opkg update
To install espeak,
The espeak program accepts command-line parameters that you can use to change how
the program speaks your text. The following table shows a few of the most common
options that you may need to use.
Parameter Description
a Sets the volume. This is a number in the range 0 through 200. The
default setting is 100.
Sets the pitch. This is a number in the range 0 through 99. The default
p
setting is 50.
Sets the speed in words per minute. The default setting is 175. There is
s
no upper limit, but the lowest setting that you can use is 80.
To set an option: put a dash before the parameter name, then a space, and then type
the value for the option. To set more than option, separate each with a space. For
example:
espeak -a 50 -v en -w /mnt/sda1/P7/test.wav "Hello"
There are an additional seven male voice variants, and five female voice variants. To
use these, specify +m1, +m2, +m3, +m4, +m5, +m6, +m7, +f1, +f2, +f3, +f4, or +f5 after
the language.
espeak -v en-n+m3 "Hello"
Calling espeak directly can sometimes cause crackles and noise through the speakers.
To ensure reliable audio, this project tells espeak to save the speech to a .wav file, and
then the script plays the file through PyAudio.
In your Python script, above the line that declares APP_KEY, add the following Python
class:
class AudioPlay:
chunk = 1024
def play(self):
data = self.wf.readframes(self.chunk)
while data != '':
self.stream.write(data)
data = self.wf.readframes(self.chunk)
def close(self):
self.stream.close()
self.p.terminate()
You create an AudioPlay object by passing in the file name of the wave file that you
want to play. The play() method plays the audio through your speakers or headphones,
and the close() method closes the file and releases the audio device.
To run this code, press and hold the button on the circuit. Then, after the tone, either:
Stay silent; or
Say something that the processCommand() function does not understand.
Reading from Sensors and Speaking the Result
Using speech to tell the user the value of an input can be very useful, particularly in
projects that do not have a display. The process for doing this is:
The following example uses the Adafruit M CP9808 temperature sensor from Project 1
Building a Web-Based Temperature M onitor. If you have not completed that project, it is
recommended that you do so now.
In particular, make sure you install the SoftI2CMaster library in your Arduino IDE. Then
unplug the power from your Arduino and connect the Adafruit M CP9808 as shown in
Connecting the Adafruit M CP9808 Temperature Sensor to the Arduino.
When you speak the command tell me the temperature, the example responds.
3. In your sketchs setup() function, before the call to Bridge.begin(), add the following
code:
pinMode(A0, OUTPUT);
digitalWrite(A0, HIGH);
pinMode(A1, OUTPUT);
digitalWrite(A1, LOW);
4. Copy the getTemperature() function from Source Code Sketch, and then paste it
into your current sketch.
5. In your sketchs loop() function, add the following code to the end:
Bridge.put("MCP9808_Temperature", String(getTemperature()));
In the yuri.py script, find the processCommand() function. Then add the following code
after the second processed = True statement. Indent this code with four spaces, so that
it joins onto the first if statement in the function.
elif len(words) == 4:
if words[0] == "tell" and words[1] == "me" and words[2] == "the":
if words[3] == "temperature":
temp = (float(client.get("MCP9808_Temperature")) * 1.5) + 32
speech = "The temperature is {0:.1f} degrees fahrenheit.".format(temp)
os.system('espeak -w /mnt/sda1/P7/tmp.wav "' + speech + '"')
spk = AudioPlay("/mnt/sda1/P7/tmp.wav")
spk.play()
spk.close()
os.remove("/mnt/sda1/P7/tmp.wav")
processed = True
To extend this example so that the Python script speaks the time when you say tell me
the time, add the following code underneath the processed = True statement in the code
above. Indent this code with 12 spaces, so that it joins onto the if words[3] ==
"temperature" statement.
elif words[3] == "time":
speech = "The time is " + strftime("%I %M %p", gmtime())
os.system('espeak -w /mnt/sda1/P7/tmp.wav "' + speech + '"')
spk = AudioPlay("/mnt/sda1/P7/tmp.wav")
spk.play()
spk.close()
os.remove("/mnt/sda1/P7/tmp.wav")
processed = True
And add the following import statement to the list at the top of the script:
from time import gmtime, strftime
Save the file and then exit nano. You have reached the end of this project.
This project is only an example of how you can add voice control to your Arduino
projects, but the steps for building a more complicated system are the same. As your
code for handling the different voice commands grows larger, you may want to research
how compilers and interpreters work. The techniques involved in writing compilers can
help you process natural language.
Source Code Sketch
#include <Bridge.h>
#include <Mailbox.h>
#include <SoftI2CMaster.h>
#define BTN_1 8
Process p;
SoftI2CMaster i2c = SoftI2CMaster(A3, A2);
float getTemperature() {
i2c.beginTransmission(0x18);
i2c.send(0x05);
i2c.endTransmission();
i2c.requestFrom(0x18);
uint16_t v = i2c.receive();
v <<= 8;
v |= i2c.receive();
void setup() {
pinMode(BTN_1, INPUT);
digitalWrite(BTN_1, HIGH);
pinMode(A0, OUTPUT);
digitalWrite(A0, HIGH);
pinMode(A1, OUTPUT);
digitalWrite(A1, LOW);
Bridge.begin();
Mailbox.begin();
}
void loop() {
if (digitalRead(BTN_1) == LOW) {
delay(750);
if (digitalRead(BTN_1) == LOW) {
p.runShellCommand("/mnt/sda1/P7/yuri.py");
}
}
if (Mailbox.messageAvailable()) {
String message;
Mailbox.readMessage(message);
if (message.length() >= 2) {
int pin = atoi(&message[1]);
pinMode(pin, OUTPUT);
if (message[0] == 'n') {
digitalWrite(pin, HIGH);
}
else if (message[0] == 'f') {
digitalWrite(pin, LOW);
}
}
}
Bridge.put("MCP9808_Temperature", String(getTemperature()));
}
Source Code Python
#!/usr/bin/python
import audioop
import json
import os
import pyaudio
import urllib
import urllib2
import wave
from collections import deque
from time import gmtime, strftime
import sys
sys.path.insert(0, "/usr/lib/python2.7/bridge/")
from bridgeclient import BridgeClient
class AudioPlay:
chunk = 1024
def play(self):
data = self.wf.readframes(self.chunk)
while data != '':
self.stream.write(data)
data = self.wf.readframes(self.chunk)
def close(self):
self.stream.close()
self.p.terminate()
def processCommand(words):
processed = False
if len(words) == 3:
if words[0] == "turn":
if words[1] == "on":
client.mailbox("n" + pins[words[2]])
processed = True
elif words[1] == "off":
client.mailbox("f" + pins[words[2]])
processed = True
elif len(words) == 4:
if words[0] == "tell" and words[1] == "me" and words[2] == "the":
if words[3] == "temperature":
temp = (float(client.get("MCP9808_Temperature")) * 1.5) + 32
speech = "The temperature is {0:.1f} degrees fahrenheit.".format(temp)
os.system('espeak -w /mnt/sda1/P7/tmp.wav "' + speech + '"')
spk = AudioPlay("/mnt/sda1/P7/tmp.wav")
spk.play()
spk.close()
os.remove("/mnt/sda1/P7/tmp.wav")
processed = True
elif words[3] == "time":
speech = "The time is " + strftime("%I %M %p", gmtime())
os.system('espeak -w /mnt/sda1/P7/tmp.wav "' + speech + '"')
spk = AudioPlay("/mnt/sda1/P7/tmp.wav")
spk.play()
spk.close()
os.remove("/mnt/sda1/P7/tmp.wav")
processed = True
if processed == False:
os.system('espeak -w /mnt/sda1/P7/tmp.wav "Sorry. I do not understand."')
spk = AudioPlay("/mnt/sda1/P7/tmp.wav")
spk.play()
spk.close()
os.remove("/mnt/sda1/P7/tmp.wav")
os.system("madplay /mnt/sda1/P7/yuri-off.mp3")
client = BridgeClient()
audio = pyaudio.PyAudio()
os.system("madplay /mnt/sda1/P7/yuri-on.mp3")
mic.stop_stream()
mic.close()
audio.terminate()
wf = wave.open("/mnt/sda1/P7/data.wav", 'wb')
wf.setnchannels(1)
wf.setsampwidth(audio.get_sample_size(pyaudio.paInt16))
wf.setframerate(8000)
wf.writeframes(b''.join(frames))
wf.close()
token = {}
params = {"client_id" : APP_KEY,
"client_secret" : APP_SECRET,
"grant_type" : "client_credentials",
"scope" : "SPEECH"}
tReq = urllib2.Request("https://api.att.com/oauth/v4/token",
urllib.urlencode(params))
tReq.add_header("Accept", "application/json")
tReq.add_header("Content-Type", "application/x-www-form-urlencoded")
try:
response = urllib2.urlopen(tReq)
token = json.loads(response.read())
except:
os.system("madplay /mnt/sda1/P7/yuri-off.mp3")
pass
if "access_token" in token:
print token["access_token"]
os.system("madplay /mnt/sda1/P7/yuri-ok.mp3")
hdrs = {"Authorization" : "Bearer " + token["access_token"],
"Accept" : "application/json",
"Content-Type" : "audio/wav"}
f = open("/mnt/sda1/P7/data.wav")
wav = f.read()
f.close()
sReq = urllib2.Request("https://api.att.com/speech/v3/speechToText",
data=wav,
headers=hdrs)
try:
response = urllib2.urlopen(sReq)
vData = json.loads(response.read())
if vData["Recognition"]["Status"] == "OK":
processCommand(vData["Recognition"]["NBest"][0]["Hypothesis"].split(' '))
else:
os.system('espeak -w /mnt/sda1/P7/tmp.wav "Sorry. I do not understand."')
spk = AudioPlay("/mnt/sda1/P7/tmp.wav")
spk.play()
spk.close()
os.remove("/mnt/sda1/P7/tmp.wav")
os.system("madplay /mnt/sda1/P7/yuri-off.mp3")
except:
os.system("madplay /mnt/sda1/P7/yuri-off.mp3")
os.remove("/mnt/sda1/P7/data.wav")
1
Or tactile switch.
2
For more information about the languages that espeak supports, see http://espeak.sourceforge.net/languages.html
Other Books by Bob Hammell
The Arduino Ethernet Shield is a powerful device for connecting Arduinos to local area
networks and to the Internet. But despite its popularity, few authors have attempted to
explain how to use this shield to its full potential leaving new users and less-
experienced programmers to piece together fragments of information.
In Connecting Arduino: Programming and Networking with the Ethernet Shield, Bob
Hammell guides the reader through the processes and key concepts involved in writing
projects that use the Ethernet Shield. M ore than just a recipe book, this in-depth series
of tutorials explores all aspects of the Ethernet library, and discusses how to work with
Internet protocols such as HTTP and DNS. You dont need a computer science degree
to understand it, only a basic knowledge of how to write Arduino sketches.