An Embedded Engineer’s Blog

とある組み込みエンジニアの備忘録的なブログです。

UMLのステートマシン図を実装する for C# - その5

まえがき

今回は、エアコンモデル(AirConditionerクラス)を実装していきます。
エアコンモデルは、今回のテーマである「ステートマシン図の実装」からは離れてしまうので、それっぽく動作するものに留めておきます。

f:id:an-embedded-engineer:20190414174321p:plain
エアコンステートマシン


StainLevel 列挙体

StainLevel列挙体は、汚れレベル解析処理結果を表し、レベルに応じてクリーニングモードを切り替えるために使用します。

public enum StainLevel
{
    // 汚れレベル未確定
    Unknown,
    // 汚れレベル低
    Low,
    // 汚れレベル高
    High,
}


AirConditioner クラス

エアコンモデルのAirConditionerクラスを実装していきます。

定数定義

定数として、目標温度や湿度の最大/最小値を定義します。

// 最大目標温度[℃]
public const int MaxTargetTemperature = 40;

// 最小目標温度[℃]
public const int MinTargetTemperature = 15;

// 最大湿度[%]
public const int MaxHumidity = 90;

// 最小湿度[%]
public const int MinHumidity = 20;


プロパティ定義

プロパティとして、エアコンの各種状態値(温度、湿度、汚れレベルなど)を定義します。

// 現在温度[℃]
public int Temperature { get; private set; }

// 目標温度[℃]
public int TargetTemperature { get; private set; }

// 湿度[%]
public int Humidity { get; private set; }

// 汚れレベル
public StainLevel StainLevel { get; private set; }

// 前回の汚れレベル
private StainLevel PrevStainLevel { get; set; }

// 汚れレベル解析時間カウント
private int AnalyseCount { get; set; }

// クリーニング時間カウント
private int CleanCount { get; set; }

// 汚れレベル解析完了フラグ : 汚れレベルがUnknown以外でtrue
private bool AnalyseFinished => (this.StainLevel != StainLevel.Unknown);

// クリーニング完了フラグ : 汚れレベルがHighでクリーニング時間20カウント以上 or 汚れレベルがLowでクリーニング時間10カウント以上
private bool CleanFinished => ((this.StainLevel == StainLevel.High && this.CleanCount >= 20) 
                            || (this.StainLevel == StainLevel.Low && this.CleanCount >= 10));


初期化処理

初期化処理では、各種プロパティに初期値をセットします。
今回はサンプルのため、設定値は適当です。
実際には、初期値設定以外にも各種ハードウェアの初期化処理などが実行されます。

public void Initialize()
{
    // 初期温度 = 30[℃]
    this.Temperature = 30;

    // 初期目標温度 = 最低目標温度[℃]
    this.TargetTemperature = AirConditioner.MinTargetTemperature;

    // 初期湿度 = 50[%]
    this.Humidity = 50;

    // 初期汚れレベル = Unknown
    this.StainLevel = StainLevel.Unknown;

    // 初期前回汚れレベル = Unknown
    this.PrevStainLevel = StainLevel.Unknown;

    // 汚れレベル解析時間カウントクリア
    this.AnalyseCount = 0;

    // クリーニング時間カウントクリア
    this.CleanCount = 0;
}


動作開始処理

動作開始処理では、エアコンの温度/湿度制御の開始処理を行います。
今回はサンプルのため、現在温度と湿度を初期値にリセットしています。

public void Start()
{
    // 現在温度 = 30[℃]
    this.Temperature = 30;

    // 現在湿度 = 50[%]
    this.Humidity = 50;
}


動作停止処理

動作開始処理では、エアコンの温度/湿度制御やクリーニングの停止処理を行います。
今回はサンプルのため、各種クリーニング状態を初期値にリセットしています。

public void Stop()
{
    // 汚れレベルリセット
    this.StainLevel = StainLevel.Unknown;

    // 前回汚れレベルリセット
    this.PrevStainLevel = StainLevel.Unknown;

    // 汚れレベル解析時間カウントクリア
    this.AnalyseCount = 0;

    // クリーニング時間カウントクリア
    this.CleanCount = 0;
}


目標温度設定処理

エアコンの目標温度は、ユーザがアップボタン / ダウンボタンを押下することによって上下させる事ができるようにします。
アップボタン / ダウンボタンそれぞれのインタフェースとして、それぞれUpメソッド、Downメソッドを用意しています。

public void Up()
{
    // 現在の目標温度が最大目標温度より小さい
    if (this.TargetTemperature < AirConditioner.MaxTargetTemperature)
    {
        // 目標温度をインクリメント
        this.TargetTemperature++;
    }
}

public void Down()
{
    // 現在の目標温度が最小目標温度より大きい
    if (this.TargetTemperature > AirConditioner.MinTargetTemperature)
    {
        // 目標温度をデクリメント
        this.TargetTemperature--;
    }
}


温度/湿度制御処理

エアコンが動作状態となっている時の、動作モードに応じた温度/湿度制御処理を実装します。
今回はサンプルのため、単純な温度や湿度の上下とリミット処理のみを実装します。
実際には、センサから取得した現在温度と、設定された目標温度からフィードバック制御などを行います。

public void CoolControl()
{
    // 目標温度が現在温度より低い
    if (this.TargetTemperature < this.Temperature)
    {
        // 現在温度をデクリメント
        this.Temperature--;
    }
}

public void HeatControl()
{
    // 目標温度が現在温度より高い
    if (this.TargetTemperature > this.Temperature)
    {
        // 現在温度をインクリメント
        this.Temperature++;
    }
}

public void DryControl()
{
    // 現在湿度が最小湿度より大きい
    if (this.Humidity > AirConditioner.MinHumidity)
    {
        // 現在湿度をデクリメント
        this.Humidity--;
    }
}


汚れレベル解析処理

クリーニング開始前の、汚れレベル解析処理を実装します。
今回はサンプルのため、時間経過と前回の解析結果に応じて汚れレベルを変化させます。
実際には、センサなどを使用して内部の汚れ具合を判定し、それに応じた汚れレベルを返すようにします。

public StainLevel StainLevelAnalys()
{
    // 汚れレベル解析時間カウントアップ
    this.AnalyseCount++;

    // 解析時間が5カウント以上(メソッドが5回以上呼び出された)
    if (this.AnalyseCount >= 5)
    {
        // 前回の汚れレベル解析結果
        switch (this.PrevStainLevel)
        {
            case StainLevel.Unknown:    // 不明(初回解析)
                // 汚れレベル = 低
                this.StainLevel = StainLevel.Low;
                break;
            case StainLevel.Low:        // 汚れレベル低
                // 汚れレベル = 高
                this.StainLevel = StainLevel.High;
                break;
            case StainLevel.High:       // 汚れレベル高
                // 汚れレベル = 低
                this.StainLevel = StainLevel.Low;
                break;
            default:
                break;
        }
    }
    else
    {
        // 汚れレベル = 未確定
        this.StainLevel = StainLevel.Unknown;
    }

    return this.StainLevel;
}


クリーニング処理

解析した汚れレベルに応じたクリーニング処理を行います。
今回はサンプルのため、単純な時間経過でクリーニング処理完了判定を行います。

public bool DeepCleanControl()
{
    // クリーニング時間カウントアップ
    this.CleanCount++;

    // クリーニング完了判定結果を返す
    return this.CleanFinished;
}

public bool LightCleanControl()
{
    // クリーニング時間カウントアップ
    this.CleanCount++;

    // クリーニング完了判定結果を返す
    return this.CleanFinished;
}


クリーニング完了処理

クリーニング処理が完了したら、その後始末処理を行います。 今回はサンプルのため、汚れレベル解析結果のバックアップと、各クリーニング状態のクリアを行います。

public void CleanEnd()
{
    // 汚れレベルを前回値としてバックアップ
    this.PrevStainLevel = this.StainLevel;

    // 汚れレベル解析結果クリア
    this.StainLevel = StainLevel.Unknown;

    // 汚れレベル解析時間カウントクリア
    this.AnalyseCount = 0;

    // クリーニング時間カウントクリア
    this.CleanCount = 0;
}


ソース全体を示します。

public class AirConditioner
{
    // 最大目標温度[℃]
    public const int MaxTargetTemperature = 40;

    // 最小目標温度[℃]
    public const int MinTargetTemperature = 15;

    // 最大湿度[%]
    public const int MaxHumidity = 90;

    // 最小湿度[%]
    public const int MinHumidity = 20;

    // 現在温度[℃]
    public int Temperature { get; private set; }

    // 目標温度[℃]
    public int TargetTemperature { get; private set; }

    // 現在湿度
    public int Humidity { get; private set; }

    // 汚れレベル
    public StainLevel StainLevel { get; private set; }

    // 前回の汚れレベル
    private StainLevel PrevStainLevel { get; set; }

    // 汚れレベル解析時間カウント
    private int AnalyseCount { get; set; }

    // クリーニング時間カウント
    private int CleanCount { get; set; }

    // 汚れレベル解析完了フラグ
    private bool AnalyseFinished => (this.StainLevel != StainLevel.Unknown);

    // クリーニング完了フラグ
    private bool CleanFinished => ((this.StainLevel == StainLevel.High && this.CleanCount >= 20) 
                                || (this.StainLevel == StainLevel.Low && this.CleanCount >= 10));

    // 初期化処理
    public void Initialize()
    {
        this.Temperature = 30;

        this.TargetTemperature = AirConditioner.MinTargetTemperature;

        this.Humidity = 50;

        this.StainLevel = StainLevel.Unknown;

        this.PrevStainLevel = StainLevel.Unknown;

        this.AnalyseCount = 0;

        this.CleanCount = 0;
    }

    // 動作開始処理
    public void Start()
    {
        this.Temperature = 30;

        this.Humidity = 50;
    }

    // 動作停止処理
    public void Stop()
    {
        this.StainLevel = StainLevel.Unknown;

        this.PrevStainLevel = StainLevel.Unknown;

        this.AnalyseCount = 0;

        this.CleanCount = 0;
    }

    // 目標温度アップ
    public void Up()
    {
        if (this.TargetTemperature < AirConditioner.MaxTargetTemperature)
        {
            this.TargetTemperature++;
        }
    }

    // 目標温度ダウン
    public void Down()
    {
        if (this.TargetTemperature > AirConditioner.MinTargetTemperature)
        {
            this.TargetTemperature--;
        }
    }

    // 冷房制御処理
    public void CoolControl()
    {
        if (this.TargetTemperature < this.Temperature)
        {
            this.Temperature--;
        }
    }

    // 暖房制御処理
    public void HeatControl()
    {
        if (this.TargetTemperature > this.Temperature)
        {
            this.Temperature++;
        }
    }

    // 除湿制御処理
    public void DryControl()
    {
        if (this.Humidity > AirConditioner.MinHumidity)
        {
            this.Humidity--;
        }
    }

    // 汚れレベル解析処理
    public StainLevel StainLevelAnalys()
    {
        this.AnalyseCount++;

        if (this.AnalyseCount >= 5)
        {
            switch (this.PrevStainLevel)
            {
                case StainLevel.Unknown:
                    this.StainLevel = StainLevel.Low;
                    break;
                case StainLevel.Low:
                    this.StainLevel = StainLevel.High;
                    break;
                case StainLevel.High:
                    this.StainLevel = StainLevel.Low;
                    break;
                default:
                    break;
            }
        }
        else
        {
            this.StainLevel = StainLevel.Unknown;
        }

        return this.StainLevel;
    }

    // 入念クリーニング処理
    public bool DeepCleanControl()
    {
        this.CleanCount++;

        return this.CleanFinished;
    }

    // あっさりクリーニング処理
    public bool LightCleanControl()
    {
        this.CleanCount++;

        return this.CleanFinished;
    }

    // クリーニング完了処理
    public void CleanEnd()
    {
        this.PrevStainLevel = this.StainLevel;

        this.StainLevel = StainLevel.Unknown;

        this.AnalyseCount = 0;

        this.CleanCount = 0;
    }
}


次回予告

次回は、ステートマシンをCUI(コマンドライン)で動作させるアプリケーションを実装していきます。

an-embedded-engineer.hateblo.jp