ReactivePropertyでチャタリング防止


最近ReactivePropertyを勉強しています。

ふとチャタリング防止が簡単に書けるのでは！？
と思ったんですが意外と思いつきませんでした。


##チャタリングとは？
簡単に説明。
押しボタンスイッチやリレーのメカニカルな接点はバネ駆動で信号のON/OFFを実現します。
その関係でON/OFFの切り替わりの瞬間に接点が微少に跳ねてごく短時間ON/OFFを繰り返す事があります。
これをチャタリングと呼びます。

FA用の小型メカニカルリレーだと大体40msec程度ON/OFFが暴れます。

例えば

+ スイッチを押すとモータ運転
+ スイッチを離すとモータ停止+ブレーキ

という制御をする装置が有ったとして、
スイッチ入力のチャタリングが起こるとします。
チャタリングの高速ON/OFFを全て拾ってしまうとモータの運転/停止を高速で繰り返すような出力をしてしまいます。

モータのサイズやブレーキ機構によっては重大な故障を招くことも。

と言う事でチャタリング防止としてある一定時間のONを維持した時にONとするディレイ回路を入れるのが一般的です。


##チャタリング防止の実現

> 一定時間値が変化しなければその値を採用する。

というのが言葉で表現すると適当かと思います。

今までPLCとかFPGAを使ってた限りでは特に悩むことなくやってました。

PLCはタイマーという機能があって簡単に実現できます。

FPGAではクロックで周期的に入力端子を監視してバッファーに突っ込んでその中の値が変化しなければ・・・
といった感じで実現します。

ReactiveなLinqで書くとどうなるんだ？？と悩んでしまいました。
まだまだ全然理解できてないなぁと痛感です。

始めは

DelayしてBufferしてシーケンスの中の値の変化を見て・・・。

なんて考えてしまいましたが、これはReactiveな感じじゃないなぁと。



(都合があって.NET4.0 C#4.0です)

```C#:チャタリングTest
//Test用
public class ChatteringTest
{
    //チャタリングを模擬します
    private ChatteringSensor _chatteringSensor;
    //チャタリングをフィルタします
    private ChatteringFiltered _chatteringFiltered;

    public ChatteringTest()
    {
        _chatteringSensor = new ChatteringSensor();
        _chatteringFiltered = new ChatteringFiltered(_chatteringSensor.Sensor);

        //チャタリングの開始
        _chatteringSensor.Start();
        //一定時間経過したらチャタリングを終了
        Observable.Timer(TimeSpan.FromMilliseconds(2000))
            .Subscribe(_ =>
            {
                //必ず最後に値が変わるように終了値を引数で与えてしまってます・・・。
                _chatteringSensor.Stop(!_chatteringFiltered.Sensor.Value);
            });
    }

}
```


```C#:チャタリング模擬
public class ChatteringSensor : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public ReactiveProperty<bool> Sensor { get; private set; }

    public ChatteringSensor()
    {
        Sensor = new ReactiveProperty<bool>(false);

        Sensor.Subscribe(val => 
            Console.WriteLine(
                DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff") 
                + " : Chattering ! : " + val)
            );

    }

    private IDisposable _chattering;
    
    /// <summary>
    /// チャタリング動作の模擬
    /// 一定時間Sensor_Chattering.Valueをガチャガチャします。
    /// </summary>
    public void Start()
    {
        //値をガチャガチャする
        _chattering = Observable.Interval(TimeSpan.FromMilliseconds(500))
                .Subscribe(_ => Sensor.Value = !Sensor.Value);
    }

    public void Stop(bool lastValue)
    {
        //値をガチャガチャするやつを消す
        _chattering.Dispose();

        Sensor.Value = lastValue;
    }
}
```

###失敗パターン(Bufferでこねこね)
色々こねこねしてやっとここまで辿り着けました。
しかしメソッドを間違った使い方をしてしまってます。


```C#:チャタリング防止(失敗)
public class ChatteringFiltered
{
    public event PropertyChangedEventHandler PropertyChanged;

    public ReactiveProperty<bool> Sensor { get; private set; }

    private ReactiveProperty<bool> _chatteringSensor;

    public ChatteringFiltered(ReactiveProperty<bool> chatteringSensor)
    {
        Sensor = new ReactiveProperty<bool>(false);
        _chatteringSensor = chatteringSensor;

        _chatteringSensor
        //指定時間 Sensor_Chattering.Valueを溜めて
        .Buffer(TimeSpan.FromMilliseconds(3000))
        .Subscribe(list =>
        {
            Console.WriteLine(
                DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff")
                + " : Buffer.Subscribe list.Count = " + list.Count);

            if (list.Count == 0//つまり値の変化が無かった
            )
            {
                Sensor.Value = _chatteringSensor.Value;
            }
        });

        Sensor.Subscribe(_ => 
            Console.WriteLine(
                DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff") 
                + " : Filtered ! : " + Sensor.Value)
        );

    }
}

```

```:Buffer使ったとき
2019/01/25 22:07:28.934 : Chattering ! : False
2019/01/25 22:07:29.069 : Filtered ! : False
2019/01/25 22:07:29.738 : Chattering ! : True
2019/01/25 22:07:30.203 : Chattering ! : False
2019/01/25 22:07:30.706 : Chattering ! : True
2019/01/25 22:07:32.073 : Buffer.Subscribe list.Count = 4
2019/01/25 22:07:35.076 : Buffer.Subscribe list.Count = 0
2019/01/25 22:07:35.076 : Filtered ! : True
2019/01/25 22:07:38.089 : Buffer.Subscribe list.Count = 0
2019/01/25 22:07:41.099 : Buffer.Subscribe list.Count = 0
```

> チャタリングしていないと判断できる時間Bufferしてlist.Countが0になったら、

で実現できてしまいました。
が、値を自分のプロパティから取ってきたり、変なことをやってます。
もっと素直な方法がありました。

###素直なパターン(Throttle)
@naminodarie さん
@herara_ofnir3 さん
にコメントで指摘いただきました通り、

> 一定時間値が変化しなければその値を採用する。

は正にThrottleと言うメソッドで実現できました。

```C#:チャタリング防止
public class ChatteringFiltered
{
    public event PropertyChangedEventHandler PropertyChanged;

    public ReactiveProperty<bool> Sensor { get; private set; }

    private ReactiveProperty<bool> _chatteringSensor;

    public ChatteringFiltered(ReactiveProperty<bool> chatteringSensor)
    {
        Sensor = new ReactiveProperty<bool>(false);
        _chatteringSensor = chatteringSensor;

        _chatteringSensor
        .Throttle(TimeSpan.FromMilliseconds(3000))
        .Subscribe(value =>
        {
            Console.WriteLine(
                DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff")
                + " : LatestValue = " + value.ToString());
            Sensor.Value = value;

        });

        Sensor.Subscribe(_ => 
            Console.WriteLine(
                DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff") 
                + " : Filtered ! : " + Sensor.Value)
        );

    }
}


```

```:Throttle使ったとき
2019/01/28 22:41:32.727 : Chattering ! : False
2019/01/28 22:41:32.805 : Filtered ! : False
2019/01/28 22:41:33.357 : Chattering ! : True
2019/01/28 22:41:33.871 : Chattering ! : False
2019/01/28 22:41:34.390 : Chattering ! : True
2019/01/28 22:41:37.401 : LatestValue = True
2019/01/28 22:41:37.401 : Filtered ! : True
```


とてもストレートに分かりやすいですね！

###ThrottleでON側のみDelay OFFは即時反映
チャタリングで大抵欲しくなる、

+ ON側はチャタリング防止で遅らせる 
+ OFF側は即時OFF

と言う機能を忘れてました。
あれー？こういったことはどうすれば？？
CombineLatestとかでまとめて・・・。
なんて思ってたらやっぱりThrottleだけで出来ました。

```C#:値でThrottleの時間切り替え
public class ChatteringFiltered
{
    public event PropertyChangedEventHandler PropertyChanged;

    public ReactiveProperty<bool> Sensor { get; private set; }

    private ReactiveProperty<bool> _chatteringSensor;

    public ChatteringFiltered(ReactiveProperty<bool> chatteringSensor)
    {
        Sensor = new ReactiveProperty<bool>(false);
        _chatteringSensor = chatteringSensor;

        _chatteringSensor
        .Throttle(val =>
        {
            var ts = val ? TimeSpan.FromMilliseconds(3000) : TimeSpan.Zero;
            return Observable.Timer(ts);
        })
        .Subscribe(value =>
        {
            Console.WriteLine(
                DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff")
                + " : LatestValue = " + value.ToString());
            Sensor.Value = value;

        });

        Sensor.Subscribe(_ => 
            Console.WriteLine(
                DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff") 
                + " : Filtered ! : " + Sensor.Value)
        );

    }
}

```



Reactive Propertyって凄いですね。
自分の常識をぶっ壊してくれてる感じが溜まりません。
まだまだ全然使えてないですが、やる気が出てきます！

もしModel側でReactive Propertyを使うときは、Schedulerの指定をした方が良いそうです。
Reactive PropertyはデフォルトでUIスレッドで動くそうなので。

[(参考)【C#】ReactivePropertyをModelで使う場合の留意点 ](http://einherjar1632.hatenablog.com/entry/2015/12/26/123312)

##Bufferの動作が予想と違った
コンソールの動きを見たらイメージとちょっと違いました。

コンソールに出してみて初めてBufferの動きが分かりました。

初めもっていたイメージは

> + 値の変化をトリガーに
> + 指定時間の間値の変化を記録して
> + SubScribeが実行される

と思ってました。
つまり一定時間以上の変化が無ければ、Bufferは一旦終わるものかと思ってました。

しかし、コンソールの動きを見てみると、

> + Bufferを呼び出した時点から
> + 指定時間ごとに
> + Subscribeが実行される

と言う動きでした。


しかし前者の考えで居た私は

```
if (list.Count == 1)
{
   Sensor.Value = list.Last();
}
```

とかやってしまい、全然思った動きになりませんでした(汗