We know how to control GPIO in FreeBSD from command line and C programs.
But it’s better to use Raspberry Pi as a standalone server and control it through web interface.
Here is a description how to control GPIO through web in Raspberry Pi with FreeBSD.
We can use C promgram for CGI, but this is «the old way».
Modern Web applications are dynamic and are built on a framework.
We need to control GPIO in languages such as Perl, Python and Ruby.
Python
We need to build Python from ports.
First, I build python27:
And then build the «meta-port» for version 2:
GPIO in Python
There is a Python module RPi.GPIO.
But it is Linux’s oriented. It uses memory mapping to access GPIO. So we can’t use it in FreeBSD.
To control GPIO in FreeBSD from C program we need to use ioctl for GPIO device.
I am not an expert in Python and not sure that we can do it natively from Python.
Fortunately Oleksandr Tymoshenko wrote simple wrappers around FreeBSD’s GPIO ioctl for Per, Python, and Ruby.
It is available here: https://github.com/gonzoua/freebsd-gpio
Let’s download and extract it:
To build this python module we need to do this command:
# python setup.py build
This module comes with demo program.
It outputs information about all known pins and then blink one pin.
I have connected one LED to pin 25 and changed pin number for the LED in this demo program.
Now we can run demo program:
It’s work!
To install this module in system we need to run this command:
# python setup.py install
Python in Web
There is a Web Server Gateway Interface originally specified in PEP 333.
This is a specification for simple and universal interface between web servers and web applications or frameworks for the Python.
There are some application servers realisation for python.
I decide to use uWSGI as compact and performance solution.
uWSGI
We can build uWSGI from ports:
I used the demo program from the freebsd-gpio module as a basis for creating an web application.
It outputs table with information about all known pins.
#!/usr/bin/env python
import gpio
html1 = """\
<!doctype html>
<html>
<head><title>GPIO Configuration</title></head>
<body>
<h1>GPIO Configuration</h1>
<table border=1>
<tr><th>Name</th><th>Value</th><th>Config</th><th>Caps</th></tr>
"""
html2 = """\
</table>
</body>
</html>
"""
row_template = " <tr><td>%s</td><td>%d</td><td>%s</td><td>%s</td></tr>"
def flags2str(flags):
strs = []
if (flags & gpio.PIN_INPUT):
strs.append('INPUT')
if (flags & gpio.PIN_OUTPUT):
strs.append('OUTPUT')
if (flags & gpio.PIN_OPENDRAIN):
strs.append('OPENDRAIN')
if (flags & gpio.PIN_PUSHPULL):
strs.append('PUSHPULL')
if (flags & gpio.PIN_TRISTATE):
strs.append('TRISTATE')
if (flags & gpio.PIN_PULLUP):
strs.append('PULLUP')
if (flags & gpio.PIN_PULLDOWN):
strs.append('PULLDOWN')
if (flags & gpio.PIN_INVIN):
strs.append('INVIN')
if (flags & gpio.PIN_INVOUT):
strs.append('INVOUT')
if (flags & gpio.PIN_PULSATE):
strs.append('PULSATE')
return ','.join(strs)
def application(env, start_response):
status = "200 OK"
headers = [('Content-type', 'text/html'), ]
start_response(status, headers)
gpioc = gpio.controller()
max_pin = gpioc.max_pin
gpios = ""
content = html1
# Dump information about all known pins
for p in range(0, max_pin + 1):
pin = gpioc.pin(p)
try:
value = pin.value
except gpio.GpioError as e:
# pin with this number is unknown
continue
caps = flags2str(pin.caps)
config = flags2str(pin.config)
name = pin.name
content += row_template % (name, value, config, caps)
content += html2
return [content]
Firts deploy it on port 9090 with this command:
# uwsgi --http :9090 --wsgi-file gpioweb.py
Now we can try to access it:
We can adding concurrency and monitoring to our application:
# uwsgi --http :9090 --wsgi-file gpioweb.py --master --processes 4 --threads 2 --stats 127.0.0.1:9191
We can use “uwsgitop” to see a top-like monitoring of the uWSGI:
Before using uwsgitop we need to install py-pip:
# cd /usr/ports/devel/py-pip
# make
# make install
And than to install uwsgitop:
# pip install uwsgitop
uWSGI behind a full webserver
Next step is using a «normal» webserver.
FreeBSD Handbook advises to use Apache HTTP Server but I decide to use Nginx.
It is compact and performance. And OpenBSD uses it as a default webserver.
We can build and install nginx from ports:
# cd /usr/ports/www/nginx
# make
# make install
Then we need to change configuration file for nginx: /usr/local/etc/nginx/nginx.conf.
Nginx will use Unix sockets to communicate with uWSGI.
And our application will be available at location /gpio.
We need to add this strings in section «http» in the file /usr/local/etc/nginx/nginx.conf:
upstream pi {
server unix:///tmp/uwsgi.sock;
}
And this strings in section «server»:
location /gpio {
include uwsgi_params;
uwsgi_pass pi;
}
We will use this file /usr/local/etc/uwsgi.ini as ini-file for uWSGI:
[uwsgi]
chdir = /usr/local/www/pi
wsgi-file = gpioweb.py
master = true
processes = 4
stats = 127.0.0.1:9191
vacuum = true
To run Nginx and uWSGI at startup we need to add this strings to /etc/rc.conf:
nginx_enable="YES"
uwsgi_enable="YES"
uwsgi_uid=root
uwsgi_flags="--ini /usr/local/etc/uwsgi.ini"
I run uWSGI from root account to have access to GPIO device.
This isn’t secure of course, but at this time we can do this.
Let’s check our Nginx server first:
It’s works!
To check uWSGI server we can look into its log file:
Interactive application
It is not very interesting to see only status of GPIO pins. Let’s add some interactivity.
I have changed first application and add a possibility to change GPIO value and configuration.
#!/usr/bin/env python
import cgi
import gpio
html1 = """\
<!doctype html>
<html>
<head><title>GPIO Configuration</title></head>
<body>
<script>
function formSubmit(fname) {
document.forms[fname].submit();
}
</script>
<h1>GPIO Configuration</h1>
<form name="freload" method="get">
<input type="button" value="reload" onClick="formSubmit(this.form.name)">
</form>
<p><table border=1 cellpadding=3>
<tr><th>Name</th><th colspan=2>Value</th><th colspan=2>Config</th></tr>
"""
form1 = """\
<tr>
<form name="form%d" method="post">
<input type="hidden" name="pin" value="%d">
<td>%s</td>
<td>%d</td>
<td><select name="value" onChange="formSubmit(this.form.name)">
"""
option0_selected = " <option value=\"0\" selected>0</option>"
option0_nonselected = " <option value=\"0\">0</option>"
option1_selected = " <option value=\"1\" selected>1</option>"
option1_nonselected = " <option value=\"1\">1</option>"
form2 = """\
</select>
</td>
<td>%s</td>
<td><select name="config" onChange="formSubmit(this.form.name)">
"""
option_selected = " <option value=\"%s\" selected>%s</option>"
option_nonselected = " <option value=\"%s\">%s</option>"
form3 = """\
</select>
</td>
</form>
</tr>
"""
html2 = """\
</table>
"""
html3 = """\
</body>
</html>
"""
def flags2list(flags):
lists = []
if (flags & gpio.PIN_INPUT):
lists.append('INPUT')
if (flags & gpio.PIN_OUTPUT):
lists.append('OUTPUT')
if (flags & gpio.PIN_OPENDRAIN):
lists.append('OPENDRAIN')
if (flags & gpio.PIN_PUSHPULL):
lists.append('PUSHPULL')
if (flags & gpio.PIN_TRISTATE):
lists.append('TRISTATE')
if (flags & gpio.PIN_PULLUP):
lists.append('PULLUP')
if (flags & gpio.PIN_PULLDOWN):
lists.append('PULLDOWN')
if (flags & gpio.PIN_INVIN):
lists.append('INVIN')
if (flags & gpio.PIN_INVOUT):
lists.append('INVOUT')
if (flags & gpio.PIN_PULSATE):
lists.append('PULSATE')
return lists
def flags2str(flags):
return ','.join(flags2list(flags))
def application(env, start_response):
configs = { 'INPUT' : gpio.PIN_INPUT,
'OUTPUT' : gpio.PIN_OUTPUT,
'OPENDRAIN' : gpio.PIN_OPENDRAIN,
'PUSHPULL' : gpio.PIN_PUSHPULL,
'TRISTATE' : gpio.PIN_TRISTATE,
'PULLUP' : gpio.PIN_PULLUP,
'PULLDOWN' : gpio.PIN_PULLDOWN,
'INVIN' : gpio.PIN_INVIN,
'INVOUT' : gpio.PIN_INVOUT,
'PULSATE' : gpio.PIN_PULSATE }
gpioc = gpio.controller()
max_pin = gpioc.max_pin
# Parse Post request
try:
request_body_size = int(env.get('CONTENT_LENGTH', 0))
except (ValueError):
request_body_size = 0
request_body = env['wsgi.input'].read(request_body_size)
d = cgi.parse_qs(request_body)
rpin = d.get('pin',[''])[0]
rvalue = d.get('value',[''])[0]
rconfig = d.get('config',[''])[0]
rpin = cgi.escape(rpin)
rvalue = cgi.escape(rvalue)
rconfig = cgi.escape(rconfig)
if request_body_size > 0:
cpin = gpioc.pin(int(rpin))
if int(rvalue) == 1:
cpin.value = gpio.HIGH
else:
cpin.value = gpio.LOW
cpin.config = configs[ rconfig ]
# Forming answer: creating a list with all known pins
content = html1
for p in range(0, max_pin + 1):
pin = gpioc.pin(p)
try:
value = pin.value
except gpio.GpioError as e:
# pin with this number is unknown
continue
name = pin.name
config = flags2str(pin.config)
caps = flags2list(pin.caps)
content += form1 % (p, p, name, value)
if value == 0:
content += option0_selected
content += option1_nonselected
else:
content += option0_nonselected
content += option1_selected
content += form2 % (config)
for cap in caps:
if cap in config:
content += option_selected % (cap, cap)
else:
content += option_nonselected % (cap, cap)
content += form3
content += html2
# Print request for debug
# if request_body_size > 0:
# content += "<p>Post request: pin = %d, value = %d, config = %s" % (int(rpin), int(rvalue), rconfig)
content += html3
status = "200 OK"
headers = [('Content-type', 'text/html'),('Content-Length', str(len(content)))]
start_response(status, headers)
return [content]
Let’s check it:
We can see here pin name, it current value, selection to change pin value, it current config and selection to change config.
Changing value for pin 25 we can turn on and turn off our LED.
To see how changes value of pin configured as input we can use button «Reload» at the top of the page.
This is simple application: ugly interface, no computing on client side, no REST API and so on.
But this a good first step on controlling Raspberry Pi with FreeBSD through web.





















Hallo and a big THX for your work.
There must be a typo in the second script — I got an error in line 150 — you know — boys form IT are the lazy ones 🙂
Once again many thanks for your blog.
armin
Yes, when I put second script in this post, WordPress added extra spaces in some lines. I have updated this script.
thx for the quick Response — and shame on me because i had’t found the error on my self — armin
Another quality teaching moment. Thanks So Much
I had to mod this file per this post:
https://forums.freebsd.org/threads/50291/page-3#post-307247
Works great on the APU1 too.. Thanks
Time has passed but this «freebsd-gpio» project has not updated about 8 years. It’s not working with recent python releases (3.7; 3.8 etc..).
Yes, it is outdated. I might replace it with something over time.