This is a Filter plugin for MADS.
It applies a configurable IIR filter to signals picked out of the frames it subscribes to. The signals are selected in the settings as a map of topic to a list of keypaths (JSON pointers), so that one agent can filter several signals coming from several topics. Each selected signal gets its own filter instance, so their states never mix.
The filters come from the iir1 library: Butterworth and Chebyshev (type I and II) designs, as lowpass, highpass, bandpass or bandstop. Family, response, order and frequencies are all set in the INI file.
Required MADS version: 2.4.3.
Currently, the supported platforms are:
- Linux
- MacOS
- Windows
Linux and MacOS:
cmake -Bbuild -DCMAKE_INSTALL_PREFIX="$(mads -p)"
cmake --build build -j4
sudo cmake --install buildWindows:
cmake -Bbuild -DCMAKE_INSTALL_PREFIX="$(mads -p)"
cmake --build build --config Release
cmake --install build --config ReleaseThe plugin supports the following settings in the INI file:
[iir_filter]
sub_topic = ["signals"] # agent setting: what to subscribe to
pub_topic = "filtered" # agent setting: must differ from sub_topic
sampling_rate = 10.0 # Hz, the rate at which frames actually arrive
family = "butterworth" # butterworth | chebyshev1 | chebyshev2
response = "lowpass" # lowpass | highpass | bandpass | bandstop
order = 4
cutoff = 0.5 # Hz, lowpass and highpass only
center = 1.0 # Hz, bandpass and bandstop only
width = 0.5 # Hz, bandpass and bandstop only
ripple_db = 1.0 # dB of passband ripple, chebyshev1 only
stopband_db = 40.0 # dB of stopband attenuation, chebyshev2 only
passthrough = true # republish the whole input frame, filtered fields added
suffix = "_filt" # appended to each output keypath
# the signals to filter: one entry per subscribed topic
[iir_filter.inputs]
signals = ["/temperature", "/pressure"]All settings are optional except inputs, which must select at least one
signal; if omitted, the default values below are used.
| Key | Type | Default | Meaning |
|---|---|---|---|
inputs |
table | (empty) | Map of topic = ["/keypath", ...]. Mandatory. The keypaths are JSON pointers into the incoming frame; a bare field name is accepted as a shorthand for a top-level one ("temperature" means "/temperature"). A single string is accepted instead of a one-element array. |
sampling_rate |
float | 10.0 |
Hz. The rate at which frames arrive on the subscribed topics, i.e. the reciprocal of the upstream source's period. Every cutoff is interpreted against it, so a wrong value detunes the filter. |
family |
string | "butterworth" |
butterworth, chebyshev1 or chebyshev2. Case and separators are ignored, and chebyshevI / cheby1 are accepted spellings. |
response |
string | "lowpass" |
lowpass, highpass, bandpass or bandstop (notch is a synonym of the last one). |
order |
int | 4 |
1 to 16. For bandpass and bandstop this is the prototype order: the realised filter has twice as many poles. |
cutoff |
float | 1.0 |
Hz, for lowpass and highpass. Must lie strictly between 0 and the Nyquist frequency, sampling_rate / 2. |
center |
float | 1.0 |
Hz, center of the band, for bandpass and bandstop. |
width |
float | 0.5 |
Hz, width of the band, for bandpass and bandstop. The whole band must fall between 0 and Nyquist. |
ripple_db |
float | 1.0 |
dB of allowed passband ripple. chebyshev1 only. |
stopband_db |
float | 40.0 |
dB of stopband attenuation. chebyshev2 only. |
passthrough |
bool | false |
When true, the output frame is a copy of the input frame with the filtered values added to it, so that raw and filtered signals travel together. When false, only the filtered values are published. |
suffix |
string | "" |
Appended to the last token of each output keypath, e.g. "_filt" publishes /temperature as /temperature_filt. With the default empty suffix the filtered value takes the place of the raw one. May not contain / or ~. |
Any of these can also be set on the command line, e.g.
mads filter iir_filter.plugin -o cutoff=2.0 -o order=6. Note that
[iir_filter.inputs] is a nested table, so it can only come from the INI file.
An invalid setting (an unknown family, a cutoff above Nyquist, an order above
16, no input signal at all) is reported by info() at startup and makes the
agent stop on its first frame with a critical return.
With passthrough = false, the published frame carries one field per filtered
signal, at the same keypath it was read from plus suffix:
{ "temperature": 24.98, "pressure": 3.01, "source_topic": "signals" }With passthrough = true the whole input frame is republished, with the
filtered values added to it:
{
"n": 121, "t": 12.35, "sample_rate": 9.91,
"temperature": 30.25, "temperature_filt": 17.73,
"pressure": 2.49, "pressure_filt": 2.47,
"source_topic": "signals"
}In both cases:
source_topicis the topic the frame was received on, which is how a downstream agent tells apart the sources of a multi-topic filter.- The fields the upstream agent stamped (
agent_id,hostname,timestamp,timecode,clock_offset_us,clock_ref,topic) are dropped from a passed-through frame, so that this agent's own host stamps it with its own identity and time base.
Topics are matched the way ZMQ subscriptions are, by prefix: a topic key of
"sig" picks up frames arriving on signals.
An IIR filter starts from a zeroed delay line, so the first frames after startup
carry a transient: the output climbs from 0 to the signal over roughly
sampling_rate / cutoff samples.
Per frame, the plugin returns:
| Situation | Return | Effect |
|---|---|---|
| Every configured signal found | success |
the filtered frame is published |
| The frame came from a topic with no configured signal | retry |
nothing is published, nothing is logged |
| Some of the signals of that topic are missing or not numeric | warning |
the rest is published, with the reason attached |
| None of them is usable, or the frame is not a JSON object | error |
nothing is published; the reason goes out as an agent event |
| The settings are invalid | critical |
the agent stops |
Non-finite values (NaN, infinities) are treated as missing and never reach the
delay line, where they would stick forever.
The build also produces a standalone test driver from the same source file
(./build/iir_filter.plugin on MacOS, ./build/iir_filter on Linux,
build/iir_filter.exe on Windows). It needs neither a broker nor a network: it
drives the plugin exactly as mads filter does and checks, among others, that
- a constant passes a lowpass unchanged and is removed by a highpass;
- a tone well above the cutoff is rejected, and a notch removes its center frequency while leaving a nearby tone alone;
- frames from an unconfigured topic are skipped, partial frames are a warning, and malformed ones are an error;
passthroughandsuffixproduce the documented frame;- every invalid setting is rejected.
It prints one line per check and exits non-zero if any of them fails.