This document explains how to build and integrate the standalone Xedge firmware application with the Barracuda App Server (BAS) C library. Xedge is included under the BAS examples tree because it demonstrates a complete BAS-based firmware integration, but it is more than a small example application.
Use this page when you are compiling or porting Xedge. Use Using Xedge when you want to learn the IDE, applications, configuration, deployment, and APIs. For the product overview and design philosophy, see the online Xedge product page.
| Task | Where to Start |
|---|---|
| Evaluate Xedge on supported hardware | Use one of the ready-to-use RTOS projects below. |
| Compile a desktop test build | Use the host build approach described in the build notes and videos linked below. |
| Integrate Xedge into your own RTOS firmware | Include the required C files, provide the two required integration functions, and start barracuda() in a suitable thread. |
| Expose product-specific hardware or C services to Lua | Add Lua bindings and implement the required Xedge integration hooks in your binding module. |
| Create a production firmware image | Use the release build section after the basic build is running. |
Figure 1: Xedge components
The Xedge Lua application embedded in the executable can be viewed as Lua firmware. It is packaged as a compressed ZIP file, converted to C, linked into the executable, and mounted through ZipIo so the Lua VM can access it as its required vmio.
The following tutorials provide useful background before you design Lua bindings or decide how much application logic belongs in Lua versus C:
This section gives the generic build requirements. If you are evaluating Xedge on supported hardware, start with one of the ready-to-use RTOS projects:
Figure 2: Xedge build process
Although Xedge is primarily designed to be embedded in firmware, the same C code can also be compiled and tested as a standalone process on Windows and Linux. The Embedded Web Server vs. Embedded App Server video demonstrates this host-build workflow.
| File | Required | Purpose |
|---|---|---|
amalgamation/BAS.c | Yes | The Barracuda App Server C library. |
examples/xedge/src/xedge.c | Yes | Xedge startup and server integration code. |
examples/xedge/obj/XedgeZip.c | Yes | The generated C array containing Xedge.zip, including the Xedge Lua engine and web UI. |
xrc/DiskIo/[platform]/BaFile.c | Optional | File system plugin for targets with a supported persistent file system. |
| Your Lua binding C files | Usually | Product-specific glue code that lets Lua call your hardware drivers or C services. |
examples/xedge/src/led.c | Example | Simple Lua binding example based on the Lua binding tutorial. |
examples/xedge/src/AsynchLua.c | Example | Shows the asynchronous C-to-Lua API used for sending real-time events from C code to Lua. |
Create XedgeZip.c by running Xedge.cmd on Windows or Xedge.sh on Linux. See the BAS-Resources build directory for details. The generated file is a C array containing the zipped Xedge resources. The Xedge C startup code mounts this ZIP as a ZipIo and uses it as the Lua VM's vmio. Inspecting the generated ZIP is useful because it shows exactly which Lua and web resources are embedded in the firmware.
In addition to including the above files in your IDE/Makefile, add the required include path and #define directives as required by your platform. See the readme file builds/[platform]/readme.txt for details.
The file system plugin xrc/DiskIo/[platform]/BaFile.c is optional. Include it when the target has a file system and BAS provides a matching DiskIo port. Define NO_BAIO_DISK in your IDE or Makefile when the target does not use a local file system. See the DiskIo documentation for details.
The Xedge C code in examples/xedge/src/xedge.c calls two application-supplied functions:
| Function | Purpose |
|---|---|
int xedgeInitDiskIo(DiskIo* dio); | Initializes or customizes the DiskIo configuration. See the example code in led.c. |
int xedgeOpenAUX(XedgeOpenAUX* aux); | Installs auxiliary services such as Lua bindings, persistent configuration hooks, embedded ZIP applications, or product-specific secrets. See XedgeInitDiskIo.c and led.c. |
These functions are typically implemented in your Lua binding module. They can usually be left empty during the first device-porting test. The xedgeOpenAUX() hook is skipped only when you compile with NO_XEDGE_AUX; otherwise, provide the function even if it initially does nothing. See examples/xedge/src/xedge.h and the example function xedgeOpenAUX() in led.c for details. The file examples/xedge/src/XedgeInitDiskIo.c shows how to initialize a base path or mount point when the file system requires one.
barracuda()In an RTOS environment, create a dedicated thread to run barracuda() (Figure 2). This function never returns, and the thread must normally have a stack of at least BA_STACKSZ bytes in most RTOS environments. The startup code should also install an error handler and trace flush callback by calling HttpServer_setErrHnd() and HttpTrace_setFlushCallback(). See HostInit.c for examples.
Operating systems such as VxWorks and INTEGRITY supporting the standard C code entry main() can optionally use the two following C code files instead of creating a dedicated thread as explained above. These generic files are used by all examples included with BAS, not just Xedge. However, note that the stack for main() must be as detailed above.
examples/misc/standalone/Main.c - the main() function, which calls barracuda()examples/misc/standalone/HostInit.c - Sets an error handler and trace functionBAS.c includes optional features that are not compiled by default. Enable only the features your product or development workflow needs. The following options are relevant to Xedge standalone builds, including RTOS targets.
| Macro | Use When |
|---|---|
USE_DBGMON | You want Lua debugger support during development. |
USE_REVCON | You plan to use the reverse connection bridge in SharkTrustX. |
USE_OPCUA | You need OPC UA support and your build includes the required OPC UA Lua resources and C module. |
USE_REDIRECTOR | You need the Reverse Proxy. |
USE_UBJSON | You need Universal Binary JSON. |
| Macro | Effect |
|---|---|
NO_BAIO_DISK | Build without the DiskIo file system plugin. Use this when the target has no local file system or when first testing a minimal embedded port. |
NO_XEDGE_AUX | Do not call xedgeOpenAUX(). Use this only when you do not need product-specific Lua bindings, embedded application hooks, persistent configuration hooks, or additional softTPM secrets. |
NO_LDEBUG | Exclude the Lua debug module. This is recommended for release builds unless a controlled service workflow requires Lua debugging. |
NO_SHARKTRUST | Exclude the built-in SharkTrustX demo portal integration. See Additional Configuration and Security Settings. |
NO_ENCRYPTIONKEY | Exclude the softTPM encryption key support. See soft Trusted Platform Module. |
MAXTHREADS MacroBy default, the Xedge C code initializes an environment similar to the default configuration of the Mako Server. It creates two thread pools:
This setup results in a total of 5 threads by default.
For RTOS environments, you will likely want to customize the total number of threads by defining the MAXTHREADS macro at compile time. When you set MAXTHREADS, the two pools are merged into a single shared thread pool. The total number of threads will then be equal to the value specified by MAXTHREADS.
Important: Defining MAXTHREADS also disables the Lua function ba.thread.create(), preventing the creation of additional threads via Lua scripts.
For detailed information about thread management, refer to the Lua Thread Library documentation and the Thread C code documentation.
The Xedge C source code, specifically in the createServer() function, sets up several hardcoded Barracuda App Server configurations optimized for typical RTOS-based systems. For instance, the HTTP server engine is configured to handle a maximum of 8 concurrent TCP connections by calling HttpServerConfig::setNoOfHttpConnections(). This means the web server will maintain up to 8 active HTTP connections, recycling idle ones as needed. This connection limit applies only to HTTP server connections. It does not affect WebSocket server connections or any client-initiated TCP connections.
Xedge includes NetIo, which is useful during development and especially important on devices compiled with NO_BAIO_DISK. NetIo lets the embedded target access files served by a host computer, so you can edit Lua applications on the host while the target runs them. Start the NetIo file server on the host before booting the embedded device. See the Xedge NetIo tutorial for the host setup.
Xedge stores configured applications and their state in an encrypted JSON file. With file system support, this file is stored persistently on the embedded file system. When Xedge is compiled with NO_BAIO_DISK, the configuration is stored in the browser using Web Storage.
Note:
NO_BAIO_DISK is defined, Web Storage is limited to application details and state stored by the connected browser.The Lua application(s) startup state differs between a device with and without file system support:
You must set up custom Lua bindings if you want your Lua code to interact with your device's hardware through the embedded system's existing/new C code. These bindings act as bridges, allowing Lua scripts to call C functions that control hardware or perform other low-level tasks.
The Xedge code base includes two examples to help you get started:
When integrating Xedge as part of an OEM product, a common approach is to create Lua bindings for existing high-level C functions in your firmware. However, you can take integration a step further by directly interfacing with the microcontroller's hardware.
For example, the ESP32 port, Xedge32, includes a comprehensive south bridge (GPIO) integration, demonstrating how deep hardware-level integration can be achieved, including turning interrupts into real-time events. If you're looking for a reference, the Xedge32 source code and its south bridge API are available on GitHub.
When using a secure TLS client connection, such as HTTPS, or enabling the Automatic Certificate Management feature, the device must either have a clock with the correct time or fetch the time from the Internet using SNTP. The Barracuda App Server relies on the time returned by the platform-dependent function baGetUnixTime() defined in TargConfig.h. This header may also include baSetUnixTime(), which SNTP code can use when updating the time on a device without a clock. When updating the clock using SNTP, send the "sntp" event upon setting the time. See the Events below for details. Example 5 in led.c shows how to send the "sntp" event.
Xedge features an event mechanism via the Lua function xedge.event() that allows simple events to be sent asynchronously from C code to Lua code. The Xedge Lua code includes the following global Lua function, which the C code can call to distribute events throughout the system:
function _XedgeEvent(eventname,...)
Lua code can subscribe to events, and the Lua callbacks are triggered when the C code calls _XedgeEvent. The arguments can be any value, with one exception: Xedge requires the "sntp" event to activate Automatic Certificate Management if the device does not include a clock. To activate the certificate management, the C code must call _XedgeEvent("sntp"); see time above for details.
See Send Events Via The Lua Thread Library for more information on how to call _XedgeEvent from C code.
This optional header file enables setting a global password for all ZIP files loaded by the server. See method io:setpasswd() for details on AES encrypted ZIP files. The header file is included if the code is compiled with the USE_ZIPBINPWD macro. The data for this header file is typically created by using binpwd2str.
BAS enhances the security of your deployed Lua applications by supporting signed ZIP files, a feature especially valuable for Xedge Multipart Systems. For an overview, refer to the Signed and Encrypted ZIP Files tutorial. To enable this feature, compile Xedge with the USE_ZIPSIGNATURE macro and update ZipPublicKey.h.
When enforcing signed ZIP files, all ZIPs, both embedded and external, must be signed, including Xedge.zip. This means the XedgeZip.c file generated by running Xedge.cmd or Xedge.sh cannot be used as-is. Instead, follow these steps to sign the ZIP file:
signzip.sh Xedge.zip private_key.pem bin2c -z getLspZipReader Xedge.zip XedgeZip.c
Keep in mind, this feature prevents unsigned ZIP files from being loaded in production environments, but does not restrict a user with IDE access from creating or modifying non-deployed applications. See Creating a Release Build for how to secure or remove the IDE.
The generated C file tokengen.c includes SharkTrustX settings for the demo portal x.realtimelogic.com. If you're using your own SharkTrustX portal, replace tokengen.c with the version generated by your portal. To exclude this code, compile with the NO_SHARKTRUST macro. Do not include tokengen.c in your build, as xedge.c includes it as a header file.
The server includes a soft TPM (Trusted Platform Module), as detailed below. To exclude the TPM functionality, compile with the NO_ENCRYPTIONKEY macro.
Xedge includes a softTPM implementation that, when properly configured, provides reasonable security. See the TPM API and How the TPM Works for details and an introduction to initializing the softTPM from C code.
Xedge TPM details and required configuration:
The src/EncryptionKey.h file contains a binary random number of variable length the product designer (you) set. This number is consistent across all firmware copies and supplemented with additional per-device-specific entropy (DDUI).
Unless the code is compiled with NO_XEDGE_AUX, Xedge.c calls xedgeOpenAUX() at startup. This function can add additional secrets for making the pre-master key more secure by calling the provided function pointer addSecret(). See the source code in led.c for how to use this function.
The softTPM relies on a Device-Derived Unique Identifier (DDUI) to function correctly. It is recommended that you generate a DDUI and send it to Xedge by calling addSecret(). This identifier ensures that each device is truly unique. Without a DDUI, all devices would generate identical private keys for a given name handle, making the softTPM less secure.
For best results, the DDUI should be derived from hardware-based uniqueness (e.g., eFuse registers, CPU serial number, MAC address, or another hardware fingerprint). This guarantees that the identifier is both deterministic and device-specific.
Suggested release compile time macros:
In addition to NDEBUG, the following settings are recommended to secure the pre-master key in a release build: do not define USE_DBGMON, and compile with NO_LDEBUG to disable the Lua debug API.
This section explains how to assemble the Lua resources and configuration for a production firmware image. The examples assume that xedge.c is used as-is and that product-specific C code is kept in separate files, such as your Lua binding module or the example led.c.
| Decision | Typical Choice |
|---|---|
| Keep applications outside the firmware or embed them? | Use multipart deployment for easier OTA updates. Use a monolithic image when the target has no file system or must be shipped as one firmware image. |
| Keep the Xedge IDE? | Keep it during development. Restrict or remove it in production unless the product intentionally exposes the IDE. |
| Use signed or encrypted ZIP applications? | Use signed ZIP files when deployed applications must be authenticated before loading. |
| Protect per-device secrets? | Configure the softTPM and add a device-derived unique identifier through xedgeOpenAUX(). |
| Enable debug features? | Disable USE_DBGMON and compile with NO_LDEBUG in release builds unless a controlled service workflow requires them. |
Before exploring the various options, let's distinguish between creating a product with a single component and multiple components. You've worked with multiple components during the development of your Lua application(s) with Xedge. For a release, you can either maintain this multipart system or consolidate everything into a monolithic component that embeds all of your Lua code within the firmware.
The primary advantage of a multipart system is its efficiency in over-the-air (OTA) upgrades. Since the Barracuda App Server is a mature product, firmware upgrades will likely be infrequent. Most OTA updates will be focused on your applications, and the multipart system ensures these updates are lightweight and fast, making it much faster to update a ZIP file than the larger Xedge firmware.
However, the initial setup of a multipart system involves a multi-step process, including flashing the Xedge firmware and uploading the configuration file and applications. With Xedge32 (the ESP32 port of Xedge), we've simplified this process by enabling the assembly of all components into a single package. This is achieved by pre-populating the embedded file system as part of the firmware installation. For more details, refer to the Xedge32 partition page.
Embedding everything into a monolithic release eliminates the need for a device file system.
In the following section, we'll explore several release build options. These options are flexible - you can mix and match them to suit your needs. Option 1, including the Xedge IDE in your build, provides the most straightforward option as you do not need to understand any of the details regarding the files that are included in the resource file Xedge.zip (XedgeZip.c). This option is recommended if you include a web interface. If your device does not include a web interface, options 2 and 3 below may be worthwhile.
Remove any unused resource files, including the IDE, to compact your build.
Replace the default resources entirely and build XedgeZip.c using exclusively your own files.
Below are examples for each build option:
This option is the recommended method as it is the most straightforward. Assuming you have Xedge already running on your device, start by zipping together all your applications, as explained in the Creating a COTS Product section, which describes how to create a multipart system.
Creating a monolithic build with the application ZIP files and the Xedge configuration file embedded in the firmware requires a few more steps.
Start by converting all deployed applications (ZIP files) to C code using the tool bin2c, e.g.
bin2c -z myapp myapp.zip myapp.c
In the function xedgeOpenAUX, install the ZIP file as follows:
balua_installZIO(aux->L, "myapp", myapp());
See the C code example led.c, which includes an embedded ZIP file and an example showing how to install the ZIP file.
Build your system and download it to the target, or build it for HLOS. Use the Xedge UI to create or modify your Xedge configuration file. In a browser, navigate to the Xedge UI, right-click the NET file system, and select "New app" to open the application dialog. In the Name field, enter "myapp" or the name you used when calling balua_installZIO. In the URL field, enter $. This unique combination tells Xedge to mount the embedded ZIP file. Configure the rest of the fields as required by your application.
When you have everything configured, download the Xedge configuration file xcfg.bin or use the xcfg.bin produced on your host operating system if you performed this step on a HLOS such as Windows or Linux. Convert xcfg.bin to C code as follows:
bin2c -d xedgeCfg xcfg.bin xedgeCfg.h
The next step is to create a driver for the content of this header file. The example led.c explains how to make a read/write driver, but we only need one to read the content.
#include <xedgeCfg.h>
static int loadXedgeCfgFile(lua_State* L)
{
if (lua_isstring(L, 1)) /* Write */
lua_pushnil(L); /* Driver is in read only mode */
else /* Read */
lua_pushlstring(L, (char*)xedgeCfg, sizeof(xedgeCfg));
return 1;
}
int xedgeOpenAUX(XedgeOpenAUX* aux)
{
balua_installZIO(aux->L, "myapp", myapp());
/* Install Xedge config file driver */
aux->xedgeCfgFile = loadXedgeCfgFile;
}
To test the two setups discussed below, compile the code for your host platform, such as Windows or Linux, as demonstrated in the App Server vs. Web Server video. However, when compiling, include the compile-time macro -DBAIO_DISK_PATH=resources. This directive tells the xedge.c C code not to use the ZIP driver in XedgeZip.c, but instead to load resources from the "resources" directory. In other words, xedge.c will mount and use the "resources" directory on your host computer as the main IO (the VM IO).
In this example, we retain the Xedge engine along with all its services and APIs but remove the web-based editor and all resources not used by Xedge. Additionally, we eliminate all modules, such as MQTT, from the resources/test/Xedge/.lua directory. You can reintroduce these components as needed.
Below is the minimum set of files required by the Xedge engine. The engine will throw an exception if any of these files are missing:
| .config -- Xedge standalone startup code
|
+---.certificate
| cacert.shark -- Enabling trust for client TLS connection
| -- Key and cert used by the default HTTPS object
| device_RSA_2048.key
| device_RSA_2048.pem
|
\---.lua
| xedge.lua -- The Xedge engine
| 404.html
| EventEmitter.lua
| jwt.lua
| rwfile.lua
| tpm.lua -- softTPM
| wfs.lua
|
\---acme -- The Let's encrypt plugin must be included even if not used
| bot.lua
| dns.lua
| engine.lua
| log.lua
| rtoken.lua
\---XedgePlugins -- Add your Lua files here
(*) Any file you add to the XedgePlugins directory will be executed by the Xedge engine at startup. From this point, you can start building your application. Add Lua modules you have developed to the parent directory, i.e., to resources/test/Xedge/.lua.
With this approach, you forgo the benefits provided by the Xedge engine, such as the secure vault user database and the softTPM.
Follow these steps:
At this point, you should encounter the following error: .config error: no start server or onunload. To resolve this, open the .config file in an editor, add the code below, and save the file. Re-run the compiled code, and the error should no longer appear.
local vmio=ba.openio"vm" -- this is currently our "resources" directory
local function start()
-- Your startup code goes here.
-- Example code:
ba.timer(function() trace(ba.datetime"NOW") return true end):set(1000,true,true)
end
local function initialize(x)
if true == x or nil == x then
start()
end
-- Ignore all other arguments
end
local function onunload()
-- not used
end
return initialize,onunload -- Send to C code. The C code will then call initialize()
Figure 3: Xedge .config script example
At a minimum, the .config script must have an initialize and on-unload function. Depending on the compile flags, the C code may call the initialize function multiple times. For example, the key material for the pre-master key is inserted into the initialize function. We ignore the parameters in this code and look for two possible start conditions where the provided argument is either true or nil, depending on xedge.c compile-time macros. See the original .config file for details.
The tutorial Rapid Firmware Development provides a good introduction to using option 3 for creating an IoT device.