Embedded Edge Agent API
This document contains information on the Embedded Edge Agent API, including how to integrate the EEA into your native code.
Definitions
i8
: 8-bit signed integer.i16
: 16-bit signed integer.i32
: 32-bit signed integer.i64
: 64-bit signed integer.u8
: 8-bit unsigned integer.u16
: 16-bit unsigned integer.u32
: 32-bit unsigned integer.u64
: 64-bit unsigned integer.f32
: 32-bit floating-point number.f64
: 64-bit floating-point number.WASM
: Short for WebAssembly.EEA
: Short for Embedded Edge Agent.bundle
: The compiled WASM that is deployed to devices. Contains both the EEA and all deployed workflows.native code
: The user-provided code instantiating and managing the WebAssembly module.imported function
: Function defined in native code and invoked the EEA.exported function
: Function defined in the EEA and invoked by the native code.
EEA Versioning
Everything outlined in this document represents the interface (or API) between the EEA and your native code. Any time “version” is mentioned in the context of EEA, it is referencing the version of the interface defined in this document.
WEGnology follows semantic versioning, and the major version (1.x.x → 2.x.x) will only change if the interface in this document changes in a way that becomes incompatible with existing native code.
For example, changing the signature of any imported or exported function will break compatibility with native code and result in a major version change (1.x.x → 2.x.x). However, adding a new exported function does not break compatibility since your native code was never invoking that function. This would result in a minor version change (1.0.0 → 1.1.0).
Since the EEA’s and your workflow’s implementations are entirely compiled into the WASM bundle, WEGnology has the ability to periodically update the bundle’s inner implementation without impacting the interface. This allows us to release bug fixes, performance optimizations, and entirely new workflow nodes without requiring a version change, and therefore no changes to your native code.
Because WEGnology periodically updates a bundle’s implementation, you may notice bundle sizes (in bytes) change. This only matters if you are allocating memory to hold bundles, because the size of the bundle may change even if no changes were made to your workflows. Each time a bundle is deployed to a device, WEGnology will compile the WASM bundle using the most up-to-date implementation. WEGnology maintains backwards compatibility, so even if an implementation has changed, the behavior will remain the same.
System Time and Timer Triggers
The current system time, in milliseconds, is passed to the EEA on each invocation of eea_loop
. The system time is either since Epoch or since boot (whichever is available on your system). This timestamp is used by the EEA to determine when Timer Triggers should invoke their corresponding workflows.
Depending on the specifics of your system, there may be jumps in time due to NTP updates (Linux) or integer rollovers (embedded). Jumps in time impact Timer Triggers in the following ways:
- Jumps forward: If the time jumps forward, timers will use the new time as-is and assume that much time has actually passed. This could cause a timer to trigger back-to-back if the time jumps forward a significant amount (more than the configured duration of a timer).
- Jumps backward: If the time jumps backwards, meaning that the timestamp passed to
eea_loop
is less than the timestamp passed previously, timers will ignore this single iteration in their time calculation. This will result in all timers being delayed for the amount of time between invocations ofeea_loop
. For example, ifeea_loop
is called every 100 milliseconds and the time jumps backwards, the loop iteration after the jump will be ignored, which will result in timers being delayed by approximately 100 milliseconds.
Message Buffers
The EEA contains integrated memory management on top of WASM’s linear memory buffer. Because the memory is managed by the EEA, it is not possible for the native code to directly allocate memory as a way to pass data to the EEA. There is no way for the native code to know what memory addresses are available.
To solve this, the EEA pre-allocates buffers and provides pointers that can be populated by the native code as a way to send data to the EEA.
On initialization (eea_init
), the EEA invokes the imported function, eea_set_message_buffers
, and passes it two pointers to pre-allocated buffers (topic and payload). The native code must maintain references to these pointers for the lifetime of the WASM bundle. These pointers will change each time a new WASM bundle is initialized, even if it is the same bundle being re-initialized after something like a power cycle.
The messages buffers have the following default sizes:
- Topic Buffer: 256 bytes
- Payload Buffer: 4,096 bytes
The size of these buffers can be changed by invoking eea_config_set_message_buffer_lengths
at any time. Whenever this function is invoked, the EEA will invoke eea_set_message_buffers
with pointers to the newly allocated buffers.
These buffers are used to both forward MQTT messages to the EEA and when invoking the Direct Trigger using the eea_direct_trigger
function. The required size, therefore, depends on your specific topic and payload sizes. The largest possible payload size that can be received over WEGnology’s MQTT broker is 1MB. The largest possible topic size is 230 bytes.
To use these buffers, the native code must populate the topic and payload buffers with UTF-8 encoded strings and then invoke eea_message_received
.
eea_message_received
takes two arguments:
- The length of the topic string, in bytes.
- The length of the payload string, in bytes.
The two length parameters represent how much data was actually written to the pre-allocated buffers. The native code must ensure that the amount of data written is less than the available capacity of each buffer.
Invoking eea_message_received
can result in your workflows executing one of the following triggers, if your workflow(s) have a corresponding trigger configured:
If you send a message to the EEA for a trigger that is not present on any deployed workflow, the message will be ignored. For example, if you send a message with the topic wnology/<device-id>/command
(which represents a Device Command) and your workflows do not have any Device: Command Triggers, the message will be ignored by the EEA and no action will be taken.
Error Codes
The following table lists the error codes that can be returned by the EEA’s exported functions. Error codes 2 through 15 will never be used by the EEA and are good options for custom error codes returned by imported and registered functions.
It’s important to use non-conflicting error codes because in some cases an exported function may return an error code from an imported function. For example, eea_init
will invoke eea_storage_read
. If you return an error code from eea_storage_read
, the eea_init
function will fail and will return your custom error as its error code.
Code | Name | Description |
---|---|---|
0 | Success | This code is returned for a successful execution of an operation. |
1 | General Error | Return this code to signify a general error. |
16 | Init Already Called | eea_init was called by native code after it was already called. |
17 | No Init Called | eea_init was not invoked prior to invoking eea_loop , eea_shutdown , eea_direct_trigger , or eea_message_received . |
18 | Trigger Not Found | eea_message_received was called, but the workflow contained no Device Command Triggers; Virtual Button Triggers matching the provided ID; or MQTT Triggers configured to fire on the provided topic. This error can also occur when invoking eea_direct_trigger with a key not matching any Direct Triggers deployed to the device. |
19 | Trigger Queue Full | A workflow run failed to queue due to the queue size exceeding its limit. The size of the queue can be adjusted by calling eea_config_set_queue_size . |
20 | Invalid Trigger ID | A trigger ID was not provided (or an invalid ID was provided) for a Virtual Button Trigger. |
21 | Storage Full | A Storage: Set Value Node failed to add a value to storage as there was no more available space. The available storage space can be adjusted by calling eea_config_set_storage_size . |
22 | Loop Break Code | (Internal use only) Signifies that a Loop Node encountered a Break Node and should break. |
23 | Invalid Topic | An MQTT Node attempted to publish a message to an invalid topic. |
24 | Invalid Payload | A trigger was invoked with an invalid payload; usually this means eea_direct_trigger was called with a payload that is not valid JSON. |
25 | Invalid Path | A node failed because it attempted to read or set a value at an invalid payload path. |
26 | Invalid Type | A node failed because an argument passed to it was not of the correct data type. |
27 | Invalid JSON | A node failed because invalid JSON was provided as an argument. |
28 | Invalid FFT Input | A Fast Fourier Transform Node failed because the input array contained 0 items or more than the maximum number of items (2,048). |
29 | Invalid Loop Iteration | A Loop Node failed because it attempted to run more than the maximum number of iterations (2,048). |
30 | Invalid Delay Input | A Delay Node failed because an invalid length of time was provided (less than 0 seconds or more than 60 seconds). |
31 | Invalid RMS Input | A Root Mean Square Node failed because the input array contained 0 items or more than the maximum number of items (2,048). |
32 | Invalid Array Index | An Array Node failed because an operation that required an index received an index that was out of range for the provided array (less than 0 or greater than the index of the array’s last item). |
33 | Array Sort Error | An Array Node failed because it attempted to execute a “Sort” operation on an array containing values of incongruous types (such as a mix of strings and numbers). |
34 | Array Sum Error | An Array Node failed because it attempted to execute a “Sum” operation on an array containing non-numeric values. |
35 | Invalid Store Value Input | A Storage: Set Value Node failed because an operation was attempted on a value of the wrong type (such as incrementing a stored value by the amount of a string), or if a storage key resolved to a blank string. |
36 | Invalid Get Value Input | A Storage: Get Value Node failed because the storage key resolved to a blank string. |
37 | Invalid Device ID | eea_get_device_id failed because it returned an invalid device ID. |
38 | Invalid Trace Config | eea_config_set_trace_level failed because it attempted to set a trace level higher than was allowed by the compiler configuration. |
39 | Invalid Debug Config | eea_config_set_debug_enabled failed because it attempted to enable Debug Node messages when compiler configuration does not allow doing so. |
40 | Invalid Storage Config | eea_config_set_storage_size failed because 0 was passed in as the requested storage size. |
Reading the Bundle Identifier and Interface Version
The WASM bundle exposes information about itself that can be accessed by your native code.
Bundle Identifier
The bundle identifier is a string that contains the unique ID for the bundle deployed to this device. It is required when reporting back to the platform via the Hello Message. The bundle ID can be accessed in the following ways:
- Read from the
bundleIdentifier
WASM custom section. -
Decoded as a string using the following two WASM globals:
BUNDLE_IDENTIFIER_LENGTH
: Pointer to au8
that contains the length of the bundle ID string, in bytes.BUNDLE_IDENTIFIER
: Pointer to the bundle ID string.
Interface Version
The interface version represents the version of the EEA API (i.e. this document). If the signatures of any function in this document change, the version of the interface will also change. Your native code can inspect this version to ensure it contains the correct implementation to support the bundle you’ve received.
The interface version can be read from the interfaceVersion
WASM custom section.
Exported Functions
These function are exported by the EEA WASM module.
eea_config_set_debug_enabled
i32 eea_config_set_debug_enabled(enabled: bool)
Changes the debug configuration for the EEA. This controls whether the EEA will invoke eea_send_message
whenever a Debug Node is invoked. You may want to disable this to reduce the amount of bandwidth consumed by your device. This configuration option is constrained by the disableDebugMessage
compiler option sent in the Hello Message. For example, if your device sent disableDebugMessage: true
, attempting to enable debug messages using this function will have no effect and will return an error code.
Parameters
enabled
: Whether debug messages will be sent by the EEA. Defaults totrue
.
Returns
- Error code.
0
for success. See Error Codes for more details.
eea_config_set_message_buffer_lengths
i32 eea_config_set_message_buffer_lengths(topic_buffer_length: u16, payload_buffer_length: u32)
Sets the length, in bytes, of the message buffers, which are used to send data from native code to the EEA.
Parameters
topic_buffer_length
: The size, in bytes, to pre-allocate for the message buffer’s topic. Defaults to 256 bytes.payload_buffer_length
: The size, in bytes, to pre-allocate for the message buffer’s payload. Defaults to 4096 bytes.
Returns
- Error code.
0
for success. See Error Codes for more details.
eea_config_set_queue_size
i32 eea_config_set_queue_size(size: u32)
Limits the total amount of memory the workflow queue can consume. Each call to eea_loop
will execute, at most, one workflow. So for example, if your native code calls eea_direct_trigger
or eea_message_received
several times between calls to eea_loop
, each of those triggers and their corresponding workflows will be queued for eventual execution in subsequent calls to eea_loop
.
If the queue is full, any attempts to trigger additional workflows will fail with Error Code 19
(triggerQueueFull).
Parameters
size
: The maximum amount of memory, in bytes, that the workflow queue can consume. Defaults to1024000
bytes (1024 * 1000).
Returns
- Error code.
0
for success. See Error Codes for more details.
eea_config_set_storage_interval
i32 eea_config_set_storage_interval(interval: u32)
Sets the interval, in milliseconds, that the EEA will invoke eea_storage_save
to persist all workflow storage values. Setting this value to 0
will disable persisting workflow storage and storage values will only exist in memory.
Parameters
interval
: The interval, in milliseconds, that the EEA will persist workflow storage. Defaults to0
(disabled).
Returns
- Error code.
0
for success. See Error Codes for more details.
eea_config_set_storage_size
i32 eea_config_set_storage_size(size: u32)
Sets the maximum size, in bytes, that workflow storage is allowed to consume. This also controls the size of the buffer sent to eea_storage_read
when the EEA needs to read persisted storage values. For embedded devices with constrained memory, you will likely be required to lower this value.
If a Storage: Set Value Node is used and the value cannot be stored because the result would cause storage to exceed this limit, the workflow will error.
Parameters
size
: The maximum size, in bytes, that workflow storage will consume. Defaults to1024000
(1024 * 1000).
Returns
- Error code.
0
for success. See Error Codes for more details.
eea_config_set_trace_level
i32 eea_config_set_trace_level(level: u8)
Filters the trace logs by a specific level. The maximum level is constrained by the traceLevel
compiler option sent in the Hello Message. For example, if your device sent traceLevel: 1
in the compiler options, attempting to set the trace level to 2
using this function will have no effect and will return an error code.
Parameters
-
level
: The trace log level. Defaults to0
(disabled). Options:0
: Disabled1
: Errors only2
: All/verbose
Returns
- Error code.
0
for success. See Error Codes for more details.
eea_direct_trigger
i32 eea_direct_trigger(id_length: u16, payload_length: u32)
Invokes one or more Direct Triggers. The trigger ID and payload must be populated using the buffers provided by eea_set_message_buffers
before invoking this function. See Message Buffers for details. The payload must be a valid JSON string. Workflows can contain any number of Direct Triggers. The user-specified ID can be the same for multiple triggers. The trigger’s ID is defined using the workflow editor. This function invokes every Direct Trigger that matches the ID provided.
Parameters
id_length
: The length, in bytes, of the UTF-8 encoded trigger ID string.payload_length
: The length, in bytes, of the UTF-8 encoded payload JSON string.
Returns
- Error code.
0
for success. See Error Codes for more details.
eea_init
i32 eea_init()
Initializes the EEA. Must be the first thing called after loading the EEA WASM bundle. This function will invoke several imported functions as part of the initialization process:
eea_set_message_buffers
: Provides the native code with pre-allocated message buffers.eea_get_device_id
: Provides the EEA with the current device ID.eea_storage_read
: Provides the EEA with the persisted workflow storage values.
Returns
- Error code.
0
for success. See Error Codes for more details.
eea_loop
i32 eea_loop(ticks_milliseconds: u64)
The main worker loop for the EEA. This must be called continually by the native code with minimal delay between invocations (< 100ms). This function requires the current system time, in milliseconds, either since Epoch or since boot (whichever is available on your system).
A call to eea_loop
will execute, at most, a single workflow in each iteration. Workflows are executed in the order they are queued. For example, if your native code queued a workflow by invoking eea_direct_trigger
, and there are no other workflows queued, the next call to eea_loop
will execute the workflow. That same call to eea_loop
may result in a timer being triggered, but since a workflow has already run on this iteration, any workflows triggered from that timer will be queued for the next call to eea_loop
.
Parameters
ticks_milliseconds
: The milliseconds of time since Epoch or since device boot.
Returns
- Error code.
0
for success. See Error Codes for more details.
eea_message_received
i32 eea_message_received(topic_length: u16, payload_length: u32)
Forwards MQTT data to the EEA. The topic and payload must be populated using the buffers provided by eea_set_message_buffers
before invoking this function. See Message Buffers for details.
Parameters
topic_length
: The length, in bytes, of the UTF-8 encoded topic string.payload_length
: The length, in bytes, of the UTF-8 encoded payload string.
Returns
- Error code.
0
for success. See Error Codes for more details.
eea_set_connection_status
i32 eea_set_connection_status(connected: bool)
Called by your native code whenever the MQTT connection status changes. This information is primarily used to set the isConnectedToWnology
field that is automatically added to every workflow payload.
Parameters
connected
: Whether the device is connected to the MQTT broker (true
= connected,false
= not connected).
Returns
- Error code.
0
for success. See Error Codes for more details.
eea_shutdown
i32 eea_shutdown()
Called by your native code prior to destroying an EEA WASM bundle. This is primarily used to invoke eea_storage_save
to ensure any pending workflow storage values are persisted. The most common reason to invoke eea_shutdown
is when a device receives a new WASM bundle. The native code must destroy the prior bundle before loading the new bundle.
Returns
- Error code.
0
for success. See Error Codes for more details.
Imported Functions
These functions are defined in native code and imported into the EEA WebAssembly module.
eea_get_device_id
i32 eea_get_device_id(out_id: u8*, buffer_length: u8, out_id_length: u8*)
Invoked by the EEA to obtain the current WEGnology device ID. This is invoked as part of the initialization process when eea_init
is called.
Parameters
out_id
: Pointer to the pre-allocated buffer on which to place the UTF-8 encoded device ID string.buffer_length
: The length, in bytes, of theout_id
pre-allocated buffer.out_id_length
: Pointer to the pre-allocatedu8
to write the number of bytes written toout_id
.
Returns
- Error code.
0
for success. Return any other value to represent a custom error code.
eea_get_time
i32 eea_get_time(out_ticks_milliseconds: u64*)
Invoked by the EEA to retrieve the current system time since Epoch. If your system does not support time since Epoch, you must provide 0
.
Parameters
out_ticks_milliseconds
: The milliseconds of time since Epoch. If time since Epoch is not available, set this value to0
.
Returns
- Error code.
0
for success. Return any other value to represent a custom error code.
eea_send_message
i32 eea_send_message(topic: u8*, topic_length: u16, payload: u8*, payload_length: u32, qos: u8)
Invoked by the EEA whenever a message needs to be sent to the WEGnology platform. This is primarily done whenever an MQTT, Device State, or Debug Node is executed. Every message includes a topic, payload, and quality of service (QoS) value. If your application is connected to WEGnology’s MQTT broker, these messages can be directly published, as-is, using your MQTT client.
Parameters
topic
: Pointer to the UTF-8 encoded topic string.topic_length
: The length, in bytes, of thetopic
string.payload
: Pointer to the UTF-8 encoded payload string.payload_length
: The length, in bytes, of thepayload
string.qos
: The MQTT QoS value. This will be0
for messages generated by the Debug Node and1
for messages generated by the MQTT and Device State Nodes.
Returns
- Error code.
0
for success. Return any other value to represent a custom error code.
eea_set_message_buffers
i32 eea_set_message_buffers(out_topic: u8*, out_topic_length: u16, out_message: u8*, out_message_length: u32)
Invoked by the EEA to provide pre-allocated buffers that are used when passing data from native code to the EEA by invoking eea_message_received
and eea_direct_trigger
. See Message Buffers for details.
Parameters
out_topic
: Pre-allocated buffer to hold a message topic UTF-8 string.out_topic_length
: Length, in bytes, of the topic buffer.out_message
: Pre-allocated buffer to hold a message payload UTF-8 string.out_message_length
: Length, in bytes, of the message buffer.
Returns
- Error code.
0
for success. Return any other value to represent a custom error code.
eea_sleep
i32 eea_sleep(milliseconds: u32)
Invoked by the EEA to sleep the executing thread. This is invoked whenever a Delay Node is executed.
Parameters
milliseconds
: The amount of time to sleep, in milliseconds.
Returns
- Error code.
0
for success. Return any other value to represent a custom error code.
eea_storage_read
i32 eea_storage_read(out_values: u8*, buffer_length: u32, out_values_length: u32*)
Invoked by the EEA to read persisted workflow storage values. This is called by eea_init
whenever a new WASM bundle is initialized. If you have no persisted workflow values, your native code can return 0
without populating anything in the provided buffers. This is the companion function to eea_storage_save
and the data you provide to the EEA should be identical to the data sent to your native code.
Parameters
out_values
: Pointer to the pre-allocated buffer to place the UTF-8 encoded string of storage values.buffer_length
: The length, in bytes, of the pre-allocatedout_values
buffer. The value is determined by the value passed toeea_config_set_storage_size
. Defaults to1024000
bytes (1024 * 1000).out_values_length
: Pre-allocatedu32
pointer to place the amount of data, in bytes, that has been written to theout_values
buffer.
Returns
- Error code.
0
for success. Return any other value to represent a custom error code.
eea_storage_save
i32 eea_storage_save(values: u8*, values_length: u32)
Invoked by the EEA to persist all workflow storage values. This function will be invoked on an interval defined by eea_config_set_storage_interval
and whenever eea_shutdown
is called (if the configured interval is not 0
). The values are provided as a JSON string and it’s up to your native code to determine how to save the data (e.g. file or flash storage).
Parameters
values
: Pointer to UTF-8 encoded string.values_length
: The length, in bytes, of the values string.
Returns
- Error code.
0
for success. Return any other value to represent a custom error code.
eea_trace
i32 eea_trace(message: u8*, message_length: u32, level: u8)
Invoked by the EEA to log tracing information. This is useful for early development or to debug unexpected behavior. The most common practice is to write these messages to a console or standard output. Tracing can be dynamically enabled or disabled by invoking eea_config_set_trace_level
.
Parameters
message
: Pointer to UTF-8 encoded string.message_length
: The length of the string, in bytes.level
: The level of the current trace message.
Returns
- Error code.
0
for success. Return any other value to represent a custom error code.
Registered Function API
The EEA can invoke custom functions that are defined in native code. This is primarily used to communicate with GPIO, sensors, or other peripherals. Registered functions are treated as WebAssembly imports.
To avoid naming conflicts with built-in functions, all registered functions are prefixed with eea_fn_
. For example, if you configure a Registered Function Node to invoke a function named read_accelerometer
, the compiled WebAssembly module will import a function named eea_fn_read_accelerometer
.
Inputs
Registered functions can optionally be passed input values from the current workflow payload. Input values are limited to the following types:
string
i8
,i16
,i32
,i64
u8
,u16
,u32
,u64
f32
,f64
bool
array
of any numerical type orbool
string
and array
inputs are represented by two arguments when passed to registered functions:
- Pointer to the string or array.
-
Length of the buffer (
u32
):- For strings, the length is in bytes.
- For arrays, the length is the number of elements in the array.
For example, if a Registered Function Node contains four inputs of the following types:
i8
string
f64
array
ofi32
values
The following is an example C-code function signature for the registered function:
int my_registered_function(
int8_t i8Val,
char *stringVal,
uint32_t stringBufferLen,
double f64Val,
int32_t *i32Vals,
uint32_t i32ValsLength)
Outputs
Registered functions can optionally provide output values that will be added to the current workflow payload. All outputs are provided to the registered function as pre-allocated pointers that must be populated by the native code. Output values are limited to the following types:
string
json
bool
i8
,i16
,i32
,i64
u8
,u16
,u32
,u64
f32
,f64
array
of any numerical type orbool
string
, array
, and json
outputs are represented by three arguments when passed to registered functions:
- Pre-allocated pointer to string or array to populate.
-
The length of the pre-allocated buffer (
u32
):- For strings, the length is the number of bytes in the pre-allocated buffer.
- For arrays, the length is the total number of elements the pre-allocated array can hold.
-
Pre-allocated
u32
pointer that must be populated with the amount of data written to the buffer:- For strings, the length is in bytes.
- For arrays, the length is in number of elements.
For example, if a registered function node contains four outputs of the following types:
i8
string
f64
array
ofi32
values
The following is an example C-code function signature for the registered function:
int my_registered_function(
int8_t *outi8Val,
char *outStringVal,
uint32_t stringBufferLen,
uint32_t *outStringBytesWritten,
double *outf64Val,
int32_t *outi32Vals,
uint32_t i32ValsLength,
uint32_t *outi32ValsWritten)
Return Values
Registered functions are required to return a u8
to represent success or error. On success, the function must return 0
. If the function failed, you can return any custom u8
to represent the error. Returned error codes are added to the workflow payload.
Was this page helpful?
Still looking for help? You can also search the WEGnology Forums or submit your question there.