三菱MELSEC MCプロトコルをpythonで送信

# Pythonで三菱MELSECのMCプロトコル(Ethernet)を送信します
MCプロトコルは三菱のシーケンサのデバイスを値を読み書きすることができます
ここではpythonを使ってEthernet(UDP)でMCプロトコルで通信するクラスを作りました
MCP.py

https://github.com/OkitaSystemDesign/MC-Protocol

# 環境
#### 伝文
3Eフレームでバイナリコードを使用します
3EフレームはMELSEC-QnAシリーズのEthernetインタフェースで使用する伝文構成です
#### クライアント側環境
python 3.9

# 使い方
### オブジェクトの作成
```python
mcp = MCProtcol3E('192.168.0.41', 4999)
```
MCProtocol3Eクラスは通信相手のIPアドレスとポート番号を指定してオブジェクトを作ります

### ワード単位でデバイスの読出し (最大640点)
```python
data = mcp.read('D0', 10)
```
readメソッドでアドレスと点数を指定して実行すると戻り値には読み出したデータがバイト配列（bytes）で取得できます

### ビット単位で読み出し (最大7168点)
```python
data = mcp.read('SM0', 8, True)
```
第3引数をTrueにするとビット単位での読出しになります

### 対象のデバイスは以下のデバイスです
X:  入力
Y:  出力
M:  内部リレー
L:  ラッチ
F  アナンシエータ
V:  エッジリレー
B:  リンクリレー
D:  データレジスタ
W:  リンクレジスタ
TS: タイマ（接点）
TC: タイマ（コイル）
TN: タイマ（現在値）
SS: 積算タイマ（接点）
SC: 積算タイマ（コイル）
SN: 積算タイマ（現在値）
CS: カウンタ（接点）
CC: カウンタ（コイル）
CN: カウンタ（現在値）
SB: リンク特殊リレー
SW: リンク特殊レジスタ
S: ステップリレー
DX：ダイレクト入力
DY: ダイレクト出力
SM: 特殊リレー
SD: 特殊レジスタ
### ワード単位で書込み
```python
# 数値の場合の例
data = struct.pack('hhh', 123, 456, 789)
rcv = mcp.write('D20', data)

# 実数の場合の例
data = struct.pack('f', 1.23)
rcv = mcp.write('D10', data)
```
writeメソッドでアドレスと書込みデータを渡して実行すると戻り値に終了コードが返ってきます
書込みデータはバイト配列で渡します
ちなみに数値をバイト列にする[struct.pack](https://docs.python.org/ja/3/library/struct.html)でよく使いそうなフォーマットを書いておきます
| フォーマット | 型 | バイト数 |
|:-:|:-:|:-:|
| h | Int16 | 2 |
| H | UInt16 | 2 |
| i | Int32 | 4  |
| I | UInt32 | 4 |
| q | Int64 | 8 |
| Q | UInt64 | 8 |
| f | Float | 4 |
| d | Double | 8 |
### ビット単位で書込み
```python
# ビットの場合の例
data = [0x11, 0x11, 0x10]
rcv = mcp.write('M10', data, 5)
```
第3引数に書き込むビット数を入れるとビット単位での書込みになります
### ランダム読出し
```python
rcv = mcp.RandomRead('D0,TN0,M100,X20', 'D1500,Y160,M1111')
```
第1引数にワード単位で読み出すデバイスを、第2引数にダブルワード単位で読み出すデバイスをセットして実行すると読み出したデータがバイト配列（bytes）で取得できます
### モニタ登録
```python
data = mcp.MonitorSet('D50, D55', 'D60, D64')
```
第1引数にワード単位で読み出すデバイスを、第2引数にダブルワード単位で読み出すデバイスをセットして実行します
### モニタデバイス読出し
```python
data = mcp.MonitorGet()
```
モニタ登録してあるデバイスの値を読み出します
### データ変換
|  メソッド名  |  変換するデータの長さ  | 例 |
| --- | --- | --- |
| toBin | バイナリ | data = mcp.read('SM0', 8)<br> print(mcp.toBin(data))<br>> 0b11000100 |
| toInt16 | 16bit数値 | data = mcp.read('D0', 5)<br>print(mcp.toInt16(data))<br>> [-1, 1, 2, 3, 4] |
| toUInt16 | 16bit符号なし | data = mcp.read('D0', 5)<br>print(mcp.toUInt16(data))<br>> [65535, 1, 2, 3, 4] |
| toInt32 | 32bit数値 | data = mcp.read('D0', 6)<br>print(mcp.toInt32(data))<br>>  [-1, 196610, 327684] |
| toUInt32 | 32bit符号なし | data = mcp.read('D0', 6)<br>print(mcp.toUInt32(data))<br>> [131071, 196610, 327684] |
| toInt64 | 64bit数値 | data = mcp.read('D0', 8)<br>print(mcp.toInt64(data))<br>> [-1, 1970350607106052] |
| toUInt64 | 64bit符号なし | data = mcp.read('D0', 8)<br>print(mcp.toUInt64(data))<br>>  [844433520197631, 1970350607106052] |
| toFloat | 浮動小数点 | data = mcp.read('D0', 4)<br>print(mcp.toFloat(data))<br>> [-2.2, 0.0] |
| toDouble | 倍精度 | data = mcp.read('D0', 8)<br>print(mcp.toDouble(data))<br>> [-2.2, 0.0] |
| toString | 文字列| data = mcp.read('D0', 2)<br>print(mcp.toString(data))<br>> ABCD |

### サンプル
```python

# example
# set IPAddress,Port
mcp = MCProtcol3E('192.168.0.41', 4999)

# words
data = mcp.read('D10000', 1)
print(mcp.toInt16(data))        # convert int16
rcv = mcp.write('D10', data)
print(rcv)                      # normal recieve = 0x00 0x00

# bits
data = mcp.read('M8000', 8, True)
print(data)
print(mcp.toBin(data))          # convert bin
rcv = mcp.write('M100', data, 8)
print(rcv)

# numeric
data = struct.pack('hhh', 123, 456, 789)
rcv = mcp.write('D20', data)
print(rcv)
# float
data = struct.pack('f', 1.23)
rcv = mcp.write('D10020', data)
print(rcv)
# bits
data = [0x11, 0x11, 0x10]
rcv = mcp.write('M8100', data, 5)

# RandomRead
rcv = mcp.RandomRead('D0,TN0,M100,X20', 'D1500,Y160,M1111')
print(rcv)
print(mcp.toInt16(rcv[:8]))
print(mcp.toInt32(rcv[8:]))

# Monitor
data = mcp.MonitorSet('D50, D55', 'D60, D64')
rcv = mcp.MonitorGet()
print(mcp.toInt16(rcv[:4]))
print(mcp.toInt32(rcv[4:]))
```

# 参考
### 伝文フォーマット
#### 一括読出し
D100から10ワード読出しコマンド
| サブヘッダ | ネットワーク番号 | PC番号 | 要求先ユニットI/O番号 | 要求先ユニット局番号 |
|:-:|:-:|:-:|:-:|:-:|
| 50 00 | 00 | FF | FF 03 | 00 |

| 要求データ長 *1 | 監視タイマ | コマンド | サブコマンド *2 | 先頭番号 | デバイスコード | 点数 |
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 0C 00 | 10 00 | 01 04 | 00 00 | 64 00 00 | A8 | 0A 00 |

*1 要求データ長から後ろのバイト数
*2 ビット読出しのときはサブコマンドを01 00にする

##### 正常時の応答伝文
| サブヘッダ | ネットワーク番号 | PC番号 | 要求先ユニットI/O番号 | 要求先ユニット局番号 |
|:-:|:-:|:-:|:-:|:-:|
| D0 00 | 00 | FF | FF 03 | 00 |

| 応答データ長 *1 | 終了コード | 読出しデータ |
|:-:|:-:|:-:|
| 16 00 | 00 00 | ... |

 *1 応答データ長から後ろのバイト数

#### 一括書込み
D100から10ワード書込みコマンド
| サブヘッダ | ネットワーク番号 | PC番号 | 要求先ユニットI/O番号 | 要求先ユニット局番号 |
|:-:|:-:|:-:|:-:|:-:|
| 50 00 | 00 | FF | FF 03 | 00 |

| 要求データ長 *1 | 監視タイマ | コマンド | サブコマンド *2 | 先頭番号 | デバイスコード |
|:-:|:-:|:-:|:-:|:-:|:-:|
| 20 00 | 10 00 | 01 14 | 00 00 | 64 00 00 | A8 |

| 点数 | 書込みデータ |
|:-:|:-:|
| 0A 00 | 01 00 02 00 03 00 04 00 05 00 06 00 07 00 08 00 09 00 0A 00 |

*1 要求データ長から後ろのバイト数
*2 ビット読出しのときはサブコマンドを01 00にする

##### 正常時の応答伝文
| サブヘッダ | ネットワーク番号 | PC番号 | 要求先ユニットI/O番号 | 要求先ユニット局番号 |
|:-:|:-:|:-:|:-:|:-:|
| D0 00 | 00 | FF | FF 03 | 00 |

| 応答データ長 *1 | 終了コード |
|:-:|:-:|
| 02 00 | 00 00 |

*1 応答データ長から後ろのバイト数