Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C37.118 synchrophasor protocol node #652

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ cmake_dependent_option(WITH_TOOLS "Build auxilary tools"
cmake_dependent_option(WITH_WEB "Build with internal webserver" "${WITH_DEFAULTS}" "LIBWEBSOCKETS_FOUND" OFF)

cmake_dependent_option(WITH_NODE_AMQP "Build with amqp node-type" "${WITH_DEFAULTS}" "RABBITMQ_C_FOUND" OFF)
cmake_dependent_option(WITH_NODE_C37_118 "Build with c37.118 node-type" "${WITH_DEFAULTS}" "" OFF)
cmake_dependent_option(WITH_NODE_CAN "Build with can node-type" "${WITH_DEFAULTS}" "" OFF)
cmake_dependent_option(WITH_NODE_COMEDI "Build with comedi node-type" "${WITH_DEFAULTS}" "COMEDILIB_FOUND" OFF)
cmake_dependent_option(WITH_NODE_ETHERCAT "Build with ethercat node-type" "${WITH_DEFAULTS}" "ETHERLAB_FOUND; NOT WITHOUT_GPL" OFF)
Expand Down Expand Up @@ -281,6 +282,7 @@ add_feature_info(TOOLS WITH_TOOLS "Build auxil
add_feature_info(WEB WITH_WEB "Build with internal webserver")

add_feature_info(NODE_AMQP WITH_NODE_AMQP "Build with amqp node-type")
add_feature_info(NODE_C37_118 WITH_NODE_C37_118 "Build with c37.118 node-type")
add_feature_info(NODE_CAN WITH_NODE_CAN "Build with can node-type")
add_feature_info(NODE_COMEDI WITH_NODE_COMEDI "Build with comedi node-type")
add_feature_info(NODE_ETHERCAT WITH_NODE_ETHERCAT "Build with ethercat node-type")
Expand Down
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
boxfort
clang-tools
criterion
gdb
jq
libffi
libgit2
Expand Down
77 changes: 77 additions & 0 deletions include/villas/nodes/c37_118.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* Node type: C37-118.
*
* Author: Philipp Jungkamp <[email protected]>
* SPDX-FileCopyrightText: 2014-2024 Institute for Automation of Complex Power Systems, RWTH Aachen University
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <cstdint>
#include <string>
#include <optional>
#include <variant>
#include <vector>
#include <villas/node.hpp>
#include <villas/node/config.hpp>
#include <villas/nodes/c37_118/parser.hpp>
#include <villas/queue_signalled.h>
#include <villas/signal.hpp>
#include <villas/socket_addr.hpp>
#include <villas/timing.hpp>

namespace villas::node::c37_118 {

using parser::Parser;
using types::Frame;
using types::Config;

class C37_118 final : public Node {
private:
struct Server {
std::string addr;
uint16_t port;
int socktype;
Config config;

int listener_fd;
std::vector<int> connection_fds;
};

struct Client {
std::string addr;
uint16_t port;
uint16_t idcode;
int socktype;

std::optional<Config> config;
int connection_fd;
std::vector<unsigned char> recv_buf = std::vector<unsigned char>(1024);
Parser parser;

void send(Frame::Variant message, timespec ts = time_now());
std::optional<Frame> recv();
};

std::variant<Server, Client> role;

void parseServer(json_t *json);
void parseClient(json_t *json);

void prepareServer(Server &server);
void prepareClient(Client &client);

virtual int _read(struct Sample *smps[], unsigned cnt) override;

public:
C37_118(const uuid_t &id = {}, const std::string &name = "") : Node{id, name} {}

virtual ~C37_118() override;
virtual int parse(json_t *json) override;
virtual int prepare() override;
virtual int start() override;
virtual int stop() override;
virtual std::vector<int> getPollFDs() override;
};

} // namespace villas::node::c37_118
90 changes: 90 additions & 0 deletions include/villas/nodes/c37_118/parser.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/* Parser for C37-118.
*
* Author: Philipp Jungkamp <[email protected]>
* SPDX-FileCopyrightText: 2014-2024 Institute for Automation of Complex Power Systems, RWTH Aachen University
* SPDX-License-Identifier: Apache-2.0
*/

#pragma once

#include <cstring>
#include <optional>
#include <string>
#include <villas/nodes/c37_118/types.hpp>

namespace villas::node::c37_118::parser {
using namespace villas::node::c37_118::types;

class Parser {
public:
std::optional<Frame> deserialize(const unsigned char *buffer,
std::size_t length,
const Config *config);

std::vector<unsigned char> serialize(const Frame &frame,
const Config *config);

private:
template <typename T> void de_copy(T *value, std::size_t count = sizeof(T)) {
if (de_cursor + count > de_end)
throw RuntimeError{"c37_118: broken frame"};

std::memcpy((void *)value, de_cursor, count);
de_cursor += count;
}

template <typename T>
void se_copy(const T *value, std::size_t count = sizeof(T)) {
auto index = se_buffer.size();
se_buffer.insert(se_buffer.end(), count, 0);
std::memcpy(se_buffer.data() + index, (const void *)value, count);
}

uint16_t deserialize_uint16_t();
uint32_t deserialize_uint32_t();
int16_t deserialize_int16_t();
float deserialize_float();
std::string deserialize_name1();
std::complex<float> deserialize_phasor(uint16_t format, const PhasorInfo &info);
float deserialize_freq(const PmuConfig &pmu);
float deserialize_dfreq(uint16_t format);
float deserialize_analog(uint16_t format, const AnalogInfo &aninfo);
uint16_t deserialize_digital(const DigitalInfo &dginfo);
PmuData deserialize_pmu_data(const PmuConfig &pmu_config);
PmuConfig deserialize_pmu_config_simple();
Config deserialize_config_simple();
Config1 deserialize_config1();
Config2 deserialize_config2();
Data deserialize_data(const Config &config);
Header deserialize_header();
Command deserialize_command();
std::optional<Frame> try_deserialize_frame(const Config *config);

void serialize_uint16_t(const uint16_t &value);
void serialize_uint32_t(const uint32_t &value);
void serialize_int16_t(const int16_t &value);
void serialize_float(const float &value);
void serialize_name1(const std::string &value);
void serialize_phasor(const std::complex<float> &value, uint16_t format, const PhasorInfo &phinfo);
void serialize_freq(const float &value, const PmuConfig &pmu);
void serialize_dfreq(const float &value, uint16_t format);
void serialize_analog(const float &value, uint16_t format, const AnalogInfo &aninfo);
void serialize_digital(const uint16_t &value, const DigitalInfo &dginfo);
void serialize_pmu_data(const PmuData &value, const PmuConfig &pmu_config);
void serialize_pmu_config_simple(const PmuConfig &value);
void serialize_config_simple(const Config &value);
void serialize_config1(const Config1 &value);
void serialize_config2(const Config2 &value);
void serialize_data(const Data &value, const Config &config);
void serialize_header(const Header &value);
void serialize_command(const Command &value);
void serialize_frame(const Frame &value, const Config *config);

const unsigned char *de_cursor;
const unsigned char *de_end;
std::vector<unsigned char> se_buffer;
};

uint16_t calculate_crc(const unsigned char *frame, uint16_t size);

} // namespace villas::node::c37_118::parser
163 changes: 163 additions & 0 deletions include/villas/nodes/c37_118/types.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/* Node type: C37-118.
*
* Author: Philipp Jungkamp <[email protected]>
* SPDX-FileCopyrightText: 2014-2024 Institute for Automation of Complex Power Systems, RWTH Aachen University
* SPDX-License-Identifier: Apache-2.0
*/

#include <array>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing file header

#include <complex>
#include <stdint.h>
#include <variant>
#include <vector>

using namespace std::literals;

namespace villas::node::c37_118::types {

struct PmuData final {
uint16_t stat;
std::vector<std::complex<float>> phasor;
float freq;
float dfreq;
std::vector<float> analog;
std::vector<uint16_t> digital;
};

struct Data final {
std::vector<PmuData> pmus;
};

struct Header final {
std::string data;
};

struct PhasorInfo final {
static constexpr uint8_t UNIT_VOLT = 0;
static constexpr uint8_t UNIT_AMPERE = 1;

std::string chnam;
uint32_t phunit;

uint8_t unit() const noexcept {
return phunit >> 24;
}

std::string unit_str() const noexcept {
switch (unit()) {
case UNIT_VOLT:
return "volt"s;
case UNIT_AMPERE:
return "ampere"s;
default:
return "other"s;
}
}

float scale() const noexcept {
return static_cast<float>(phunit & 0xFFFFFF) / 10'000;
}
};

struct AnalogInfo final {
static constexpr uint8_t UNIT_POINT_ON_WAVE = 0;
static constexpr uint8_t UNIT_RMS = 1;
static constexpr uint8_t UNIT_PEAK = 2;

std::string chnam;
uint32_t anunit;

uint8_t unit() const noexcept {
return anunit >> 24;
}

std::string unit_str() const noexcept {
switch (unit()) {
case UNIT_POINT_ON_WAVE:
return "point-on-wave"s;
case UNIT_RMS:
return "rms"s;
case UNIT_PEAK:
return "peak"s;
default:
return "other"s;
}
}

float scale() const noexcept {
return static_cast<float>(anunit & 0xFFFFFF);
}
};

struct DigitalInfo final {
std::array<std::string, 16> chnam;
uint32_t dgunit;
};

struct PmuConfig final {
std::string stn;
uint16_t idcode;
uint16_t format;
std::vector<PhasorInfo> phinfo;
std::vector<AnalogInfo> aninfo;
std::vector<DigitalInfo> dginfo;
uint16_t fnom;
uint16_t cfgcnt;
};

struct Config {
uint32_t time_base;
std::vector<PmuConfig> pmus;
uint16_t data_rate;
};

class Config1 {
private:
Config inner;

public:
Config1() = delete;
Config1(Config config) noexcept : inner(config) {}
operator Config &() noexcept { return inner; }
operator Config const &() const noexcept { return inner; }
Config *operator->() { return &inner; }
Config const *operator->() const { return &inner; }
};

class Config2 {
private:
Config inner;

public:
Config2() = delete;
Config2(Config config) noexcept : inner(config) {}
operator Config &() noexcept { return inner; }
operator Config const &() const noexcept { return inner; }
Config *operator->() { return &inner; }
Config const *operator->() const { return &inner; }
};

struct Command final {
uint16_t cmd;
std::vector<unsigned char> ext;

static constexpr uint16_t DATA_STOP = 0x1;
static constexpr uint16_t DATA_START = 0x2;
static constexpr uint16_t GET_HEADER = 0x3;
static constexpr uint16_t GET_CONFIG1 = 0x4;
static constexpr uint16_t GET_CONFIG2 = 0x5;
//static constexpr uint16_t GET_CONFIG3 = 0x6;
};

struct Frame final {
using Variant = std::variant<Data, Header, Config1, Config2, Command>;

uint16_t version;
uint16_t framesize;
uint16_t idcode;
uint32_t soc;
uint32_t fracsec;
Variant message;
};

} // namespace villas::node::c37_118::types
4 changes: 4 additions & 0 deletions lib/nodes/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ if(WITH_NODE_SOCKET)
list(APPEND NODE_SRC socket.cpp)
endif()

if(WITH_NODE_C37_118)
list(APPEND NODE_SRC c37_118.cpp c37_118/parser.cpp)
endif()

if(WITH_NODE_FILE)
list(APPEND NODE_SRC file.cpp)
endif()
Expand Down
Loading