View Javadoc
1   /*
2    * Copyright (C) 2020 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.navigation.inertial.calibration.generators;
17  
18  import com.irurueta.navigation.LockedException;
19  import com.irurueta.navigation.inertial.calibration.AccelerationTriad;
20  import com.irurueta.navigation.inertial.calibration.AccelerometerNoiseRootPsdSource;
21  import com.irurueta.navigation.inertial.calibration.intervals.AccelerationTriadStaticIntervalDetector;
22  import com.irurueta.navigation.inertial.calibration.intervals.AccelerationTriadStaticIntervalDetectorListener;
23  import com.irurueta.navigation.inertial.calibration.intervals.TriadStaticIntervalDetector;
24  import com.irurueta.navigation.inertial.calibration.noise.WindowedTriadNoiseEstimator;
25  import com.irurueta.units.Acceleration;
26  import com.irurueta.units.Time;
27  import com.irurueta.units.TimeConverter;
28  import com.irurueta.units.TimeUnit;
29  
30  /**
31   * Base class to generate measurements for the calibration of accelerometers, gyroscopes or
32   * magnetometers after detection of static/dynamic intervals.
33   *
34   * @param <T> type of measurement to be generated.
35   * @param <G> type of generator.
36   * @param <L> type of listener.
37   * @param <I> type of input data to be processed.
38   */
39  public abstract class MeasurementsGenerator<T, G extends MeasurementsGenerator<T, G, L, I>,
40          L extends MeasurementsGeneratorListener<T, G, L, I>, I> implements AccelerometerNoiseRootPsdSource {
41  
42      /**
43       * Default minimum number of samples required in a static interval to be taken into account.
44       * Smaller static intervals will be discarded.
45       */
46      public static final int DEFAULT_MIN_STATIC_SAMPLES = 2 * TriadStaticIntervalDetector.DEFAULT_WINDOW_SIZE;
47  
48      /**
49       * Default maximum number of samples allowed in dynamic intervals.
50       * Larger dynamic intervals will be discarded.
51       */
52      public static final int DEFAULT_MAX_DYNAMIC_SAMPLES = 30 * TriadStaticIntervalDetector.DEFAULT_WINDOW_SIZE;
53  
54      /**
55       * Listener to handle generated events.
56       */
57      protected L listener;
58  
59      /**
60       * An acceleration triad.
61       * This is reused for memory efficiency.
62       */
63      protected final AccelerationTriad triad = new AccelerationTriad();
64  
65      /**
66       * Static/dynamic interval detector using accelerometer samples.
67       */
68      protected final AccelerationTriadStaticIntervalDetector staticIntervalDetector;
69  
70      /**
71       * Indicates whether generator is running or not.
72       */
73      private boolean running;
74  
75      /**
76       * Minimum number of samples required in a static interval to be taken into account.
77       * Smaller static intervals will be discarded.
78       */
79      private int minStaticSamples = DEFAULT_MIN_STATIC_SAMPLES;
80  
81      /**
82       * Maximum number of samples allowed in dynamic intervals.
83       * Larger dynamic intervals will be discarded.
84       */
85      private int maxDynamicSamples = DEFAULT_MAX_DYNAMIC_SAMPLES;
86  
87      /**
88       * Number of samples that have been processed in a static period so far.
89       */
90      private int processedStaticSamples;
91  
92      /**
93       * Number of samples that have been processed in a dynamic period so far.
94       */
95      private int processedDynamicSamples;
96  
97      /**
98       * Indicates whether static interval must be skipped.
99       */
100     private boolean skipStaticInterval;
101 
102     /**
103      * Indicates whether dynamic interval must be skipped.
104      */
105     private boolean skipDynamicInterval;
106 
107     /**
108      * Constructor.
109      */
110     protected MeasurementsGenerator() {
111         staticIntervalDetector = new AccelerationTriadStaticIntervalDetector();
112         try {
113             setupListener();
114         } catch (final LockedException ignore) {
115             // never happens
116         }
117     }
118 
119     /**
120      * Constructor.
121      *
122      * @param listener listener to handle events raised by this generator.
123      */
124     protected MeasurementsGenerator(final L listener) {
125         this();
126         this.listener = listener;
127     }
128 
129     /**
130      * Gets time interval between input samples expressed in seconds (s).
131      *
132      * @return time interval between input samples.
133      */
134     public double getTimeInterval() {
135         return staticIntervalDetector.getTimeInterval();
136     }
137 
138     /**
139      * Sets time interval between input samples expressed in seconds (s).
140      *
141      * @param timeInterval time interval between input samples.
142      * @throws IllegalArgumentException if provided value is negative.
143      * @throws LockedException          if generator is currently running.
144      */
145     public void setTimeInterval(final double timeInterval) throws LockedException {
146         if (running) {
147             throw new LockedException();
148         }
149 
150         if (timeInterval < 0.0) {
151             throw new IllegalArgumentException();
152         }
153 
154         staticIntervalDetector.setTimeInterval(timeInterval);
155     }
156 
157     /**
158      * Gets time interval between input samples.
159      *
160      * @return time interval between input samples.
161      */
162     public Time getTimeIntervalAsTime() {
163         return new Time(getTimeInterval(), TimeUnit.SECOND);
164     }
165 
166     /**
167      * Gets time interval between input samples.
168      *
169      * @param result instance where time interval will be stored.
170      */
171     public void getTimeIntervalAsTime(final Time result) {
172         result.setValue(getTimeInterval());
173         result.setUnit(TimeUnit.SECOND);
174     }
175 
176     /**
177      * Sets time interval between input samples.
178      *
179      * @param timeInterval time interval between input samples.
180      * @throws IllegalArgumentException if provided value is negative.
181      * @throws LockedException          if estimator is currently running.
182      */
183     public void setTimeInterval(final Time timeInterval) throws LockedException {
184         setTimeInterval(TimeConverter.convert(timeInterval.getValue().doubleValue(), timeInterval.getUnit(),
185                 TimeUnit.SECOND));
186     }
187 
188     /**
189      * Gets minimum number of samples required in a static interval to be taken into account.
190      * Smaller static intervals will be discarded.
191      *
192      * @return minimum number of samples required in a static interval to be taken into account.
193      */
194     public int getMinStaticSamples() {
195         return minStaticSamples;
196     }
197 
198     /**
199      * Sets minimum number of samples required in a static interval to be taken into account.
200      * Smaller static intervals will be discarded.
201      *
202      * @param minStaticSamples minimum number of samples required in a static interval to be
203      *                         taken into account.
204      * @throws LockedException          if generator is busy.
205      * @throws IllegalArgumentException if provided value is less than 2.
206      */
207     public void setMinStaticSamples(final int minStaticSamples) throws LockedException {
208         if (running) {
209             throw new LockedException();
210         }
211 
212         if (minStaticSamples < WindowedTriadNoiseEstimator.MIN_WINDOW_SIZE) {
213             throw new IllegalArgumentException();
214         }
215 
216         this.minStaticSamples = minStaticSamples;
217     }
218 
219     /**
220      * Gets maximum number of samples allowed in dynamic intervals.
221      *
222      * @return maximum number of samples allowed in dynamic intervals.
223      */
224     public int getMaxDynamicSamples() {
225         return maxDynamicSamples;
226     }
227 
228     /**
229      * Sets maximum number of samples allowed in dynamic intervals.
230      *
231      * @param maxDynamicSamples maximum number of samples allowed in dynamic intervals.
232      * @throws LockedException          if generator is busy.
233      * @throws IllegalArgumentException if provided value is less than 2.
234      */
235     public void setMaxDynamicSamples(final int maxDynamicSamples) throws LockedException {
236         if (running) {
237             throw new LockedException();
238         }
239 
240         if (maxDynamicSamples < WindowedTriadNoiseEstimator.MIN_WINDOW_SIZE) {
241             throw new IllegalArgumentException();
242         }
243 
244         this.maxDynamicSamples = maxDynamicSamples;
245     }
246 
247     /**
248      * Gets listener to handle generated events.
249      *
250      * @return listener to handle generated events.
251      */
252     public L getListener() {
253         return listener;
254     }
255 
256     /**
257      * Sets listener to handle generated events.
258      *
259      * @param listener listener to handle generated events.
260      * @throws LockedException if generator is busy.
261      */
262     public void setListener(final L listener) throws LockedException {
263         if (running) {
264             throw new LockedException();
265         }
266 
267         this.listener = listener;
268     }
269 
270     /**
271      * Gets length of number of samples to keep within the window being processed
272      * to determine instantaneous accelerometer noise level.
273      *
274      * @return length of number of samples to keep within the window.
275      */
276     public int getWindowSize() {
277         return staticIntervalDetector.getWindowSize();
278     }
279 
280     /**
281      * Sets length of number of samples to keep within the window being processed
282      * to determine instantaneous accelerometer noise level.
283      * Window size must always be larger than allowed minimum value, which is 2 and
284      * must have an odd value.
285      *
286      * @param windowSize length of number of samples to keep within the window.
287      * @throws LockedException          if detector is busy processing a previous sample.
288      * @throws IllegalArgumentException if provided value is not valid.
289      */
290     public void setWindowSize(final int windowSize) throws LockedException {
291         if (running) {
292             throw new LockedException();
293         }
294 
295         staticIntervalDetector.setWindowSize(windowSize);
296     }
297 
298     /**
299      * Gets number of samples to be processed initially while keeping the sensor static in order
300      * to find the base noise level when device is static.
301      *
302      * @return number of samples to be processed initially.
303      */
304     public int getInitialStaticSamples() {
305         return staticIntervalDetector.getInitialStaticSamples();
306     }
307 
308     /**
309      * Sets number of samples to be processed initially while keeping the sensor static in order
310      * to find the base noise level when device is static.
311      *
312      * @param initialStaticSamples number of samples to be processed initially.
313      * @throws LockedException          if detector is busy.
314      * @throws IllegalArgumentException if provided value is less than
315      *                                  {@link TriadStaticIntervalDetector#MINIMUM_INITIAL_STATIC_SAMPLES}
316      */
317     public void setInitialStaticSamples(final int initialStaticSamples) throws LockedException {
318         if (running) {
319             throw new LockedException();
320         }
321 
322         staticIntervalDetector.setInitialStaticSamples(initialStaticSamples);
323     }
324 
325     /**
326      * Gets factor to be applied to detected base noise level in order to
327      * determine threshold for static/dynamic period changes. This factor is
328      * unit-less.
329      *
330      * @return factor to be applied to detected base noise level.
331      */
332     public double getThresholdFactor() {
333         return staticIntervalDetector.getThresholdFactor();
334     }
335 
336     /**
337      * Sets factor to be applied to detected base noise level in order to
338      * determine threshold for static/dynamic period changes. This factor is
339      * unit-less.
340      *
341      * @param thresholdFactor factor to be applied to detected base noise level.
342      * @throws LockedException          if detector is busy.
343      * @throws IllegalArgumentException if provided value is zero or negative.
344      */
345     public void setThresholdFactor(final double thresholdFactor) throws LockedException {
346         if (running) {
347             throw new LockedException();
348         }
349 
350         staticIntervalDetector.setThresholdFactor(thresholdFactor);
351     }
352 
353     /**
354      * Gets factor to determine that a sudden movement has occurred during
355      * initialization if instantaneous noise level exceeds accumulated noise
356      * level by this factor amount.
357      * This factor is unit-less.
358      *
359      * @return factor to determine that a sudden movement has occurred.
360      */
361     public double getInstantaneousNoiseLevelFactor() {
362         return staticIntervalDetector.getInstantaneousNoiseLevelFactor();
363     }
364 
365     /**
366      * Sets factor to determine that a sudden movement has occurred during
367      * initialization if instantaneous noise level exceeds accumulated noise
368      * level by this factor amount.
369      * This factor is unit-less.
370      *
371      * @param instantaneousNoiseLevelFactor factor to determine that a sudden
372      *                                      movement has occurred during
373      *                                      initialization.
374      * @throws LockedException          if detector is busy.
375      * @throws IllegalArgumentException if provided value is zero or negative.
376      */
377     public void setInstantaneousNoiseLevelFactor(final double instantaneousNoiseLevelFactor) throws LockedException {
378         if (running) {
379             throw new LockedException();
380         }
381 
382         staticIntervalDetector.setInstantaneousNoiseLevelFactor(instantaneousNoiseLevelFactor);
383     }
384 
385     /**
386      * Gets overall absolute threshold to determine whether there has been
387      * excessive motion during the whole initialization phase.
388      * Failure will be detected if estimated base noise level exceeds this
389      * threshold when initialization completes.
390      * This threshold is expressed in meters per squared second (m/s^2).
391      *
392      * @return overall absolute threshold to determine whether there has
393      * been excessive motion.
394      */
395     public double getBaseNoiseLevelAbsoluteThreshold() {
396         return staticIntervalDetector.getBaseNoiseLevelAbsoluteThreshold();
397     }
398 
399     /**
400      * Sets overall absolute threshold to determine whether there has been
401      * excessive motion during the whole initialization phase.
402      * Failure will be detected if estimated base noise level exceeds this
403      * threshold when initialization completes.
404      * This threshold is expressed in meters per squared second (m/s^2).
405      *
406      * @param baseNoiseLevelAbsoluteThreshold overall absolute threshold to
407      *                                        determine whether there has been
408      *                                        excessive motion.
409      * @throws LockedException          if detector is busy.
410      * @throws IllegalArgumentException if provided value is zero or negative.
411      */
412     public void setBaseNoiseLevelAbsoluteThreshold(final double baseNoiseLevelAbsoluteThreshold)
413             throws LockedException {
414         if (running) {
415             throw new LockedException();
416         }
417 
418         staticIntervalDetector.setBaseNoiseLevelAbsoluteThreshold(baseNoiseLevelAbsoluteThreshold);
419     }
420 
421     /**
422      * Gets overall absolute threshold to determine whether there has been
423      * excessive motion during the whole initialization phase.
424      * Failure will be detected if estimated base noise level exceeds this
425      * threshold when initialization completes.
426      *
427      * @return overall absolute threshold to determine whether there has been
428      * excessive motion.
429      */
430     public Acceleration getBaseNoiseLevelAbsoluteThresholdAsMeasurement() {
431         return staticIntervalDetector.getBaseNoiseLevelAbsoluteThresholdAsMeasurement();
432     }
433 
434     /**
435      * Gets overall absolute threshold to determine whether there has been
436      * excessive motion during the whole initialization phase.
437      * Failure will be detected if estimated base noise level exceeds this
438      * threshold when initialization completes.
439      *
440      * @param result instance where result will be stored.
441      */
442     public void getBaseNoiseLevelAbsoluteThresholdAsMeasurement(final Acceleration result) {
443         staticIntervalDetector.getBaseNoiseLevelAbsoluteThresholdAsMeasurement(result);
444     }
445 
446     /**
447      * Sets overall absolute threshold to determine whether there has been
448      * excessive motion during the whole initialization phase.
449      * Failure will be detected if estimated base noise level exceeds this
450      * threshold when initialization completes.
451      *
452      * @param baseNoiseLevelAbsoluteThreshold overall absolute threshold to
453      *                                        determine whether there has been
454      *                                        excessive motion.
455      * @throws LockedException          if detector is busy.
456      * @throws IllegalArgumentException if provided value is zero or negative.
457      */
458     public void setBaseNoiseLevelAbsoluteThreshold(final Acceleration baseNoiseLevelAbsoluteThreshold)
459             throws LockedException {
460         if (running) {
461             throw new LockedException();
462         }
463 
464         staticIntervalDetector.setBaseNoiseLevelAbsoluteThreshold(baseNoiseLevelAbsoluteThreshold);
465     }
466 
467     /**
468      * Gets internal status of this generator.
469      *
470      * @return internal status of this generator.
471      */
472     public TriadStaticIntervalDetector.Status getStatus() {
473         return staticIntervalDetector.getStatus();
474     }
475 
476     /**
477      * Gets accelerometer base noise level that has been detected during
478      * initialization expressed in meters per squared second (m/s^2).
479      * This is equal to the standard deviation of the accelerometer measurements
480      * during initialization phase.
481      *
482      * @return accelerometer base noise level.
483      */
484     public double getAccelerometerBaseNoiseLevel() {
485         return staticIntervalDetector.getBaseNoiseLevel();
486     }
487 
488     /**
489      * Gets accelerometer base noise level that has been detected during
490      * initialization.
491      * This is equal to the standard deviation of the accelerometer measurements
492      * during initialization phase.
493      *
494      * @return measurement base noise level.
495      */
496     public Acceleration getAccelerometerBaseNoiseLevelAsMeasurement() {
497         return staticIntervalDetector.getBaseNoiseLevelAsMeasurement();
498     }
499 
500     /**
501      * Gets accelerometer base noise level that has been detected during
502      * initialization.
503      * This is equal to the standard deviation of the accelerometer measurements
504      * during initialization phase.
505      *
506      * @param result instance where result will be stored.
507      */
508     public void getAccelerometerBaseNoiseLevelAsMeasurement(final Acceleration result) {
509         staticIntervalDetector.getBaseNoiseLevelAsMeasurement(result);
510     }
511 
512     /**
513      * Gets accelerometer base noise level PSD (Power Spectral Density)
514      * expressed in (m^2 * s^-3).
515      *
516      * @return accelerometer base noise level PSD.
517      */
518     public double getAccelerometerBaseNoiseLevelPsd() {
519         return staticIntervalDetector.getBaseNoiseLevelPsd();
520     }
521 
522     /**
523      * Gets accelerometer base noise level root PSD (Power Spectral Density)
524      * expressed in (m * s^-1.5).
525      *
526      * @return accelerometer base noise level root PSD.
527      */
528     @Override
529     public double getAccelerometerBaseNoiseLevelRootPsd() {
530         return staticIntervalDetector.getBaseNoiseLevelRootPsd();
531     }
532 
533     /**
534      * Gets threshold to determine static/dynamic period changes expressed in
535      * meters per squared second (m/s^2).
536      *
537      * @return threshold to determine static/dynamic period changes.
538      */
539     public double getThreshold() {
540         return staticIntervalDetector.getThreshold();
541     }
542 
543     /**
544      * Gets threshold to determine static/dynamic period changes.
545      *
546      * @return threshold to determine static/dynamic period changes.
547      */
548     public Acceleration getThresholdAsMeasurement() {
549         return staticIntervalDetector.getThresholdAsMeasurement();
550     }
551 
552     /**
553      * Gets threshold to determine static/dynamic period changes.
554      *
555      * @param result instance where result will be stored.
556      */
557     public void getThresholdAsMeasurement(final Acceleration result) {
558         staticIntervalDetector.getThresholdAsMeasurement(result);
559     }
560 
561     /**
562      * Gets number of samples that have been processed in a static period so far.
563      *
564      * @return number of samples that have been processed in a static period so far.
565      */
566     public int getProcessedStaticSamples() {
567         return processedStaticSamples;
568     }
569 
570     /**
571      * Gets number of samples that have been processed in a dynamic period so far.
572      *
573      * @return number of samples that have been processed in a dynamic period so far.
574      */
575     public int getProcessedDynamicSamples() {
576         return processedDynamicSamples;
577     }
578 
579     /**
580      * Indicates whether last static interval must be skipped.
581      *
582      * @return true if last static interval must be skipped.
583      */
584     public boolean isStaticIntervalSkipped() {
585         return skipStaticInterval;
586     }
587 
588     /**
589      * Indicates whether last dynamic interval must be skipped.
590      *
591      * @return true if last dynamic interval must be skipped.
592      */
593     public boolean isDynamicIntervalSkipped() {
594         return skipDynamicInterval;
595     }
596 
597     /**
598      * Indicates whether generator is running or not.
599      *
600      * @return true if generator is running, false otherwise.
601      */
602     public boolean isRunning() {
603         return running;
604     }
605 
606     /**
607      * Processes a sample of data.
608      *
609      * @param sample sample of data to be processed.
610      * @return true if provided samples has been processed, false if provided triad has been skipped because
611      * generator previously failed. If generator previously failed, it will need to be reset before
612      * processing additional samples.
613      * @throws LockedException if generator is busy processing a previous sample.
614      */
615     public boolean process(final I sample) throws LockedException {
616         if (running) {
617             throw new LockedException();
618         }
619 
620         running = true;
621         checkProcessedSamples();
622 
623         getAccelerationTriadFromInputSample(sample);
624         final var result = staticIntervalDetector.process(triad);
625 
626         if (result) {
627             updateCounters();
628             postProcess(sample);
629         }
630         running = false;
631 
632         return result;
633     }
634 
635     /**
636      * Resets this generator.
637      *
638      * @throws LockedException if generator is busy.
639      */
640     public void reset() throws LockedException {
641         if (running) {
642             throw new LockedException();
643         }
644 
645         staticIntervalDetector.reset();
646 
647         processedDynamicSamples = 0;
648         processedStaticSamples = 0;
649 
650         skipDynamicInterval = false;
651         skipStaticInterval = false;
652 
653         if (listener != null) {
654             //noinspection unchecked
655             listener.onReset((G) this);
656         }
657     }
658 
659     /**
660      * Post process provided input sample.
661      *
662      * @param sample an input sample.
663      * @throws LockedException if generator is busy.
664      */
665     protected abstract void postProcess(final I sample) throws LockedException;
666 
667     /**
668      * Gets corresponding acceleration triad from provided input sample.
669      * This method must store the result into {@link #triad}.
670      *
671      * @param sample input sample.
672      */
673     protected abstract void getAccelerationTriadFromInputSample(final I sample);
674 
675     /**
676      * Handles a static-to-dynamic interval change.
677      *
678      * @param accumulatedAvgX average x-coordinate of measurements during last
679      *                        static period expressed in meters per squared
680      *                        second (m/s^2).
681      * @param accumulatedAvgY average y-coordinate of specific force during last
682      *                        static period expressed in meters per squared
683      *                        second (m/s^2).
684      * @param accumulatedAvgZ average z-coordinate of specific force during last
685      *                        static period expressed in meters per squared
686      *                        second (m/s^2).
687      * @param accumulatedStdX standard deviation of x-coordinate of measurements
688      *                        during last static period expressed in meters per
689      *                        squared second (m/s^2).
690      * @param accumulatedStdY standard deviation of y-coordinate of measurements
691      *                        during last static period expressed in meters per
692      *                        squared second (m/s^2).
693      * @param accumulatedStdZ standard deviation of z-coordinate of measurements
694      *                        during last static period expressed in meters per
695      *                        squared second (m/s^2).
696      */
697     protected abstract void handleStaticToDynamicChange(
698             final double accumulatedAvgX, final double accumulatedAvgY, final double accumulatedAvgZ,
699             final double accumulatedStdX, final double accumulatedStdY, final double accumulatedStdZ);
700 
701     /**
702      * Handles a dynamic-to-static interval change.
703      */
704     protected abstract void handleDynamicToStaticChange();
705 
706     /**
707      * Handles an initialization completion.
708      */
709     protected abstract void handleInitializationCompleted();
710 
711     /**
712      * Handles an error during initialization.
713      */
714     protected abstract void handleInitializationFailed();
715 
716     /**
717      * Check processed samples so far before processing a new one.
718      */
719     protected void checkProcessedSamples() {
720         if (processedDynamicSamples > maxDynamicSamples) {
721             final var wasSkipped = skipDynamicInterval;
722             skipDynamicInterval = true;
723 
724             if (listener != null && !wasSkipped) {
725                 //noinspection unchecked
726                 listener.onDynamicIntervalSkipped((G) this);
727             }
728         }
729     }
730 
731     /**
732      * Updates counters of processed samples.
733      */
734     protected void updateCounters() {
735         final TriadStaticIntervalDetector.Status status = staticIntervalDetector.getStatus();
736         if (status == TriadStaticIntervalDetector.Status.STATIC_INTERVAL) {
737             processedStaticSamples++;
738             processedDynamicSamples = 0;
739         } else if (status == TriadStaticIntervalDetector.Status.DYNAMIC_INTERVAL) {
740             processedDynamicSamples++;
741             processedStaticSamples = 0;
742         }
743     }
744 
745     /**
746      * Setups listener for static interval detector.
747      *
748      * @throws LockedException if static interval detector is busy.
749      */
750     private void setupListener() throws LockedException {
751         final var listener = new AccelerationTriadStaticIntervalDetectorListener() {
752             @Override
753             public void onInitializationStarted(final AccelerationTriadStaticIntervalDetector detector) {
754 
755                 if (MeasurementsGenerator.this.listener != null) {
756                     //noinspection unchecked
757                     MeasurementsGenerator.this.listener.onInitializationStarted((G) MeasurementsGenerator.this);
758                 }
759             }
760 
761             @Override
762             public void onInitializationCompleted(
763                     final AccelerationTriadStaticIntervalDetector detector, final double baseNoiseLevel) {
764 
765                 handleInitializationCompleted();
766 
767                 if (MeasurementsGenerator.this.listener != null) {
768                     //noinspection unchecked
769                     MeasurementsGenerator.this.listener.onInitializationCompleted((G) MeasurementsGenerator.this, baseNoiseLevel);
770                 }
771             }
772 
773             @Override
774             public void onError(
775                     final AccelerationTriadStaticIntervalDetector detector,
776                     final double accumulatedNoiseLevel,
777                     final double instantaneousNoiseLevel,
778                     final TriadStaticIntervalDetector.ErrorReason reason) {
779 
780                 handleInitializationFailed();
781 
782                 if (MeasurementsGenerator.this.listener != null) {
783                     //noinspection unchecked
784                     MeasurementsGenerator.this.listener.onError((G) MeasurementsGenerator.this, reason);
785                 }
786             }
787 
788             @Override
789             public void onStaticIntervalDetected(
790                     final AccelerationTriadStaticIntervalDetector detector,
791                     final double instantaneousAvgX,
792                     final double instantaneousAvgY,
793                     final double instantaneousAvgZ,
794                     final double instantaneousStdX,
795                     final double instantaneousStdY,
796                     final double instantaneousStdZ) {
797 
798                 handleDynamicToStaticChange();
799                 skipDynamicInterval = false;
800 
801                 if (MeasurementsGenerator.this.listener != null) {
802                     //noinspection unchecked
803                     MeasurementsGenerator.this.listener.onStaticIntervalDetected((G) MeasurementsGenerator.this);
804                 }
805             }
806 
807             @Override
808             public void onDynamicIntervalDetected(
809                     final AccelerationTriadStaticIntervalDetector detector,
810                     final double instantaneousAvgX,
811                     final double instantaneousAvgY,
812                     final double instantaneousAvgZ,
813                     final double instantaneousStdX,
814                     final double instantaneousStdY,
815                     final double instantaneousStdZ,
816                     final double accumulatedAvgX,
817                     final double accumulatedAvgY,
818                     final double accumulatedAvgZ,
819                     final double accumulatedStdX,
820                     final double accumulatedStdY,
821                     final double accumulatedStdZ) {
822 
823                 if (processedStaticSamples < minStaticSamples) {
824                     final var wasSkipped = skipStaticInterval;
825                     skipStaticInterval = true;
826 
827                     if (MeasurementsGenerator.this.listener != null && !wasSkipped) {
828                         //noinspection unchecked
829                         MeasurementsGenerator.this.listener.onStaticIntervalSkipped((G) MeasurementsGenerator.this);
830                     }
831                 }
832 
833                 handleStaticToDynamicChange(
834                         accumulatedAvgX, accumulatedAvgY, accumulatedAvgZ,
835                         accumulatedStdX, accumulatedStdY, accumulatedStdZ);
836                 skipStaticInterval = false;
837 
838                 if (MeasurementsGenerator.this.listener != null) {
839                     //noinspection unchecked
840                     MeasurementsGenerator.this.listener.onDynamicIntervalDetected((G) MeasurementsGenerator.this);
841                 }
842             }
843 
844             @Override
845             public void onReset(final AccelerationTriadStaticIntervalDetector detector) {
846                 // no action needed
847             }
848         };
849         staticIntervalDetector.setListener(listener);
850     }
851 }