add flatbuffer dependencies

This commit is contained in:
Paulchen-Panther 2018-12-28 17:55:49 +01:00
parent d762aa2f3e
commit 3700566d10
12 changed files with 980 additions and 0 deletions

3
.gitmodules vendored
View File

@ -5,3 +5,6 @@
path = dependencies/external/rpi_ws281x path = dependencies/external/rpi_ws281x
url = https://github.com/hyperion-project/rpi_ws281x.git url = https://github.com/hyperion-project/rpi_ws281x.git
branch = master branch = master
[submodule "dependencies/external/flatbuffers"]
path = dependencies/external/flatbuffers
url = git://github.com/google/flatbuffers.git

1
dependencies/external/flatbuffers vendored Submodule

@ -0,0 +1 @@
Subproject commit 0eb7b3beb037748bf5b469e4df9db862c4833e35

View File

@ -0,0 +1,127 @@
#pragma once
// Qt includes
#include <QString>
#include <QColor>
#include <QImage>
#include <QTcpSocket>
#include <QTimer>
#include <QMap>
// hyperion util
#include <utils/Image.h>
#include <utils/ColorRgb.h>
#include <utils/VideoMode.h>
#include <utils/Logger.h>
// flatbuffer FBS
#include "hyperion_reply_generated.h"
#include "hyperion_request_generated.h"
///
/// Connection class to setup an connection to the hyperion server and execute commands. Used from standalone capture binaries (x11/dispamnx/...)
///
class FlatBufferConnection : public QObject
{
Q_OBJECT
public:
///
/// Constructor
///
/// @param address The address of the Hyperion server (for example "192.168.0.32:19444)
///
FlatBufferConnection(const QString & address);
///
/// Destructor
///
~FlatBufferConnection();
/// Do not read reply messages from Hyperion if set to true
void setSkipReply(bool skip);
///
/// Set all leds to the specified color
///
/// @param color The color
/// @param priority The priority
/// @param duration The duration in milliseconds
///
void setColor(const ColorRgb & color, int priority, int duration = 1);
///
/// Set the leds according to the given image (assume the image is stretched to the display size)
///
/// @param image The image
/// @param priority The priority
/// @param duration The duration in milliseconds
///
void setImage(const Image<ColorRgb> & image, int priority, int duration = -1);
///
/// Clear the given priority channel
///
/// @param priority The priority
///
void clear(int priority);
///
/// Clear all priority channels
///
void clearAll();
///
/// Send a command message and receive its reply
///
/// @param message The message to send
///
void sendMessage(const uint8_t* buffer, uint32_t size);
private slots:
/// Try to connect to the Hyperion host
void connectToHost();
///
/// Slot called when new data has arrived
///
void readData();
signals:
///
/// emits when a new videoMode was requested from flatbuf client
///
void setVideoMode(const VideoMode videoMode);
private:
///
/// Parse a reply message
///
/// @param reply The received reply
///
/// @return true if the reply indicates success
///
bool parseReply(const flatbuf::HyperionReply * reply);
private:
/// The TCP-Socket with the connection to the server
QTcpSocket _socket;
/// Host address
QString _host;
/// Host port
uint16_t _port;
/// Skip receiving reply messages from Hyperion if set
bool _skipReply;
QTimer _timer;
QAbstractSocket::SocketState _prevSocketState;
Logger * _log;
flatbuffers::FlatBufferBuilder _builder;
};

View File

@ -0,0 +1,67 @@
#pragma once
// util
#include <utils/Logger.h>
#include <utils/settings.h>
// qt
#include <QVector>
class QTcpServer;
class FlatBufferClient;
class NetOrigin;
///
/// @brief A TcpServer to receive images of different formats with Google Flatbuffer
/// Images will be forwarded to all Hyperion instances
///
class FlatBufferServer : public QObject
{
Q_OBJECT
public:
FlatBufferServer(const QJsonDocument& config, QObject* parent = nullptr);
~FlatBufferServer();
public slots:
///
/// @brief Handle settings update
/// @param type The type from enum
/// @param config The configuration
///
void handleSettingsUpdate(const settings::type& type, const QJsonDocument& config);
void initServer();
private slots:
///
/// @brief Is called whenever a new socket wants to connect
///
void newConnection();
///
/// @brief is called whenever a client disconnected
///
void clientDisconnected();
private:
///
/// @brief Start the server with current _port
///
void startServer();
///
/// @brief Stop server
///
void stopServer();
private:
QTcpServer* _server;
NetOrigin* _netOrigin;
Logger* _log;
int _timeout;
quint16 _port;
const QJsonDocument _config;
QVector<FlatBufferClient*> _openConnections;
};

View File

@ -0,0 +1,42 @@
# Define the current source locations
set(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/flatbufserver)
set(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/flatbufserver)
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${FLATBUFFERS_INCLUDE_DIRS}
)
FILE ( GLOB FLATBUFSERVER_SOURCES "${CURRENT_HEADER_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" )
set(Flatbuffer_GENERATED_FBS
hyperion_reply_generated.h
hyperion_request_generated.h
)
set(Flatbuffer_FBS
${CURRENT_SOURCE_DIR}/hyperion_reply.fbs
${CURRENT_SOURCE_DIR}/hyperion_request.fbs
)
FOREACH(FBS_FILE ${Flatbuffer_FBS})
compile_flattbuffer_schema(${FBS_FILE} ${CMAKE_CURRENT_BINARY_DIR})
ENDFOREACH(FBS_FILE)
# let cmake know about new generated source files
set_source_files_properties(
${Flatbuffer_GENERATED_FBS} PROPERTIES GENERATED TRUE
)
add_library(flatbufserver
${FLATBUFSERVER_SOURCES}
${Flatbuffer_GENERATED_FBS}
)
target_link_libraries(flatbufserver
hyperion-utils
flatbuffers
Qt5::Network
Qt5::Core
)

View File

@ -0,0 +1,195 @@
#include "FlatBufferClient.h"
// qt
#include <QTcpSocket>
#include <QHostAddress>
#include <QTimer>
#include <hyperion/Hyperion.h>
#include <QDebug>
FlatBufferClient::FlatBufferClient(QTcpSocket* socket, const int &timeout, QObject *parent)
: QObject(parent)
, _log(Logger::getInstance("FLATBUFSERVER"))
, _socket(socket)
, _clientAddress(socket->peerAddress().toString())
, _timeoutTimer(new QTimer(this))
, _timeout(timeout * 1000)
, _priority()
, _hyperion(Hyperion::getInstance())
{
// timer setup
_timeoutTimer->setSingleShot(true);
_timeoutTimer->setInterval(_timeout);
connect(_timeoutTimer, &QTimer::timeout, this, &FlatBufferClient::forceClose);
// connect socket signals
connect(_socket, &QTcpSocket::readyRead, this, &FlatBufferClient::readyRead);
connect(_socket, &QTcpSocket::disconnected, this, &FlatBufferClient::disconnected);
}
void FlatBufferClient::readyRead()
{
qDebug()<<"readyRead";
_timeoutTimer->start();
_receiveBuffer += _socket->readAll();
// check if we can read a header
while(_receiveBuffer.size() >= 4)
{
uint32_t messageSize =
((_receiveBuffer[0]<<24) & 0xFF000000) |
((_receiveBuffer[1]<<16) & 0x00FF0000) |
((_receiveBuffer[2]<< 8) & 0x0000FF00) |
((_receiveBuffer[3] ) & 0x000000FF);
// check if we can read a complete message
if((uint32_t) _receiveBuffer.size() < messageSize + 4) return;
// remove header + msg from buffer
const QByteArray& msg = _receiveBuffer.remove(0, messageSize + 4);
const uint8_t* msgData = reinterpret_cast<const uint8_t*>(msg.mid(3, messageSize).constData());
flatbuffers::Verifier verifier(msgData, messageSize);
if (flatbuf::VerifyHyperionRequestBuffer(verifier))
{
auto message = flatbuf::GetHyperionRequest(msgData);
handleMessage(message);
continue;
}
qDebug()<<"Unable to pasrse msg";
sendErrorReply("Unable to parse message");
}
//emit newMessage(msgData,messageSize);
// Emit this to send a new priority register event to all Hyperion instances,
// emit registerGlobalInput(_priority, hyperion::COMP_FLATBUFSERVER, QString("%1@%2").arg("PLACE_ORIGIN_STRING_FROM_SENDER_HERE",_socket->peerAddress()));
// Emit this to send the image data event to all Hyperion instances
// emit setGlobalInput(_priority, _image, _timeout);
}
void FlatBufferClient::forceClose()
{
_socket->close();
}
void FlatBufferClient::disconnected()
{
qDebug()<<"Socket Closed";
//emit clearGlobalPriority(_priority, hyperion::COMP_FLATBUFSERVER);
_socket->deleteLater();
emit clientDisconnected();
}
void FlatBufferClient::handleMessage(const flatbuf::HyperionRequest * message)
{
switch (message->command())
{
case flatbuf::Command_COLOR:
qDebug()<<"handle colorReuest";
if (!flatbuffers::IsFieldPresent(message, flatbuf::HyperionRequest::VT_COLORREQUEST))
{
sendErrorReply("Received COLOR command without ColorRequest");
break;
}
//handleColorCommand(message->colorRequest());
break;
case flatbuf::Command_IMAGE:
qDebug()<<"handle imageReuest";
if (!flatbuffers::IsFieldPresent(message, flatbuf::HyperionRequest::VT_IMAGEREQUEST))
{
sendErrorReply("Received IMAGE command without ImageRequest");
break;
}
handleImageCommand(message->imageRequest());
break;
case flatbuf::Command_CLEAR:
if (!flatbuffers::IsFieldPresent(message, flatbuf::HyperionRequest::VT_CLEARREQUEST))
{
sendErrorReply("Received CLEAR command without ClearRequest");
break;
}
//handleClearCommand(message->clearRequest());
break;
case flatbuf::Command_CLEARALL:
//handleClearallCommand();
break;
default:
qDebug()<<"handleNotImplemented";
handleNotImplemented();
}
}
void FlatBufferClient::handleImageCommand(const flatbuf::ImageRequest *message)
{
// extract parameters
int priority = message->priority();
int duration = message->duration();
int width = message->imagewidth();
int height = message->imageheight();
const auto & imageData = message->imagedata();
// make sure the prio is registered before setInput()
if(priority != _priority)
{
_hyperion->clear(_priority);
_hyperion->registerInput(priority, hyperion::COMP_FLATBUFSERVER, "proto@"+_clientAddress);
_priority = priority;
}
// check consistency of the size of the received data
if ((int) imageData->size() != width*height*3)
{
sendErrorReply("Size of image data does not match with the width and height");
return;
}
// create ImageRgb
Image<ColorRgb> image(width, height);
memcpy(image.memptr(), imageData->data(), imageData->size());
_hyperion->setInputImage(_priority, image, duration);
// send reply
sendSuccessReply();
}
void FlatBufferClient::handleNotImplemented()
{
sendErrorReply("Command not implemented");
}
void FlatBufferClient::sendMessage()
{
auto size = _builder.GetSize();
const uint8_t* buffer = _builder.GetBufferPointer();
uint8_t sizeData[] = {uint8_t(size >> 24), uint8_t(size >> 16), uint8_t(size >> 8), uint8_t(size)};
_socket->write((const char *) sizeData, sizeof(sizeData));
_socket->write((const char *)buffer, size);
_socket->flush();
_builder.Clear();
}
void FlatBufferClient::sendSuccessReply()
{
auto reply = flatbuf::CreateHyperionReplyDirect(_builder, flatbuf::Type_REPLY, true);
_builder.Finish(reply);
// send reply
sendMessage();
}
void FlatBufferClient::sendErrorReply(const std::string &error)
{
// create reply
auto reply = flatbuf::CreateHyperionReplyDirect(_builder, flatbuf::Type_REPLY, false, error.c_str());
_builder.Finish(reply);
// send reply
sendMessage();
}

View File

@ -0,0 +1,122 @@
#pragma once
// util
#include <utils/Logger.h>
#include <utils/Image.h>
#include <utils/ColorRgb.h>
#include <utils/Components.h>
// flatbuffer FBS
#include "hyperion_reply_generated.h"
#include "hyperion_request_generated.h"
class QTcpSocket;
class QTimer;
class Hyperion;
namespace flatbuf {
class HyperionRequest;
}
///
/// @brief Socket (client) of FlatBufferServer
///
class FlatBufferClient : public QObject
{
Q_OBJECT
public:
///
/// @brief Construct the client
/// @param socket The socket
/// @param timeout The timeout when a client is automatically disconnected and the priority unregistered
/// @param parent The parent
///
explicit FlatBufferClient(QTcpSocket* socket, const int &timeout, QObject *parent = nullptr);
signals:
///
/// @brief forward register data to HyperionDaemon
///
void registerGlobalInput(const int priority, const hyperion::Components& component, const QString& origin = "System", const QString& owner = "", unsigned smooth_cfg = 0);
///
/// @brief forward prepared image to HyperionDaemon
///
const bool setGlobalInputImage(const int priority, const Image<ColorRgb>& image, const int timeout_ms = -1);
///
/// @brief forward clear to HyperionDaemon
///
void clearGlobalPriority(const int& priority, const hyperion::Components& component);
///
/// @brief Emits whenever the client disconnected
///
void clientDisconnected();
public slots:
///
/// @brief close the socket and call disconnected()
///
void forceClose();
private slots:
///
/// @brief Is called whenever the socket got new data to read
///
void readyRead();
///
/// @brief Is called when the socket closed the connection, also requests thread exit
///
void disconnected();
private:
///
/// @brief Handle the received message
///
void handleMessage(const flatbuf::HyperionRequest *message);
///
/// Handle an incoming Image message
///
/// @param message the incoming message
///
void handleImageCommand(const flatbuf::ImageRequest * message);
///
/// Send handle not implemented
///
void handleNotImplemented();
///
/// Send a message to the connected client
///
void sendMessage();
///
/// Send a standard reply indicating success
///
void sendSuccessReply();
///
/// Send an error message back to the client
///
/// @param error String describing the error
///
void sendErrorReply(const std::string & error);
private:
Logger *_log;
QTcpSocket *_socket;
const QString _clientAddress;
QTimer *_timeoutTimer;
int _timeout;
int _priority;
Hyperion* _hyperion;
QByteArray _receiveBuffer;
// Flatbuffers builder
flatbuffers::FlatBufferBuilder _builder;
};

View File

@ -0,0 +1,223 @@
// stl includes
#include <stdexcept>
// Qt includes
#include <QRgb>
// protoserver includes
#include <flatbufserver/FlatBufferConnection.h>
FlatBufferConnection::FlatBufferConnection(const QString & address) :
_socket(),
_skipReply(false),
_prevSocketState(QAbstractSocket::UnconnectedState),
_log(Logger::getInstance("FLATBUFCONNECTION"))
{
QStringList parts = address.split(":");
if (parts.size() != 2)
{
throw std::runtime_error(QString("FLATBUFCONNECTION ERROR: Wrong address: Unable to parse address (%1)").arg(address).toStdString());
}
_host = parts[0];
bool ok;
_port = parts[1].toUShort(&ok);
if (!ok)
{
throw std::runtime_error(QString("FLATBUFCONNECTION ERROR: Wrong port: Unable to parse the port number (%1)").arg(parts[1]).toStdString());
}
// try to connect to host
Info(_log, "Connecting to Hyperion: %s:%d", _host.toStdString().c_str(), _port);
connectToHost();
// start the connection timer
_timer.setInterval(5000);
_timer.setSingleShot(false);
connect(&_timer,SIGNAL(timeout()), this, SLOT(connectToHost()));
connect(&_socket, SIGNAL(readyRead()), this, SLOT(readData()));
_timer.start();
}
FlatBufferConnection::~FlatBufferConnection()
{
_timer.stop();
_socket.close();
}
void FlatBufferConnection::readData()
{
qint64 bytesAvail;
while((bytesAvail = _socket.bytesAvailable()))
{
// ignore until we get 4 bytes.
if (bytesAvail < 4) {
continue;
}
char sizeBuf[4];
_socket.read(sizeBuf, sizeof(sizeBuf));
uint32_t messageSize =
((sizeBuf[0]<<24) & 0xFF000000) |
((sizeBuf[1]<<16) & 0x00FF0000) |
((sizeBuf[2]<< 8) & 0x0000FF00) |
((sizeBuf[3] ) & 0x000000FF);
QByteArray buffer;
while((uint32_t)buffer.size() < messageSize)
{
_socket.waitForReadyRead();
buffer.append(_socket.read(messageSize - buffer.size()));
}
const uint8_t* replyData = reinterpret_cast<const uint8_t*>(buffer.constData());
flatbuffers::Verifier verifier(replyData, messageSize);
if (!flatbuf::VerifyHyperionReplyBuffer(verifier))
{
Error(_log, "Error while reading data from host");
return;
}
auto reply = flatbuf::GetHyperionReply(replyData);
parseReply(reply);
}
}
void FlatBufferConnection::setSkipReply(bool skip)
{
_skipReply = skip;
}
void FlatBufferConnection::setColor(const ColorRgb & color, int priority, int duration)
{
auto colorReq = flatbuf::CreateColorRequest(_builder, priority, (color.red << 16) | (color.green << 8) | color.blue, duration);
auto req = flatbuf::CreateHyperionRequest(_builder,flatbuf::Command_COLOR, colorReq);
_builder.Finish(req);
sendMessage(_builder.GetBufferPointer(), _builder.GetSize());
}
void FlatBufferConnection::setImage(const Image<ColorRgb> &image, int priority, int duration)
{
/* #TODO #BROKEN auto imgData = _builder.CreateVector<flatbuffers::Offset<uint8_t>>(image.memptr(), image.width() * image.height() * 3);
auto imgReq = flatbuf::CreateImageRequest(_builder, priority, image.width(), image.height(), imgData, duration);
auto req = flatbuf::CreateHyperionRequest(_builder,flatbuf::Command_IMAGE,0,imgReq);
_builder.Finish(req);
sendMessage(_builder.GetBufferPointer(), _builder.GetSize());*/
}
void FlatBufferConnection::clear(int priority)
{
auto clearReq = flatbuf::CreateClearRequest(_builder, priority);
auto req = flatbuf::CreateHyperionRequest(_builder,flatbuf::Command_CLEAR,0,0,clearReq);
_builder.Finish(req);
sendMessage(_builder.GetBufferPointer(), _builder.GetSize());
}
void FlatBufferConnection::clearAll()
{
auto req = flatbuf::CreateHyperionRequest(_builder,flatbuf::Command_CLEARALL);
// send command message
_builder.Finish(req);
sendMessage(_builder.GetBufferPointer(), _builder.GetSize());
}
void FlatBufferConnection::connectToHost()
{
// try connection only when
if (_socket.state() == QAbstractSocket::UnconnectedState)
{
_socket.connectToHost(_host, _port);
//_socket.waitForConnected(1000);
}
}
void FlatBufferConnection::sendMessage(const uint8_t* buffer, uint32_t size)
{
// print out connection message only when state is changed
if (_socket.state() != _prevSocketState )
{
switch (_socket.state() )
{
case QAbstractSocket::UnconnectedState:
Info(_log, "No connection to Hyperion: %s:%d", _host.toStdString().c_str(), _port);
break;
case QAbstractSocket::ConnectedState:
Info(_log, "Connected to Hyperion: %s:%d", _host.toStdString().c_str(), _port);
break;
default:
Debug(_log, "Connecting to Hyperion: %s:%d", _host.toStdString().c_str(), _port);
break;
}
_prevSocketState = _socket.state();
}
if (_socket.state() != QAbstractSocket::ConnectedState)
{
return;
}
const uint8_t header[] = {
uint8_t((size >> 24) & 0xFF),
uint8_t((size >> 16) & 0xFF),
uint8_t((size >> 8) & 0xFF),
uint8_t((size ) & 0xFF)};
// write message
int count = 0;
count += _socket.write(reinterpret_cast<const char *>(header), 4);
count += _socket.write(reinterpret_cast<const char *>(buffer), size);
if (!_socket.waitForBytesWritten())
{
Error(_log, "Error while writing data to host");
return;
}
}
bool FlatBufferConnection::parseReply(const flatbuf::HyperionReply *reply)
{
bool success = false;
switch (reply->type())
{
case flatbuf::Type_REPLY:
{
if (!_skipReply)
{
if (!reply->success())
{
if (flatbuffers::IsFieldPresent(reply, flatbuf::HyperionReply::VT_ERROR))
{
throw std::runtime_error("PROTOCONNECTION ERROR: " + reply->error()->str());
}
else
{
throw std::runtime_error("PROTOCONNECTION ERROR: No error info");
}
}
else
{
success = true;
}
}
break;
}
case flatbuf::Type_VIDEO:
{
VideoMode vMode = (VideoMode)reply->video();
emit setVideoMode(vMode);
break;
}
}
return success;
}

View File

@ -0,0 +1,113 @@
#include <flatbufserver/FlatBufferServer.h>
#include "FlatBufferClient.h"
// qt
#include <QJsonObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
FlatBufferServer::FlatBufferServer(const QJsonDocument& config, QObject* parent)
: QObject(parent)
, _server(new QTcpServer(this))
, _log(Logger::getInstance("FLATBUFSERVER"))
, _timeout(5000)
, _config(config)
{
}
FlatBufferServer::~FlatBufferServer()
{
stopServer();
delete _server;
}
void FlatBufferServer::initServer()
{
qDebug()<<"Thread in InitServer is"<<this->thread();
connect(_server, &QTcpServer::newConnection, this, &FlatBufferServer::newConnection);
// apply config
handleSettingsUpdate(settings::FLATBUFSERVER, _config);
}
void FlatBufferServer::handleSettingsUpdate(const settings::type& type, const QJsonDocument& config)
{
qDebug()<<"Thread in handleSettingsUpdate is"<<this->thread();
if(type == settings::FLATBUFSERVER)
{
const QJsonObject& obj = config.object();
quint16 port = obj["port"].toInt(19400);
// port check
if(_server->serverPort() != port)
{
stopServer();
_port = port;
}
// new timeout just for new connections
_timeout = obj["timeout"].toInt(5000);
// enable check
obj["enable"].toBool(true) ? startServer() : stopServer();
}
}
void FlatBufferServer::newConnection()
{
qDebug()<<"Thread in newConnection is"<<this->thread();
while(_server->hasPendingConnections())
{
if(QTcpSocket* socket = _server->nextPendingConnection())
{
Debug(_log, "New connection from %s", QSTRING_CSTR(socket->peerAddress().toString()));
FlatBufferClient *client = new FlatBufferClient(socket, _timeout, this);
// internal
connect(client, &FlatBufferClient::clientDisconnected, this, &FlatBufferServer::clientDisconnected);
// forward data
//connect(clientThread, &FlatBufferClient::);
_openConnections.append(client);
}
}
}
void FlatBufferServer::clientDisconnected()
{
FlatBufferClient* client = qobject_cast<FlatBufferClient*>(sender());
client->deleteLater();
_openConnections.removeAll(client);
}
void FlatBufferServer::startServer()
{
if(!_server->isListening())
{
if(!_server->listen(QHostAddress::Any, _port))
{
Error(_log,"Failed to bind port %d", _port);
}
else
{
Info(_log,"Started on port %d", _port);
}
}
}
void FlatBufferServer::stopServer()
{
if(_server->isListening())
{
// close client connections
for(auto client : _openConnections)
{
client->forceClose();
}
_server->close();
Info(_log, "Stopped");
}
}

View File

@ -0,0 +1,15 @@
namespace flatbuf;
enum Type : int {
REPLY = 0,
VIDEO = 1,
}
table HyperionReply {
type:Type;
success:bool;
error:string;
video:int;
}
root_type HyperionReply;

View File

@ -0,0 +1,35 @@
namespace flatbuf;
enum Command : int {
COLOR = 0,
IMAGE = 1,
CLEAR = 2,
CLEARALL = 3,
}
table HyperionRequest {
command:Command;
colorRequest:flatbuf.ColorRequest;
imageRequest:flatbuf.ImageRequest;
clearRequest:flatbuf.ClearRequest;
}
table ColorRequest {
priority:int;
RgbColor:int;
duration:int;
}
table ImageRequest {
priority:int;
imagewidth:int;
imageheight:int;
imagedata:[ubyte];
duration:int;
}
table ClearRequest {
priority:int;
}
root_type HyperionRequest;

View File

@ -0,0 +1,37 @@
{
"type" : "object",
"required" : true,
"title" : "edt_conf_fbs_heading_title",
"properties" :
{
"enable" :
{
"type" : "boolean",
"required" : true,
"title" : "edt_conf_general_enable_title",
"default" : true,
"propertyOrder" : 1
},
"port" :
{
"type" : "integer",
"required" : true,
"title" : "edt_conf_general_port_title",
"minimum" : 1024,
"maximum" : 65535,
"default" : 19400,
"propertyOrder" : 2
},
"timeout" :
{
"type" : "integer",
"required" : true,
"title" : "edt_conf_fbs_timeout_title",
"append" : "edt_append_s",
"minimum" : 1,
"default" : 5,
"propertyOrder" : 3
}
},
"additionalProperties" : false
}