2020-08-29 22:40:49 +02:00
/** 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>
2021-02-16 14:13:49 +01:00
# include <complex>
# include <vector>
2021-06-25 18:02:08 +02:00
# include <villas/timing.h>
2021-02-16 12:49:15 +01:00
2021-02-10 14:54:38 +01:00
# include <villas/dumper.hpp>
2020-08-29 22:40:49 +02:00
# include <villas/hook.hpp>
# include <villas/path.h>
# include <villas/sample.h>
2021-06-29 12:40:59 -04:00
/* Uncomment to enable dumper of memory windows */
2021-06-29 15:39:31 +02:00
//#define DFT_MEM_DUMP
2021-06-25 15:38:18 +02:00
2020-08-29 22:40:49 +02:00
namespace villas {
namespace node {
2021-07-07 08:58:26 +02:00
class DftHook : public MultiSignalHook {
2020-08-29 22:40:49 +02:00
protected :
2021-06-25 18:02:08 +02:00
enum class PaddingType {
2020-09-04 17:24:14 +02:00
ZERO ,
SIG_REPEAT
} ;
2021-06-25 18:02:08 +02:00
enum class WindowType {
2020-09-04 17:24:14 +02:00
NONE ,
FLATTOP ,
HANN ,
HAMMING
} ;
2021-06-25 18:02:08 +02:00
enum class FreqEstimationType {
NONE ,
QUADRATIC
} ;
struct Point {
double x ;
double y ;
} ;
2021-06-29 15:31:22 +02:00
struct Phasor {
double frequency ;
double amplitude ;
double phase ;
2021-06-29 12:40:59 -04:00
double rocof ; /**< Rate of change of frequency. */
2021-06-29 15:31:22 +02:00
} ;
2021-07-01 20:54:23 +02:00
2021-02-16 13:25:37 +01:00
enum WindowType windowType ;
enum PaddingType paddingType ;
2021-06-25 18:02:08 +02:00
enum FreqEstimationType freqEstType ;
2021-02-16 14:13:49 +01:00
std : : vector < std : : vector < double > > smpMemory ;
2021-06-29 12:40:59 -04:00
# ifdef DFT_MEM_DUMP
std : : vector < double > ppsMemory ;
# endif
std : : vector < std : : vector < std : : complex < double > > > matrix ;
std : : vector < std : : vector < std : : complex < double > > > results ;
2021-02-16 14:13:49 +01:00
std : : vector < double > filterWindowCoefficents ;
2021-06-29 12:40:59 -04:00
std : : vector < std : : vector < double > > absResults ;
std : : vector < double > absFrequencies ;
2020-09-04 17:24:14 +02:00
2021-06-29 12:40:59 -04:00
uint64_t calcCount ;
2021-02-16 13:14:26 +01:00
unsigned sampleRate ;
2021-06-29 12:40:59 -04:00
double startFrequency ;
2021-02-16 11:43:53 +01:00
double endFreqency ;
double frequencyResolution ;
2021-06-29 12:40:59 -04:00
unsigned rate ;
2021-06-15 18:23:14 +00:00
unsigned ppsIndex ;
2021-02-16 13:14:26 +01:00
unsigned windowSize ;
unsigned windowMultiplier ; /**< Multiplyer for the window to achieve frequency resolution */
unsigned freqCount ; /**< Number of requency bins that are calculated */
2021-06-29 12:40:59 -04:00
bool sync ;
2021-02-16 11:43:53 +01:00
uint64_t smpMemPos ;
uint64_t lastSequence ;
2021-02-10 13:06:19 +01:00
2020-08-29 22:40:49 +02:00
std : : complex < double > omega ;
2020-09-03 12:05:45 +02:00
2021-06-29 12:40:59 -04:00
double windowCorrectionFactor ;
struct timespec lastCalc ;
double nextCalc ;
2020-08-31 18:09:25 +02:00
2021-06-29 15:31:22 +02:00
Phasor lastResult ;
2021-02-10 13:06:19 +01:00
2021-06-29 12:40:59 -04:00
std : : string dumperPrefix ;
bool dumperEnable ;
# ifdef DFT_MEM_DUMP
Dumper origSigSync ;
Dumper windowdSigSync ;
Dumper ppsSigSync ;
# endif
Dumper phasorRocof ;
Dumper phasorPhase ;
Dumper phasorAmplitude ;
Dumper phasorFreq ;
2021-07-19 12:09:07 +02:00
double angleUnitFactor ;
2020-08-29 22:40:49 +02:00
public :
2020-10-07 15:52:43 +02:00
DftHook ( struct vpath * p , struct vnode * n , int fl , int prio , bool en = true ) :
2021-07-07 08:58:26 +02:00
MultiSignalHook ( p , n , fl , prio , en ) ,
2021-02-16 12:53:27 +01:00
windowType ( WindowType : : NONE ) ,
paddingType ( PaddingType : : ZERO ) ,
2021-06-25 18:02:08 +02:00
freqEstType ( FreqEstimationType : : NONE ) ,
2021-02-16 14:13:49 +01:00
smpMemory ( ) ,
2021-06-29 12:40:59 -04:00
# ifdef DFT_MEM_DUMP
2021-06-25 18:02:08 +02:00
ppsMemory ( ) ,
2021-06-29 12:40:59 -04:00
# endif
matrix ( ) ,
results ( ) ,
2021-02-16 14:13:49 +01:00
filterWindowCoefficents ( ) ,
2021-06-29 12:40:59 -04:00
absResults ( ) ,
absFrequencies ( ) ,
calcCount ( 0 ) ,
2021-02-16 11:43:53 +01:00
sampleRate ( 0 ) ,
2021-06-29 12:40:59 -04:00
startFrequency ( 0 ) ,
2021-02-16 11:43:53 +01:00
endFreqency ( 0 ) ,
frequencyResolution ( 0 ) ,
2021-06-29 12:40:59 -04:00
rate ( 0 ) ,
2021-06-15 18:23:14 +00:00
ppsIndex ( 0 ) ,
2021-02-16 11:43:53 +01:00
windowSize ( 0 ) ,
windowMultiplier ( 0 ) ,
freqCount ( 0 ) ,
2021-06-29 12:40:59 -04:00
sync ( 0 ) ,
2021-06-25 18:02:08 +02:00
smpMemPos ( 0 ) ,
lastSequence ( 0 ) ,
2021-06-29 12:40:59 -04:00
windowCorrectionFactor ( 0 ) ,
lastCalc ( { 0 , 0 } ) ,
nextCalc ( 0.0 ) ,
lastResult ( { 0 , 0 , 0 , 0 } ) ,
dumperPrefix ( " /tmp/plot/ " ) ,
2021-07-01 20:54:23 +02:00
dumperEnable ( false ) ,
2021-06-25 18:02:08 +02:00
# ifdef DFT_MEM_DUMP
2021-06-29 12:40:59 -04:00
origSigSync ( dumperPrefix + " origSigSync " ) ,
windowdSigSync ( dumperPrefix + " windowdSigSync " ) ,
ppsSigSync ( dumperPrefix + " ppsSigSync " ) ,
2021-06-25 18:02:08 +02:00
# endif
2021-06-29 12:40:59 -04:00
phasorRocof ( dumperPrefix + " phasorRocof " ) ,
phasorPhase ( dumperPrefix + " phasorPhase " ) ,
phasorAmplitude ( dumperPrefix + " phasorAmplitude " ) ,
2021-07-19 12:09:07 +02:00
phasorFreq ( dumperPrefix + " phasorFreq " ) ,
angleUnitFactor ( 1 )
2021-06-29 12:40:59 -04:00
{ }
2020-08-29 22:40:49 +02:00
2021-02-10 13:06:19 +01:00
virtual void prepare ( )
{
2021-07-12 12:39:43 +02:00
MultiSignalHook : : prepare ( ) ;
2021-07-09 19:05:40 +02:00
2021-07-01 20:54:23 +02:00
dumperEnable = logger - > level ( ) < = SPDLOG_LEVEL_DEBUG ;
2020-10-21 21:04:28 +02:00
signal_list_clear ( & signals ) ;
2021-07-07 08:58:26 +02:00
for ( unsigned i = 0 ; i < signalIndices . size ( ) ; i + + ) {
2020-10-21 21:04:28 +02:00
struct signal * freqSig ;
struct signal * amplSig ;
struct signal * phaseSig ;
struct signal * rocofSig ;
2020-09-04 17:24:14 +02:00
2020-10-21 21:04:28 +02:00
/* Add signals */
2021-07-12 17:51:09 +02:00
freqSig = signal_create ( " frequency " , " Hz " , SignalType : : FLOAT ) ;
amplSig = signal_create ( " amplitude " , " V " , SignalType : : FLOAT ) ;
phaseSig = signal_create ( " phase " , " rad " , SignalType : : FLOAT ) ;
2021-06-25 18:02:08 +02:00
rocofSig = signal_create ( " rocof " , " Hz/s " , SignalType : : FLOAT ) ;
2020-09-04 17:24:14 +02:00
2020-10-21 21:04:28 +02:00
if ( ! freqSig | | ! amplSig | | ! phaseSig | | ! rocofSig )
throw RuntimeError ( " Failed to create new signals " ) ;
2020-09-04 21:06:23 +02:00
2020-10-21 21:04:28 +02:00
vlist_push ( & signals , freqSig ) ;
vlist_push ( & signals , amplSig ) ;
vlist_push ( & signals , phaseSig ) ;
vlist_push ( & signals , rocofSig ) ;
2021-06-25 15:38:18 +02:00
}
2021-06-15 18:23:14 +00:00
2021-06-25 18:02:08 +02:00
/* Initialize sample memory */
smpMemory . clear ( ) ;
2021-07-07 08:58:26 +02:00
for ( unsigned i = 0 ; i < signalIndices . size ( ) ; i + + )
2021-06-25 18:02:08 +02:00
smpMemory . emplace_back ( windowSize , 0.0 ) ;
2021-06-15 18:23:14 +00:00
# ifdef DFT_MEM_DUMP
/* Initialize temporary ppsMemory */
ppsMemory . clear ( ) ;
ppsMemory . resize ( windowSize , 0.0 ) ;
# endif
2021-02-16 13:16:38 +01:00
/* Calculate how much zero padding ist needed for a needed resolution */
2021-06-25 18:02:08 +02:00
windowMultiplier = ceil ( ( ( double ) sampleRate / windowSize ) / frequencyResolution ) ;
2020-09-04 17:24:14 +02:00
2021-06-29 12:40:59 -04:00
freqCount = ceil ( ( endFreqency - startFrequency ) / frequencyResolution ) + 1 ;
2020-09-10 14:57:45 +02:00
2021-02-16 13:16:38 +01:00
/* Initialize matrix of dft coeffients */
2021-06-29 12:40:59 -04:00
matrix . clear ( ) ;
2021-02-16 14:13:49 +01:00
for ( unsigned i = 0 ; i < freqCount ; i + + )
2021-06-29 12:40:59 -04:00
matrix . emplace_back ( windowSize * windowMultiplier , 0.0 ) ;
2021-02-10 13:06:19 +01:00
2021-06-25 18:02:08 +02:00
/* Initalize dft results matrix */
2021-06-29 12:40:59 -04:00
results . clear ( ) ;
2021-07-07 08:58:26 +02:00
for ( unsigned i = 0 ; i < signalIndices . size ( ) ; i + + ) {
2021-06-29 12:40:59 -04:00
results . emplace_back ( freqCount , 0.0 ) ;
absResults . emplace_back ( freqCount , 0.0 ) ;
2021-06-25 18:02:08 +02:00
}
2021-02-16 14:13:49 +01:00
filterWindowCoefficents . resize ( windowSize ) ;
2021-02-16 10:27:08 +01:00
2021-07-07 08:58:26 +02:00
for ( unsigned i = 0 ; i < freqCount ; i + + )
2021-06-29 12:40:59 -04:00
absFrequencies . emplace_back ( startFrequency + 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 ) ;
2020-08-29 22:40:49 +02:00
state = State : : PREPARED ;
}
2021-06-29 12:40:59 -04:00
virtual void parse ( json_t * json )
2020-08-29 22:40:49 +02:00
{
2021-07-12 12:39:43 +02:00
MultiSignalHook : : parse ( json ) ;
2020-09-04 17:24:14 +02:00
int ret ;
2021-06-29 12:40:59 -04:00
int windowSizeFactor = 1 ;
const char * paddingTypeC = nullptr ;
const char * windowTypeC = nullptr ;
const char * freqEstimateTypeC = nullptr ;
2021-07-19 12:09:07 +02:00
const char * angleUnitC = nullptr ;
2020-08-29 22:40:49 +02:00
2021-06-29 12:40:59 -04:00
json_error_t err ;
2020-10-21 21:04:28 +02:00
2020-08-29 22:40:49 +02:00
assert ( state ! = State : : STARTED ) ;
2021-06-29 12:40:59 -04:00
Hook : : parse ( json ) ;
2020-08-29 22:40:49 +02:00
2021-07-09 19:05:40 +02:00
ret = json_unpack_ex ( json , & err , 0 , " { s?: i, s?: F, s?: F, s?: F, s?: i, s?: i, s?: s, s?: s, s?: s, s?: b, s?: i, s?: s} " ,
2021-02-16 12:47:56 +01:00
" sample_rate " , & sampleRate ,
2021-06-29 12:40:59 -04:00
" start_freqency " , & startFrequency ,
2021-02-16 12:47:56 +01:00
" end_freqency " , & endFreqency ,
" frequency_resolution " , & frequencyResolution ,
2021-06-29 12:40:59 -04:00
" dft_rate " , & rate ,
" window_size_factor " , & windowSizeFactor ,
2021-02-16 12:47:56 +01:00
" window_type " , & windowTypeC ,
" padding_type " , & paddingTypeC ,
2021-06-25 18:02:08 +02:00
" freq_estimate_type " , & freqEstimateTypeC ,
2021-06-29 12:40:59 -04:00
" sync " , & sync ,
2021-07-19 12:09:07 +02:00
" pps_index " , & ppsIndex ,
" angle_unit " , & angleUnitC
2020-09-04 17:24:14 +02:00
) ;
2021-02-16 11:53:42 +01:00
if ( ret )
2021-06-29 12:40:59 -04:00
throw ConfigError ( json , err , " node-config-hook-dft " ) ;
2020-09-04 17:24:14 +02:00
2021-06-29 12:40:59 -04:00
windowSize = sampleRate * windowSizeFactor / ( double ) rate ;
2021-07-01 17:52:14 +02:00
logger - > debug ( " Set windows size to {} samples which fits {} / rate {}s " , windowSize , windowSizeFactor , 1.0 / rate ) ;
2021-06-29 15:39:31 +02:00
2021-06-29 12:40:59 -04:00
if ( ! windowTypeC )
2021-02-16 14:15:14 +01:00
logger - > info ( " No Window type given, assume no windowing " ) ;
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 ;
2021-06-29 12:40:59 -04:00
else
throw ConfigError ( json , " node-config-hook-dft-window-type " , " Invalid window type: {} " , windowTypeC ) ;
2020-09-04 17:24:14 +02:00
2021-07-19 12:09:07 +02:00
if ( ! angleUnitC )
logger - > info ( " No angle type given, assume rad " ) ;
else if ( strcmp ( angleUnitC , " rad " ) = = 0 )
angleUnitFactor = 1 ;
else if ( strcmp ( angleUnitC , " degree " ) = = 0 )
angleUnitFactor = 180 / M_PI ;
else
throw ConfigError ( json , " node-config-hook-dft-angle-unit " , " Angle unit {} not recognized " , angleUnitC ) ;
2021-06-29 12:40:59 -04:00
if ( ! paddingTypeC )
2021-02-16 14:15:14 +01:00
logger - > info ( " No Padding type given, assume no zeropadding " ) ;
2021-07-19 12:09:37 +02:00
else if ( strcmp ( paddingTypeC , " zero " ) = = 0 )
paddingType = PaddingType : : ZERO ;
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 ;
2021-06-29 12:40:59 -04:00
else
throw ConfigError ( json , " node-config-hook-dft-padding-type " , " Padding type {} not recognized " , paddingTypeC ) ;
2020-09-04 17:24:14 +02:00
2021-06-25 18:02:08 +02:00
if ( ! freqEstimateTypeC ) {
logger - > info ( " No Frequency estimation type given, assume no none " ) ;
freqEstType = FreqEstimationType : : NONE ;
}
else if ( strcmp ( freqEstimateTypeC , " quadratic " ) = = 0 )
freqEstType = FreqEstimationType : : QUADRATIC ;
2021-06-29 12:40:59 -04:00
state = State : : PARSED ;
}
virtual void check ( )
{
assert ( state = = State : : PARSED ) ;
2021-02-16 11:53:42 +01:00
if ( endFreqency < 0 | | endFreqency > sampleRate )
2021-06-29 12:40:59 -04:00
throw RuntimeError ( " End frequency must be smaller than sampleRate {} " , sampleRate ) ;
2021-02-16 11:53:42 +01:00
2021-06-29 12:40:59 -04:00
if ( frequencyResolution > ( double ) sampleRate / windowSize )
throw RuntimeError ( " The maximum frequency resolution with smaple_rate:{} and window_site:{} is {} " , sampleRate , windowSize , ( ( double ) sampleRate / windowSize ) ) ;
2020-09-04 17:24:14 +02:00
2021-06-29 12:40:59 -04:00
state = State : : CHECKED ;
2020-08-29 22:40:49 +02:00
}
2021-02-16 13:16:38 +01:00
virtual Hook : : Reason process ( struct sample * smp )
2020-08-29 22:40:49 +02:00
{
assert ( state = = State : : STARTED ) ;
2021-02-16 13:14:26 +01:00
2021-07-07 08:58:26 +02:00
/* Update sample memory */
unsigned i = 0 ;
for ( auto index : signalIndices )
smpMemory [ i + + ] [ smpMemPos % windowSize ] = smp - > data [ index ] . f ;
2021-02-16 10:19:04 +01:00
2021-06-29 12:40:59 -04:00
# ifdef DFT_MEM_DUMP
ppsMemory [ smpMemPos % windowSize ] = smp - > data [ ppsIndex ] . f ;
# endif
2021-02-16 11:43:53 +01:00
smpMemPos + + ;
2020-09-01 21:18:00 +02:00
2021-06-29 12:40:59 -04:00
bool run = false ;
if ( sync ) {
2021-06-29 15:39:31 +02:00
double smpNsec = smp - > ts . origin . tv_sec * 1e9 + smp - > ts . origin . tv_nsec ;
2021-06-25 15:38:18 +02:00
2021-06-29 12:40:59 -04:00
if ( smpNsec > nextCalc ) {
run = true ;
nextCalc = ( smp - > ts . origin . tv_sec + ( ( ( calcCount % rate ) + 1 ) / ( double ) rate ) ) * 1e9 ;
2021-06-29 15:39:31 +02:00
}
2021-06-16 16:36:18 +00:00
}
2021-02-10 13:06:19 +01:00
2021-06-29 12:40:59 -04:00
if ( run ) {
lastCalc = smp - > ts . origin ;
2021-06-15 18:23:14 +00:00
2021-06-29 12:40:59 -04:00
# ifdef DFT_MEM_DUMP
double tmpPPSWindow [ windowSize ] ;
2021-06-23 20:36:39 +02:00
2021-06-29 12:40:59 -04:00
for ( unsigned i = 0 ; i < windowSize ; i + + )
tmpPPSWindow [ i ] = ppsMemory [ ( i + smpMemPos ) % windowSize ] ;
if ( dumperEnable )
ppsSigSync . writeDataBinary ( windowSize , tmpPPSWindow ) ;
# endif
2021-06-15 18:23:14 +00:00
2021-06-23 20:37:11 +02:00
# pragma omp parallel for
2021-07-07 08:58:26 +02:00
for ( unsigned i = 0 ; i < signalIndices . size ( ) ; i + + ) {
2021-06-29 15:31:22 +02:00
Phasor currentResult = { 0 , 0 , 0 , 0 } ;
2021-06-29 12:40:59 -04:00
calculateDft ( PaddingType : : ZERO , smpMemory [ i ] , results [ i ] , smpMemPos ) ;
2021-06-23 20:25:46 +02:00
2021-06-23 20:36:39 +02:00
unsigned maxPos = 0 ;
2020-10-21 21:04:28 +02:00
2021-06-23 20:25:46 +02:00
for ( unsigned j = 0 ; j < freqCount ; j + + ) {
int multiplier = paddingType = = PaddingType : : ZERO
? 1
: windowMultiplier ;
2021-06-29 12:40:59 -04:00
absResults [ i ] [ j ] = abs ( results [ i ] [ j ] ) * 2 / ( windowSize * windowCorrectionFactor * multiplier ) ;
if ( currentResult . amplitude < absResults [ i ] [ j ] ) {
currentResult . frequency = absFrequencies [ j ] ;
currentResult . amplitude = absResults [ i ] [ j ] ;
2021-06-23 20:25:46 +02:00
maxPos = j ;
2020-10-21 21:04:28 +02:00
}
2020-09-04 21:06:23 +02:00
}
2020-10-12 19:27:32 +02:00
2021-06-25 18:02:08 +02:00
if ( freqEstType = = FreqEstimationType : : QUADRATIC ) {
if ( maxPos < 1 | | maxPos > = freqCount - 1 )
logger - > warn ( " Maximum frequency bin lies on window boundary. Using non-estimated results! " ) ;
else {
2021-06-29 12:40:59 -04:00
Point a = { absFrequencies [ maxPos - 1 ] , absResults [ i ] [ maxPos - 1 ] } ;
Point b = { absFrequencies [ maxPos + 0 ] , absResults [ i ] [ maxPos + 0 ] } ;
Point c = { absFrequencies [ maxPos + 1 ] , absResults [ i ] [ maxPos + 1 ] } ;
2021-06-25 18:02:08 +02:00
Point estimate = quadraticEstimation ( a , b , c , maxPos ) ;
2021-06-29 15:31:22 +02:00
currentResult . frequency = estimate . x ;
currentResult . amplitude = estimate . y ;
2021-06-25 18:02:08 +02:00
}
}
2021-06-16 14:00:52 +00:00
2021-06-25 18:02:08 +02:00
if ( windowSize < smpMemPos ) {
2020-10-21 21:04:28 +02:00
2021-06-29 15:31:22 +02:00
smp - > data [ i * 4 + 0 ] . f = currentResult . frequency ; /* Frequency */
smp - > data [ i * 4 + 1 ] . f = ( currentResult . amplitude / pow ( 2 , 0.5 ) ) ; /* Amplitude */
2021-07-19 12:09:07 +02:00
smp - > data [ i * 4 + 2 ] . f = atan2 ( results [ i ] [ maxPos ] . imag ( ) , results [ i ] [ maxPos ] . real ( ) ) * angleUnitFactor ; /* Phase */
2021-06-29 12:40:59 -04:00
smp - > data [ i * 4 + 3 ] . f = ( currentResult . frequency - lastResult . frequency ) / ( double ) rate ; /* RoCof */
2020-10-21 21:04:28 +02:00
2021-06-23 20:34:40 +02:00
}
2021-06-29 15:31:22 +02:00
lastResult = currentResult ;
2021-06-23 20:34:40 +02:00
}
2021-06-29 12:40:59 -04:00
// The following is a debug output and currently only for channel 0
if ( dumperEnable & & windowSize * 5 < smpMemPos ) {
phasorFreq . writeDataBinary ( 1 , & ( smp - > data [ 0 * 4 + 0 ] . f ) ) ;
phasorPhase . writeDataBinary ( 1 , & ( smp - > data [ 0 * 4 + 2 ] . f ) ) ;
phasorAmplitude . writeDataBinary ( 1 , & ( smp - > data [ 0 * 4 + 1 ] . f ) ) ;
phasorRocof . writeDataBinary ( 1 , & ( smp - > data [ 0 * 4 + 3 ] . f ) ) ;
2021-02-10 13:06:19 +01:00
}
2021-06-23 20:27:09 +02:00
2021-07-07 08:58:26 +02:00
smp - > length = windowSize < smpMemPos ? signalIndices . size ( ) * 4 : 0 ;
2021-06-23 20:27:09 +02:00
2021-06-29 12:40:59 -04:00
calcCount + + ;
2020-10-12 19:27:32 +02:00
}
2021-02-10 13:06:19 +01:00
2021-06-25 18:02:08 +02: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 ) ;
2020-09-04 21:06:23 +02:00
2021-02-16 11:43:53 +01:00
lastSequence = smp - > sequence ;
2020-09-04 17:59:29 +02:00
2021-06-29 12:40:59 -04:00
if ( run & & windowSize < smpMemPos )
2020-10-21 21:04:28 +02:00
return Reason : : OK ;
2020-10-12 19:27:32 +02:00
2020-10-21 21:04:28 +02:00
return Reason : : SKIP_SAMPLE ;
2020-08-29 22:40:49 +02:00
}
2021-06-25 18:02:08 +02:00
/**
* This function generates the furie coeffients for the calculateDft function
*/
2021-02-16 13:16:38 +01:00
void generateDftMatrix ( )
{
2020-08-29 22:40:49 +02:00
using namespace std : : complex_literals ;
2021-02-16 13:16:38 +01:00
omega = exp ( ( - 2 i * M_PI ) / ( double ) ( windowSize * windowMultiplier ) ) ;
2021-06-29 12:40:59 -04:00
unsigned startBin = floor ( startFrequency / frequencyResolution ) ;
2021-06-15 18:23:14 +00:00
for ( unsigned i = 0 ; i < freqCount ; i + + ) {
for ( unsigned j = 0 ; j < windowSize * windowMultiplier ; j + + )
matrix [ i ] [ j ] = pow ( omega , ( i + startBin ) * j ) ;
}
}
/**
* This function calculates the discrete furie transform of the input signal
*/
void calculateDft ( enum PaddingType padding , std : : vector < double > & ringBuffer , std : : vector < std : : complex < double > > & results , unsigned ringBufferPos )
{
/* RingBuffer size needs to be equal to windowSize
* prepare sample window The following parts can be combined */
double tmpSmpWindow [ windowSize ] ;
for ( unsigned i = 0 ; i < windowSize ; i + + )
2021-07-01 18:29:22 +02:00
tmpSmpWindow [ i ] = ringBuffer [ ( i + ringBufferPos ) % windowSize ] * filterWindowCoefficents [ i ] ; ;
2021-06-15 18:23:14 +00:00
2021-06-23 20:18:12 +02:00
# ifdef DFT_MEM_DUMP
2021-06-29 12:40:59 -04:00
if ( dumperEnable )
origSigSync . writeDataBinary ( windowSize , tmpSmpWindow ) ;
2021-06-23 20:18:12 +02:00
# endif
2020-10-12 19:27:32 +02:00
2021-07-01 18:29:22 +02:00
/*for (unsigned i = 0; i < windowSize; i++)
2021-02-16 11:43:53 +01:00
tmpSmpWindow [ i ] * = filterWindowCoefficents [ i ] ;
2021-07-01 18:29:22 +02:00
*/
2021-02-16 13:14:26 +01:00
for ( unsigned i = 0 ; i < freqCount ; i + + ) {
2021-06-25 18:02:08 +02:00
results [ i ] = 0 ;
2021-07-01 20:54:23 +02:00
2021-02-16 13:14:26 +01:00
for ( unsigned j = 0 ; j < windowSize * windowMultiplier ; j + + ) {
2021-02-16 12:53:27 +01:00
if ( padding = = PaddingType : : ZERO ) {
2021-07-01 18:29:22 +02:00
if ( j < windowSize )
2021-06-29 12:40:59 -04:00
results [ i ] + = tmpSmpWindow [ j ] * matrix [ i ] [ j ] ;
2021-02-10 13:06:19 +01:00
else
2021-06-25 18:02:08 +02:00
results [ 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-06-29 12:40:59 -04:00
results [ i ] + = tmpSmpWindow [ j % windowSize ] * matrix [ i ] [ j ] ;
2020-08-29 22:40:49 +02:00
}
}
}
2020-08-31 11:48:35 +02:00
2021-06-25 18:02:08 +02:00
/**
* This function prepares the selected window coefficents
*/
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 + + ) {
2021-06-25 18:02:08 +02:00
filterWindowCoefficents [ i ] = 0.21557895
- 0.41663158 * cos ( 2 * M_PI * i / ( windowSize ) )
2021-02-16 13:16:38 +01:00
+ 0.277263158 * cos ( 4 * M_PI * i / ( windowSize ) )
- 0.083578947 * cos ( 6 * M_PI * i / ( windowSize ) )
+ 0.006947368 * cos ( 8 * M_PI * i / ( windowSize ) ) ;
2021-06-29 12:40:59 -04:00
windowCorrectionFactor + = filterWindowCoefficents [ i ] ;
2021-02-16 13:16:38 +01:00
}
break ;
2020-09-04 17:24:14 +02:00
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 ) ) ;
2021-06-29 12:40:59 -04:00
windowCorrectionFactor + = filterWindowCoefficents [ i ] ;
2021-02-16 13:16:38 +01:00
}
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 ;
2021-06-29 12:40:59 -04:00
windowCorrectionFactor + = filterWindowCoefficents [ i ] ;
2021-02-16 13:16:38 +01:00
}
break ;
2020-08-31 11:48:35 +02:00
}
2021-02-16 13:16:38 +01:00
2021-06-29 12:40:59 -04:00
windowCorrectionFactor / = windowSize ;
2020-08-31 11:48:35 +02:00
}
2021-06-25 18:02:08 +02:00
/**
* This function is calculating the mximum based on a quadratic interpolation
2021-06-29 12:40:59 -04:00
*
2021-06-25 18:02:08 +02:00
* This function is based on the following paper :
* https : //mgasior.web.cern.ch/pap/biw2004.pdf
* https : //dspguru.com/dsp/howtos/how-to-interpolate-fft-peak/
2021-06-29 12:40:59 -04:00
* *
2021-06-25 18:02:08 +02:00
* In particular equation 10
*/
Point quadraticEstimation ( const Point & a , const Point & b , const Point & c , unsigned maxFBin )
{
// Frequency estimation
double maxBinEst = ( double ) maxFBin + ( c . y - a . y ) / ( 2 * ( 2 * b . y - a . y - c . y ) ) ;
2021-06-29 12:40:59 -04:00
double y_Fmax = startFrequency + maxBinEst * frequencyResolution ; // convert bin to frequency
2021-06-25 18:02:08 +02:00
// Amplitude estimation
double f = ( a . x * ( b . y - c . y ) + b . x * ( c . y - a . y ) + c . x * ( a . y - b . y ) ) / ( ( a . x - b . x ) * ( a . x - c . x ) * ( c . x - b . x ) ) ;
double g = ( pow ( a . x , 2 ) * ( b . y - c . y ) + pow ( b . x , 2 ) * ( c . y - a . y ) + pow ( c . x , 2 ) * ( a . y - b . y ) ) / ( ( a . x - b . x ) * ( a . x - c . x ) * ( b . x - c . x ) ) ;
2021-06-29 12:40:59 -04:00
double h = ( pow ( a . x , 2 ) * ( b . x * c . y - c . x * b . y ) + a . x * ( pow ( c . x , 2 ) * b . y - pow ( b . x , 2 ) * c . y ) + b . x * c . x * a . y * ( b . x - c . x ) ) / ( ( a . x - b . x ) * ( a . x - c . x ) * ( b . x - c . x ) ) ;
2021-06-25 18:02:08 +02:00
double i = f * pow ( y_Fmax , 2 ) + g * y_Fmax + h ;
return { y_Fmax , i } ;
}
2020-08-29 22:40:49 +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 */
/** @} */