Versuchsaufbau:
Easy822-DC-TC
Easy800 USB-CAB
Raspberry Pi mit Python und PySerial Modul
# raspi_com is a script providing serial communication between Raspberry Pi and Easy800 devices via USB-CAB
#
# raspi_com needs pySerial to be previously installed -> http://pyserial.sourceforge.net/pyserial.html
#
# Since this code is Python, it should be portable to other plattforms, but has just been written
# and tested on Raspberry Pi's ARM architecture under Raspbian (a subset of Debian Wheezy)
#
# (c) 2013 Peter Sommer ---------- Dilettanten Ole! ------------ <peter at 3d-check dot com> ---------------
#
# -- This code is free software subject to the GNU General Public License <http://www.gnu.org/licenses/>----
#
# WARNING :
#
# The PLC or it's program MUST restrain or reject received data whenever they don't match process-safe values
#
# The PLC or it's program MUST take care of the process security, being allways and anytime able to deny or
# stop the execution of unsafe actions and undo unsafe process-states triggered by this software
#
# USE ON YOUR OWN RISK
#-----------------------------------------------------------------------------------------------------------
import sys
import struct
import serial
import subprocess
import time
# ----------------------------------------- driver ----------------------------------------------------------
# The cp210x driver is included in recent Linux distributions and just needs --------------------------------
# our USB-Serial bridge's vendor and product ID notifyed to the cp210x driver -------------------------------
# Thanks and free beer plenty for Jon at http://www.ha19.no/usb/ --------------------------------------------
subprocess.Popen('echo 188a 3001 > /sys/bus/usb-serial/drivers/cp210x/new_id',shell=True)
# --------------------------------------------------- ttyUSB port usage -------------------------------------
# -- pySerial encapsulates the access for the serial port -- http://pyserial.sourceforge.net/pyserial.html --
# -----------------------------------------------------------------------------------------------------------
def open_port( _name='/dev/ttyUSB0' , _baudrate=9600, _timeout=1 ):
global o_serial
global urb
try:
o_serial = serial.Serial(_name, baudrate = _baudrate, timeout = _timeout) # create and open serial port
test_mesg = (69,7,0,0,1,0,80,119) # ---- test the port
test_mesg = struct.pack('<5BHBB', *test_mesg)
req=o_serial.write(test_mesg)
res=o_serial.read(8)
i_mesg = struct.unpack('<c7B',res)
in_crc = i_mesg[6:] # ---- validate PLC response by CRC16
in_payload = i_mesg[1:6]
c_data = struct.pack('<5B',*in_payload)
chk_crc = getCRC(c_data)
if chk_crc == in_crc:
return 0 # ---- success: communication ok ---------------- >>>
else:
return 1 # ---- communication is messy (wrong settings?)
except:
return 2 # ---- no succes creating port
def close_port():
try:
o_serial.close()
return 0 # ---- success
except:
return 3 # ---- no such port
def write_port(outData):
try:
if o_serial.inWaiting() > 0:
urb.append(o_serial.read(o_serial.inWaiting())) # --- flush unrequested data to urb
o_serial.write(outData)
return listen_port() # --- go wait for data
except:
return 4 # --- no port?
urb = [] # ---- buffer for unrequested data
def listen_port():
try:
cnt = 0
while cnt < 50 :
data_len=o_serial.inWaiting()
if data_len > 0:
in_header = o_serial.read(1) # --- get header
# -------------------------------------- REQUESTED DEVICE TO HOST -------------
if in_header == 'e': # --- header from device = response on host request
pl_len = o_serial.inWaiting()
in_data = o_serial.read(pl_len)
in_data = struct.unpack('<'+str(pl_len)+'B',in_data)
in_data = list(int(val) for val in in_data)
msg_len = in_data[0] # ---- payload length announced by device
if len(in_data)-1 == msg_len: # ---- if true, guess transfer is completed
msg_crc_1 = in_data.pop() # ---- check data integrity by CRC16
msg_crc_2 = in_data.pop()
c_payload = struct.pack('<'+str(len(in_data))+'B', *in_data)
crc = getCRC(c_payload)
if msg_crc_1 == crc[1] and msg_crc_2 == crc[0]: # data is good!
return in_data # --- return validated data -->>>
else:
return 5 # --- data was a mess
else:
return 6 # --- data wrong length
# -------------------------------------- UNREQUESTED DEVICE TO HOST -------------
else:
urb.append(in_header+o_serial.readline())
while o_serial.inWaiting() > 0:
urb.append(o_serial.readline()) # flush unrequested data to urb
return 7 # just had unrequested stuff
else:
time.sleep(.1)
cnt=cnt+1
return 8 # ---- function timed out
except:
return 9 # ---- port is closed or does not exist
# --------------------------------------------------------------------------------------------------
# ---- plc_item objects provide easy read and write access ----
# ---- they build the "front end" of raspi_com ----
# --------------------------------------------------------------------------------------------------
# ---- dictionaries used for message encoding/decoding (kept public allow edit during runtime)
d_write= {'M':(4,1,'B'),'MB':(10,1,'B'),'MW':(10,2,'H'),'MD':(10,4,'i')} #(type,indx-mult,width)
d_read = {'QA':(9,0,'H'),'IA':(8,0,'4H'),'I':(0,0,'H'),'Q':(1,0,'H'),'M':(4,1,'B'),
'MB':(10,1,'32B'), 'MW':(10,2,'16H'),'MD':(10,4,'8i')} #(type,indx-mult, fmt)
# --------- create plc_item instances using this sintax: -------------------------------------------
# ----
# object = plc_item(location as int, item as string, index as int) ----
# ----
# --------------------------------------------------------------------------------------------------
class plc_item:
def __init__(self, location=0, item_type='Q', index=0):
self.error = None
self.timestamp = 0
self.is_writeable = False
try:
self.location = int(location)
if d_read.has_key(item_type):
self.item_type = item_type
else:
return # --- unknown PLC item type
self.index = int(index)-1
self.msg_h = [self.location,d_read[self.item_type][0],self.index*d_read[self.item_type][1]]
self.is_writeable = d_write.has_key(item_type)
self.error = 0
except:
return # --- error creating plc_item
def set_value(self, value):
if self.is_writeable == True:
try:
data = struct.pack(d_write[self.item_type][2],value)
data = struct.unpack(str(d_write[self.item_type][1])+'B',data)
o_mesg = [self.location,d_write[self.item_type][0],self.index*d_write[self.item_type][1], data]
resp = write_object(*o_mesg)
try:
resp.pop(0)
err_b_1 = resp.pop(0)
err_b_2 = resp.pop(0)
if err_b_1+err_b_2 == 0:
return 0 # --------- success ----- >
else:
self.error=(err_b_1,err_b_2,resp.pop(0))
return 10 # -------- device reported error to be read from self.error
except:
return resp # ------ internal error code (int)
except:
return 11 # wrong data ?
else:
return 12 # --- plc_item is not writeable
def get_value(self):
if self.error != None :
resp = read_object(*self.msg_h)
try:
resp.pop(0)
err_b_1 = resp.pop(0)
err_b_2 = resp.pop(0)
if err_b_1+err_b_2 == 0:
c_mesg = struct.pack(str(len(resp))+'B',*resp)
o_mesg = struct.unpack(d_read[self.item_type][2],c_mesg)
self.values = o_mesg
self.error = ()
self.timestamp = time.clock()
return self.values # ----- success (data as tuple)
else:
self.error = resp.pop(0)
return 13 #---- device reported error to be read from self.error
except:
return resp # ----- internal error code
else:
return 14 # ---- this plc_item is not working
# ------------------------------------------------ message encoding -------------------------------
# -------------------------------------------------------------------------------------------------
# -------------------------------------------------------------------------------------------------
def write_object(B_net_id,B_object,H_index,data): # ----- request write to device memory
try:
a_data = [int(val) for val in data]
o_msg = [len(a_data)+7, 32, B_net_id, B_object, H_index]+a_data
c_msg = struct.pack('<4BH'+str(len(a_data))+'B', *o_msg)
except:
return 15 # ---- wrong data
crc = getCRC(c_msg)
o_msg.insert(0,0x45) # --- header ('E')
o_msg.append(crc[0]) # --- CRC-16 byte 1
o_msg.append(crc[1]) # --- CRC-16 byte 0
c_msg = struct.pack('<5BH'+str(2+len(a_data))+'B',*o_msg)
return write_port(c_msg) # --- wait for success ------------------------------------------->
def read_object(B_net_id,B_object,H_index): # ----- request data from device memory
try:
o_msg = [7, 0, B_net_id, B_object, H_index]
c_msg = struct.pack('<4BH', *o_msg)
except:
return 16 # ---- wrong data
crc = getCRC(c_msg)
o_msg.insert(0,0x45) # --- header ('E')
o_msg.append(crc[0]) # --- CRC-16 byte 1
o_msg.append(crc[1]) # --- CRC-16 byte 0
c_msg = struct.pack('<5BHBB',*o_msg)
return write_port(c_msg) # --- wait for data ----------------------------------------------->
# --------------------------------------------------------------------------------------------------
# ------ methods dealing with unrequested device to host messages ----
# --------------------------------------------------------------------------------------------------
# ------ flush_data set to False performs a conservative dump
# --------------------------------------------------------- read one item from urb -----------------
def pop_urb(flush_data = True, offset=0):
global urb
if len(urb) > offset+1:
if flush_data:
return urb.pop(offset) # - returns one line as string and deletes it from the list
else:
return urb[offset] # - just returns one line as string
else:
return ''
# -------------------------------------------------------------- read all data from urb -------------
def dump_urb(flush_data = True):
global urb
ret = urb
if flush_data:
urb=[]
return ret # - returns urb's data as a list
#---------------------------------------------------- CRC ------------------------------------------------
# Table-based CRC-16 calculation from http://www.digi.com/wiki/developer/index.php/Python_CRC16_Modbus_DF1
# shortened functionality for ease of use in our case, and messy byte juggling added by needs ------------
# Lots of thanks and plenty of gallons free beer go to Greg Cook, for his tool "crc revEng" + advise -----
# http://regregex.bbcmicro.net ---------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------------
def getCRC( st ):
crc16_table = (
0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40,
0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841,
0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40,
0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41,
0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641,
0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040,
0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441,
0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41,
0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840,
0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41,
0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40,
0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640,
0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041,
0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41,
0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840,
0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41,
0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40,
0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640,
0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041,
0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241,
0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440,
0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841,
0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40,
0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41,
0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641,
0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040 )
crc = 0x0000
for ch in st:
crc = (crc >> 8) ^ crc16_table[(crc ^ ord(ch)) & 0xFF]
crc = struct.pack('<H',crc)
crc = struct.unpack('<BB',crc)
return crc
# ------------------------------------------------------------------------------------------------ END
Zusätzlich braucht es das PySerial Modul.
So wird es am einfachsten benutzt :
Man kreiert beliebige Instanzen von plc_item, die dann die Methoden set_value(data) und get_value() zur verfügung stellen.
o_item = plc_item(net_id , type, index)
net_id ist 1 byte Integer
type ist String
index ist 2 byte Integer
Implementiert sind folgende "typen":
M = Merker Bit / Index 1 bis 96 / set_value 1 Bit (0 oder 1) / get_value 1 Bit (0 oder 1)
MB = Merker Byte / Index 1 bis 384 / set_value 1 Byte (0 bis 255) / get_value 32 byte
MW = Merker Wort / Index 1 bis 192 / set_value 1 Word (0 bis 65535) / get_value 16 Word
MD = Merker Doppelwort / Index 1 bis 96 / set_value 1 DWord (+/- 2147483646) / get_value 8 DWord
Q = Ausgänge / kein Index / kein Schreibzugriff / get_value 1 Word
QA = Analogausgang / kein Index / kein Schreibzugriff / get_value 1 Word
IA = Analogeingänge / kein Index / kein Schreibzugriff / get_value 4 Word
I = Digitaleingänge / kein Index / kein Schreibzugriff / get_value 1 Word
Zu mehr war ich nicht in der Lage...
get_value() gibt bei Erfolg ein Tuple zurück, mit den Werten ab Index.
Beim lesen von MB,MW und MD bekommt man immer 32 Byte Daten zurück (ab Index).
Ein kleines Beispiel aus der IDLE Konsole:
>>> open_port()
0
>>> M1 = plc_item(0,'M',1)
>>> M1.set_value(1)
[5,0,0,0] <-------------------- die Nullen hinter der 5 bedeuten Erfolg
>>> M1.get_value()
(1,)
>>> MD1 = plc_item(0,'MD',1)
>>> MD1.set_value(-12345)
[5,0,0,0]
>>> MD1.get_value()
(-12345,0,0,0,0,0,0,0)
>>>close_port()
0
Übrigens: wenn jemand den Baustein "Serielles Protokoll" (LS) in sein Easy-Projekt einbaut, kann die Easy ungefragt auf die Serielle Schnittstelle schreiben.
Bei plc_item.set_value() und plc_item.get_value() werden diese "ungefragten" Daten, sollten sie Anliegen, erst einmal ausgelesen und in der Liste urb gepuffert und können mit pop_urb() und dump_urb() danach ausgelesen werden.
Möglicher Einsatz : Data-Logging
Vielleicht hilft es jemanden.
Gruß,
Peter