Skip to content

Brill-Power/thingsetplusplus

Repository files navigation

ThingSet++

ThingSet framework in C++.

Introduction

ThingSet is a transport-agnostic data accessibility framework for embedded devices. For more information on ThingSet, please visit the ThingSet web site.

There are four transports in various stages of completeness and maturity:

  • SocketCAN - client and server (Linux)
  • Zephyr CAN - client and server (Zephyr RTOS)
  • TCP/UDP Sockets - client and server (Linux, macOS (client only), FreeBSD, OpenBSD, Zephyr RTOS)
  • TCP/UDP asio Sockets - client and server (Linux, macOS, FreeBSD, OpenBSD)

Both text (JSON) and binary (CBOR) encodings are supported.

Getting Started

Server

The basic building blocks are properties, functions and groups. Each has an ID, a parent, a name and a value of a certain type.

Properties

A basic property is declared thus:

ThingSetReadWriteProperty<float> voltage { 0x300, 0, "voltage" };

The type will be inferred if the value is specified at initialisation:

ThingSetReadWriteProperty voltage { 0x300, 0, "voltage", 24.0f };

A property so declared provides its own storage for the given value. Alternatively, a property may be declared with a pointer:

float voltage;
ThingSetReadWriteProperty voltageProperty { 0x300, 0, "voltage", &voltage };

The type will be inferred from the pointer type. Assignment to the property will update the underlying value:

voltageProperty = 25.0;
std::cout << voltage << std::endl; // prints 25

All the basic primitive types are supported. Properties may also be structures*, or arrays of primitives or structures (known in C ThingSet as 'records').

Structures should be declared thus:

struct ModuleRecord
{
    ThingSetReadWriteRecordMember<0x601, 0x600, "voltage", float> voltage;
    ThingSetReadWriteRecordMember<0x602, 0x600, "current", float> current;
    ThingSetReadWriteRecordMember<0x603, 0x600, "error", uint64_t> error;
    ThingSetReadWriteRecordMember<0x604, 0x600, "cellVoltages", std::array<float, 6>> cellVoltages;
};

As in C ThingSet, the above will be serialised as a map, with either integer ID or string name keys.

(* There is no direct equivalent of a single structure being a value in C ThingSet, which may cause compatibility problems.)

Functions

Functions are declared thus:

// inline lambda function
ThingSetUserFunction<0x400, 0x0, float, float, float> xAdd([](float x, float y) { return x + y});

int getStringLength(std::string &value)
{
    return value.size();
}

// call existing function
ThingSetUserFunction<0x410, 0x0, int, std::string &> xStringLength(getStringLength);

Note that, by default, parameters' IDs will be generated by incrementing the function ID. It is possible to override this.

Once you have declared your properties and functions, instantiate a server with an appropriate transport to expose them to clients.

ThingSetSocketServerTransport transport;
auto server = ThingSetServerBuilder::build(transport);
server.listen();

Client

The client is instantiated along similar lines:

std::array<uint8_t, 1024> rxBuffer;
std::array<uint8_t, 1024> txBuffer;
ThingSetSocketClientTransport clientTransport("127.0.0.1");
ThingSetClient client(clientTransport, rxBuffer, txBuffer);
client.connect();

Values can then be retrieved:

float voltage;
if (client.get(0x300, voltage)) {
    ...
}

...or updated:

client.update("voltage", 25.0f);

...and functions invoked:

int result;
if (client.exec(0x1000, &result, 2, 3)) {
    ...
}

Marshalling Custom Types

See the Buffer class for an example of how serialisation and deserialisation support can be added to custom types.

Examples and Tests

See the examples folder for some samples of the code in action. The unit tests also give an idea of what can be done.

About

C++ ThingSet framework

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors