🢀 Back to programming guide
Your application is responsible for booting the device and initialising the hardware. You will have to:
Once your device is booted and the application/firmware is running, the next step is to initialise an instance of the stack. The stack library provides a static factory function for this. To create an instance of the stack call:
TSNStack *pStack = TSNStack::newInstance();
Assuming you didn’t run out of memory already, you will now have a pointer to a stack instance. The next step is to initialise it. The prototype for the initialisation function is:
virtual bool init(IConfigLoader *configLoader, IPersister *persister);
The init
function takes two parameters, pointers to a config loader and persister. The config loader is responsible for loading the stack configuration (and entity model for the AVB device), while the persister is responsible for handling persistent data. The stack provides two implementations of each, one to handle XML data, and the other to handle binary data.
!!! info Configuration data describes the default (factory boot) state of the device, whilst the persistent data overrides some of the configuration with ‘current’ settings. So for example, your AVB device might appear as ‘ACME Loudspeaker’ by default (on first boot), so this name string will be in the configuration data. The end user may rename the device ‘studio main L’, and this name string would be stored in the persistent data. Another example is that the configuration data contains lists of all supported streams and stream formats, and the default stream format, whereas the persistent data would store the currently selected stream format (which persists across reboots).
The actual configuration data stored in XML format or binary format is identical, and contains things such as:
XML configuration data may be loaded directly into the stack at initialisation (/boot) time, or alternatively may be converted to a binary format to be embedded into the AVB device to be loaded into the stack at initialisation (/boot) time.
XML is a very convenient format to describe an AVB entity and AVB stack configuration, and will usually be used at some point during product development to create your device’s entity model and configuration. For this reason the stack can parse XML content (via the XML config loader), and persist data in XML format too. This is particularly useful on systems with a file system, where XML files can be stored, read and saved. XML files may however be excessively large for small embedded systems and so the Sienda AVB stack also allows proprietary binary files to be used for stack/entity configuration and persistency. The binary files are usually much smaller than the XML equivalents, and are more suited to baking into executable files or storing in eeprom or flash (with no file system). Sienda provide a small command line application entityConvert to convert between XML format and binary format, so that firmware projects may contain an XML file in the project sources, which is converted to binary format and baked into the executable as part of the build process.
Size comparisons for some example configuration files are shown below:
description | XML data size | binary data size |
---|---|---|
simple loudspeaker | 17KB | 3KB |
large mixing desk | 265KB | 24KB |
48 port switch | 7KB | 2KB |
As can be seen the size of the binary data is considerably smaller than that of the XML. Another consideration is that the runtime memory requirements of parsing XML data is much higher than that of parsing binary data. Parsing XML at runtime requires at least the same amount of memory again (to construct an in-memory XML tree), whereas binary data is read directly from the buffer provided to the stack at initialisation. For memory constrained embedded devices Sienda highly recommend that binary configuration data is used by the device at runtime.
When persistent data is written (via a stack callback function), the binary persister will only write the values/bytes that actually need modifying, whereas the XML persister will request that the whole XML persistency data is rewritten. For this reason it may be prudent to use binary data when persistent storage is eeprom or flash based and so access may be slow, or where the number of eeprom/flash write cycles is limited and must be considered. (Please note that even when using binary persistent data it is prudent to use multiple banks for wear levelling and atomic write handling (brickless persistency)).
If you would like to use an XML file to configure the entity model, and XML to represent the persistent data for the system, then you can instantiate an XML config loader:
virtual IConfigLoader *createXMLConfigLoader(char *entityContent);
Pass the XML content directly to this function and an XML config loader will be returned, which can be passed to the stack init function.
If you are using XML to configure the entity and stack then you probably also want to use XML to manage persistent data. Create an XML persister using the following class factory:
virtual IPersister *createXMLPersister(char *persisterContent);
If persistent data already exists then pass it in and the stack will initialise with the device in the persisted state. If no persisted data exists yet (first boot) then pass either nullptr
or empty XML content.
If you would prefer to use a binary blob to configure the stack and entity, and to manage persistent data, then instantiate a binary config loader to pass to the stack init function:
virtual IConfigLoader* createBinaryConfigLoader(uint8_t* entityContent, uint32_t size);
The parameters are a pointer to the binary buffer, and the size of the buffer. To create a binary persister do similarly:
virtual IPersister *createBinaryPersister(uint8_t *persisterContent, uint32_t size, uint32_t maxBufferSize);
If persistent data already exists then pass it in and the stack will initialise with the device in the persisted state. If no persisted data exists yet (first boot) then pass either nullptr
or size 0
. The maxBufferSize
parameter should be set to the amount of eeprom/flash/disk buffer that you have allocated to persistent storage on your device. The binary data passed into the stack is copied in the createBinaryPersister()
function so the buffer may be destroyed after the function returns.
The Stack assumes that there is a flat persistent memory section somewhere on the device that it can use to store its current settings (that must be persisted across reboots).
The Stack will call the application supplied function appSavePersistentState
whenever it needs to persist some data. The parameters to this callback are:
parameter | description |
---|---|
uint32_t offset | the offset in the persistent data where the data is to be written |
uint8_t* data | a pointer to the data to be persisted |
uint32_t size | the size of the data to be persisted |
PERSIST dp | which type of data is being persisted |
void *pContext | pointer to the stack instance that is calling the callback |
One major difference between using XML data and binary data for persistent data, is that whenever XML persistent data changes, the whole persistent data XML string is regenerated and must be saved to the device’s persistent storage. Thus when using the XMLPersister, offset will always be zero, and size will always be large (the whole XML persistent data size). In comparison, when using the BinaryPersister only the actual bytes that are required to persist the change need to be written.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<sourceEntityXML md5="ec04ca06439305f2a2ce09f9b1cc9283" />
<entity>
<entity_name>Studio main L</entity_name>
<group_name></group_name>
<current_configuration>1</current_configuration>
<configurations>
<configuration>
<object_name>Daisy Chain (bridge) mode</object_name>
<avb_interfaces>
<avb_interface>
<object_name>Bridged Interface</object_name>
... [snip]
Section of XML persistent data. When anything changes (in this case the current_configuration index), the whole XML must be rewritten.
offset | data |
---|---|
0x00 | 70 22 27 91 c8 1c 3e f3 2b a9 65 01 2f 19 9a cf |
0x10 | 8f f4 80 62 b4 be 6b f3 fc 50 29 01 a9 d9 de 7a |
0x20 | 0c 39 70 18 90 6e 08 06 d0 c7 d2 53 5c 4b 7e 42 |
0x30 | fe 30 56 38 a6 1c 6b ff f8 f2 36 09 a0 f2 35 a5 |
0x40 | 7d 51 1a 38 e2 e2 46 03 39 0a 89 45 92 3e 57 42 … [snip] |
Section of binary persistent data. When something changes (in this case the current_configuration index), only the bytes that have actually changed must be rewritten.
The size (or max size) of the persistent data cannot be obtained from the stack at run time. Instead, the application developer must ensure that there is sufficient storage available to store all the required data.
In the case of XML persistent data, the size of the XML string depends mostly on the number and length of customisable strings in the configuration, and number of stream mappings enabled. For example, a device called A
in group B
is going to use less space in the XML string than a device called George's Tube Microphone Studio 2
in group George's Studio 2
. It is assumed that any device using XML persistent data will have ample available free disc space (SSD, HD, MMC etc) and so this shouldn’t be an issue. To determine the maximum XML persistent data size the system designer may set all the settable strings to their maximum length (64 chars), and create stream mappings with all streams mapped for all streams (in all configurations). This will give an indication of the maximum XML persistent data storage size. If however there is concern about the maximum size then it may be prudent to consider using binary persistent data instead.
The size of the binary persistent data is fixed. Again this cannot be queried at runtime, but it can be obtained from the entityConvert
utility:
entityConvert x config.xml b config.bin
Conversion complete
Binary persistent data size for this configuration file is 3002 bytes
Thus the system designer can ensure there is sufficient eeprom/flash/file to store the persistent data.
appSavePersistentState
The stack assumes that, for the purposes of data integrity and to ensure the device cannot brick itself, that all data written in a single call to appSavePersistentState
is written atomically. When writing XML data it’s clear that the entire XML string must be written to persistent storage, or data corruption can occur which could cause the device to fail to boot. In the binary data case the assumption is the same, although usually the chunks of data written in each call are much smaller. The first time the binary persistent data is written (on first boot or the first time a persisted value changes) the whole binary data is written in a single call to appSavePersistentState
, and this must (as with the XML case) be written atomically. The usual way to accomplish this is to have a dual bank storage area with an atomic flag showing which of the two banks is currently valid. This may also be extended to provide wear levelling. Please contact Sienda if you require more information or advice on this.