Utilities
Importing files
For String and Array objects you can import data directly from a file
using IMPORT_FSTR()
or IMPORT_FSTR_ARRAY()
. For example:
IMPORT_FSTR(myData, PROJECT_DIR "/files/myData.bin");
This defines a C++ reference to the data called myData
so it can be referred to using
DECLARE_FSTR()
if required.
Attention
File paths must be absolute or the compiler won’t be able to locate it reliably.
Sming provides PROJECT_DIR
and COMPONENT_PATH
to help with this.
Note
A corresponding C symbol will also be defined, based on the provided name, to provide linkage with the imported data.
You generally shouldn’t have an issue with this as the symbols are restricted to file scope, but it is something to be aware of.
One use for imported files is to serve content via HTTP, like this:
void onFile(HttpRequest& request, HttpResponse& response)
{
Serial.printf("myData is %u bytes long\n", myData.length());
auto fs = new FSTR::Stream(myData);
response.sendDataStream(fs);
}
Therefore files can be bound into the firmware and accessed without requiring a filing system. This idea is extended further using Maps.
Custom Imports
Use IMPORT_FSTR_DATA()
to import the contents of a file without defining any C/C++ variable:
IMPORT_FSTR_DATA(myCustomData, PROJECT_DIR "/files/data.bin");
You’ll need to define an appropriate symbol:
struct MyCustomStruct {
uint32_t length;
char name[12];
char description[20];
uint8_t data[1024];
};
extern "C" const MyCustomStruct myCustomData;
You’ll still have to consider how the data is accessed. If it’s small and un-complicated you can just copy it into RAM:
MyCustomStruct buf;
memcpy_P(&buf, &myCustomData, sizeof(buf));
Custom Objects
A better way to handle large, complex structures is to define a custom Object to handle it.
You can find an example of how to do this in test/app/custom.cpp
, which does this:
Define
MyCustomStruct
:struct MyCustomStruct { FSTR::ObjectBase object; char name[12]; char description[20]; FSTR::ObjectBase dataArray; };
Define a base object type (
CustomObject
) using theFSTR::Object
class template. This determines the underlying element type, generallychar
oruint8_t
are most useful.Derive an Object class (
MyCustomObject
) to encapsulate access toMyCustomStruct
.Use the
IMPORT_FSTR_OBJECT()
macro to import the custom data and define a global reference (customObject
) of typeMyCustomObject&
.Use
DECLARE_FSTR_OBJECT()
macro to declare the reference in a header.
More complex examples may involve multiple custom Object types.
API Reference
-
DECL(t)
Wrap a type declaration so it can be passed with commas in it.
Example:
These fail:template <typename ElementType, size_t Columns> struct MultiRow { ElementType values[Columns]; }
Use DECL like this:DECLARE_FSTR_ARRAY(myArray, MultiRow<double, 3>); DECLARE_FSTR_ARRAY(myArray, (MultiRow<double, 3>));
Although for this example we should probably do this:DECLARE_FSTR_ARRAY(myArray, DECL((MultiRow<double, 3>)) );
using MultiRow_double_3 = MultiRow<double, 3>; DECLARE_FSTR_ARRAY(myArray, MultiRow_double_3);
-
IMPORT_FSTR_DATA(name, file)
Link the contents of a file.
This provides a more efficient way to read constant (read-only) file data. The file content is bound into firmware image at link time.
We need inline assembler’s
.incbin
instruction to actually import the data. We use a macro STR() so that if required the name can be resolved from a#defined
value.Use PROJECT_DIR to locate files in your project’s source tree:
Use COMPONENT_PATH within a component.IMPORT_FSTR_DATA(myFlashData, PROJECT_DIR "/files/my_flash_file.txt");
No C/C++ symbol is declared, this is type-dependent and must be done separately:
If the symbol is not referenced the content will be discarded by the linker.extern "C" FSTR::String myFlashData;
-
STR(x)
-
XSTR(x)
-
template<typename T>
struct argument_type