GNU-Linux Rapid Embedded Programming
上QQ阅读APP看书,第一时间看更新

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 .