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:

  1. Define MyCustomStruct:

    struct MyCustomStruct {
       FSTR::ObjectBase object;
       char name[12];
       char description[20];
       FSTR::ObjectBase dataArray;
    };
    
  2. Define a base object type (CustomObject) using the FSTR::Object class template. This determines the underlying element type, generally char or uint8_t are most useful.

  3. Derive an Object class (MyCustomObject) to encapsulate access to MyCustomStruct.

  4. Use the IMPORT_FSTR_OBJECT() macro to import the custom data and define a global reference (customObject) of type MyCustomObject&.

  5. 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:

template <typename ElementType, size_t Columns>
struct MultiRow
{
    ElementType values[Columns];
}

These fail:

    DECLARE_FSTR_ARRAY(myArray, MultiRow<double, 3>);
    DECLARE_FSTR_ARRAY(myArray, (MultiRow<double, 3>));

Use DECL like this:

    DECLARE_FSTR_ARRAY(myArray, DECL((MultiRow<double, 3>)) );

Although for this example we should probably do this:

    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:

IMPORT_FSTR_DATA(myFlashData, PROJECT_DIR "/files/my_flash_file.txt");

Use COMPONENT_PATH within a component.

No C/C++ symbol is declared, this is type-dependent and must be done separately:

extern "C" FSTR::String myFlashData;

If the symbol is not referenced the content will be discarded by the linker.

STR(x)
XSTR(x)
template<typename T>
struct argument_type
#include <Utility.hpp>
template<typename T, typename U>
struct argument_type<T(U)>
#include <Utility.hpp>