Skip to content

0.2.0

Latest

Choose a tag to compare

@LK-Simon LK-Simon released this 18 Aug 15:08

ESPressio Units v0.2.0

ESPressio Units 0.2.0 introduces optional ESPressio Serializable integration across the complete Unit type catalogue.

The key design goal for this release is to provide serializable versions of ESPressio Unit types without adding serialization overhead or dependencies to applications that do not require serialization.

Existing Unit types remain unchanged. Serializable variants are explicitly opt-in and are provided by ESPressio Units itself through corresponding *_Serializable.hpp headers.


Serializable Unit Types

Every concrete ESPressio Unit family now has a corresponding Serializable variant.

For example:

Distance<float> distance;
SerializableDistance<float> serializableDistance;

Mass<double> mass;
SerializableMass<double> serializableMass;

Velocity<float> velocity;
SerializableVelocity<float> serializableVelocity;

ElectricResistance<float> resistance;
SerializableElectricResistance<float> serializableResistance;

This support is provided across the complete ESPressio Units type catalogue.


Per-Type Serializable Headers

Serializable types follow the same file organization as their ordinary Unit counterparts.

For example:

ESPressio_Time.hpp
ESPressio_Time_Serializable.hpp

ESPressio_Distance.hpp
ESPressio_Distance_Serializable.hpp

ESPressio_Mass.hpp
ESPressio_Mass_Serializable.hpp

ESPressio_ElectricResistance.hpp
ESPressio_ElectricResistance_Serializable.hpp

This allows applications to include only the Serializable Unit families they actually require.

For example:

#include <ESPressio_Distance_Serializable.hpp>

using namespace ESPressio::Units;

SerializableDistance<float> distance(
    12.5f
);

Including ESPressio_Distance_Serializable.hpp provides the Serializable version of Distance<T> without requiring every other Serializable Unit type to be imported.


Serializable Units Umbrella Header

Applications requiring many or all Serializable Unit types can instead use:

#include <ESPressio_SerializableUnits.hpp>

ESPressio_SerializableUnits.hpp serves the same purpose for Serializable types that:

#include <ESPressio_Units.hpp>

serves for ordinary Unit types.

It is purely an umbrella/batch-import header and contains no Unit implementations itself.


No Mandatory Serialization Dependency

One of the most important aspects of this release is that ESPressio Serializable remains completely optional.

An application using ordinary Unit types:

#include <ESPressio_Units.hpp>

Distance<float> distance(
    12.5f
);

Mass<float> mass(
    2.0f
);

does not require ESPressio Serializable.

The existing Unit types:

  • do not inherit from Serializable;
  • do not contain serialization metadata;
  • do not require serialization property descriptors;
  • do not require ESPressio Serializable to be installed;
  • do not incur additional serialization-related RAM or object-size overhead.

The normal ESPressio Units dependency therefore remains:

lib_deps =
    flowduino/ESPressio-Units

Opting Into Serialization

A project only needs ESPressio Serializable when it explicitly uses one of the Serializable Unit headers.

For example:

#include <ESPressio_Distance_Serializable.hpp>

or:

#include <ESPressio_SerializableUnits.hpp>

Such a project should declare both libraries:

lib_deps =
    flowduino/ESPressio-Units@^0.2.0
    flowduino/ESPressio-Serializable@^0.9.0

This keeps serialization a true pay-for-what-you-use capability.


Generic SerializableUnit

The generic Unit type now has a corresponding optional Serializable counterpart through:

#include <ESPressio_Unit_Serializable.hpp>

which provides:

SerializableUnit<
    TValue,
    TBaseOrderOfMagnitude,
    TContext
>

This mirrors the generic:

Unit<
    TValue,
    TBaseOrderOfMagnitude,
    TContext
>

while adding the ESPressio Serializable contract.


Existing Unit Types Remain Non-Polymorphic

The existing Unit hierarchy has deliberately not been made runtime-polymorphic.

No virtual destructor, virtual methods, or vtable have been introduced.

The existing public inheritance hierarchy is sufficient for the Serializable variants, while ESPressio Serializable itself uses compile-time CRTP/property traversal.

This means ordinary Unit instances retain their existing lightweight memory and runtime characteristics.


Serializable Time Types

Time retains its existing magnitude-aware template structure.

The normal type:

Time<uint32_t, Milli>

has the corresponding Serializable type:

SerializableTime<uint32_t, Milli>

The existing convenience aliases are also mirrored.

For example:

MilliSeconds<uint32_t>
SerializableMilliSeconds<uint32_t>

MicroSeconds<uint32_t>
SerializableMicroSeconds<uint32_t>

Seconds<uint32_t>
SerializableSeconds<uint32_t>

KiloSeconds<uint32_t>
SerializableKiloSeconds<uint32_t>

Serializable aliases are provided across the complete supported SI magnitude range from QuectoSeconds through QuettaSeconds.


Serialized Unit State

Serializable Unit types expose the runtime state of the underlying Unit through ESPressio Serializable.

The serialized properties are:

value
orderOfMagnitude

For example:

SerializableDistance<float> distance(
    1250.0f,
    Milli
);

can be represented conceptually as:

{
    "__schemaVersion": 1,
    "value": 1250.0,
    "orderOfMagnitude": -3
}

The Unit's:

baseOrderOfMagnitude
context

are deliberately not serialized.

These values are compile-time/static properties of the concrete C++ Unit type rather than per-instance state.

For example, a SerializableDistance<float> already knows from its C++ type that it represents a Distance quantity and what its canonical base magnitude is.

Duplicating this information into every serialized instance would increase payload size while also allowing serialized metadata to potentially contradict the receiving C++ type.


Existing Unit API Is Preserved

Serializable Unit variants inherit the existing concrete Unit implementation.

They therefore retain the normal Unit functionality, including:

  • constructors;
  • magnitude handling;
  • value access;
  • conversions;
  • formatting;
  • arithmetic behavior;
  • typed Unit context;
  • existing helper functions.

For example:

SerializableDistance<float> distance(
    1250.0f,
    Milli
);

can be used as a normal Distance while also supporting the ESPressio Serializable interface.


Promoting Existing Units

Existing non-Serializable Unit values can be promoted only when serialization becomes necessary.

For example:

Distance<float> distance(
    12.5f
);

auto serializable =
    MakeSerializableUnit(
        distance
    );

This is particularly useful when existing Unit calculations or conversion functions return ordinary Unit types but the final result needs to be persisted or transmitted.

The original Unit remains non-Serializable.


Architecture

The relationship between the two libraries is intentionally one-way and optional:

ESPressio Units
│
├── ESPressio_Unit.hpp
├── ESPressio_Time.hpp
├── ESPressio_Distance.hpp
├── ESPressio_Mass.hpp
├── ...
│
│   Ordinary Unit types
│   └── no Serializable dependency
│
└── Optional Serializable layer
    │
    ├── ESPressio_Unit_Serializable.hpp
    ├── ESPressio_Time_Serializable.hpp
    ├── ESPressio_Distance_Serializable.hpp
    ├── ESPressio_Mass_Serializable.hpp
    ├── ...
    │
    └── ESPressio_SerializableUnits.hpp
            │
            └── umbrella import

ESPressio Units owns the Unit-specific Serializable types.

ESPressio Serializable supplies the underlying serialization framework but has no knowledge of ESPressio Units.


Example

#include <Arduino.h>

#include <ESPressio_Distance_Serializable.hpp>
#include <ESPressio_Serializable_JSON.hpp>

using namespace ESPressio;
using namespace ESPressio::Units;

void setup() {
    Serial.begin(115200);

    SerializableDistance<float> distance(
        1250.0f,
        Milli
    );

    Serializable::JsonArchive archive;

    distance.Serialize(
        archive
    );

    Serial.println(
        archive.ToString().c_str()
    );
}

void loop() {
}

If serialization is not required, the equivalent application remains simply:

#include <ESPressio_Distance.hpp>

using namespace ESPressio::Units;

Distance<float> distance(
    1250.0f,
    Milli
);

with no ESPressio Serializable dependency.


New Headers

This release adds:

ESPressio_Unit_Serializable.hpp
ESPressio_SerializableUnits.hpp

along with a corresponding Serializable sibling header for every concrete Unit family:

ESPressio_<UnitType>_Serializable.hpp

including:

ESPressio_Time_Serializable.hpp
ESPressio_Distance_Serializable.hpp
ESPressio_Mass_Serializable.hpp
ESPressio_Velocity_Serializable.hpp
ESPressio_Acceleration_Serializable.hpp
ESPressio_Force_Serializable.hpp
ESPressio_Pressure_Serializable.hpp
ESPressio_Energy_Serializable.hpp
ESPressio_Power_Serializable.hpp
ESPressio_ElectricResistance_Serializable.hpp
ESPressio_MagneticFlux_Serializable.hpp
ESPressio_StorageCapacity_Serializable.hpp
...

The Serializable catalogue mirrors the existing ESPressio Units catalogue.


New Example

A new example is included:

examples/
└── SerializableUnits/
    └── SerializableUnits.ino

It demonstrates:

  • using an ordinary Unit;
  • explicitly selecting its Serializable counterpart;
  • JSON serialization;
  • promoting an existing ordinary Unit to a Serializable Unit only when required.

Compatibility

This release is additive.

Existing ESPressio Units code using:

#include <ESPressio_Units.hpp>

and the existing Unit types should require no source changes.

The ordinary Unit API and dependency model remain unchanged.

Serializable functionality is only introduced when one of the new Serializable headers is explicitly included.


Summary

ESPressio Units 0.2.0 adds optional serialization support while preserving the lightweight nature of the original library.

The core design principles are:

  • ordinary Units remain ordinary Units;
  • Serializable Units are explicitly selected types;
  • every concrete Unit has a Serializable counterpart;
  • each Unit family has its own *_Serializable.hpp sibling header;
  • ESPressio_SerializableUnits.hpp is an umbrella header only;
  • ESPressio Serializable is not required unless Serializable Units are actually used;
  • no virtual/polymorphic overhead is added to ordinary Units;
  • existing Unit behavior and APIs are preserved;
  • existing Unit values can be promoted to Serializable variants on demand.

This provides serialization where it is useful without making it a cost paid by every ESPressio Units application.