Caffa is an Application Framework for Embedded server applications written in C++. It features Runtime Introspection, serialisation and a REST-interface
Caffa is intended to help write applications with strong separation between application logic and the data model and allow for unforseen new methods of accessing objects using introspection. The main target for Caffa is to create simple control applications for embedded Linux systems or client/server c++-applications with a shared client and server code base.
As an example, you would write Data Model Objects with Fields holding data instead of simple variables. This gives you runtime introspection of the fields without using a pre-compiler and all objects can easily be written out to JSON. Caffa is set up for allowing scripting access by utilising the introspection capabilites to optionally expose fields and objects to scripting languages with little additional work from the application developer.
The REST-interface is designed to be as transparent as possible, and fields and methods can be accessed remotely from the client as if they are local fields through the use of remote REST-accessors being applied to them when the object is instantiated on the client.
The REST-interface communicates via JSON adhering to the latest draft JSON Schema specification and the REST-server provides access to both schemas and data.
#pragma once
#include "cafObject.h"
class ChildObject : public Object
{
CAFFA_HEADER_INIT(DemoObject, Object)
public:
ChildObject(const std::string& childName = "");
public:
Field<std::string> name;
};
class TinyDemoDocument : public Document
{
CAFFA_HEADER_INIT_WITH_DOC("A tiny object with documentation", TinyDemoDocument, Object);
public:
enum TestEnumType
{
T1,
T2,
T3
};
TinyDemoDocument();
~TinyDemoDocument() noexcept override;
public:
Field<bool> toggleField;
Field<double> doubleField;
Field<int> intField;
Field<std::vector<int>> intVectorField;
Field<AppEnum<TestEnumType>> enumField;
ChildArrayField<ChildObject> children;
ChildField<ChildObject> specialChild;
public:
Method<void(double)> scaleDoubleField;
};
Main Caffa namespace.
Definition: __init__.py:1
In the cpp file you then register the object and fields.
CAFFA_SOURCE_INIT(ChildObject)
ChildObject::ChildObject(const std::string& childName)
{
initField(name, "name").withDefault(childName).withScripting();
}
CAFFA_SOURCE_INIT(TinyDemoDocument)
{
template <>
void AppEnum<TinyDemoDocument::TestEnumType>::setUp()
{
addItem( TinyDemoDocument::T1, "T1" );
addItem( TinyDemoDocument::T2, "T2" );
addItem( TinyDemoDocument::T3, "T3" );
setDefault( TinyDemoDocument::T1 );
}
}
TinyDemoDocument::TinyDemoDocument()
{
initField(toggleField, "Toggle").withDefault(true).withScripting();
initField(doubleField, "Number").withDefault(11.0).withScripting();
initField(intField, "Integer").withDefault(42).withScripting();
initField(enumField, "Enum").withScripting();
initField(intVectorField, "Integers").withScripting();
initField(children, "Children").withScripting();
initFIeld(specialChild, "SpecialChild");
initMethod(scaleDoubleField, "scaleDouble", {"scalingFactor"}, [this](double scalingFactor)
{
this->doubleField.setValue(this->doubleField.value() * scalingFactor);
});
children.push_back(std::make_shared<ChildObject>("Alice"));
children.push_back(std::make_shared<ChildObject>("Bob"));
specialChild = std::make_shared<ChildObject>("Balthazar");
}
Fields and methods can be accessed locally in the following way:
auto doc = std::make_shared<TinyDemoDocument>();
doc->toggleField = true;
int currentIntValue = doc->intField;
doc->scaleDoubleField(3.0);
If your application inherits the caffa::rpc::RestServerApplication and the document is provided by the server app through your implementation of the virtual document and documents() methods, you can access the same fields and methods remotely.
auto doc = std::dynamic_pointer_cast<TinyDemoDocument>(client->document("TinyDemoDocument"));
doc->toggleField = true;
int currentIntValue = doc->intField;
doc->scaleDoubleField(3.0);
See ExampleServer.cpp and ExampleClient.cpp for a more complete example.
Requirements
Caffa uses modern C++ and requires a C++20 compatible compiler, Boost 1.71.0+ and CMake 3.16+.
Building
Caffa uses git submodules so it is important to initialise submodules recursively first
git submodule update --init --recursive
Licensing
Caffa is licensed under the LGPL 2.1 or newer.