ControlDesk上でsimulinkブロックをPythonの変数として扱う方法

# はじめに
計測用ソフトウェアのControlDeskでは，入力値Constantやセンサ値ScopeなどのSimulinkのブロックを変数 (Variable) として扱います．
基本はInstrumentをLayoutに設置し，VariableとリンクしてNumeric inputを手動で数値を変更したり，Displayの数値を視認します．
しかし，Automation機能の1つであるPython記述による計測自動化などを実行したい場合，
その変数をプログラム内部で扱う必要があります．
今回はその記述手法について，イベント処理を用いて簡単にまとめました．

# 環境
- Windows 10 64bit (Home)
- CPU: Intel Core i7-9750H 2.60GHz
- メモリ: 16GB
- dSPACE board: MicroLabBox (ds1202)
- ControlDesk: 7.5 (ver. 4.3以降)
- MATLAB: R2021b
- Python: 3.7?

# 手順
## 1. プログラムを記述する場所
ControlDeskではPythonプログラムを実行できる機能が2つあります．
(1) Pythonファイルを直接実行
(2) イベント駆動
今回は(2)のイベント駆動について解説します．

### 1. 記述する画面を出す
画面上部`Automationタブ -> Configure Events`
雷マークのボタンを押します．
![variable_python_ConfigureEvents.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2654082/4aae331e-e73a-f4a6-9989-cab7d9ba36b0.png)
ウィンドウがポップアップしたら左上のタブ
`Event Configurationウィンドウ -> Experimentタブ -> CalibrationManagement`
基本的にExperimentの中から目的のEvenet Handlerをダブルクリックしてプログラムに反映します．
OnClickは特によく使います．

今回は試しにGo Onlineボタンを押したとき発火するプログラムとして`OnlineCalibrationStarted`を選択．
すると，以下のようなプログラムが表示されます．
この関数内にボタンを押したときに実行したいことを記述します．
![variable_python_default_program.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2654082/9bd6a9ac-92f6-ac8a-41d1-90307e319235.png)
そのまま画面右下のOKボタンを押して元の画面に戻ります．
<font color="#FF0000">※Cancelや×ボタンで閉じると記述したプログラムが反映されないので注意！</font>
Go Onlineを押すとInterpreterコントロールバーにprint()の内容が出力されます．
※Interpreterが画面上に存在しない場合：Viewタブ -> Swith Controlbars -> Interpreterをクリック．
![variable_python_ConfigureEvents_Experiment_OnlineCalibration_exe.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2654082/05ef8d0a-1714-406b-95fa-1839cfa3c259.png)

## 2. 構文
### 2.1. プラットフォームの抽出
プラットフォームとは，dSPACEのボード本体のことです．
デフォルトは"Platform"という名前になっています．
変数にアクセスする場合は確実に必要なので忘れないようにしてください．
<!--TODO: プラットフォーム名の探し方-->
```python
MyPlatform = Application.ActiveExperiment.Platforms["PLATFORM_NAME"]
```

### 2.2. 扱いたい変数のパスを取得
変数はPathとして定義されており，`Properties`パネルの`Path`から右クリックでコピーできます．また，`Variables`パネルの変数のシグナルを右クリックしてもコピーすることができます．
一応違いとして，後者の方法ではコピーテキストにクオテーションマークが付きます．
![variable_python_Properties_Path.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2654082/2d216cc7-a2da-5117-0d9e-a1297c6bd653.png)
下記コードの[ ]内にVariableのPathを入力することにより，Simulink上のブロックを識別します．
```python
# Pathから直接指定
# <class 'win32com.client.CDispatch'>
myVar = myPlatform.ActiveVariableDescription.Variables["PlatformName()://Model Root/Constant/Value"]
# インデックス指定も可能
myVar = myPlatform.ActiveVariableDescription.Variables[8]
```
<!--TODO: インデックスの順番は？TCLファイルの順番？Variableの中のデフォルトの順番？-->

### 2.3. 変数にアクセス
Variableオブジェクトの持つ数値はValueConverted (float型)属性に格納されています．

```python
# Interpreterタブのコンソールに数値を出力
print(myVar.ValueConverted)
# 属性に数値を代入
myVar.ValueConverted = 100.0
```

## 3. 実行例
比例電磁弁を制御する簡単なプログラムでConstantブロックの値を変更してみます．
### 1. ブロック配置
![variable_python.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2654082/ee238419-24ee-46c1-6c2e-e3693db549dd.png)
↑使用するsimulinkプロジェクト
https://drive.google.com/drive/folders/1Xkvh1oQzEWb-FYsvmoVCNdy-vL23dO5w?usp=drive_link

[ブロックの説明]
- <font color="#D95319">solenoid valve [MPa]</font>: 指令値監視用
- <font color="#00FFFF">pressure [MPa]</font>: 指令値
- Data Type Conversion: Constantブロックに直接Scopeを接続するとControlDesk内でVariableのSignalが空欄になることを防止．どうやら，ビルド後に生成されるTRCファイルのSignal情報が空欄になるのが原因のようです．（Simulinkのモデル設定を弄れば回避できたような...）

### 2. プログラム記述
Numeric InputとDisplayのInstrumentを配置し，以下のコードを記述します．
Go Online時に指令値を0.100 MPaにするプログラム
```Python
MyPlatfrom = Application.ActiveExperiment.Platforms["Platform"]

def On_Application_CalibrationManagement_OnlineCalibrationStarted():
    """
    The application has changed state to online calibration.
    Syntax    : On_Application_CalibrationManagement_OnlineCalibrationStarted()
    Parameters: None
    """
    print("On_Application_CalibrationManagement_OnlineCalibrationStarted")

    PressVar = MyPlatform.ActiveVariableDescription.Variables["Platform()://Model Root/pressure [MPa]/Value"]
    print(type(PressVar))
    PressVar.ValueConverted = 0.100
    print("PressVar: " + format(PressVar.ValueConverted, '.3f'))
```
![variable_python_ConfigureEvents_Experiment_example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2654082/5fbc15ee-9a64-ad76-c38c-da2a7935b0e5.png)

### 3. 実行
実行結果
指令値のnumeric inputの表記が0.100になり，interperterにも出力されています．
![variable_python_ConfigureEvents_Experiment_example_exe.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2654082/dc86b88d-5354-1c69-56bb-548f6e646254.png)


# 参考文献
[1] dSPACE, "ツールの自動化によるControlDeskでの変数への直接的なアクセス",
https://www.dspace.com/ja/jpn/home/support/kb/faqs/faq101.cfm, 
(ref. 2023/10/08)

[2] dSPACE, "ControlDeskがツール自動化により次のレベルに到達",
https://www.dspace.com/ja/jpn/home/news/engineers-insights/blog-inc-controldesk-0517.cfm, 
(ref. 2023/10/08)

[3] dSPACE, "ControlDeskイベントの使用に関する基礎(要ログイン)",
https://www.dspace.com/ja/jpn/home/support/documentation.cfm?helpsetid=ControlDeskAutomation&externalid=Topic_fddcddb3-e618-48bc-9b8c-38072fd25354_--_&Language=ja-jp&Release=RLS2023-A

### 4. 注意事項
VariablesをPythonの変数に代入数する際，オンライン状態でないとエラーが発生します．
エラー発生後，以下の処置をするまでイベント駆動のプログラムが動作しなくなります．
- Automationタブの`Reload Events`ボタンを押してリロード
- Configure Eventsを開いて`OK`を押す

この症状を回避するために例外処理(try-except)を導入するか，以下の構文を関数内の先頭に記述することにより，そもそもオフライン状態でプログラムの中身が実行されないようにすることができます．
```Python
# Go Onlineしたときに実行する関数
def On_Application_CalibrationManagement_OnlineCalibrationStarted():
    """
    The application has changed state to online calibration.
    Syntax    : On_Application_CalibrationManagement_OnlineCalibrationStarted()
    Parameters: None
    """
    print("On_Application_CalibrationManagement_OnlineCalibrationStarted")

    # Go Onlineでなければ終了
    # Online : State=1
    # Offline: State=0
    # --------
    if Application.CalibrationManagement.State:
      print("[error] Please click 'Go Online' Button.")
      exit()
    # --------
    
    PressVar = MyPlatform.ActiveVariableDescription.Variables["Platform()://Model Root/pressure
    #...
    #..
    #.
```