resol-vbus-c

A C library for processing RESOL VBus data.

Continuous Integration GitHub release (latest by date) License Codecov

  • Source code: https://github.com/danielwippermann/resol-vbus-c
  • Documentation: https://danielwippermann.github.io/resol-vbus-c/
  • Doxygen-generated documentation: https://danielwippermann.github.io/resol-vbus-c/doxygen/_resol_v_bus_8h.html

Features

  • Encoding and decoding VBus data according to the protocol specification

Contributors

RESOL, VBus, VBus.net and others are trademarks or registered trademarks of RESOL - Elektronische Regelungen GmbH.

All other trademarks are the property of their respective owners.

License

resol-vbus-c is distributed under the terms of both the MIT license and the Apache License (Version 2.0).

See LICENSE.txt for details.

Details

This section describes details about some of the library's modules.

Error handling

Most of the functions in this library return a RESOLVBUS_RESULT which is an integer type under the hood. If the function did not encounter any error, the result will be RESOLVBUS_OK which is guaranteed to be 0. All other values are positive integers greater than zero. See include/ResolVBus.h for a list of possible values.

Error propagation could look like this:

RESOLVBUS_RESULT SomeEventHandler(...)
{
    RESOLVBUS_RESULT Result = 0;

    if (Result == 0) {
        Result = DoFirstThing();
    }

    if (Result == 0) {
        Result = DoSecondThing();
    }

    // ...

    return Result;
}

Internally the library uses a macro for error propagation: __WRAP(Expression), defined in the respective src/Debug{On,Off}.h. It:

  • does nothing if the Result local variable already is not equal to RESOLVBUS_OK
  • otherwise:
    • executes the expression and assigns its result to the Result local variable
    • if debugging is enabled and executing the expression resulted in an error, adds a backtrace info

See Debugging for details.

Inside the library the example above would look like this:

RESOLVBUS_RESULT SomeEventHandler(...)
{
    RESOLVBUS_RESULT Result = 0;

    __WRAP(DoFirstThing());

    __WRAP(DoSecondThing());

    // ...

    return Result;
}

LiveEncoder

The "LiveEncoder" module implements the RESOLVBUS_LIVEENCODER type and its associated functions, which can be used to convert VBus primitives (like e.g. packets) into their over-the-wire representation.

In addition to that it also supports energy management functions and timeout handling.

Initialization and integration

Before the encoder can be used, it must be initialized by calling ResolVBus_LiveEncoder_Initialize:

// ...
RESOLVBUS_LIVEENCODER Encoder = RESOLVBUS_LIVEENCODER_INITIALIZER;
uint8_t Buffer [512] = { 0 };
if (Result == RESOLVBUS_OK) {
    Result = ResolVBus_LiveEncoder_Initialize(&Encoder, Buffer, sizeof (Buffer), LiveEncoderEventHandler));
}
// ...

The function takes four arguments: the encoder instance itself, a transmission buffer as well as its length and an event handler function.

The transmission buffer is used by the ResolVBus_LiveEncoder_Queue... functions to store the over-the-wire representation of the VBus primitives. It must be large enough to hold the largest VBus primitive that is expected to be transmitted.

The event handler is a function that is called whenever the encoder wants to inform its user about an event:

  • RESOLVBUS_LIVEENCODEREVENTTYPE_IDLE: the encoder is idle and would accept queueing VBus primitives for transmission
  • RESOLVBUS_LIVEENCODEREVENTTYPE_TRANSMIT: the encoder wants to transmit a chunk of a VBus primitive

The event handler is called from the event pump implemented in ResolVBus_LiveEncoder_HandleTimer. This function must be called "frequently" (see its documentation for details). The function takes the time that has passed since the last call as its second argument. That time is used to drive forward the internal state machine.

The state machine and its phases

The encoder contains a state machine that can be in one of the following phases:

  • RESOLVBUS_LIVEENCODERPHASE_IDLE: nothing to do yet, can be used to queue VBus primitives for transmission, emits RESOLVBUS_LIVEENCODEREVENTTYPE_IDLE event
  • RESOLVBUS_LIVEENCODERPHASE_TRANSMITTING: currently transmitting VBus data, emits RESOLVBUS_LIVEENCODEREVENTTYPE_TRANSMIT event
  • RESOLVBUS_LIVEENCODERPHASE_GAININGENERGY: used to regain energy after transmitting VBus data
  • RESOLVBUS_LIVEENCODERPHASE_SUSPENDED: suspended until manually resumed
  • RESOLVBUS_LIVEENCODERPHASE_SUSPENDEDWITHTIMEOUT: suspended until manually resumed or timeout elapses

Queuing VBus primitives for transmission

The encoder provides several ResolVBus_LiveEncoder_Queue... functions to queue VBus primitives into its internal transmit buffer. On the next call to ResolVBus_LiveEncoder_HandleTimer the actual transmission is started.

Queuing VBus primitives is only allowed while the encoder is idle. If the encoder is used in a VBus master context, it is recommended to queue VBus primitives for the RESOLVBUS_LIVEENCODEREVENTTYPE_IDLE event handling code. If the encoder is used in a VBus minion context, it is recommended to manually resume the encoder from its RESOLVBUS_LIVEENCODERPHASE_SUSPENDED phase before actually queueing the response.

Energy management

The VBus transports both information and electrical energy. Since transmitting information disrupts transporting energy, care must be taken to not transmit too much data at once. For that purpose the encoder implements a basic energy management functionality.

That functionality can be configured using three configuration fields:

  • MaxEnergy: maximum amount of energy that can be consume before a transmission must be segmented into multiple parts
  • EnergyLossPerByte: assumed loss of energy per transmitted byte
  • EnergyGainPerUs: assumed gain of energy per microsecond in the RESOLVBUS_LIVEENCODERPHASE_GAININGENERGY phase

When the encoder is in its idle phase the energy level is assumed to be at its maximum capacity. When data is queued into the transmit buffer, the encoder switches into its RESOLVBUS_LIVEENCODERPHASE_TRANSMITTING phase. It then transmits as many bytes as possible, summing up the energy loss accordingly, but not exceeding the MaxEnergy value. After the bytes have been transmitted the encoder switches into the RESOLVBUS_LIVEENCODERPHASE_GAININGENERGY phase, waiting for the consumed energy to refill. Once the energy level is back at full capacity the encoder either continues transmitting the remaining bytes from the buffer or switches into one of the suspended or idle phases.

Setting MaxEnergy to zero disables the energy management functionality.

Suspension and timeout handling

The VBus uses a single-master topology. Only the master is allowed to transmit at any time. Minions attached to the VBus must not transmit unless they have been requested to do so by the master. In those situations the master has to grant the minion a predefined time period to transmit its reply.

The encoder supports both the VBus master and VBus minion contexts.

In a VBus master context the user of the encoder can queue a VBus request primitive and then use ResolVBus_LiveEncoder_SuspendWithTimeout to request that the encoder enters a suspended state for a predefined period of time after the request was transmitted, giving the minion enough time to respond to the request.

In a VBus minion context the user of the encoder can immediately use ResolVBus_LiveEncoder_Suspend to switch the encoder into a suspended state for an indefinite period of time. Once the request has been received, the encoder can be manually resumed using ResolVBus_LiveEncoder_Resume, the reply can be queued for transmission and then ResolVBus_LiveEncoder_Suspend can be used to request entering the suspended state again after the transmission.

LiveDecoder

The "LiveDecoder" module implements the RESOLVBUS_LIVEDECODER type and its associated functions, which can be used to parse a stream of over-the-wire VBus bytes into their respective primitives (like e.g. packets).

Initialization and integration

Before the decoder can be used, it must be initialized by calling ResolVBus_LiveDecoder_Initialize:

// ...
RESOLVBUS_LIVEDECODER Decoder = RESOLVBUS_LIVEDECODER_INITIALIZER;
uint8_t FrameDataBuffer [512] = { 0 };
if (Result == RESOLVBUS_OK) {
    Result = ResolVBus_LiveDecoder_Initialize(Decoder, FrameDataBuffer, sizeof (FrameDataBuffer), LiveDecoderEventHandler);
}
// ...

The function takes four arguments: the decoder instance itself, an optional buffer for holding decoded frame data as well as its length and an event handler function.

The optional frame data buffer is used to reassemble the contents of multiple VBus packet or telegram frames. If no buffer is provided, the reassembly must be done manually.

The event handler is a function that is called whenever the decoder wants to inform its user about an event:

  • RESOLVBUS_LIVEDECODEREVENTTYPE_PACKETHEADER: a VBus version 1.x packet header has been decoded
  • RESOLVBUS_LIVEDECODEREVENTTYPE_PACKETFRAME: a VBus version 1.x packet frame has been decoded
  • RESOLVBUS_LIVEDECODEREVENTTYPE_PACKETEND: a VBus version 1.x packet (consisting of the header and all associated frames) has been decoded
  • RESOLVBUS_LIVEDECODEREVENTTYPE_DATAGRAM: a VBus version 2.x datagram has been decoded
  • RESOLVBUS_LIVEDECODEREVENTTYPE_TELEGRAMHEADER: a VBus version 3.x telegram header has been decoded
  • RESOLVBUS_LIVEDECODEREVENTTYPE_TELEGRAMFRAME: a VBus version 3.x telegram frame has been decoded
  • RESOLVBUS_LIVEDECODEREVENTTYPE_TELEGRAMEND: a VBus version 3.x telegram (consisting of the header and all associated frames) has been decoded

The event handler is called from the ResolVBus_LiveDecoder_Decode function which should be called whenever VBus bytes have been received.

LiveTransceiver

The "LiveTransceiver" module implements the RESOLVBUS_LIVETRANSCEIVER type and its associated functions, which combines the functionalities of the "LiveEncoder" and "LiveDecoder" modules to communicate with a VBus controller, e.g. to access its parameters.

Initialization and integration

Before the transceiver can be used, it must be initialized by calling ResolVBus_LiveTransceiver_Initialize:

// ...
RESOLVBUS_LIVETRANSCEIVER Transceiver = RESOLVBUS_LIVETRANSCEIVER_INITIALIZER;
uint8_t EncoderBuffer [512];
uint8_t DecoderBuffer [512];
if (Result == RESOLVBUS_OK) {
    Result = ResolVBus_LiveTransceiver_Initialize(Transceiver, EncoderBuffer, sizeof (EncoderBuffer), DecoderBuffer, sizeof (DecoderBuffer), LiveTransceiverHandler);
}
// ...

The function takes six arguments: the transceiver instance itself, a transmission buffer as well as its length, an optional buffer for holding decoded frame data as well as its length and an event handler function.

The transmission buffer is used by the ResolVBus_LiveEncoder_Queue... functions to store the over-the-wire representation of the VBus primitives. It must be large enough to hold the largest VBus primitive that is expected to be transmitted.

The optional decoder buffer is used to reassemble the contents of multiple VBus packet or telegram frames. If no buffer is provided, the reassembly must be done manually.

The event handler is a function that is called whenever the transceiver wants to inform its user about an event:

  • RESOLVBUS_LIVETRANSCEIVEREVENTTYPE_TIMEOUT: the action started on the transceiver has timed out (optionally after several retries)
  • RESOLVBUS_LIVETRANSCEIVEREVENTTYPE_ENCODER: the "LiveEncoder" part of the transceiver has emitted an event (e.g. to transmit data)
  • RESOLVBUS_LIVETRANSCEIVEREVENTTYPE_DECODER: the "LiveDecoder" part of the transceiver has emitted an event (e.g. from receiving VBus primitives)

The event handler can be called from multiple sources:

  • from the event pump implemented in ResolVBus_LiveTransceiver_HandleTimer: this function must be called "frequently" (see its documentation for details). The function takes the time that has passed since the last call as its second argument. That time is used to drive forward the internal state machine.
  • from the decoding state machine in ResolVBus_LiveTransceiver_Decode: this function must be called for every chunk of VBus bytes. If a VBus primitive can be decoded from those bytes, an event is emitted.

Accessing the "LiveEncoder"

To queue VBus primitives to be transmitted the user can access the "LiveEncoder" part using the ResolVBus_LiveTransceiver_GetEncoder function.

Accessing the "LiveDecoder"

To decode a chunk of bytes received, those bytes have to be passed to the ResolVBus_LiveTransceiver_Decode function.

Performing an action

To communicate with a VBus controller an action can be started on the "LiveTransceiver". Those actions can:

  • optionally transmit VBus primitives (requests)
  • wait for reception of VBus primitives (requests or responses)
  • have an optional timeout and retry managment

The following actions are currently implemented:

  • ResolVBus_LiveTransceiver_WaitForFreeBus: wait for a VBus datagram with command 0x0500, indicating that the VBus controller offers control over the VBus timing to another device
  • ResolVBus_LiveTransceiver_ReleaseBus: return VBus timing control back to the VBus controller
  • ResolVBus_LiveTransceiver_GetValueById: get a value from the controller
  • ResolVBus_LiveTransceiver_SetValueById: set a value in the controller
  • ResolVBus_LiveTransceiver_GetValueIdByIdHash: lookup a value's ID by the corresponding ID hash

All those actions take multiple arguments:

  • the transceiver instance
  • zero or more action related arguments like VBus controller address, value ID, etc.
  • an optional RESOLVBUS_LIVETRANSCEIVEROPTIONS pointer, if custom timeout and/or retry options should be used
  • a function pointer to report completion or timeout back to the caller

Debugging

The library includes a minimal debugging functionality that can be enabled by compiling the library with the define RESOLVBUS_DEBUG=1.

Every module in the library includes src/Debug.h. That file simply checks RESOLVBUS_DEBUG and either includes src/DebugOn.h (if RESOLVBUS_DEBUG >= 1) or src/DebugOff.h.

Both src/Debug{On,Off}.h provide the same set of macros:

  • __FAIL(ErrorSuffix)
  • __WRAP(Expression)
  • __ASSERT_WITH(ErrorSuffix, Expression)
  • __DLOG(FormatString, ...)

All of those macros first check, whether the local variable Result is still set to RESOLVBUS_OK. If it is not, the macro does nothing.

__FAIL(ErrorSuffix)

The __FAIL macro:

  • checks the local variable Result as described above, optionally skipping the other steps below
  • sets the local variable Result to RESOLVBUS_ERROR_##ErrorSuffix
  • replaces the contents in the backtrace buffer with information about the failure (if debugging is enabled)

__WRAP(Expression)

The __WRAP macro:

  • checks the local variable Result as described above, optionally skipping the other steps below
  • executes the expression
  • stores the result of the expression into the local variable Result
  • if the local variable Result now is not RESOLVBUS_OK it adds information about the call site to the backtrace buffer (if debugging is enabled)

__ASSERT_WITH(ErrorSuffix, Expression)

The __ASSERT_WITH macro:

  • checks the local variable Result as described above, optionally skipping the other steps below
  • executes the expression
  • if the result of the expression is false:
    • sets the local variable Result to RESOLVBUS_ERROR_##ErrorSuffix
    • replaces the contents in the backtrace buffer with information about the failure (if debugging is enabled)

__DLOG(FormatString, ...)

The __DLOG macro:

  • checks the local variable Result as described above, optionally skipping the other steps below
  • uses printf to print the formatted string to stdout (if debugging is enabled)

The internal backtrace buffer

Enabling the debugging functionality adds a library-internal backtrace buffer. That buffer is used by the macros __FAIL, __WRAP and __ASSERT_WITH in case an error occurred to track the error's reason and call stack.

Along with that buffer a set of functions is added to work with the backtrace buffer:

void ResolVBus_ResetBacktrace(const char *Message, const char *Expression, const char *File, int Line, const char *Func);
void ResolVBus_AddBacktrace(const char *Expression, const char *File, int Line, const char *Func);
const char *ResolVBus_GetBacktrace(void);
void ResolVBus_PrintBacktrace(void);

Examples

This section describes the examples provided with this library.

Master example

A minimal VBus master is implemented in the examples/Master.c example. It features:

  • a LiveDecoder instance for decoding incoming VBus bytes
  • a LiveEncoder instance for transmitting VBus primitives
  • a minimal state machine for repeatedly transmitting information

The main function

The main function:

  • initializes both the LiveDecoder and LiveEncoder instances using the __Initialize function
  • opens a serial port / network socket / ...
  • enters a loop
    • reads from the serial port / network socket / ... with a small timeout / non-blocking
    • calculates the time passed since the last loop cycle
    • decides whether it would be a appropriate time to start sending information (e.g. once every second)
    • calls __HandleLoopCycle handing over that information for state machine handling

Initialization

The __Initialize function:

  • intializes both the LiveDecoder and LiveEncoder instances
  • suspends the LiveEncoder (it will be resumed once it is supposed to transmit information)

Handling state machine

The __HandleLoopCycle function:

  • starts the internal state machine if requested and possible (because the LiveEncoder is idle)
  • decodes received VBus bytes using the LiveDecoder, calling __DecoderHandler if an event occurred
  • advances the LiveEncoder's state machine, calling __EncoderHandler if an event occurred

Reacting to LiveDecoder events

The __DecoderHandler function is called whenever the LiveDecoder emits an event. This function can be used to store received information from VBus minions.

Reacting to LiveEncoder events

The __EncoderHandler function is called whenever the LiveEncoder emits an event:

  • RESOLVBUS_LIVEENCODEREVENTTYPE_IDLE can be used to advance the internal state machine
  • RESOLVBUS_LIVEENCODEREVENTTYPE_TRANSMIT must be used to transmit the provided bytes over the serial port / network socket / ...

Minion Example

A minimal VBus minion is implemented in the examples/Minion.c example. It features:

  • a LiveDecoder instance for decoding incoming VBus bytes
  • a LiveEncoder instance for transmitting VBus primitives

The main function

The main functoin:

  • initializes both the LiveDecoder and LiveEnceder instances using the __Initialize function
  • opens a serial port / network socket / ...
  • enters a loop
    • reads from the serial port / network socket / ... with a small timeout / non-blocking
    • calculates the time passed since the last loop cycle
    • calls __HandleLoopCycle handing over that information for state machine handling

Initialization

The __Initialize function:

  • intializes both the LiveDecoder and LiveEncoder instances
  • suspends the LiveEncoder (it will be resumed once it is supposed to transmit information)

Handling state machine

The __HandleLoopCycle function:

  • decodes received VBus bytes using the LiveDecoder, calling __DecoderHandler if an event occurred
  • advances the LiveEncoder's state machine, calling __EncoderHandler if an event occurred

Reacting to LiveDecoder events

The __DecoderHandler function is called whenever the LiveDecoder emits an event. This function can be used to react to requests from the VBus master. For that it must:

  • first resume the suspended LiveEncoder to allow any ResolVBus_LiveEncoder_Queue... functions to be called
  • call the appropriate ResolVBus_LiveEncoder_Queue... function
  • suspend the LiveEncoder again to wait for the next request

Reacting to LiveEncoder events

The __EncoderHandler function is called whenever the LiveEncoder emits an event:

  • RESOLVBUS_LIVEENCODEREVENTTYPE_IDLE should never be emitted because the LiveEncoder is kept in a suspended state most of the time
  • RESOLVBUS_LIVEENCODEREVENTTYPE_TRANSMIT must be used to transmit the provided bytes over the serial port / network socket / ...