1
0
Fork 0
mirror of https://git.rwth-aachen.de/acs/public/villas/node/ synced 2025-03-09 00:00:00 +01:00

api: implement first version of universal data-exchange API

This commit is contained in:
Steffen Vogel 2022-03-28 18:06:47 +02:00
parent f09f91a9e2
commit 56fa561493
12 changed files with 604 additions and 3 deletions

View file

@ -109,7 +109,7 @@ protected:
void worker();
public:
/** Initalize the API.
/** Initialize the API.
*
* Save references to list of paths / nodes for command execution.
*/

View file

@ -0,0 +1,49 @@
/** Universal Data-exchange API request.
*
* @file
* @author Steffen Vogel <svogel2@eonerc.rwth-aachen.de>
* @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <villas/nodes/api.hpp>
#include <villas/api/requests/node.hpp>
namespace villas {
namespace node {
/* Forward declarations */
class Node;
namespace api {
class UniversalRequest : public NodeRequest {
protected:
APINode *api_node;
public:
using NodeRequest::NodeRequest;
virtual void
prepare();
};
} /* namespace api */
} /* namespace node */
} /* namespace villas */

View file

@ -0,0 +1,62 @@
/** Node type: Universal Data-exchange API
*
* @file
* @author Steffen Vogel <svogel2@eonerc.rwth-aachen.de>
* @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#pragma once
#include <villas/node.hpp>
#include <pthread.h>
namespace villas {
namespace node {
/* Forward declarations */
struct Sample;
class APINode : public Node {
public:
APINode(const std::string &name = "");
struct Direction {
Sample *sample;
pthread_cond_t cv;
pthread_mutex_t mutex;
};
// Accessed by api::universal::SignalRequest
Direction read, write;
virtual
int prepare();
protected:
virtual
int _read(struct Sample *smps[], unsigned cnt);
virtual
int _write(struct Sample *smps[], unsigned cnt);
};
} /* namespace node */
} /* namespace villas */

View file

@ -129,13 +129,13 @@ public:
os << CLR_MAG("=>");
if (p.destinations.size() > 1)
if (p.destinations.size() != 1)
os << " [";
for (auto pd : p.destinations)
os << " " << pd->getNode()->getNameShort();
if (p.destinations.size() > 1)
if (p.destinations.size() != 1)
os << " ]";
return os;

View file

@ -28,6 +28,7 @@ set(API_SRC
requests/node.cpp
requests/path.cpp
requests/universal.cpp
requests/status.cpp
requests/capabiltities.cpp
@ -43,6 +44,11 @@ set(API_SRC
requests/paths.cpp
requests/path_info.cpp
requests/path_action.cpp
requests/universal/config.cpp
requests/universal/info.cpp
requests/universal/signal.cpp
requests/universal/signals.cpp
)
if(WITH_GRAPHVIZ)

View file

@ -0,0 +1,36 @@
/** Universal Data-exchange API request
*
* @file
* @author Steffen Vogel <svogel2@eonerc.rwth-aachen.de>
* @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <villas/api/requests/universal.hpp>
using namespace villas::node;
using namespace villas::node::api;
void UniversalRequest::prepare()
{
NodeRequest::prepare();
api_node = dynamic_cast<APINode*>(node);
if (!api_node)
throw BadRequest("Node {} is not an univeral API node!", node->getNameShort());
}

View file

@ -0,0 +1,57 @@
/** The Universal Data-exchange API.
*
* @author Steffen Vogel <svogel2@eonerc.rwth-aachen.de>
* @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <villas/api/requests/universal.hpp>
#include <villas/api/response.hpp>
#include <villas/node.hpp>
namespace villas {
namespace node {
namespace api {
namespace universal {
class ConfigRequest : public UniversalRequest {
public:
using UniversalRequest::UniversalRequest;
virtual Response * execute()
{
if (method != Session::Method::GET)
throw InvalidMethod(this);
if (body != nullptr)
throw BadRequest("This endpoint does not accept any body data");
return new JsonResponse(session, HTTP_STATUS_OK, node->getConfig());
}
};
/* Register API requests */
static char n[] = "universal/config";
static char r[] = "/universal/(" RE_NODE_NAME ")/config";
static char d[] = "get configuration of universal data-exchange API";
static RequestPlugin<ConfigRequest, n, r, d> p;
} /* namespace universal */
} /* namespace api */
} /* namespace node */
} /* namespace villas */

View file

@ -0,0 +1,74 @@
/** The Universal Data-exchange API.
*
* @author Steffen Vogel <svogel2@eonerc.rwth-aachen.de>
* @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <uuid.h>
#include <villas/api/requests/universal.hpp>
#include <villas/api/response.hpp>
#include <villas/node.hpp>
namespace villas {
namespace node {
namespace api {
namespace universal {
class InfoRequest : public UniversalRequest {
public:
using UniversalRequest::UniversalRequest;
virtual Response * execute()
{
if (method != Session::Method::GET)
throw InvalidMethod(this);
if (body != nullptr)
throw BadRequest("This endpoint does not accept any body data");
auto uid = node->getUuid();
char uid_str[UUID_STR_LEN];
uuid_unparse(uid, uid_str);
auto *info = json_pack("{ s: s, s: s, s: { s: s, s: s, s: s } }",
"id", node->getNameShort().c_str(),
"uuid", uid_str,
"transport",
"type", "villas",
"version", PROJECT_VERSION,
"build", PROJECT_BUILD_ID
);
return new JsonResponse(session, HTTP_STATUS_OK, info);
}
};
/* Register API requests */
static char n[] = "universal/info";
static char r[] = "/universal/(" RE_NODE_NAME ")/info";
static char d[] = "get infos of universal data-exchange API";
static RequestPlugin<InfoRequest, n, r, d> p;
} /* namespace universal */
} /* namespace api */
} /* namespace node */
} /* namespace villas */

View file

@ -0,0 +1,139 @@
/** The Universal Data-exchange API.
*
* @author Steffen Vogel <svogel2@eonerc.rwth-aachen.de>
* @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <villas/api/requests/universal.hpp>
#include <villas/api/response.hpp>
#include <villas/timing.hpp>
namespace villas {
namespace node {
namespace api {
namespace universal {
class SignalRequest : public UniversalRequest {
public:
using UniversalRequest::UniversalRequest;
Response * executeGet(const std::string &signalName)
{
if (body != nullptr)
throw BadRequest("This endpoint does not accept any body data");
pthread_mutex_lock(&api_node->write.mutex);
auto *smp = api_node->write.sample;
if (!smp) {
pthread_mutex_unlock(&api_node->write.mutex);
throw Error(HTTP_STATUS_NOT_FOUND, "No data available");
}
auto idx = smp->signals->getIndexByName(signalName);
if (idx < 0) {
pthread_mutex_unlock(&api_node->write.mutex);
throw Error(HTTP_STATUS_NOT_FOUND, "Unknown signal id: {}", signalName);
}
auto sig = smp->signals->getByIndex(idx);
auto *json_signal = json_pack("{ s: f, s: o }",
"timestamp", time_to_double(&smp->ts.origin),
"value", smp->data[idx].toJson(sig->type)
);
if (smp->length <= (unsigned) idx)
smp->length = idx + 1;
smp->flags |= (int) SampleFlags::HAS_TS_ORIGIN | (int) SampleFlags::HAS_DATA;
pthread_mutex_unlock(&api_node->write.mutex);
logger->info("resp: {}", (void *) json_signal);
return new JsonResponse(session, HTTP_STATUS_OK, json_signal);
}
Response * executePut(const std::string &signalName)
{
int ret;
pthread_mutex_lock(&api_node->read.mutex);
auto *smp = api_node->read.sample;
if (!smp) {
pthread_mutex_unlock(&api_node->read.mutex);
throw Error(HTTP_STATUS_INTERNAL_SERVER_ERROR, "Not initialized yet");
}
auto idx = smp->signals->getIndexByName(signalName);
if (idx < 0) {
pthread_mutex_unlock(&api_node->read.mutex);
throw BadRequest("Unknown signal id: {}", signalName);
}
auto sig = smp->signals->getByIndex(idx);
double timestamp = 0;
double value = 0;
json_error_t err;
ret = json_unpack_ex(body, &err, 0, "{ s: f, s: f }",
"timestamp", &timestamp,
"value", &value
);
if (ret) {
pthread_mutex_unlock(&api_node->read.mutex);
throw BadRequest("Malformed body: {}", err.text);
}
smp->ts.origin = time_from_double(timestamp);
smp->data[idx].f = value;
pthread_cond_signal(&api_node->read.cv);
pthread_mutex_unlock(&api_node->read.mutex);
return new JsonResponse(session, HTTP_STATUS_OK, json_object());
}
virtual Response * execute()
{
auto const &signalName = matches[2];
switch (method) {
case Session::Method::GET:
return executeGet(signalName);
case Session::Method::PUT:
return executePut(signalName);
default:
throw InvalidMethod(this);
}
}
};
/* Register API requests */
static char n[] = "universal/signal";
static char r[] = "/universal/(" RE_NODE_NAME ")/signal/([a-z0-9_-]+)/state";
static char d[] = "get or set signal of universal data-exchange API";
static RequestPlugin<SignalRequest, n, r, d> p;
} /* namespace universal */
} /* namespace api */
} /* namespace node */
} /* namespace villas */

View file

@ -0,0 +1,81 @@
/** The Universal Data-exchange API.
*
* @author Steffen Vogel <svogel2@eonerc.rwth-aachen.de>
* @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <villas/api/requests/universal.hpp>
#include <villas/api/response.hpp>
namespace villas {
namespace node {
namespace api {
namespace universal {
class SignalsRequest : public UniversalRequest {
public:
using UniversalRequest::UniversalRequest;
virtual Response * execute()
{
if (method != Session::Method::GET)
throw InvalidMethod(this);
if (body != nullptr)
throw BadRequest("This endpoint does not accept any body data");
auto *json_sigs = json_array();
for (auto const &sig : *node->getOutputSignals()) {
auto *json_sig = json_pack("{ s: s, s: s, s: b, s: b, s: o }",
"id", fmt::format("out/{}", sig->name).c_str(),
"source", node->getNameShort().c_str(),
"readable", true,
"writable", false,
"value", json_null()
);
json_array_append(json_sigs, json_sig);
}
for (auto const &sig : *node->getInputSignals()) {
auto *json_sig = json_pack("{ s: s, s: s, s: b, s: b, s: o }",
"id", fmt::format("in/{}", sig->name).c_str(),
"source", node->getNameShort().c_str(),
"readable", false,
"writable", true,
"value", json_null()
);
json_array_append(json_sigs, json_sig);
}
return new JsonResponse(session, HTTP_STATUS_OK, json_sigs);
}
};
/* Register API requests */
static char n[] = "universal/signals";
static char r[] = "/universal/(" RE_NODE_NAME ")/signals";
static char d[] = "get signals of universal data-exchange API node";
static RequestPlugin<SignalsRequest, n, r, d> p;
} /* namespace universal */
} /* namespace api */
} /* namespace node */
} /* namespace villas */

View file

@ -24,6 +24,10 @@ set(NODE_SRC
loopback_internal.cpp
)
if(WITH_WEB)
list(APPEND NODE_SRC api.cpp)
endif()
# Enable Golang support
if(WITH_NODE_GO)
list(APPEND NODE_SRC go.cpp)

93
lib/nodes/api.cpp Normal file
View file

@ -0,0 +1,93 @@
/** Node type: Universal Data-exchange API
*
* @author Steffen Vogel <svogel2@eonerc.rwth-aachen.de>
* @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC
* @license GNU General Public License (version 3)
*
* VILLASnode
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************************/
#include <vector>
#include <villas/exceptions.hpp>
#include <villas/nodes/api.hpp>
using namespace villas;
using namespace villas::node;
APINode::APINode(const std::string &name) :
Node(name)
{
int ret;
auto dirs = std::vector{&read, &write};
for (auto dir : dirs) {
ret = pthread_mutex_init(&dir->mutex, nullptr);
if (ret)
throw RuntimeError("failed to initialize mutex");
ret = pthread_cond_init(&dir->cv, nullptr);
if (ret)
throw RuntimeError("failed to initialize mutex");
}
}
int APINode::prepare()
{
auto signals_in = getInputSignals(false);
read.sample = sample_alloc_mem(signals_in->size());
if (!read.sample)
throw MemoryAllocationError();
write.sample = sample_alloc_mem(64);
if (!write.sample)
throw MemoryAllocationError();
unsigned j = 0;
for (auto sig : *signals_in)
read.sample->data[j++] = sig->init;
read.sample->length = j;
read.sample->signals = signals_in;
return Node::prepare();
}
int APINode::_read(struct Sample *smps[], unsigned cnt)
{
assert(cnt == 1);
pthread_cond_wait(&read.cv, &read.mutex);
sample_copy(smps[0], read.sample);
return 1;
}
int APINode::_write(struct Sample *smps[], unsigned cnt)
{
assert(cnt == 1);
sample_copy(write.sample, smps[0]);
pthread_cond_signal(&write.cv);
return 1;
}
static char n[] = "api";
static char d[] = "A node providing a HTTP REST interface";
static NodePlugin<APINode, n , d, (int) NodeFactory::Flags::SUPPORTS_READ | (int) NodeFactory::Flags::SUPPORTS_WRITE, 1> p;