KEYENCE 上位リンク通信

# PythonでKEYENCEの上位リンク通信(Ethernet)します
この上位リンク通信はメッセージサービス用の通信コマンドでPC-PLC間で使用してPLCのメモリの値を読み書きできます
ここではpythonを使ってEtherenetで上位リンク通信するクラスを作りました
kvHostLink

https://github.com/OkitaSystemDesign/kvHostLink

# 環境
### PLC対象機種
KV-EP21V
KV-8000(A)
KV-7500
KV-5500
KV-NC1EP
### クライアント側環境
python 3.9

# 対応コマンド
| 機能 | コマンド | メソッド |
|:-:|:-:|:-:|
| モード変更  | Mn  | mode  |
| エラークリア  | ER  | errclr  |
| エラー番号確認  | ?E  | er  |
| 機種の問い合わせ  | ?K  | unittype  |
| 時刻設定  | WRT  | settime  |
| 強制セット  | ST  | set  |
| 強制リセット  | RS  | reset  |
| 連続強制セット  | STS  | sts  |
| 連続強制リセット  | RSS  |  rss |
| データ読み出し  | RD  | read  |
| 連続データ読み出し  | RDS  | reads  |
| データ書き込み  | WR  | write  |
| 連続データ書き込み  | WRS  | writes  |

# 使い方
上位リンク通信のコマンドとレスポンスはすべて文字列です  
レスポンスは文字列で最後にはCR+LFが付きます

#### オブジェクトの作成
```python
kv = kvHostLink('192.168.0.31')
```
kvhostlinkクラスは通信相手のPLCのIPアドレスを指定してオブジェクトを作ります
#### モード変更
```python
data = kv.mode('1')
```
引数には0か1を入れて動作状態を切り替えます
0: PROGRAMモード
1: RUNモード
#### エラークリア
```python
data = kv.errclr()
```
#### エラー番号確認
```python
data = kv.er()
```
エラー番号が返ってきます
#### 機種の問い合わせ
```python
data = kv.unittype()
```
レスポンスは以下の番号が各機種に割り当たっています
KV-8000 ：57
KV-8000A ：58
KV-7300 ：54
KV-7500 ：55
KV-3000 ：51
KV-5000 ：52
KV-5500 ：53
KV-NC32T ：128
KV-N60□□ ：132
KV-N40□□ ：133
KV-N24□□ ：134
#### 時刻設定
```python
data = kv.settime()
```
PLCの時刻を送信元の時刻に合わせます
#### 強制セット
```python
data = kv.set('MR0')
```
引数にはデバイス種別と番号を入力します
| デバイス名 | 種別 | 番号 |
|:-:|:-:|:-:|
| リレー  | R  | 0-199915  |
| リンクリレー  | B  | 0-7FFF  |
| 内部補助リレー  | MR  | 0-399915  |
| ラッチリレー  | LR  | 0-99915  |
| コントロールリレー  | CR  | 0-7915  |
| タイマ  | T  | 0-3999  |
| カウンタ  | C  | 0-3999  |
| 高速カウンタコンパレータ  | CTC  | 0-3  |
| ワークリレー  | VB  | 0-F9FF  |

※ 番号は機種によってサイズが変わります
#### 強制リセット
```python
data = kv.reset('MR0')
```
引数にはデバイス種別と番号を入力します
#### 連続強制セット
```python
data = kv.sts('MR10', 5)
```
引数1はデバイス種別と番号を入力します
引数2はセットする個数を入力します
#### 連続強制リセット
```python
data = kv.rts('MR10', 5)
```
引数1はデバイス種別と番号を入力します
引数2はリセットする個数を入力します
#### データ読み出し
```python
data = kv.read('MR0.U')
```
引数1はデバイス種別と番号とデータ形式を入力します
データ形式は以下で指定します
| サフィックス |データ形式 |
|:-:|:-:|
| .U  | 10進数16ビット符号なし  |
| .S  | 10進数16ビット符号あり  |
| .D  | 10進数32ビット符号なし  |
| .L  | 10進数16ビット符号あり  |
| .H  | 16進数16ビット  |

デバイス種別と番号
| デバイス名 | 種別 | 番号 |
|:-:|:-:|:-:|
| リレー  | R  | 0-199915  |
| リンクリレー  | B  | 0-7FFF  |
| 内部補助リレー  | MR  | 0-399915  |
| ラッチリレー  | LR  | 0-99915  |
| コントロールリレー  | CR  | 0-7915  |
| ワークリレー  | VB  | 0-F9FF  |
| データメモリ  | DM  | 0-65534  |
| 拡張データメモリ  | EM  | 0-65534  |
| ファイルレジスタ  | FM  | 0-32767  |
| ファイルレジスタ  | ZF  | 0-524287  |
| リンクレジスタ  | W  | 0-7FFF  |
| テンポラリデータメモリ  | TM  | 0-511  |
| インデックスレジスタ  | Z  | 1-12  |
| タイマ  | T  | 0-3999  |
| タイマ（現在値）  | TC  | 0-3999  |
| タイマ（設定値）  | TS  | 0-3999  |
| カウンタ  | C  | 0-3999  |
| カウンタ（現在値）  | CC  | 0-3999  |
| カウンタ（設定値）  | CS  | 0-3999  |
| デジタルトリマ  | AT  | 0-7  |
| コントロールメモリ  | CM  | 0-5999  |
| ワークメモリ  | VM  | 0-50999  |

※ 番号は機種によってサイズが変わります
#### 連続データ読み出し
```python
data = kv.reads('MR0.S',4)
```
引数1はデバイス種別と番号とデータ形式を入力します
引数2は読み出す個数を入力します
#### データ書き込み
```python
data = kv.write('DM0.U', '2')
```
引数1はデバイス種別と番号とデータ形式を入力します
引数2は書き込む値を入力します
#### 連続データ書き込み
```python
data = kv.writs('DM1.S', 4, '1 2 3 4')
```
引数1はデバイス種別と番号とデータ形式を入力します
引数2は書き込む個数を入力します
引数3にスペースで区切って書き込む値を入力します

# コード
```python:kvhostlink.py
import time
from socket import *
import struct
import datetime

BUFSIZE = 4096

class kvHostLink:
    addr = ()
    destfins = []
    srcfins = []
    port = 8501
    

    def __init__(self, host):
        self.addr = host, self.port

    def sendrecive(self, command):
        s = socket(AF_INET, SOCK_DGRAM)
        s.bind(('', self.port))
        s.settimeout(2)

        starttime = time.time()

        s.sendto(command, self.addr)
        #print("send:%r" % (command))
        rcvdata = s.recv(BUFSIZE)

        elapsedtime = time.time() - starttime
        #print ('receive: %r\t Length=%r\telapsedtime = %sms' % (rcvdata, len(rcvdata), str(elapsedtime * 1000)))
        #print()
        return rcvdata

    def mode(self, mode):
        senddata = 'M' + mode
        rcv = self.sendrecive((senddata + '\r').encode())
        return rcv

    def unittype(self):
        rcv = self.sendrecive("?k\r".encode())
        return rcv

    def errclr(self):
        senddata = 'ER'
        rcv = self.sendrecive((senddata + '\r').encode())
        return rcv

    def er(self):
        senddata = '?E'
        rcv = self.sendrecive((senddata + '\r').encode())
        return rcv

    def settime(self):
        dt_now = datetime.datetime.now()
        senddata = 'WRT ' + str(dt_now.year)[2:]
        senddata = senddata + ' ' + str(dt_now.month)
        senddata = senddata + ' ' + str(dt_now.day)
        senddata = senddata + ' ' + str(dt_now.hour)
        senddata = senddata + ' ' + str(dt_now.minute)
        senddata = senddata + ' ' + str(dt_now.second)
        senddata = senddata + ' ' + dt_now.strftime('%w')
        rcv = self.sendrecive((senddata + '\r').encode())
        return rcv
        
    def set(self, address):
        rcv = self.sendrecive(('ST ' + address + '\r').encode())
        return rcv

    def reset(self, address):
        rcv = self.sendrecive(('RS ' + address + '\r').encode())
        return rcv

    def sts(self, address, num):
        rcv = self.sendrecive(('STS ' + address + ' ' + str(num) + '\r').encode())
        return rcv

    def rss(self, address, num):
        rcv = self.sendrecive(('RSS ' + address + ' ' + str(num) + '\r').encode())
        return rcv

    def read(self, addresssuffix):
        rcv = self.sendrecive(('RD ' + addresssuffix + '\r').encode())
        return rcv

    def reads(self, addresssuffix, num):
        rcv = self.sendrecive(('RDS ' + addresssuffix + ' ' + str(num) + '\r').encode())
        return rcv

    def write(self, addresssuffix, data):
        rcv = self.sendrecive(('WR ' + addresssuffix + ' ' + data + '\r').encode())
        return rcv

    def writs(self, addresssuffix, num, data):
        rcv = self.sendrecive(('WRS ' + addresssuffix + ' ' + str(num) + ' ' + data + '\r').encode())
        return rcv


kv = kvHostLink('192.168.0.31')
data = kv.mode('1')
print(data)
data = kv.er()
print(data)
data = kv.errclr()
print(data)
data = kv.unittype()
print(data)
data = kv.settime()
print(data)
data = kv.set('MR0')
print(data)
data = kv.reset('MR1')
print(data)
data = kv.sts('MR10', 5)
print(data)
data = kv.rss('MR10', 4)
print(data)
data = kv.read('DM0.U')
print(data)
data = kv.reads('DM0.S', 4)
print(data)
data = kv.write('DM0.U', '2')
print(data)
data = kv.writs('DM1.S', 4, '1 2 3 4')
print(data)

```