ConfigDB

Sming library providing strongly typed JSON configuration database support.

Applications requiring complex non-volatile configuration data typically store this in JSON files. Although libraries such as ArduinoJson provide flexible and easy access to these, there are a few drawbacks:

  • Once configuration exceeds a certain size it becomes unmanageable as a single JSON file

  • Configuration data is typically managed on an ad-hoc basis using structures, and code is required to read/write these structures

  • Code must deal with various common conditions such as default values, range checking, formatting, etc.

  • Clients such as web applications or other external processors require further code to interpret configuration data

Design goals:

  • Describe data once using a standard schema

  • Generate API code from the schema which ensures consistent access, applies default values, checks value ranges, etc.

  • Make use of available compile-time checks where possible (definitely for value types)

  • Allow use of existing tools to enable easy access to data in other languages and on other platforms

  • Allow partitioning of database into multiple stores to control use of RAM and enable more efficient updates for related data groups

  • Reduce RAM usage as database size increases

  • Maintain good read/write performance

  • Pluggable system to support backing stores other than ArduinoJson. For example, binary formats

  • Keep application interface independent of implementation detail

Usage:

  • Database is described in a JSON schema.

  • A python script (tools/dbgen.py) parses the schema and generates class structure for application use.

  • Content of database can be streamed to/from web clients

JsonSchema

The database structure is defined using a standard JSON schema. A good introduction is to take the Tour.

An initial schema can be created from an existing sample JSON configuration file using a generation tool such as https://github.com/saasquatch/json-schema-inferrer. Go to the online demo and paste in your JSON configuration. The resulting schema can then be edited and further customised.

Standardised web editors can also be generated using tools such as https://github.com/json-editor/json-editor. Go to the online demo and scroll down to Schema.

Note

Sming uses JSON schema for:

  • Hardware configuration Sming/Components/Storage/schema.json

  • IFS build scripts Sming/Components/IFS/tools/fsbuild/schema.json

  • USB config Sming/Libraries/USB/schema.json

It’s probably fair to consider it a standard part of the framework.

Configuration JSON can be validated against the .cfgdb schema files using check-jsonschema:

pip install check-jsonschema
check-jsonschema --schemafile basic-config.cfgdb sample-config.json

Separate documentation can be generated from JSON Schema using various tools such as JSON Schema for Humans. For example:

python -m pip install json-schema-for-humans
generate-schema-doc basic-config.cfgdb basic-config.md --config template_name=md

Schema rules

See the Basic Config sample schema. The test application contains further examples.

  • Root object is always a ConfigDB::Database

  • A database is always rooted in a directory

  • An optional include array annotation can be added to specify additional header files required for custom types used in the schema.

  • Contains one or more stores. The root (un-named) object is the primary store, with the filename _root.json.

  • Immediate children of the root may have a store value attached to place them into a new store. This can have any value, typically true.

  • A custom type can be defined for Property accessors using the ctype annotation. This means that with an IP address property, for example, you can use IpAddress instead of String because it can be constructed from a String. The database still stores the value internally as a regular String.

  • Enumerated Properties can be defined for any type; ctype is used here to optionally define a custom enum class type for these values.

  • Re-useable definitions can be created using the $ref schema keyword. Such definitions should be contained within the $defs section of the schema.

  • Arrays of simple types (including Strings) or objects are supported.

  • Unions can contain any one of a number of user-defined object types, and can be used in object arrays.

  • Null values are not supported. If encountered in existing datasets then ConfigDB uses the default value for the property.

  • Multiple property types, such as “type”: [“boolean”, “null”] are not supported. A type must be defined for all properties and must be a string value. This also applies to array items.

Aliases

Properties may have alternative names to support reading legacy datasets. For example:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "trans_fin_interval": {
      "type":"object",
      "alias": "transfin_interval",
      "properties":{
        "type":"integer"
      }
    }
  }
}

Existing JSON data using the old transfin_interval name will be accepted during loading. When changes are made the new (canonical) name of trans_fin_interval will be used.

If multiple aliases are required for a property, provide them as a list.

Floating-point numbers

Items with number type are considered floating-point values. They are not stored internally as float or double but instead use a base-10 representation.

This provides more flexibility in how these values are used and allows applications to work with very large or small numbers without requiring any floating-point arithmetic.

See ConfigDB::number_t and ConfigDB::Number for details. There is also ConfigDB::const_number_t to ease support for format conversion at compile time.

Enumerated properties

JsonSchema offers the enum keyword to restrict values to a set of known values. For example:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "color": {
      "type": "string",
      "enum": [
        "red",
        "green",
        "blue"
      ]
    }
  }
}

ConfigDB treats these as an indexed map, so red has the index 0, green is 1 and blue 2. Indices are of type uint8_t. The example has an intrinsic minimum of 0 and maximum of 2. As with other numeric properties, attempting to set values outside this range are clipped.

The default is 0 (the first string in the list). If a default value is given in the schema, it must match an item in the enum array.

The corresponding setColor, getColor methods set or retrieve the value as a number. Adding “ctype”: “Color” to the property will generate an enum class definition instead. This is the preferred approach.

The color value itself will be stored as a string with one of the given values. The integer and number types are also supported, which can be useful for generating constant lookup tables.

Arrays

ConfigDB uses the array schema keyword to implement both simple arrays (containing integers, numbers or Strings) and object arrays.

Simple arrays are accessed via the ConfigDB::Array class. All elements must be of the same type. A default value may be specified which is applied automatically for uninitialised stores. The ConfigDB::Object::loadArrayDefaults() method may also be used during updates to load these default definitions.

The ConfigDB::ObjectArray type can be used for arrays of objects or unions. Default values are not currently supported for these.

Unions

These are defined using the oneOf schema keyword, which defines an array of option definitions.

Each definition can be defined using $ref. The name of the option will be taken from that definition, and can be overridden by adding a title keyword. Option definitions can also be given directly, in which case title is required.

The test application contains an example of this in the test-config-union.cfgdb schema. It is used in the Updates test module.

Like a regular C++ union, a ConfigDB::Union object has one or more object types overlaid in the same storage space. The size of the object is therefore governed by the size of the largest type stored. A uint8_t property tag indicates which type is stored.

The code generator produces an asXXX method for each type of object which can be stored. The application is responsible for checking which type is present via ConfigDB::Union::getTag(); if the wrong method is called, a runtime assertion will be generated.

The corresponding Union Updater class has a ConfigDB::Union::setTag() method. This changes the stored object type and initialises it to default values. This is done even if the tag value doesn’t change so can be used to ‘reset’ an object to defaults. The code generator produces a toXXX method which sets the tag and returns the appropriate object type.

Re-using objects

JSON Schema describes ways to structure complex schemas.

Re-useable (shared) definitions are, by convention, placed under $defs. These are referenced using the $ref keyword with JSON pointer syntax.

For example:

“$ref”: “#/$defs/MyObject”

Definitions from other schema may be used:

“$ref”: “other-schema/$defs/MyObject”

The dbgen.py code generator is passed the names of all schema found in the current Sming project, which are loaded and parsed as a set using the base name of the .cfgdb schema (without file extension) as its identifier.

Note

The full URI resolution described by JSON Schema is not currently implemented. This would require $id annotations in all schema.

When using shared objects only the name of the related property can be changed. For example:

"font-color": {
  "foreground": {
    "$ref": "#/$defs/Color"
  },
  "background": {
    "$ref": "#/$defs/Color"
  }
}

This generates a C++ property fontColor using a FontColor object definition which itself contains two properties: foreground and background. The object definition for both is Color.

Simple property definitions

References to simple (non-object) property types are handled differently. A type is not defined but instead used as a base definition which can be modified. For example, we can provide a general Pin definition:

"$defs": {
  "Pin": {
    "type": "integer",
    "minimum": 0,
    "maximum": 63
  }
}

And use it like this:

"properties": {
  "input-pin": {
    "$ref": "#/$defs/Pin",
    "default": 13
  },
  "output-pin": {
    "$ref": "#/$defs/Pin",
    "default": 4
  }
}

This is identical to the following:

"properties": {
  "input-pin": {
    "type": "integer",
    "minimum": 0,
    "maximum": 63,
    "default": 13
  }
  "output-pin": {
    "type": "integer",
    "minimum": 0,
    "maximum": 63,
    "default": 4
  }
}

This approach can make the schema more readable, reduce duplication and simplify maintainance.

This example generates a uint8_t property value. A different type may be specified for property accessors using the ctype annotation.

Store loading / saving

By default, stores are saved as JSON files to the local filesystem.

The code generator creates a default ConfigDB::Database class. This can be overridden to customise loading/saving behaviour.

The ConfigDB::Database::getFormat() method is called to get the storage format for a given Store. A ConfigDB::Format implementation provides various methods for serializing and de-serializing database and object content.

Currently only json is implemented - see ConfigDB::Json::format. Each store is contained in a separate file. The name of the store forms the JSONPath prefix for any contained objects and values.

The BasicConfig sample demonstrates using the stream classes to read and write data from a web client.

Important

Any invalid data in a JSON update file will produce a debug warning, but will not cause processing to stop. This behaviour can be changed by implementing a custom ConfigDB::Database::handleFormatError() method.

JSON Update mechanism

The default streaming update (writing) behaviour is to overwrite only those values received. This allows selective updating of properties. For example:

{
    "security": {
        "api_secured": "false"
    }
}

This updates the api_secured value in the database, leaving everything else unchanged.

Arrays are handled slightly differently. To overwrites the array with new values:

"x": [1, 2, 3, 4]

To clear the array:

"x": []

Indexed array operations

Array selectors can be used which operate in the same way as python list operations. So x[i] corresponds to a single element at index i, x[i:j] is a ‘slice’ starting at index i and ending with index (j-1). Negative numbers refer to offsets from the end of the array, so -1 is the last element.

When selecting a single array element x[5], the provided index must exist in the array or import will fail. When updating a range, index values equal to or greater than the array length will be treated as an append operation.

The following example operations demonstrate what happens with an initial JSON array x:

{
  "x": [1, 2, 3, 4]
}

The result value shows the value for x after the update operation. The same operations are supported for arrays of other types, including objects.

Update single item:

{
  "x[0]" : 8,
  "result": [8, 2, 3, 4]
},
{
  "x[2]" : 8,
  "result": [1, 2, 8, 4]
},
{
  "x[-1]" : 8,
  "result": [1, 2, 3, 8]
}

Update multiple items

Note that the assigned value must be an array or the import will fail:

{
  "x[0:2]" : [8, 9],
  "result": [8, 9, 3, 4]
},
{
  "x[1:1]": [8, 9],
  "result": [1, 8, 9, 2, 3, 4]
},
  "x[1:2]": [8, 9],
  "result": [1, 8, 9, 3, 4]
},
{
  "x[2:]": [8, 9],
  "result": [1, 2, 8, 9]
}

Insert item:

{
  "x[3:0]" : [8],
  "result": [1, 2, 3, 8, 4]
},
{
  "x[3:3]": [8],
  "result": [1, 2, 3, 8, 4]
},
{
  "x[-1:]" : [8, 9],
  "result": [1, 2, 3, 8, 9]
}

Append item:

{
  "x[]": [8, 9],
  "result": [1, 2, 3, 4, 8, 9]
},
{
  "x[]": 8,
  "result": [1, 2, 3, 4, 8]
}

Append multiple items:

{
  "x[]": [8, 9],
  "result": [1, 2, 3, 4, 8, 9]
},
{
  "x[10:]": [8, 9],
  "result": [1, 2, 3, 4, 8, 9]
}

Object array selection

The x[name=value] syntax can be used to select one object from an array of objects. Here’s the test data:

{
  "x": [
    {
      "name": "object 1",
      "value": 1
    },
    {
      "name": "object 2",
      "value": 2
    }
  ]
}

And the selector can be used like this:

{
  "x[name=object 1]": { "value": 8 },
  "result": [
    {
      "name": "object 1",
      "value": 8
    },
    {
      "name": "object 2",
      "value": 2
    }
  ]
}

or:

{
  "x[value=2]": { "value": 8 },
  "x[value=1]": { "value": 1234 },
  "result": [
    {
      "name": "object 1",
      "value": 1234
    },
    {
      "name": "object 2",
      "value": 8
    }
  ]
}

Limitations:

  • Only the first matching object will be selected

  • Only one object key can be matched

You can find more examples in the test application under resource/array-test.json.

C++ API code generation

Each .cfgdb file found in the project directory is compiled into a corresponding .h and .cpp file in out/ConfigDB. This directory is added to the #include path.

For example:

  • basic-config.cfgdb is compiled into basic-config.h and basic-config.cpp

  • The applications will #include <basic-config.h>

  • This file contains defines the BasicConfig class which contains all accessible objects and array items

  • Each object defined in the schema, such as network, gets a corresponding contained class such as ContainedNetwork, and an outer class such as Network.

  • Both of these classes provide read-only access to the data via getXXX methods.

  • Outer classes contain a ConfigDB::StoreRef, whereas contained classes do not (they obtain the store from their parent object).

  • Application code can instantiate the outer class directly BasicConfig::Network network(database);

  • Child objects within classes are defined as read-only member variables, such as network.mqtt, which is a ContainedMqtt class instance.

  • A third updater class type is also generated which adds setXXX and resetXXX methods for changing values. Child objects/arrays can be updated using their provided methods.

  • Only one updater per store can be open at a time. This ensures consistent data updates.

Updaters

Code can update database entries in several ways.

  1. Using updater created on read-only class:

    BasicConfig::Root::Security sec(database);
    if(auto update = sec.update()) {
      update.setApiSecured(true);
    }
    

    The update value is a BasicConfig::Root::Security::Updater instance.

    Any changes made via the update are immediately reflected in sec as they share the same Store instance. The update() method can be called multiple times when used in this way. Changes are committed automatically when the last updater loses scope.

  2. Directly instantiate updater class:

    BasicConfig::Root::Security::Updater update(database);
    if(update) {
      update.setApiSecured(true);
    }
    

    Only one updater instance is permitted.

  3. Asynchronous update:

    BasicConfig::Root::Security sec(database);
    bool completed = sec.update([](auto update) {
      update.setApiSecured(true);
    });
    

    If there are no other updates in progress then the update happens immediately and completed is true. Otherwise the update is queued and false is returned. The update will be executed when the store is released.

During an update, applications can optionally call Updater::commit() to save changes at any time. Changes are only saved if the Store dirty flag is set. Calling Updater::clearDirty() will prevent auto-commit, provided further changes are not made.

API Reference

class Database

Subclassed by ConfigDB::DatabaseTemplate< ClassType >

Public Functions

inline Database(const DatabaseInfo &typeinfo, const String &path)

Database instance.

Parameters:

path – Path to root directory where all data is stored

StoreRef openStore(unsigned index)

Open a store instance, load it and return a shared pointer.

void checkStoreRef(const StoreRef &ref)

Called from StoreRef destructor so database can manage caches.

Parameters:

ref – The store reference being destroyed

void queueUpdate(Store &store, Object::UpdateCallback &&callback)

Queue an asynchronous update.

Parameters:
  • store – The store to update

  • callback – Callback which will be invoked when store is available for updates

void checkUpdateQueue(Store &store)

Called by Store on completion of update so any queued updates can be started.

Note

The next queued update (if any) is popped from the queue and scheduled for handling via the task queue.

Parameters:

store – The store which has just finished updating

bool save(Store &store) const

Called from Store::commit.

StoreUpdateRef lockStore(StoreRef &store)

Lock a store for writing (called by Object)

Parameters:

storeStore reference to be locked. If possible, will be locked-in place otherwise it will be copied and updated.

Return values:

StoreUpdateRef – Invalid if locking fails

virtual const Format &getFormat(const Store &store) const

Get the storage format to use for a store.

virtual bool handleFormatError(FormatError err, const Object &object, const String &arg)

Called during import.

Default behaviour is to report errors but continue processing.

Parameters:
  • object – The object to which this error relates

  • argString parameter which failed validation

  • err – The specific error type

Return values:

bool – Return true to continue processing, false to stop

std::unique_ptr<ExportStream> createExportStream(const Format &format, const String &path = nullptr)

Create a read-only stream for serializing the database.

Parameters:
  • format

  • path – JSONPath-like expression to restrict output to specific store or object

inline size_t exportToStream(const Format &format, Print &output)

Serialize the database to a stream.

bool exportToFile(const Format &format, const String &filename)

Serialize the database to a single file.

inline Status importFromStream(const Format &format, Stream &source)

De-serialize the entire database from a stream.

Status importFromFile(const Format &format, const String &filename)

De-serialize the entire database from a file.

inline std::unique_ptr<ImportStream> createImportStream(const Format &format)

Create a write-only stream for de-serializing the database.

class Store : public ConfigDB::Object

Manages access to an object store, typically one file.

Public Functions

inline Store(Database &db)

This is an empty Store instance Object chain is built via references so need a valid Store instance which can be identifyed as empty. We don’t update the instance count here since it will never contain anything.

Store(Database &db, const PropertyInfo &propinfo)

Storage instance.

Parameters:
  • dbDatabase which manages this store

  • typeinfoStore type information

explicit Store(const Store &store)

Copy constructor.

void clear()

Reset store contents to defaults.

Note

Use caution! All reference objects will be invalidated by this call

Public Static Functions

static inline uint8_t getInstanceCount()

Get number of valid (non-empty) Store instances in existence.

class Object

An object can contain other objects, properties and arrays.

Note

This class is the base for concrete Object, Array and ObjectArray classes

Subclassed by ConfigDB::ArrayBase, ConfigDB::ObjectTemplate< ClassType >, ConfigDB::Store, ConfigDB::Union

Public Types

using UpdateCallback = Delegate<void(Store &store)>

Callback invoked by asynchronous updater.

Note

The OuterObjectTemplate::update method template handles this callback so that the caller receives the appropriate Updater object.

Param store:

Updatable store instance

Public Functions

inline bool isStore() const

Determine if this object is a store (not just a reference to it)

unsigned getObjectCount() const

Get number of child objects.

Note

ObjectArray overrides this to return number of items in the array

Object getObject(unsigned index)

Get child object by index.

Object findObject(const char *name, size_t length)

Find child object by name.

Note

For Union objects this also sets the tag on successful match, which clears the Object to its default value.

unsigned getPropertyCount() const

Get number of properties.

Note

Array types override this to return the number of items in the array.

Property getProperty(unsigned index)

Get properties.

Note

Array types override this to return array elements

Property findProperty(const char *name, size_t length)

Find property by name.

void clear()

Reset contents to defaults (except arrays, which are cleared)

Note

Use caution! All reference objects will be invalidated by this call

void loadArrayDefaults()

Clear and load all contained arrays with defaults from schema.

void resetToDefaults()

Does a ‘clear’ followed by ‘loadArrayDefaults’.

bool commit()

Commit changes to the store.

void clearDirty()

Clear store dirty flag so changes don’t get committed.

Note

Store must be reloaded to roll back any changes

void queueUpdate(UpdateCallback callback)

Called from OuterObjectTemplate::update to queue an update.

class Union : public ConfigDB::Object

Variant object, can contain one of a selection of objects types.

Subclassed by ConfigDB::UnionTemplate< ClassType >

Public Functions

inline Tag getTag() const

Get the current tag which identifies the stored type.

inline void setTag(Tag tag)

Set the current tag and reset content to object default.

inline void clear()

Reset tag to default and clear whatever object that corresponds to.

template<typename Item>
inline const Item as(Tag tag) const

Used by code generator to obtain a read-only reference to the contained object.

template<typename Item>
inline Item as(Tag tag)

Used by code generator to obtain a writeable reference to the contained object.

template<typename Item>
inline Item to(Tag tag)

Used by code generator to set the tag and return a direct reference to the contained object.

class Array : public ConfigDB::ArrayBase

Base class to provide array of properties.

Subclassed by ConfigDB::ArrayTemplate< ClassType, ItemType >

class ObjectArray : public ConfigDB::ArrayBase

Base class to provide array of objects.

Subclassed by ConfigDB::ObjectArrayTemplate< ClassType, ItemType >

class Format

Abstract base class wrapping support for a specific storage format, such as JSON.

Subclassed by ConfigDB::Json::Format

Public Functions

virtual std::unique_ptr<ExportStream> createExportStream(Database &db) const = 0

Create a stream to serialize the entire database This is used for streaming asychronously to a web client, for example in an HttpResponse.

virtual std::unique_ptr<ExportStream> createExportStream(StoreRef store, const Object &object) const = 0

Create a stream to serialize an Object.

Used for streaming asychronously to a web client, for example in an HttpResponse.

Parameters:
  • store – Shared pointer to store

  • objectObject to start streaming from

virtual size_t exportToStream(const Object &object, Print &output) const = 0

Print object.

Return values:

size_tNumber of characters written

virtual size_t exportToStream(Database &database, Print &output) const = 0

Serialise entire database directly to an output stream.

Return values:

size_tNumber of bytes written to the stream

virtual std::unique_ptr<ImportStream> createImportStream(Database &db) const = 0

Create a stream for de-serialising (writing) into the database Used when updating a database from a remote web client, for example via HttpRequest.

virtual std::unique_ptr<ImportStream> createImportStream(StoreUpdateRef &store, Object &object) const = 0

Create a stream for de-serialising (writing) into a store.

Used when updating a store from a remote web client, for example via HttpRequest

Parameters:
  • store – Shared pointer to store with write access

  • objectObject to start streaming to

virtual Status importFromStream(Object &object, Stream &source) const = 0

De-serialise content from stream into object (RAM)

virtual Status importFromStream(Database &database, Stream &source) const = 0

De-serialise content from stream into database Each store is overwritten as it is loadded. If a store entry is not represented in the data then it is left untouched.

virtual String getFileExtension() const = 0

Get the standard file extension for the reader implementation.

virtual MimeType getMimeType() const = 0

Get the MIME type for this reader format.

Format ConfigDB::Json::format
class Number

Base-10 floating-point storage format.

The base number_t type cannot have constructors so this class includes those for ease of application use.

Note

Structure is packed to accommodate use in generated class structures

Public Functions

inline Number(const char *value, unsigned length)

Parse a number from a string.

struct number_t

Basic definition of base-10 floating point value.

    value = mantissa * 10^exponent
This format is aimed to be a simple and transparent way to store floating-point numbers without the weird rounding issues of IEEE754 base-2 float / double.

Conversion to floating-point format is only done when required by the application. This can be avoided completely by using strings instead of floats to set values.

Constant values can also be specified which are converted at compile-time. See const_number_t.

Integer values can also be specified, but may be subject to rounding if too precise. Very large (or small) values can be used, such as 1.5e25. This would actually be stored as 15e4 {15, 24}.

It does not need to be computationally efficient, but does have advantages:

    - structure is transparent
- Base-10 operations can be performed efficiently without rounding errors
- Serialising (converting to strings) and de-serialising is consistent and JSON-compatible
Some similarity to python’s Decimal class, but with restriction on significant digits and exponent.

The initial version of this used 24 bits for the mantissa, but 8 bits for the exponent is overkill. Reducing exponent to 6 bits increases precision by 2 bits to give: smallest: 1e-31 largest: 33554431e31 (3.3554431e38)

Numbers always have a valid representation for ease of use and JSON compatibility. There is no definition for ‘NaN’ (not a number) or ‘infinity. Values are never rounded down to 0, but clipped to the above valid range. Thus convering any number smaller than 1e-31 (but > 0) is stored as 1e-31. Applications can consider number_t::min() and number_t::max() as the ‘overflow’ values. As with the C++ STL, lowest() indicates the most negative value which is equivalent to -max().

The mantissa/exponent fields can be manipulated directly if required. For example, to convert terabytes to gigabytes we just subtract 3 from the exponent.

An example of custom arithmetic:

        number_t multiplyBy2000(number_t value)
        {
            value.mantissa *= 2;
            value.exponent += 3;
            return value;
        }
If there’s a risk of over/under flowing in the calculation, do this:
        number_t multiplyBy2000(number_t value)
        {
            int mantissa = value.mantissa * 2;
            int exponent = value.exponent + 3;
            return number_t::normalise(mantissa, exponent);
        }
, avoiding overflow risk, multiplying a number by 2000 int mantissa = number.mantissa; int exponent = number.exponent; mantissa *= 2; exponent += 3; number_t new_number = number_t::normalise(number);

Note

This structure is not packed to ensure values stored in flash behave correctly.

Subclassed by ConfigDB::const_number_t

Public Static Functions

static inline constexpr const number_t min()

Smallest positive value.

static inline constexpr const number_t max()

Largest positive value.

static inline constexpr const number_t lowest()

Most negative value.

static const char *format(char *buf, number_t number)

Convert number to string.

Note

Always use return value to give implementation flexible use of buffer

Parameters:
  • buf – Buffer of at least number_t::minBufferSize

  • number

Return values:

char* – Points to string, may not be start of buf

static inline constexpr number_t normalise(unsigned mantissa, int exponent, bool isNeg)

Produce a normalised number_t from component values.

Mantissa has trailing 0’s removed, although may be required for large negative exponents. For example, 100000e-30 exponent is at limit so cannot be reduced further.

If necessary, the mantissa is rounded to the nearest whole value. For example, 3141592654 is rounded up to 31415927.

If the value is out of range, number_t::overflow is returned.

Parameters:
  • mantissa – Mantissa without sign, containing significant digits

  • exponent – Exponent

  • isNeg – true if mantissa is negative, false if positive

Return values:

number_t – Normalised number value For example, 1000e9 and 10e11 are normalised to 1e12. This allows values to be directly compared for equality.

struct const_number_t : public ConfigDB::number_t

Compile-time constant number.

Must be assigned to a constexpr variable to guarantee compile-time evaluation. For example:

    constexpr const_number_t constnum(3.14);
This is typically used to generate numbers stored directly in flash memory, where the structure definition is itself declared constexpr.

By inspection, compilers tend to generate runtime code when used normally:

    const_number_t constnum(3.14);
This behaviour is because floating-point numbers are not exact representations (integral) so the compiler is erring on the safe side.

References

Used by

Environment Variables

  • APP_CONFIGDB_DIR

  • CONFIGDB_SCHEMA

SoC support

  • esp32

  • esp32c2

  • esp32c3

  • esp32s2

  • esp32s3

  • esp8266

  • host

  • rp2040

  • rp2350