Debugging and error handling

This page describes how to check errors reported by electricui-embedded and recommends strategies for processing and passing error codes to the UI.

If you're having issues getting the electricui-embedded communications running, try here instead:

All code examples described in this document are implemented here:

Status Flags

Publicly accessible electricui-embedded functions return a byte which contains dense status information. This eui_errors_t status bit-field provides flags for the inbound parser, automatic variable management stages, and outbound send functionality.

The type, and the error enums are in eui_types.h.

/**
* @brief Error data type
*
* Contains bitfields representing the states of Electric UI internals.
* Matching member values against relevant enums allows for state/error checks.
*/
typedef struct {
unsigned parser : 2; ///< Refer to eui_parse_errors
unsigned untracked : 1;
unsigned action : 2; ///< Refer to eui_action_errors
unsigned ack : 1; ///< Refer to eui_ack_errors
unsigned query : 2; ///< Refer to eui_query_errors
unsigned reserved : 1; ///< Unused bit - reserved for future use.
} eui_errors_t;

To read these status values, declare a eui_errors_t to hold the returned byte when parsing data.

eui_errors_t parse_status = eui_parse( inbound_byte, &serial_interface );

Depending on your requirements, either setup an exhaustive match with switch() for each subsystem (see electricui-embedded/examples/basic/status-error-callbacks linked at the top of the page), or check for a specific error:

if( parse_status.parser == EUI_PARSER_ERROR )
{
// CRCC check failed, or an invalid packet layout was received
//
}

In general, we expect the following examples to be the next most useful/relevant errors for identifying issues with incorrect configuration of Electric UI or tracked variables,

// An inbound callback trigger was received, but the tracked pointer was null
parse_status.action == EUI_ACTION_CALLBACK_ERROR
// An inbound payload was trying to write data (or with an offset) which exceeds
// the tracked variable's memory size/position
parse_status.action == EUI_ACTION_WRITE_ERROR

The status codes for ack, query contain the status code returned by the outbound packet generation/send functions.

If either of these members return non-zero values, the response failed. This is pretty uncommon if the communications link works at all, as the common cause of failure is missing the data output callback function.

Interface Callbacks

When setting up the interface with output function callback, you can also configure a callback which fires near the end of Electric UI's internal handling steps. This provides the developer with the ability to reach into the eui_interface_t structure manually and filter/check/read data before Electric UI clears the buffer out.

Declare a C function which takes a byte (or the eui_callback_code enum) as the only argument:

void eui_callback( uint8_t message );

Modify the interface setup to provide the pointer to that callback function:

eui_interface_t serial_interface = EUI_INTERFACE_CB( &serial_write, &eui_callback );

When called, the argument can provide a basic bit of information about the type of packet received.

/**
* @brief Interface Callback codes
*
* Used as a flag passed to the developer-space interface callback function to indicate specific callback source.
*/
enum eui_callback_codes {
EUI_CB_GENERIC = 0, ///< Generic callback from library - unused
EUI_CB_TRACKED, ///< Represents callback after a tracked message has been handled
EUI_CB_UNTRACKED, ///< Represents callback after an untracked message has been handled
EUI_CB_PARSE_FAIL, ///< Represents a failure in parsing (CRC errors, etc)
EUI_CB_LAST_ENUM, ///< Used to denote the number of callback flags
};

We recommend exhaustively matching on this enum using switch( message ), then implementing handler logic specific to tracked or untracked variables. Because you own the eui_interface_t scope, it's possible to directly reach into the state and determine the parser state, inbound header and payload buffers, etc.

In most cases, matching on message identifier is most common, with the ability to read the payload and header information.

eui_header_t header = serial_interface.packet.header;
uint8_t *name_rx = serial_interface.packet.id_in;
void *payload = serial_interface.packet.data_in;
if( strcmp( (char*)name_rx, "led" ) == 0 )
{
// Check the inbound type is correct
if( header.type == TYPE_UINT16 )
{
// Do something with the payload data
}
}

These callbacks are fired last in the packet handler, which means inbound tracked packets have already been written to the local store, responses generated, etc.

These buffers will be cleared after the callback returns, so DO NOT keep pointers to the data around for future use. Copy data/states explicitly (or use tracked variables) or ensure usage of the pointer can not continue.

Error handling

When an error occurs during normal use, what do you do? This depends on what level of error is acceptable for your product

  1. Ignore the error with the hope that it's an isolated occurrence (default),
  2. Log the error, or increment an error counter to build failure metrics (build a counter in the status flag check described above),
  3. Stop communications and disable the system, or present a fault message to the user.