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

432 lines
12 KiB
C++
Raw Permalink Normal View History

/** DFT hook.
*
* @author Manuel Pitz <manuel.pitz@eonerc.rwth-aachen.de>
* @copyright 2014-2020, 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/>.
*********************************************************************************/
/** @addtogroup hooks Hook functions
* @{
*/
#include <cstring>
2021-02-16 12:49:15 +01:00
#include <cinttypes>
#include <complex>
#include <vector>
2021-02-16 12:49:15 +01:00
2021-02-10 14:54:38 +01:00
#include <villas/dumper.hpp>
#include <villas/hook.hpp>
#include <villas/path.h>
#include <villas/sample.h>
2021-05-10 00:12:30 +02:00
#include <villas/format.hpp>
namespace villas {
namespace node {
class DftHook : public Hook {
protected:
2021-02-16 12:53:27 +01:00
enum PaddingType {
ZERO,
SIG_REPEAT
};
2021-02-16 12:53:27 +01:00
enum WindowType {
NONE,
FLATTOP,
HANN,
HAMMING
};
std::shared_ptr<Dumper> origSigSync;
std::shared_ptr<Dumper> windowdSigSync;
std::shared_ptr<Dumper> phasorPhase;
std::shared_ptr<Dumper> phasorAmplitude;
std::shared_ptr<Dumper> phasorFreq;
2020-10-10 20:45:51 +02:00
2021-02-16 13:25:37 +01:00
enum WindowType windowType;
enum PaddingType paddingType;
std::vector<std::vector<double>> smpMemory;
std::vector<std::vector<std::complex<double>>> dftMatrix;
std::vector<std::complex<double>> dftResults;
std::vector<double> filterWindowCoefficents;
std::vector<double> absDftResults;
std::vector<double> absDftFreqs;
uint64_t dftCalcCnt;
unsigned sampleRate;
2021-02-16 11:43:53 +01:00
double startFreqency;
double endFreqency;
double frequencyResolution;
unsigned dftRate;
unsigned windowSize;
unsigned windowMultiplier; /**< Multiplyer for the window to achieve frequency resolution */
unsigned freqCount; /**< Number of requency bins that are calculated */
2021-02-16 11:43:53 +01:00
bool syncDft;
uint64_t smpMemPos;
uint64_t lastSequence;
2021-02-10 13:06:19 +01:00
std::complex<double> omega;
2020-09-03 12:05:45 +02:00
2021-02-16 11:43:53 +01:00
double windowCorretionFactor;
2021-02-16 13:25:37 +01:00
struct timespec lastDftCal;
std::vector<int> signalIndex; /**< A list of signalIndex to do dft on */
2021-02-10 13:06:19 +01:00
public:
2020-10-07 15:52:43 +02:00
DftHook(struct vpath *p, struct vnode *n, int fl, int prio, bool en = true) :
Hook(p, n, fl, prio, en),
2021-02-16 12:53:27 +01:00
windowType(WindowType::NONE),
paddingType(PaddingType::ZERO),
smpMemory(),
dftMatrix(),
dftResults(),
filterWindowCoefficents(),
absDftResults(),
absDftFreqs(),
dftCalcCnt(0),
2021-02-16 11:43:53 +01:00
sampleRate(0),
startFreqency(0),
endFreqency(0),
frequencyResolution(0),
dftRate(0),
windowSize(0),
windowMultiplier(0),
freqCount(0),
syncDft(0),
smpMemPos(0),
lastSequence(0),
windowCorretionFactor(0),
2021-02-16 13:16:38 +01:00
lastDftCal({0, 0}),
signalIndex()
{
bool debug = false;
if (debug) {
origSigSync = std::make_shared<Dumper>("/tmp/plot/origSigSync");
windowdSigSync = std::make_shared<Dumper>("/tmp/plot/windowdSigSync");
phasorPhase = std::make_shared<Dumper>("/tmp/plot/phasorPhase");
phasorAmplitude = std::make_shared<Dumper>("/tmp/plot/phasorAmplitude");
phasorFreq = std::make_shared<Dumper>("/tmp/plot/phasorFreq");
}
2020-10-21 21:04:28 +02:00
}
2021-02-10 13:06:19 +01:00
virtual void prepare()
{
2020-10-21 21:04:28 +02:00
signal_list_clear(&signals);
2021-02-16 13:16:38 +01:00
/* Initialize sample memory */
2021-02-16 14:41:55 +01:00
smpMemory.clear();
for (unsigned i = 0; i < signalIndex.size(); i++) {
2020-10-21 21:04:28 +02:00
struct signal *freqSig;
struct signal *amplSig;
struct signal *phaseSig;
struct signal *rocofSig;
2020-10-21 21:04:28 +02:00
/* Add signals */
freqSig = signal_create("amplitude", nullptr, SignalType::FLOAT);
amplSig = signal_create("phase", nullptr, SignalType::FLOAT);
phaseSig = signal_create("frequency", nullptr, SignalType::FLOAT);
rocofSig = signal_create("rocof", nullptr, SignalType::FLOAT);
2020-10-21 21:04:28 +02:00
if (!freqSig || !amplSig || !phaseSig || !rocofSig)
throw RuntimeError("Failed to create new signals");
2020-10-21 21:04:28 +02:00
vlist_push(&signals, freqSig);
vlist_push(&signals, amplSig);
vlist_push(&signals, phaseSig);
vlist_push(&signals, rocofSig);
smpMemory.emplace_back(windowSize, 0.0);
2020-10-21 21:04:28 +02:00
}
2021-02-16 13:16:38 +01:00
/* Calculate how much zero padding ist needed for a needed resolution */
windowMultiplier = ceil(((double)sampleRate / windowSize) / frequencyResolution);
2021-02-16 11:43:53 +01:00
freqCount = ceil((endFreqency - startFreqency) / frequencyResolution) + 1;
2020-09-10 14:57:45 +02:00
2021-02-16 13:16:38 +01:00
/* Initialize matrix of dft coeffients */
2021-02-16 14:41:55 +01:00
dftMatrix.clear();
for (unsigned i = 0; i < freqCount; i++)
dftMatrix.emplace_back(windowSize * windowMultiplier, 0.0);
2021-02-10 13:06:19 +01:00
dftResults.resize(freqCount);
filterWindowCoefficents.resize(windowSize);
absDftResults.resize(freqCount);
absDftFreqs.resize(freqCount);
2021-02-16 10:27:08 +01:00
for (unsigned i = 0; i < absDftFreqs.size(); i++)
2021-02-16 11:43:53 +01:00
absDftFreqs[i] = startFreqency + i * frequencyResolution;
2021-02-10 13:06:19 +01:00
2021-02-16 09:36:14 +01:00
generateDftMatrix();
2021-02-16 13:16:38 +01:00
calculateWindow(windowType);
state = State::PREPARED;
}
virtual void parse(json_t *cfg)
{
2021-02-16 11:43:53 +01:00
const char *paddingTypeC = nullptr, *windowTypeC= nullptr;
int ret;
json_error_t err;
2021-02-16 11:43:53 +01:00
json_t *jsonChannelList = nullptr;
2020-10-21 21:04:28 +02:00
assert(state != State::STARTED);
Hook::parse(cfg);
2020-10-21 21:04:28 +02:00
ret = json_unpack_ex(cfg, &err, 0, "{ s?: i, s?: F, s?: F, s?: F, s?: i , s?: i, s?: s, s?: s, s?: b, s?: o}",
2021-02-16 12:47:56 +01:00
"sample_rate", &sampleRate,
"start_freqency", &startFreqency,
"end_freqency", &endFreqency,
"frequency_resolution", &frequencyResolution,
"dft_rate", &dftRate,
"window_size", &windowSize,
"window_type", &windowTypeC,
"padding_type", &paddingTypeC,
2021-02-16 11:43:53 +01:00
"sync", &syncDft,
2021-02-16 12:47:56 +01:00
"signal_index", &jsonChannelList
);
2021-02-16 11:53:42 +01:00
if (ret)
throw ConfigError(cfg, err, "node-config-hook-dft");
2021-02-16 11:43:53 +01:00
if (jsonChannelList != nullptr) {
signalIndex.clear();
2021-02-16 11:43:53 +01:00
if (jsonChannelList->type == JSON_ARRAY) {
2020-10-21 21:04:28 +02:00
size_t i;
2021-02-16 11:43:53 +01:00
json_t *jsonValue;
json_array_foreach(jsonChannelList, i, jsonValue) {
if (!json_is_number(jsonValue))
throw ConfigError(jsonValue, "node-config-hook-dft-channel", "Values must be given as array of integer values!");
auto idx = json_number_value(jsonValue);
signalIndex.push_back(idx);
2020-10-21 21:04:28 +02:00
}
2021-02-10 13:06:19 +01:00
}
2021-02-16 11:43:53 +01:00
else if (jsonChannelList->type == JSON_INTEGER) {
if (!json_is_number(jsonChannelList))
throw ConfigError(jsonChannelList, "node-config-hook-dft-channel", "Value must be given as integer value!");
auto idx = json_number_value(jsonChannelList);
signalIndex.push_back(idx);
2020-10-21 21:04:28 +02:00
}
else
2021-02-16 14:15:14 +01:00
logger->warn("Could not parse channel list. Please check documentation for syntax");
2020-10-21 21:04:28 +02:00
}
else
2021-02-16 12:47:56 +01:00
throw ConfigError(jsonChannelList, "node-config-node-signal", "No parameter signalIndex given.");
2020-10-21 21:04:28 +02:00
2021-02-16 11:43:53 +01:00
if (!windowTypeC) {
2021-02-16 14:15:14 +01:00
logger->info("No Window type given, assume no windowing");
2021-02-16 12:53:27 +01:00
windowType = WindowType::NONE;
2021-02-10 13:06:19 +01:00
}
2021-02-16 11:43:53 +01:00
else if (strcmp(windowTypeC, "flattop") == 0)
2021-02-16 12:53:27 +01:00
windowType = WindowType::FLATTOP;
2021-02-16 11:43:53 +01:00
else if (strcmp(windowTypeC, "hamming") == 0)
2021-02-16 12:53:27 +01:00
windowType = WindowType::HAMMING;
2021-02-16 11:43:53 +01:00
else if (strcmp(windowTypeC, "hann") == 0)
2021-02-16 12:53:27 +01:00
windowType = WindowType::HANN;
else {
2021-02-16 14:15:14 +01:00
logger->info("Window type {} not recognized, assume no windowing", windowTypeC);
2021-02-16 12:53:27 +01:00
windowType = WindowType::NONE;
}
2021-02-16 11:43:53 +01:00
if (!paddingTypeC) {
2021-02-16 14:15:14 +01:00
logger->info("No Padding type given, assume no zeropadding");
2021-02-16 12:53:27 +01:00
paddingType = PaddingType::ZERO;
2020-10-21 21:04:28 +02:00
}
2021-02-16 11:43:53 +01:00
else if (strcmp(paddingTypeC, "signal_repeat") == 0)
2021-02-16 12:53:27 +01:00
paddingType = PaddingType::SIG_REPEAT;
2020-10-21 21:04:28 +02:00
else {
2021-02-16 14:15:14 +01:00
logger->info("Padding type {} not recognized, assume zero padding", paddingTypeC);
2021-02-16 12:53:27 +01:00
paddingType = PaddingType::ZERO;
2021-02-10 13:06:19 +01:00
}
2021-02-16 11:53:42 +01:00
if (endFreqency < 0 || endFreqency > sampleRate)
2021-02-16 13:16:38 +01:00
throw ConfigError(cfg, err, "node-config-hook-dft", "End frequency must be smaller than sampleRate {}", sampleRate);
2021-02-16 11:53:42 +01:00
if (frequencyResolution > ((double)sampleRate/windowSize))
2021-02-16 13:16:38 +01:00
throw ConfigError(cfg, err, "node-config-hook-dft", "The maximum frequency resolution with smaple_rate:{} and window_site:{} is {}", sampleRate, windowSize, ((double)sampleRate/windowSize));
2021-02-16 13:16:38 +01:00
state = State::PARSED;
}
2021-02-16 13:16:38 +01:00
virtual Hook::Reason process(struct sample *smp)
{
assert(state == State::STARTED);
for (unsigned i = 0; i < signalIndex.size(); i++)
2021-02-16 11:43:53 +01:00
smpMemory[i][smpMemPos % windowSize] = smp->data[signalIndex[i]].f;
2021-02-16 10:19:04 +01:00
2021-02-16 11:43:53 +01:00
smpMemPos++;
2020-09-10 14:57:45 +02:00
bool runDft = false;
2021-02-16 11:43:53 +01:00
if (syncDft) {
if (lastDftCal.tv_sec != smp->ts.origin.tv_sec)
2020-09-10 14:57:45 +02:00
runDft = true;
}
2021-02-16 13:16:38 +01:00
2021-02-16 11:43:53 +01:00
lastDftCal = smp->ts.origin;
2021-02-10 13:06:19 +01:00
2020-10-21 21:04:28 +02:00
if (runDft) {
for (unsigned i = 0; i < signalIndex.size(); i++) {
2021-02-16 13:16:38 +01:00
calculateDft(PaddingType::ZERO, smpMemory[i], smpMemPos);
2020-10-21 21:04:28 +02:00
double maxF = 0;
double maxA = 0;
int maxPos = 0;
for (unsigned i = 0; i<freqCount; i++) {
2021-02-16 12:53:27 +01:00
absDftResults[i] = abs(dftResults[i]) * 2 / (windowSize * windowCorretionFactor * ((paddingType == PaddingType::ZERO)?1:windowMultiplier));
2020-10-21 21:04:28 +02:00
if (maxA < absDftResults[i]) {
maxF = absDftFreqs[i];
maxA = absDftResults[i];
maxPos = i;
}
}
2020-10-21 21:04:28 +02:00
if (dftCalcCnt > 1) {
if (phasorFreq)
phasorFreq->writeData(1, &maxF);
2020-10-21 21:04:28 +02:00
2021-02-16 13:16:38 +01:00
smp->data[i * 4].f = maxF; /* Frequency */
smp->data[i * 4 + 1].f = (maxA / pow(2, 0.5)); /* Amplitude */
smp->data[i * 4 + 2].f = atan2(dftResults[maxPos].imag(), dftResults[maxPos].real()); /* Phase */
smp->data[i * 4 + 3].f = 0; /* RoCof */
2020-10-21 21:04:28 +02:00
if (phasorPhase)
phasorPhase->writeData(1, &(smp->data[i * 4 + 2].f));
2020-10-21 21:04:28 +02:00
}
2021-02-10 13:06:19 +01:00
}
dftCalcCnt++;
smp->length = signalIndex.size() * 4;
}
2021-02-10 13:06:19 +01:00
2021-02-16 11:43:53 +01:00
if ((smp->sequence - lastSequence) > 1)
2021-02-16 14:15:14 +01:00
logger->warn("Calculation is not Realtime. {} sampled missed", smp->sequence - lastSequence);
2021-02-16 11:43:53 +01:00
lastSequence = smp->sequence;
2020-10-21 21:04:28 +02:00
if (runDft)
return Reason::OK;
2020-10-21 21:04:28 +02:00
return Reason::SKIP_SAMPLE;
}
2021-02-16 13:16:38 +01:00
void generateDftMatrix()
{
using namespace std::complex_literals;
2021-02-16 13:16:38 +01:00
omega = exp((-2i * M_PI) / (double)(windowSize * windowMultiplier));
unsigned startBin = floor(startFreqency / frequencyResolution);
2021-02-10 13:06:19 +01:00
for (unsigned i = 0; i < freqCount ; i++) {
for (unsigned j = 0 ; j < windowSize * windowMultiplier ; j++)
dftMatrix[i][j] = pow(omega, (i + startBin) * j);
}
}
void calculateDft(enum PaddingType padding, std::vector<double> &ringBuffer, unsigned ringBufferPos)
2021-02-16 13:16:38 +01:00
{
/* RingBuffer size needs to be equal to windowSize
* prepare sample window The following parts can be combined */
2021-02-16 11:43:53 +01:00
double tmpSmpWindow[windowSize];
2021-02-10 13:06:19 +01:00
for (unsigned i = 0; i< windowSize; i++)
2021-02-16 11:43:53 +01:00
tmpSmpWindow[i] = ringBuffer[(i + ringBufferPos) % windowSize];
if (origSigSync)
origSigSync->writeData(windowSize, tmpSmpWindow);
if (dftCalcCnt > 1 && phasorAmplitude)
2021-02-16 13:16:38 +01:00
phasorAmplitude->writeData(1, &tmpSmpWindow[windowSize - 1]);
for (unsigned i = 0; i< windowSize; i++)
2021-02-16 11:43:53 +01:00
tmpSmpWindow[i] *= filterWindowCoefficents[i];
2021-02-10 13:06:19 +01:00
if (windowdSigSync)
windowdSigSync->writeData(windowSize, tmpSmpWindow);
for (unsigned i = 0; i < freqCount; i++) {
dftResults[i] = 0;
for (unsigned j = 0; j < windowSize * windowMultiplier; j++) {
2021-02-16 12:53:27 +01:00
if (padding == PaddingType::ZERO) {
2021-02-16 11:43:53 +01:00
if (j < (windowSize))
dftResults[i] += tmpSmpWindow[j] * dftMatrix[i][j];
2021-02-10 13:06:19 +01:00
else
dftResults[i] += 0;
2020-10-21 21:04:28 +02:00
}
2021-02-16 13:16:38 +01:00
else if (padding == PaddingType::SIG_REPEAT) /* Repeat samples */
2021-02-16 11:43:53 +01:00
dftResults[i] += tmpSmpWindow[j % windowSize] * dftMatrix[i][j];
}
}
}
2020-08-31 11:48:35 +02:00
2021-02-16 13:16:38 +01:00
void calculateWindow(enum WindowType windowTypeIn)
{
switch (windowTypeIn) {
case WindowType::FLATTOP:
for (unsigned i = 0; i < windowSize; i++) {
filterWindowCoefficents[i] = 0.21557895
- 0.41663158 * cos(2 * M_PI * i / (windowSize))
+ 0.277263158 * cos(4 * M_PI * i / (windowSize))
- 0.083578947 * cos(6 * M_PI * i / (windowSize))
+ 0.006947368 * cos(8 * M_PI * i / (windowSize));
windowCorretionFactor += filterWindowCoefficents[i];
}
break;
2021-02-16 13:16:38 +01:00
case WindowType::HAMMING:
case WindowType::HANN: {
double a0 = 0.5; /* This is the hann window */
if (windowTypeIn == WindowType::HAMMING)
a0 = 25./46;
for (unsigned i = 0; i < windowSize; i++) {
filterWindowCoefficents[i] = a0 - (1 - a0) * cos(2 * M_PI * i / (windowSize));
windowCorretionFactor += filterWindowCoefficents[i];
}
break;
2020-09-03 12:05:45 +02:00
}
2021-02-16 13:16:38 +01:00
default:
for (unsigned i = 0; i < windowSize; i++) {
filterWindowCoefficents[i] = 1;
windowCorretionFactor += filterWindowCoefficents[i];
}
break;
2020-08-31 11:48:35 +02:00
}
2021-02-16 13:16:38 +01:00
2021-02-16 11:43:53 +01:00
windowCorretionFactor /= windowSize;
2020-08-31 11:48:35 +02:00
}
};
/* Register hook */
static char n[] = "dft";
static char d[] = "This hook calculates the dft on a window";
static HookPlugin<DftHook, n, d, (int) Hook::Flags::NODE_READ | (int) Hook::Flags::NODE_WRITE | (int) Hook::Flags::PATH> p;
} /* namespace node */
} /* namespace villas */
/** @} */