resol-vbus-c
A C library for processing RESOL VBus data.
- 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
Legal Notices
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 toRESOLVBUS_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
- executes the expression and assigns its result to the
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 transmissionRESOLVBUS_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, emitsRESOLVBUS_LIVEENCODEREVENTTYPE_IDLE
eventRESOLVBUS_LIVEENCODERPHASE_TRANSMITTING
: currently transmitting VBus data, emitsRESOLVBUS_LIVEENCODEREVENTTYPE_TRANSMIT
eventRESOLVBUS_LIVEENCODERPHASE_GAININGENERGY
: used to regain energy after transmitting VBus dataRESOLVBUS_LIVEENCODERPHASE_SUSPENDED
: suspended until manually resumedRESOLVBUS_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 partsEnergyLossPerByte
: assumed loss of energy per transmitted byteEnergyGainPerUs
: assumed gain of energy per microsecond in theRESOLVBUS_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 decodedRESOLVBUS_LIVEDECODEREVENTTYPE_PACKETFRAME
: a VBus version 1.x packet frame has been decodedRESOLVBUS_LIVEDECODEREVENTTYPE_PACKETEND
: a VBus version 1.x packet (consisting of the header and all associated frames) has been decodedRESOLVBUS_LIVEDECODEREVENTTYPE_DATAGRAM
: a VBus version 2.x datagram has been decodedRESOLVBUS_LIVEDECODEREVENTTYPE_TELEGRAMHEADER
: a VBus version 3.x telegram header has been decodedRESOLVBUS_LIVEDECODEREVENTTYPE_TELEGRAMFRAME
: a VBus version 3.x telegram frame has been decodedRESOLVBUS_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 deviceResolVBus_LiveTransceiver_ReleaseBus
: return VBus timing control back to the VBus controllerResolVBus_LiveTransceiver_GetValueById
: get a value from the controllerResolVBus_LiveTransceiver_SetValueById
: set a value in the controllerResolVBus_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
toRESOLVBUS_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 notRESOLVBUS_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
toRESOLVBUS_ERROR_##ErrorSuffix
- replaces the contents in the backtrace buffer with information about the failure (if debugging is enabled)
- sets the local variable
__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 machineRESOLVBUS_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 timeRESOLVBUS_LIVEENCODEREVENTTYPE_TRANSMIT
must be used to transmit the provided bytes over the serial port / network socket / ...