chao_demiglaceのブログ

工場でPLCに対してのデータ収集等を行っています。

pythonでExcel内の文字列をPLCに書込むbyMCプロトコル

いつもお世話になっております。
駆け出しIIoTエンジニアのチャオです。

本日はExcelのセルにある文字列をPLCのデータメモリに書込むプログラムを書きたいと思います。


【注意】
対象PLCはKV-7500を想定しています。

MCプロトコルを利用するので、PLCの方でMCプログラムを用いるための設定が必要な場合があります。

今回の記述では1セル内ずつの対象になります。

Excelのセルの値はPLCへ書き込む際、アスキーコードに変換します。
Excel:セルA1[ C ] ⇒ KV:DM000[0043]
1セル内の文字が2文字以上になった場合、PLCには次のデータメモリにも書き込みます
Excel:セルA1[ CIAO ] ⇒ KV:DM000[4349]DM001[414f]
キーエンスマニュアル『KV-8000/7000/5000/3000/1000 シリーズ・KV Nano シリーズ 命令語リファレンスマニュアル-付95 ASCIIコード表一覧』参考

書き込む際は工場等で稼働中のPLCではなく、まずテスト機等で試してから行うことを推奨します。

import socket
import binascii
from contextlib import closing
from openpyxl import load_workbook

# MCプロトコル用の変換を行う際に用いる関数群

def hex_change_XX(num):
    if(type(num)==str):
        num_int=int(num)
    else:
        num_int=num
    num=hex(num_int)
    num=num[2:4]
    if(num_int<=15):
        num='0'+num
    return num

def hex_change_XXXX(data):
    if(type(data) == str):
        data_int = int(data)
    else:
        data_int = data
    data = hex(data_int)
    if(data_int <= 15):
        data = '0' + data[2:] + '00'
    if(data_int <= 255 and data_int > 15):
        data = data[2:]+'00'
    if(data_int>255 and data_int<=4095):
        data=data[3:5]+'0'+data[2:3]
    if(data_int>4095 and data_int<=65535):
        data=data[4:6]+data[2:4]
    return data

def hex_change_XXXXXX(data):
    if(type(data)==str):
        data_int=int(data)
    else:
        data_int=data
    data=hex(data_int)
    if(data_int<=15):
        data='0'+data[2:]+'0000'
    if(data_int<=255 and data_int>15):
        data=data[2:]+'0000'
    if(data_int>255 and data_int<=4095):
        data=data[3:5]+'0'+data[2:3]+'00'
    if(data_int>4095 and data_int<=65535):
        data=data[4:6]+data[2:4]+'00'
    if(data_int > 65535 and data_int <= 1048575):
        data=data[5:7]+data[3:5]+'0'+data[2:3]
    if(data_int > 1048575):
        data=data[6:8] + data[4:6] + data[2:4]
    return data

def error_check(error_code):
    if(error_code=='0000'):
        return
    else:
        print('errorコードのレスポンスがありました。'+error_code)

# MCプロトコルに用いるクラス
class Mc:
    def __init__(self,
                 ip,
                 port,
                 bit_or_word,
                 device_num,
                 device_code,
                 data_num,
                 word_num=0,
                 double_word_num='00',
                 write_list = [],
                 device_list = []):
        self.ip = ip
        self.port = port
        self.subhead = '5000'  # 今回は使わない
        self.network = '00'   # 今回は使わない
        self.pc_num = 'FF'   # 今回は使わない
        self.i_o_num = 'FF03'  # 今回は使わない
        self.office_num = '00'  # 今回は使わない
        self.monitoring_timer = '0000'# 今回は使わない
        self.bit_or_word = bit_or_word

        self.bit_or_word = bit_or_word
        self.device_num = device_num
        self.device_code = device_code
        self.data_num = data_num
        self.word_num = word_num
        self.double_word_num = double_word_num
        self.write_list = write_list
        self.device_list = device_list
        
    def excel_write_to_kv(self,load_file,read_sheet):

        # Excelの値を読み取る
        book = load_workbook(load_file)#ファイルの読込
        active_sheet = book.active#ファイルのアクティブ化
        read_word = active_sheet[read_sheet].value#ExcelファイルのA1に対して値を取得
        ascii_change = binascii.b2a_hex(read_word.encode('utf-8'))
        ascii_str = str(ascii_change)[2:]# 文字列に変換し、先頭の'bを取る
        ascii_str = ascii_str[:-1]# 後尾の'を取る replace等を使うのもあり

        # 文字列が奇数だった場合は帳尻合わせのために'00'を挿入する
        if(len(ascii_str) / 2 % 2 == 1):
            ascii_str = ascii_str[:-2] + '00'+ascii_str[-2:]

        # 以下変数はアスキーコードを上位2桁と下位2桁反転させるために行う
        ascii_str_list = [ascii_str[i: i+4] for i in range(0, len(ascii_str), 4)]       #返答数字を四分割にする
        ascii_list = [ascii_str_list[i:i+2] for i in range(0, len(ascii_str_list), 1)]  # 2文字づつチャンク化
        ascii_list = [ascii_list[i:i+1] for i in range(0, len(ascii_list), 1)]          # 2x2文字単位にブロック化
        ascii_list = [i[::2] + i[::2] for i in ascii_list]

        # 上下2位の値を反転させる
        revers_ascii_list=[]
        
        for i in ascii_list:
            n = (len(i) + 1)//4
            s = ''.join(sum(i[n:],[]))
            t = s[2:4] + s[0:2]
            h = t
            revers_ascii_list.append(h)
            
        # アスキーコード変換したリストの長さを取得
        revers_ascii_len = hex_change_XX(len(revers_ascii_list))
        # 出力先のアドレスを指定する
        device_num = self.device_num#インスタンスで指定した書込み開始アドレス
        device_code = self.device_code#インスタンスで指定した書込み先デバイスコードDM = A8
        device_list = []
        for i in range(len(revers_ascii_list)):
            device_list.append(hex_change_XXXXXX(device_num + i) + device_code)

        header = '500000FFFF0300'
        if(self.bit_or_word == 'bit'):
            bit_or_word = '0100'
        else:
            bit_or_word = '0000'# ワード(ビットは0100)
        command = '04000214' + bit_or_word # ランダム書込みワード
        word_num = hex_change_XX(len(revers_ascii_list))#ワードの書込み点数
        w_word_num = '00' #ダブルワードの書込み点数。今回は使わない設定なので'00'
        command_protocol = command + word_num + w_word_num
        for i in range(len(revers_ascii_list)):
            command_protocol += device_list[i] + revers_ascii_list[i]
        code_num = hex_change_XXXX(str(round(len(command_protocol)/2)))
        _send = header + code_num + command_protocol
        mc_send=binascii.a2b_hex(_send)
        sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        with closing(sock):
            try:
                sock.connect((self.ip,self.port))
                sock.send(mc_send)
                res_data=sock.recv(1024)
                res_data=binascii.b2a_hex(res_data)
                res_data=res_data.decode()
                error_check(res_data[18:22])
            except:
                print('write error')

mc_send = Mc(ip = '192.168.0.140',
             port = 5000,
             bit_or_word = 'word',      # 'word' or 'bit' 
             device_num = 100,          # アドレス    
             device_code = 'A8',        # デバイスコード 
             data_num = 10)             # データ数    今回は使わない



# load_fileには本プログラムと同じディレクトリ内のExcelファイルを想定しています。
# r"./07_20/sample.xlsx"でもファイル指定が可能かと思います。
# read_sheetにはload_fileの書き込みたい文字列が入ったセルを指定します。
mc_send.excel_write_to_kv(load_file = 'sample.xlsx',read_sheet='A1')