C#でMXComponentを使う（ビットデバイス編）

# はじめに
私はPLCに関しては完全に素人で触れたことがありませんでしたが、関わっていた案件で「シーケンサの制御をソフト側から行って欲しい」という要望があり、MXComponentを使って実現する流れになりました。
しかしマニュアルやI/F仕様書を読んでも初めは全く分からない状態でしたが、動かしたり試行錯誤するなかで色々分かってきたので書き記したいと思います。

# MXComponentとは
[PLC](http://nasueidensha.com/%EF%BD%90%EF%BD%8C%EF%BD%83%EF%BC%88%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B5%EF%BC%89%E3%81%A8%E3%81%AF%E4%BD%95%EF%BC%9F/)は様々な機械（ロボットとか）の制御を行う制御装置です。
特に三菱電機製のPLCは「シーケンサ」と呼びます。

MXComponentは三菱電機製のツールで、シーケンサをPCから制御できるツール・ライブラリ群です。

# 環境構築
- Windows 10 pro
- Visual Studio 2017
- MX Component v4

## インストール
MX Component v4を`setup.exe`からインストールする。
> 体験版の場合は`sw4dnc-act-j_14q_try\EnvMEL\setup.exe`

スタートメニューの[MELSOFT]-[MX Component]配下に以下3つのアプリが登録されるはずです。
- シーケンサモニタユーティリティ
- ラベル管理ユーティリティ
- 通信設定ユーティリティ

## 通信設定
「通信設定ユーティリティ」を使用してシーケンサとの通信設定を作成します。
基本はウィザード通りに進めていけば良いですが、設定内容は使用するシーケンサと接続方法になりますので、環境に合ったものを選択していきます。
プログラムから呼び出す際に重要なのは「論理局番」です。C#側からはこの数値を指定して接続するシーケンサを識別します。

- ここでは論理局番"1"とします。
- 「管理者権限」で開きましょう、権限がないと最後の最後に登録できません。

ちなみにLAN接続の通信設定を行うと以下のような画面になります。
既にシーケンサと接続できる環境なら「通信テスト」を行いましょう。

![nwutil.png](https://qiita-image-store.s3.amazonaws.com/0/204683/484caf68-753a-6a85-6dca-215d2766e6cd.png)

## ラベル設定
「ラベル管理ユーティリティ」を使用してデバイスに紐づくラベル（システムラベル）を設定します。

![labelutil.png](https://qiita-image-store.s3.amazonaws.com/0/204683/11b04b4f-88a3-8dec-cd35-49a77fe72455.png)

以下の手順でシステムラベルを登録します。
1. 管理者権限で「ラベル管理ユーティリティ」を開く
2. 「ラベルスペース」を選択（もしくは新規作成）
3. 「論理局番」に先に設定した局番を指定
4. 「システムラベルリスト」を選択（もしくは新規作成）
5.  システムラベル名に適当な識別名を設定（”M”とか”1”とか”2”とかデバイス名と被る文字は使用できないようだ。ちょっと気持ち悪いけど日本語にしておくと確実に被らない）
6.  データ型とデバイスを指定し、保存する。
7.  Windowsサービスの「MX Label Management Service」を再起動する。

## コントロール（ライブラリ）
プログラムとシーケンサを接続する為に、MXComponentは3種類のコントロールを提供しています。

||ActiveXコントロール|ActiveXコントロール|.Netコントロール|
|---|---|---|---|
|dll|ActProgType|ActUtlType|DotUtlType|
|プラットフォーム|ExcelVBA, AccessVBA, ASP.Net|ExcelVBA, AccessVBA, ASP.Net|C++.NET, C#, VB.NET|
|環境設定|プロパティ設定|ユーティリティ設定|ユーティリティ設定|

今回はC#からの使用を考えていますが、マニュアルを見る限りWindowsフォームコントロールしか提供されません。
しかしイマドキWindowsフォームは使いたくありませんし、ユーザー操作での制御ではなくプログラム制御を行いたかったので、普通にインスタンス化して使用しました。（マニュアル外の使用方法でしたが問題なく動作しました）

```csharp
using MITSUBISHI.Component;

DotUtlType dotUtlType = new DotUtlType();
```

> .Netにもプロパティ設定のコントロールがあれば前述の「通信設定」と「ラベル設定」はユーティリティでの設定ではなくプログラムに書けるのに何故か無い。。。

## 参照設定の追加
- Visual Studioでプロジェクトを作成する。
- [プロジェクト]-[参照の追加]から参照マネージャーを開き、[アセンブリ]-[拡張]-[MITSUBISHI DotUtlType Component]にチェックを入れる。  

> マニュアルにはライブラリ（dll）のプロパティで「相互運用型の埋め込み」をFalseにするよう記述があるが、デフォルトでFalseになっているので気にしなくて良い。

- [ビルド]-[構成マネージャー]を開き、プラットフォームをx86（32bit）に変更する。

# 使い方

## 初期処理
DotUtlTypeのインスタンスを作成する。

```csharp:plc.cs
DotUtlType dotUtlType = new DotUtlType() { ActLogicalStationNumber = 1 };
dotUtlType.Open();
```

ActLogicalStationNumberには論理局番を設定する。
事前に論理局番:1を作成している為、ここでは1を設定

> PLCに接続していない状態でOpenするとアプリが固まる（デフォルトで60秒後にタイムアウトする）
> この時アプリを強制終了するとブルースクリーンになるので注意

## GetDevice
デバイス1点のデータを取得する。
以下の例ではビットデバイス（内部リレー）のデバイス：M0からデータを1bit読み出す。
メソッドの第一引数はラベル名（ラベル管理ユーティリティで作成したシステムラベル）を指定する必要があるので注意

```csharp:plc.cs
string label = "テスト"; // M0のシステムラベル
int data = 0;
int ret = dotUtlType.GetDevice(ref label, ref data);
Console.WriteLine("READ : " + data.ToString());
```

`> READ : 1`

以下のようなデータが入っている場合、最下位ビットの1が返る

|M31～M24|M23～M16|M15～M8|M7～M0|
|:-:|:-:|:-:|:-:|
|0000 1000|0100 1011|0110 0001|1010 100 **1**|

## GetDevice2
基本はGetDeviceと同じだが、データをshortで取得する。
ビットデバイスを扱う分には1bitしかデータが返らないのでintでもshortでも変わらない。

## ReadDeviceBlock
デバイスの一括読出しを行う。
以下の例ではデバイス：M0～M15まで一括で取得する。

> 一括読出しの為、ビットデバイスの場合は16の倍数単位でしか読みだせない。

```csharp:plc.cs
string label = "テスト"; // 読出し開始ラベル
int size = 1; // デバイス数 ÷ 16
int[] buffer = new int[size];
int ret = dotUtlType.ReadDeviceBlock(ref label, size, ref buffer);
Console.WriteLine("READ : " + buffer[0].ToString());
```

`> READ : 25001`

以下のようなデータが入っている場合、下位16ビットが返る

|M31～M24|M23～M16|M15～M8|M7～M0|
|:-:|:-:|:-:|:-:|
|0000 1000|0100 1011|**0110 0001**|**1010 1001**|

```
BIN : 0110 0001 1010 1001
HEX : 61 A9
DEC : 25001
```

## ReadDeviceBlock2
基本はReadDeviceBlockと同じだが、データをshort配列で取得する。
ReadDeviceBlockは取得できるデータがint配列の為、1要素で最大`4Byte`取得できると思えるが
1要素あたり16ラベル分`2Byte`しかデータが返らない。（上位`2Byte`は常に0)
その為ビットデバイスを扱う分にはどちらでもよい

## SetDevice
デバイス1点にデータを書き込む。
以下の例ではビットデバイス（内部リレー）のデバイス：M0にデータを書き込む。

```csharp:plc.cs
string label = "テスト"; // M0のシステムラベル
int data = 0;
int ret = dotUtlType.SetDevice(ref label, ref data);
```

以下のようなデータが入っている場合、最下位ビットが書き換わる

||M31～M24|M23～M16|M15～M8|M7～M0|
|:-:|:-:|:-:|:-:|:-:|
|書込み前|0000 1000|0100 1011|0110 0001|1010 100 **1**|
|書込み後|0000 1000|0100 1011|0110 0001|1010 100 **0**|

## SetDevice2
基本はSetDeviceと同じ、データをshortで書き込む。

## WriteDeviceBlock
デバイスの一括書き込みを行う。
以下の例ではデバイス：M0～M15まで一括で書き込みする。

> Readと同様、ビットデバイスの場合は16の倍数単位でしか書き込めない

```csharp:plc.cs
string label = "テスト"; // 読出し開始ラベル
int size = 1; // デバイス数 ÷ 16
int[] data = new int[size] { 44806 };
int ret = dotUtlType.WriteDeviceBlock(ref label, size, data);
Console.WriteLine("WRITE : " + data[0].ToString());
```

`> WRITE : 44806`

以下のようなデータが格納される。

|M31～M24|M23～M16|M15～M8|M7～M0|
|:-:|:-:|:-:|:-:|
|0000 0000|0000 0000|**1010 1111**|**0000 0110**|

```
BIN : 1010 1111 0000 0110
HEX : AF 06
DEC : 44806
```

`size`を増やすと16の倍数で書き込めるデバイスが増えていく
※デバイスの番号順に下位ビットから格納されていく

```csharp
int size = 2;
int[] data = new int[size] { 44806, 10990 };
```

|M31～M24|M23～M16|M15～M8|M7～M0|
|:-:|:-:|:-:|:-:|
|**0010 1010**|**1110 1110**|**1010 1111**|**0000 0110**|

## WriteDeviceBlock2
基本はWriteDeviceBlockと同じだが、データをshort配列で取得する。
WriteDeviceBlockは書き込めるデータがint配列だが、下位2Byteしか書き込まれない（上位2Byteは常に0)。
その為ビットデバイスを扱う分にはどちらでもよい

## 

# 参考
- シーケンス制御口座  
http://plckouza.web.fc2.com/index.html
- MX Component Version 4 プログラミングマニュアル  
http://www.mitsubishielectric.co.jp/fa/products/cnt/plceng/smerit/mx_component/index.html