Get hands-on experience designing a web-based device management application with a functional menu system, user authentication, and real world object interaction. In this tutorial, you'll learn how to use the Mako Server and an embedded Linux GPIO library to bring your device management application to life. Take your device management skills to the next level with this comprehensive tutorial.
This hands-on Linux getting started guide shows you how to cross-compile the server's C code, use a ready-to-use web-based dashboard application as your foundation for web-based device management, and extend the server for interacting with real-world objects.
Windows 10 and up can run Linux. You can install the Windows Subsystem for Linux (WSL) and run this hands-on tutorial in the WSL shell.
The following tools are required:
As mentioned on the main page, the Mako Server requires the Barracuda App Server C Source Code Library. The following instructions explain how to compile the Mako Server, compile the Barracuda App Server Library, and link the components together. The SQLite database engine is also included, but it can easily be excluded from the build if it is not needed.
We provide a ready to run script that you can initially use for downloading all required code and for compiling and/or cross compiling the source code for your (embedded) Linux. Download and (cross) compile the code for (embedded) Linux as follows:
The script will use your default gcc host compiler if you do not set the CC environment variable. As an example, the following cross compiles the code using Toradex's iMX6 toolchain.
You can re-run the script at any time to re-compile for a different target. The script is intelligent and only downloads the code if not already downloaded.
If you have cross-compiled the code, you should copy the compiled binary 'mako' and the Mako Server's resource file 'mako.zip' to your embedded Linux system. Alternatively, you can also compile the server for your host computer and use the host version for the tutorials. To do this, simply compile the code without setting the CC environment variable.
Test the compiled server as follows; press CTRL-C when done testing:
The ready to use Light Dashboard App is a solid foundation for device management, with its ready to use navigation menu engine and authentication logic.
Execute the following to download all examples, including the Light Dashboard app, and to instruct the Mako Server to load the Light Dashboard app.
The Light Dashboard app acts as a mini content management system, and you can easily modify, remove, and add new pages to the dashboard app. See the tutorial How to Build an Interactive Dashboard App for details.
Server side applications are preferably designed in Lua and a recommendation is to initially check out our Online Interactive Lua Tutorials if you are new to the Lua programming language. The server also provides an extensive C API; however, Lua is what will help you expedite your development 👍
Lua is easy to extend and you can add your own C code to the Mako Server, allowing Lua to call your C code API via a so-called Lua binding. However, on Linux, the Mako Server already includes many APIs that enable you to interact with other Linux processes using files, pipes, sockets, etc. In the following example, we will extract information produced by the Linux kernel without adding additional C code.
The Light Dashboard app includes a JavaScript powered gauge. We can add new code on the server side that sends real time events to update the guage running in all connected browsers. The following Lua code starts the 'top' command as a subprocess using the forkpty API and continuously reads the data produced by the 'top' command. The Lua code extracts the CPU usage information and sends it as an event to all connected browsers.
-- pty read callback. Reads data asynchronously from command: top -i -b local function readTopData(pty) -- Wait for 'top' data local data = pty:read() while data do -- Extract data from the line: -- %Cpu(s): 0.0 us, 0.0 sy, 0.0 ni,100.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st local us,sy,ni=data:match"(%d+%.%d+)%s+us.-(%d+%.%d+)%s+sy.-(%d+%.%d+)%s+ni" if us then local total=tonumber(us)+tonumber(sy)+tonumber(ni) trace("CPU:",total) -- total is between 0 and 100. We need this value to be between 0 and 180. angle = math.tointeger(total*1.8) smq:publish({angle=angle},"slider") end data = pty:read() end end -- Run the Linux command 'top -i -b' as a subprocess local pty,err = ba.forkpty({async=readTopData}, "/usr/bin/top", "-i", "-b") if not pty then trace("Cannot open 'top'",err) end function onunload() if pty then pty:terminate() end end
The above Lua code is designed to interact with the existing Lua code in the Light-Dashboard's .preload script, which sets up an SMQ IoT Broker and enables the WebSocket page (the gauge/slider page) to connect to the SMQ broker instance. SMQ uses WebSockets when communicating with browsers. The real-time gauge design follows the design pattern outlined in the tutorial Modern Approach to Embedding a Web Server in a Device.
In summary, Lua is a compact, embeddable scripting engine that has a flexible extension mechanism, making it easy to add new Lua functions that map to underlying C or C++ functions. This mapping is called a Lua binding and consists of either hand-crafted or auto-generated C/C++ code that interfaces Lua with C/C++ library functions, classes, and methods.
Let's say we want to make a Lua API for the following C function, which calculates the average CPU load by using data from "/proc/stat". The C function is designed to be polled at regular intervals, and a delta value is calculated from the total time provided by "/proc/stat" each time the function is called. See /proc/stat explained for details.
#include<stdlib.h> #include<string.h> #include<stdio.h> int cpu(void) { FILE* fp = fopen("/proc/stat","r"); if(fp) { static long long prevTotal=0,prevIdle; static const char d[2] = " "; char str[100]; char* token; long long total,idle=0; int i; if(fgets(str,100,fp));/* ignore return value */ fclose(fp); token = strtok(str,d); /* Skip 'cpu' */ for(i=0,total=0 ; NULL != (token = strtok(NULL,d)) && i< 7 ; i++) { if(3 == i) idle = atoll(token); else total += atoll(token); } if(prevTotal) { /* if not the very first time this function is called */ int deltaTotal = total - prevTotal; int deltaIdle = idle - prevIdle; i = deltaTotal * 100 / (deltaTotal + deltaIdle); } else i=0; /* We have no data */ prevTotal=total; prevIdle=idle; return i; /* percentage CPU since last call */ } return 0; }
Note that we could have easily implemented the above C function directly in Lua using Lua's IO functions and the irony is that this function would have been easier to implement in Lua. However, we simply need a C function that we can use for creating the same feature we provided by using the Lua code in Example 1 and at the same time show how to map Lua to C code.
A Lua binding can be hand crafted or created automatically. The following shows how to create bindings automatically, but before proceeding, check out the Online Interactive Lua Binding Tutorial which provides introductory information and shows how to manually create a basic Lua binding.
As the Online Interactive Lua Binding Tutorial explains, the Mako Server can dynamically load Lua bindings embedded in a shared library at runtime. In the following example, we will instead directly embed the 'cpu' function and the 'cpu' Lua binding in the Mako Server executable. You should also check out the tutorial Building C/C++ Modules if you are interested in loading Lua APIs dynamically.
SWIG is an interface compiler that connects programs written in C and C++ with scripting languages, including Lua. It works by taking the declarations found in C/C++ header files and using them to generate the wrapper code (Lua bindings) needed to access the underlying C/C++ code from a Lua script. In addition, SWIG provides a variety of customization features that let you tailor the wrapping process to suit your application.
To use SWIG, you start by making a SWIG interface file as shown below:
%module cpu /* The module name used by your Lua code */ %{ /* Put headers and other declarations here */ int cpu(void); %} /* Put the same headers here, or just the function declarations you want exposed to Lua. */ int cpu(void);
The mako.mk makefile should run the command 'swig -lua MyCustomBindings.i', which produces the file MyCustomBindings_wrap.c. The makefile then compiles all code and links it together, producing a new Mako Server executable with our new 'cpu' Lua api. Note that you can also cross compile using the mako.mk makefile by either setting the CC environment variable or by directly adding the line CC=.... at the top of mako.mk.
You will find the following function in the auto generated C file MyCustomBindings_wrap.c.
int luaopen_cpu(lua_State* L);
The Mako Server C code BAS/examples/MakoServer/src/MakoMain.c must call this function at startup so the 'cpu' API gets registered. However, you do not need to modify MakoMain.c since it includes the following construction:
myCustomBindings(L);
The function is a macro that normally expands to nothing, but the makefile sets the following when compiling the code:
-DmyCustomBindings=luaopen_cpu
Open MakoMain.c in an editor and search for myCustomBindings(L). You can add additional APIs at the same code location.
local function pollCPU() -- Call the auto generated 'CPU' Lua binding, which subsequently -- calls the C function in Example 2. local cpu = cpu.cpu() trace("CPU:",cpu) -- cpu is between 0 and 100. We need this value to be between 0 and 180. angle = math.tointeger(cpu*1.8) smq:publish({angle=angle},"slider") return true -- interval timer end local timer = ba.timer(pollCPU) timer:set(1000) -- poll every second function onunload() timer:cancel() end
The CPU Lua binding we auto created would have been super easy to hand craft since the cpu() function provides such a basic API. However, automatically generating Lua bindings using SWIG is easy and convenient when you have a large set of functions/methods/classes you want to make available to Lua. For example, we use SWIG when auto generating bindings for a huge set of GPIO functions for the Barracuda App Server ESP32 Tutorial. Check out our ESP32 SWIG interface file on GitHub. See the Lua specific SWIG documentation for details on how to use SWIG with Lua. You may also want to check out an old, but still relevant C++ SWIG Tutorial.
All Lua bindings in the Barracuda App Server library are hand crafted, which makes the code smaller. Hand crafted bindings provide options not possible with auto generated code, such as making a friendlier Lua API. Some bindings such as the forkpty API we used in Example 1 cannot be auto generated since the API provides an asynchronous event based mechanism for sending events from C code to the Lua world. Our advanced Lua binding tutorial explains how to call Lua asynchronously from C code.
Statically adding (embedding) new Lua bindings to the Mako Server, as shown in the above example, makes it easy to replicate the server setup on another computer by simply copying the two files (mako and mako.zip) to the new computer. This method works as long as the computer you are copying to uses the same architecture. However, it is also possible to add Lua bindings dynamically by loading shared libraries at runtime. There are many ready-to-use Lua libraries that can easily be included in your Mako Server deployment.
Follow the instructions below to manually compile the server and the module or simply automate the process by running the following in a Raspberry Pi shell:
sudo apt-get install git unzip gcc make wget -O - https://raw.githubusercontent.com/RealTimeLogic/BAS/main/RaspberryPiBuild.sh | bashNavigate to testing the Lua GPIO bindings instructions after running the above script.
Note: the Raspberry Pi build script should work for most Linux systems. The script can also be used for cross compiling the source code. See download source code for details.
Note that the Mako Server requires some minor changes to the Lua binding's C code before building. Details about this modification can be found in the Building C/C++ Lua Modules Documentation. We will also go over the required changes in the following instructions.
The instructions show how to add the ready to use Embedded Linux GPIO Lua Bindings to the Mako Server. With this additional library, you will be able to control the GPIO ports on a Raspberry Pi. The GPIO library should work on most embedded Linux environments, including Raspberry Pi and BeagleBone.
You must compile the Mako Server on the Raspberry Pi before building the GPIO Lua Bindings. We need the Mako Server's Lua C Code Module Library:
Clone the Raspberry Pi GPIO Lua Bindings:
Navigate to the Raspberry Pi GPIO Lua Bindings directory:
As explained above, before building we need to add the initialization hook required by the Mako Server's Lua C Code Module library. To do this, open src/lua_periphery.c in an editor and change #include <lua.h> to #include <luaintf.h>. Navigate to the function luaopen_periphery and add the following line at the top of the function: luaintf(L);
As an option, use the two following 'sed' commands to automatically edit src/lua_periphery.c instead of manually editing the file.
The next step is to open the Makefile in an editor and add the Mako Server's interface module to the list of C files to be compiled. Add the following to the SRCS =: ../MakoModuleExample/src/lua/luaintf.c. The following sed command automates this:
We can now build the module, but we must first set the Lua include path as follows:
We can now simply run 'make':
This will build the shared library file periphery.so. Copy this file to the Mako Server's directory:
Note: the Mako Server is in the directory ../BAS if you used the makefile as explained at the beginning of this tutorial and in ../ if you used the LinuxBuild.sh script.
Everything is now ready for writing Lua GPIO scripts. Let's create an application:
Copy the following into the nano editor and save the file:
-- Load periphery.so and access the LED interface local LED = require('periphery').LED local function run() local led = LED("led0") -- Open LED led0 trace"Turn LED on" led:write(true) -- Turn on LED (set max brightness) ba.sleep(3000) -- 3 seconds trace"Turn LED off" led:write(false) -- Turn off LED (set zero brightness) led:close() end ba.thread.run(run) -- Defer execution to after Mako has started
Run the Mako Server as follows:
You will see the following text printed in the console: Opening LED: opening 'brightness': Permission denied. Accessing GPIO requires root access so restart the Mako Server as follows:
You should now see the LED turn on for 3 seconds. Enter CTRL-C to stop the server.
We can easily create a web page with an on/off switch for turning the Raspberry Pi’s LED on and off. Navigate to the online Lua tutorial and the Lua to C Code Tutorial. Quickly read through the tutorial and copy all of the code in example 6.3. Open the nano editor as follows and paste in the code from example 6.3.
Save the file, exit nano, and open:
Delete all code and replace with the following, which will make it possible to load a module from within the application.
mako.createloader(io)
The next step is to create a module that provides the API required by example 6.3 (the code that is now in your www/index.lsp file).
Copy the following Lua code and paste the code into www/.lua/LED.lua. Save the file and exit nano.
local PLED = require('periphery').LED local led = PLED("led0") -- Open LED led0 LED={} -- The LED API required by index.lsp function LED.setLed(on) led:write(on) end function LED.getLed() return led:read() end return LED -- our wrapper module
Start the Mako Server
Use a browser and navigate to http://ip-address, where ip-address is your Raspberry Pi's IP address. You should see an on/off switch. Note that you must first click the switch and then click the submit button. See the online tutorial for how to remove the submit button and how to enable AJAX.
You can add the LED switch page to the Light Dashboard App if you cloned the LSP-Examples as previously shown in this tutorial. We start by copying the two files we created to the Light Dashboard App’s www directory as follows:
We need to register the new page with the menu system. Open the menu file as follows:
Add the following JSON to the line just below the WebSocket page:
{ "name": "LED", "href": "led.html" },
We also have to edit the Light Dashboard App’s preload script and add the mako.createloader call to this file. Open the file as follows:
Add the following at the top of the file:
mako.createloader(io)
You can now start the Mako Server as follows:
Navigate to the server and login with the credentials admin/qwerty. Click the LED link in the left pane. You should now see the LED switch that lets you toggle the Raspberry Pi LED.
You can use what you have learning in this tutorial as a foundation for your IoT and/or web based device management application. Here is a collection of tutorials you may find useful when working with the Mako Server.
Unlock the potential of embedded web servers and IoT with our curated tutorials. They're your DIY toolkit for success. But if you're racing against the clock or need a deeper perspective, our expert consulting services are your ace in the hole. With Real Time Logic, you're never alone on your DIY journey. Let's collaborate and make magic happen!
Expedite your IoT and edge computing development with the "Barracuda App Server Network Library", a compact client/server multi-protocol stack and toolkit with an efficient integrated scripting engine. Includes Industrial Protocols, MQTT client, SMQ broker, WebSocket client & server, REST, AJAX, XML, and more. The Barracuda App Server is a programmable, secure, and intelligent IoT toolkit that fits a wide range of hardware options.
SharkSSL is the smallest, fastest, and best performing embedded TLS stack with optimized ciphers made by Real Time Logic. SharkSSL includes many secure IoT protocols.
SMQ lets developers quickly and inexpensively deliver world-class management functionality for their products. SMQ is an enterprise ready IoT protocol that enables easier control and management of products on a massive scale.
SharkMQTT is a super small secure MQTT client with integrated TLS stack. SharkMQTT easily fits in tiny microcontrollers.
An easy to use OPC UA stack that enables bridging of OPC-UA enabled industrial products with cloud services, IT, and HTML5 user interfaces.
Use our user programmable Edge-Controller as a tool to accelerate development of the next generation industrial edge products and to facilitate rapid IoT and IIoT development.
Learn how to use the Barracuda App Server as your On-Premises IoT Foundation.
The compact Web Server C library is included in the Barracuda App Server protocol suite but can also be used standalone.
The tiny Minnow Server enables modern web server user interfaces to be used as the graphical front end for tiny microcontrollers. Make sure to check out the reference design and the Minnow Server design guide.
Why use FTP when you can use your device as a secure network drive.
PikeHTTP is a compact and secure HTTP client C library that greatly simplifies the design of HTTP/REST style apps in C or C++.
The embedded WebSocket C library lets developers design tiny and secure IoT applications based on the WebSocket protocol.
Send alarms and other notifications from any microcontroller powered product.
The RayCrypto engine is an extremely small and fast embedded crypto library designed specifically for embedded resource-constrained devices.
Real Time Logic's SharkTrust™ service is an automatic Public Key Infrastructure (PKI) solution for products containing an Embedded Web Server.
The Modbus client enables bridging of Modbus enabled industrial products with modern IoT devices and HTML5 powered HMIs.