Scripting languages
No doubt, embedded developers must know the C language; however, there exist several tasks that can be resolved using a scripting language. In fact, a scripting language such as PHP or Python, or even the Bash language, can be used to implement a task to manage a computer peripheral. This is because these languages have several extensions to do it and because the kernel itself offers the possibility to manage its peripherals using common files (refer to the everything-is-a-file abstraction presented in Chapter 3 , C Compiler, Device Drivers, and Useful Developing Techniques, in What is a Device Driver? section and the following sections).
In the next sections, we're going to show a simple example on how we can manage a peripheral using a scripting language exclusively. For the moment, we have to keep our example simple because we haven't presented any peripheral in detail yet; however, in the next chapters, we're going to discover several peripheral kinds in detail, and at that time, we're going to use more complex examples on this topic. Right now, we'll use a simple GPIO line, since even if not explained in detail yet, this is the only peripheral we know how to manage a bit.
For this demonstration, we'll use the BeagleBone Black and a simple LED connected to the expansion connector. The following is the schematic of the connections:
Note
The LED anode must be connected to pin 7 of connector P8 (P8.7) and the cathode with the GND or ground (pin 1 or 2 of the same connector). Let's also remember that the flat spot on the LED is the cathode while the rounded one is the anode. A careful reader with minimum electronic basics will notice that in the preceding schematic, we did not put any resistance in series with the LED to limit the output current from the GPIO pin. Even if it should be always done to avoid damages, it has been done to keep the connection very simple.
Then, to turn the LED on and off, we need to export the corresponding GPIO line, which is gpio66
, so the commands are as follows:
root@bbb:~# echo 66 > /sys/class/gpio/export root@bbb:~# echo out > /sys/class/gpio/gpio66/direction
Now to turn the LED on and off, we simply need to write 1
or 0
in the /sys/class/gpio/gpio66/value
file, as follows:
root@bbb:~# echo 1 > /sys/class/gpio/gpio66/value root@bbb:~# echo 0 > /sys/class/gpio/gpio66/value
OK, now supposing that the GPIO is already exported, let's start to see how we can implement the task to control an LED via the web browser using a scripting language only.
Managing a LED in PHP
Now it's time to learn how to manage our LED using the PHP language. There are two different possibilities to do that: the first one is to use the LAMP (Linux - Apache - MySQL - PHP) system, while the second one is to use the PHP built-in web server.
The LAMP solution
This is the easiest and the most classic way to implement a common web application; we just need a PHP script where we can implement our LED management. So let's start with writing some code!
As a first step, we must create a file in the /var/www/html
directory of the BeagleBone Black named turn.php
by copying the chapter_04/webled/php/turn.php
file in the book's example code repository:
<?php # 1st part - Global defines & functions define("value_f", "/sys/class/gpio/gpio66/value"); function pr_str($val) { echo $val ? "on" : "off"; } # 2nd part - Set the new led status as requested if (isset($_GET["led"])) { $led_new_status = $_GET["led"]; file_put_contents(value_f, $led_new_status); } # 3rd part - Get the current led status $led_status = intval(file_get_contents(value_f)); # 4th part - Logic to change the led status on the next call $led_new_status = 1 - $led_status; # 5th part - Render the led status by HTML code ?> <html> <head> <title>Turning a led on/off using PHP</title> </head> <body> <h1>Turning a led on/off using PHP</h1> Current led status is: <? pr_str($led_status) ?> <p> Press the button to turn the led <? pr_str($led_new_status) ?> <p> <form method="get" action="/turn.php"> <button type="submit" value="<? echo $led_new_status ?>" name="led">Turn <? pr_str($led_new_status) ?></button> </form> </body> </html>
Note
This code does not export the gpio66
directory, so it must be exported as shown in the previous section before running the script! All the next examples will assume that gpio66
is already exported.
The functioning is quite simple; the first part of the code reads the LED status and stores it in the led_status
variable, while the second part is an HTML code with mixed PHP code required to simply report the LED status by echoing the led_status
variable. Note that we use a dedicated function to convert a number into the on
or off
string to display the LED status, while in the second part, we use an HTML form to retrieve the user request, that is, whether we must turn the LED on or off and then execute it.
Note that the user request is done with an HTTP GET request in the http://192.168.7.2/turn.php?led=1
form. The led=1
string means that we ask to turn on the LED, so the code will get this value, and using the PHP file_put_contents()
function, set the LED on by writing 1
in the /sys/class/gpio/gpio66/value
file.
The third part reads the GPIO status by simply reading the content of the /sys/class/gpio/gpio66/value
file (because everything-is-a-file!), while the fourth one just toggles the LED status from value 0
to 1
or vice versa. The fifth part is the HTML page that the server will return to the user with the current LED status and the needed button to toggle it. The next figure shows the resulting output in the browser:
Tip
It may happen that the code cannot change the LED status; in this case, we should check the /var/log/apache2/error.log
file where the Apache web server logs possible errors.
If we see an error message, as reported here, the problem is due to a file permission issue:
[Sat Apr 02 19:25:07.556803 2016] [:error] [pid 825 ] [client 192.168.7.1:59242] PHP Warning: file_put_contents(/sys/class/gpio/gpio66/value): fa iled to open stream: Permission denied in /var/www/ html/turn.php on line 13
So, let's check the file permission for the /sys/class/gpio/gpio66/value
file:
root@bbb:~# ls -l /sys/class/gpio/gpio66/value -rw-r--r-- 1 root root 4096 Apr 2 18:54 /sys/class /gpio/gpio66/value
Only the root user has the right privileges to write in the file, so a possible workaround could be as follows:
root@bbb:~# chown :www-data /sys/class/gpio/gpio66/ value root@bbb:~# chmod g+rw /sys/class/gpio/gpio66/value root@bbb:~# ls -l /sys/class/gpio/gpio66/value -rw-rw-r-- 1 root www-data 4096 Apr 2 18:54 /sys/class/gpio/gpio66/value
This is because the Apache web server runs with the same privileges of the www-data
user and the www-data
group, but after the preceding changes, our script should work as expected.
The built-in server solution
The PHP built-in web server can be executed with the following command line:
root@bbb:~# php -S 192.168.7.2:8080 -t /var/www/html/ PHP 5.6.19-0+deb8u1 Development Server started at Sat Apr 2 19:36:01 2016 Listening on http://192.168.7.2:8080 Document root is /var/www/html Press Ctrl-C to quit.
You should notice that we used the 192.168.7.2:8080
listening address, so this time, the web address to be used is http://192.168.7.2:8080/turn.php
; otherwise, we will get connected with the Apache server again!
If we wish to avoid specifying port 8080
, we should stop the Apache web server as follows:
root@bbb:~# /etc/init.d/apache2 stop [ ok ] Stopping apache2 (via systemctl): apache2.service.
And then, we re-run the PHP built-in web server with the following command:
root@bbb:~# php -S 192.168.7.2:80 -t /var/www/html/ PHP 5.6.19-0+deb8u1 Development Server started at Sat Apr 2 19:37:44 2016 Listening on http://192.168.7.2:80 Document root is /var/www/html Press Ctrl-C to quit.
Now we can execute our script as earlier. Note that the server will log each browser request on the terminal where it's running:
[Sat Apr 2 19:38:17 2016] 192.168.7.1:59462 [200]: /turn.php [Sat Apr 2 19:38:21 2016] 192.168.7.1:59464 [200]: /turn.php?led=0 [Sat Apr 2 19:38:21 2016] 192.168.7.1:59466 [200]: /turn.php?led=1
Note
As reported in the PHP built-in web server manual at http://php.net/manual/en/features.commandline.webserver.php , this tool should be used for testing purposes or for application demonstrations that are run in controlled environments only!
Managing a LED in Python
Now let's try to manage our LED using a Python script. There are several possibilities to get a running web server with Python, but the easiest one is definitely the BaseHTTPServer
library. A simple usage of the library is reported in the chapter_04/webled/python/httpd_show_info.py
demo script in the book's example code repository, where we show how the server handler processes incoming requests by showing all the fields available at the disposal of the programmer.
In the first part, there is a definition of the server listening address, while the second part defines the GET requests handler, that is, the function to be called each time the browser performs an HTTP GET request.
The third and fourth parts are the most important ones since they implement the web data parsing. Here, we can see how the web requests are managed and how we can use them to do our job! The fourth part simply takes the answering message built by the third part and then sends it back to the browser. Here is a snippet of the relevant function:
def do_GET(self): parsed_path = urlparse.urlparse(self.path) # 3rd part - Build the answering message message_parts = [ 'CLIENT VALUES', 'client_address -> %s (%s)' % (self.client_address, self.address_string()), 'command -> %s' % self.command, 'path -> %s' % self.path, 'real path -> t%s' % parsed_path.path, 'query -> %s' % parsed_path.query, 'request_version -> %s' % self.request_version, '', 'SERVER VALUES', 'server_version -> %s' % self.server_version, 'sys_version -> %s' % self.sys_version, 'protocol_version -> %s' % self.protocol_version, '', 'HEADERS RECEIVED', ] for name, value in sorted(self.headers.items()): message_parts.append('%s -> %s' % (name, value.rstrip())) message_parts.append('') message = '\r\n'.join(message_parts) # 4th part - Send the answer self.send_response(200) self.end_headers() self.wfile.write(message) return
The last part is executed at the beginning and it sets up the server by creating a new server object by calling the HTTPServer()
function and then runs it by calling the serve_forever()
method.
To test the code, we can use the following command:
root@bbb:~# python httpd_show_info.py Starting server at 192.168.7.2:8080, use <Ctrl-C> to stop
If everything works well, we'll see the server running by pointing the browser at the http://192.168.7.2:8080/?led=1
address.
The output in the browser should be something similar to what's shown in the following figure:
As we can see, there are tons of available data; however to manage our LED, we can just use the query
variable, which is where the server stores the HTTP GET request data. So, a possible implementation of our LED management script in Python is reported in the chapter_04/webled/python/httpd.py
file in the book's example code repository.
This time, the code is really more complex than earlier. First of all, we should note that in the first part of this new code, we've defined two functions, named put_data()
and get_data()
. These are used to put/get the gpio66
status. Here is the snippet with these two functions:
def put_data(file, data): f = open(file, "w") f.write(data) f.close() def get_data(file): f = open(file, "r") data = f.read() f.close() return data
The second part is not changed, while the third one has now been changed in order to retrieve the HTTP GET query and set up the new gpio66
status accordingly. Parts four and five are very similar to the respective ones in PHP and the same is for the sixth one too, even if its layout is a bit different (it defines the HTML code to be returned to the browser). Part seven is the same as earlier, while part eight implements the server definition and initialization.
If we execute this new script as done before, we should get the same output as the one we got with the PHP version of this script.
Note
The documentation of the BaseHTTPServer
library is at: https://wiki.python.org/moin/BaseHttpServer .
Managing a LED in Bash
Both Python and PHP are very powerful languages, and they can be used to solve a lot of complex problems; however, it may happen that the embedded system lacks both! In this case, we can use the C language, or if we like scripting, we can try to use Bash. In fact, even if the Bash scripting language is commonly used to solve system administrator tasks, it can also be used to resolve several issues with some tricks! So let's look at how we can use it in our web LED management problem.
By default, Bash has no networking features; however, as a workaround, we can use the xinetd
daemon presented earlier in this chapter. The trick was in correctly setting up the xinetd
configuration file in order to address our web LED management problem.
First of all, we should exactly know what the browser asks to a web server; to do that, we can use a modified version of our echo program, as reported here:
#!/bin/bash # Read the browser request read request # Now read the message header while /bin/true; do read header echo "$header" [ "$header" == $'\r' ] && break; done # And then produce an answer with a message dump echo -e "HTTP/1.1 200 OK\r" echo -e "Content-type: text/html\r" echo -e "\r" echo -e "request=$request\r" exit 0
Note
The code is held in the chapter_04/webled/bash/httpd_echo.sh
file in the book's example code repository.
This script is quite simple; it first reads the browser's request, and then it starts reading the message header, and when finished, it produces an answer with a message dump, so we can analyze it and understand what they say to each other.
Now we need a new xinetd configuration file to execute our Bash web server, as follows:
service http-alt { disable = no socket_type = stream protocol = tcp wait = no user = root server = /root/httpd.sh }
Note
The code is held in the chapter_04/webled/bash/httpd_sh
file in the book's example code repository.
Note also that the http-alt
service is defined as port 8080
in the /etc/services
file:
root@bbb:~# grep 8080 /etc/services http-alt 8080/tcp webcache # WWW caching service http-alt 8080/udp
Then, we have to copy the file into the xinetd configuration directory, as follows:
root@bbb:~# cp httpd_sh /etc/xinetd.d/
Now we have to put the program into the httpd_echo.sh
file in the /root/httpd.sh
file, which is executed by xinetd when a new connection arrives on port 8080
:
root@bbb:~# cp httpd_echo.sh /root/httpd.sh
To start our new web server, we have to restart the xinetd daemon:
root@bbb:~# /etc/init.d/xinetd restart Restarting xinetd (via systemctl): xinetd.service.
At this point, by pointing our web browser at the address http://192.168.7.2:8080/index.html
, we will see a message like the following one:
Host: 192.168.7.2:8080 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/201 00101 Firefox/46.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0. 8 Accept-Language: it,en-US;q=0.7,en;q=0.3 Accept-Encoding: gzip, deflate Connection: keep-alive HTTP/1.1 200 OK Content-type: text/html request=GET /index.html HTTP/1.1
The first seven lines are the message header, and then there are two lines with the server's answer, and in the end, there's the dump of the initial request. As we can see, the browser did a HTTP GET version 1.1 request asking for the /index.html
file. So, our Bash web server should simply read the browser's request, then skip the header, and in the end, return the contents of the file specified in the request.
A possible implementation is reported as follows:
#!/bin/bash # The server's root directory base=/var/www/html # Read the browser request read request # Now read the message header while /bin/true; do read header [ "$header" == $'\r' ] && break; done # Parse the GET request tmp="${request#GET }" tmp="${tmp% HTTP/*}" # Extract the code after the '?' char to capture a variable setting var="${tmp#*\?}" [ "$var" == "$tmp" ] && var="" # Get the URL and replace it with "/index.html" in case it is set to "/" url="${tmp%\?*}" [ "$url" == "/" ] && url="/index.html" # Extract the filename filename="$base$url" extension="${filename##*.}" # Check for file exist if [ -f "$filename" ]; then echo -e "HTTP/1.1 200 OK\r" echo -e "Contant-type: text/html\r" echo -e "\r" # If file's extension is "cgi" and it's executable the # execute it, otherwise just return its contents if [ "$extension" == "cgi" -a -x "$filename" ]; then $filename $var else cat "$filename" fi echo -e "\r" else # If the file does not exist return an error echo -e "HTTP/1.1 404 Not Found\r" echo -e "Content-Type: text/html\r" echo -e "\r" echo -e "404 Not Found\r" echo -e "The requested resource was not found\r" echo -e "\r" fi exit 0
Note
The code is held in the chapter_04/webled/bash/httpd.sh
file in the book's example code repository.
So we only have to replace the /root/httpd.sh
file with this file:
root@bbb:~# cp httpd.sh /root/httpd.sh
Now, after restarting the daemon, we can try our new web server written in the Bash language simply by pointing our web browser to the BeagleBone Black's IP address, as done earlier, and we'll get the same output we got earlier when we used the Apache web server. Nice, isn't it?
Note
Something different exists, in fact, we don't have the Debian logo. This is because our script can't manage binary files as images. This is left to you as an exercise.
Before going on, let's spend some words on how the web server works. After reading the browser's request, we have to parse it in order to extract some useful information for the next steps; in fact, we have to check whether the request is a normal file or a CGI script; in this last case, we have to execute the file instead of reading it with the cat
command. Here is the relevant code:
# If file's extension is "cgi" and it's executable the execute it, # otherwise just return its contents if [ "$extension" == "cgi" -a -x "$filename" ]; then $filename $var else cat "$filename" fi echo -e "\r"
That is, instead of using the cat
command to simply return the file content, we first verify that the file has the cgi
extension and whether it's executable; in this case, we simply execute it. Note that before doing this, we need to extract the code after the ?
character in order to get the variable settings when we use a URL in the http://192.168.7.2:8080/?led=1
form. This task is done by the var="${tmp#*\?}"
code.
OK, now the final version of the web server is ready, but to complete the server side actions, we need to add a CGI functionality. A possible GCI implementation is held in the chapter_04/webled/bash/turn.cgi
file in the book's example code repository, and here is a snippet of the relevant functions where the LED status is managed:
# 2nd part - Set the new led status as requested if [ -n "$1" ] ; then eval $1 ;# this evaluate the query 'led=0' led_new_status = $led echo $led_new_status > $value_f fi led_status=$(cat $value_f) led_new_status=$((1 - $led_status))
Now everything is really in place! Let's copy the turn.cgi
file into the /var/www/html
directory and then point the browser as we did for the PHP and the Python version of our LED management code; we should get a function similar to the earlier one.
Note
The Bash web server presented here is not a strictly compliant web server or a safe one! Even if it can work in most cases, it's just a simple demonstration program and it shouldn't be used in a production environment!
In these Bash examples, we used some special syntax that may be obscure for most of you (especially for beginners). Maybe looking at a Bash tutorial may help. A good starting point is at http://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO.html .