Nel nostro caso si trattava di dialogare con un Siemens 1500, non ho idea se la cosa funzioni anche per altri PLC o se sono necessarie librerie differenti.
Si tratta del package snap7, non è distributito nella installazione standard di python, pertanto si deve procedere ad una semplice installazione.
Installazione della libreria snap7:
dalla command line nella cartella di installazione di python lanciare
python -m pip install python-snap7
questi i messaggi ricevuti:
Downloading/unpacking python-snap7
Downloading python-snap7-0.5.tar.gz
Running setup.py (path:C:\...python-snap7\setup.py) egg_info for package python-snap7
Installing collected packages: python-snap7
Running setup.py install for python-snap7
Successfully installed python-snap7
Cleaning up...
scaricare le dll da sourceforge
Nel mio caso l'ambiente è Windows 7 64 bit
ho copiato da \snap7-full-1.4.1\release\Windows\Win64
snap7.dll
snap7.lib
nella cartella C:\Windows\System32
Bene ora siamo pronti per iniziare.
Vi commento brevemente le funzioni che ho usato e sotto trovate un sorgente completo per leggere e scrivere su un PLC.
Per attivare la connessione con il PLC: connect(address, rack, slot)
i parametri sono:
address: IP address del PLC
rack : rack del PLC
slot : slot del PLC
Per leggere da PLC : read_area(area, dbnumber, start, size)
area: nome del DB
dbnumber: The DB number, only used when area= S7AreaDB
start: offset dove inizia la lettura
size: numero di byte da leggere
ritorna un array di bytes che deve essere decodificato in una stringa
Per scrivere su PLC: write_area(area, dbnumber, start, data)
area: nome del DB
dbnumber: The DB number, only used when area= S7AreaDB
start: offset dove inizia la scrittura
data: dati da scrivere
si deve fornire un array di bytes (la stringa di partenza deve essere decodificata)
Ho avuto un po' di problemi a capire come indirizzare le aree su cui lavorare e a come convertire le stringhe in lettura/scrittura (è richiesto infatti di lavorare con un array di byte). Mi sono costruito un paio di funzioni che operano le conversioni necessarie alle chiamate standard.
Il programma chiede all'operatore se vuole leggere oppure scrivere, poi chiede l'offset di partenza (da quale byte iniziare a leggere/scrivere), quindi il numero di byte da leggere oppure la stringa di caratteri da scrivere.
Nel caso di scrittura il programma mostra il contenuto prima dell'aggiornamento ed esegue una lettura dopo l'aggiornamento.
questo è un esempio di funzionamento del programma da riga di comando:
prima ho chiesto di leggere 10 bytes da offset 0
poi ho chiesto di scrivere una stringa 112233 da offset 0
evidenziati in azzurro gli inserimenti dell'operatore
+--------------------------------------------------+
| Avvio Lettura/scrittura PLC |
+--------------------------------------------------+
IP PLC: nnn.nnn.nnn.nnn
Premere: W per scrittura, R per lettura, S per fermarsi
>> r
Inserire offset iniziale >> 0
Inserire num. byte da leggere >> 10
Lettura= 1234567890
Premere: W per scrittura, R per lettura, S per fermarsi
>> w
Inserire offset iniziale >> 0
Inserire stringa da scrivere >> 112233
Lettura prima write= 123456
Lettura dopo write= 112233
Premere: W per scrittura, R per lettura, S per fermarsi
>>
Sorgente completo:
evidenziato in verde il punto dove inserire l'indirizzo IP del vostro PLC; potrebbero cambiare anche riferimenti all'area con nome e numero DB.
#----------------------------------------------------------------------+
# Programma : Test_PLC |
# Python versione : 3.6.0 |
#----------------------------------------------------------------------+
# Modifiche : by |
# Note : |
#----------------------------------------------------------------------+
#-----------------------------------------
# Import moduli
#-----------------------------------------
import os
import sys
# librerie di comunicazione con PLC siemens
import snap7.client as c
from snap7.util import *
from snap7.snap7types import *
#---- Definizione variabili globali - Costanti fisse ------
# Dati di comunicazione con PLC
HOST_PLC = "<indirizzoIP>" # Indirizzo IP del PLC
DB_PLC = "DB" # Nome DB nel PLC
NUM_DB = 8 # Numero DB nel PLC
#------------------------------------------------------------------------------
# aggiorna_schermo : Aggiorna dati in elaborazione
#------------------------------------------------------------------------------
# input : nulla
# return: nulla
#------------------------------------------------------------------------------
def aggiorna_schermo():
if os.name == "posix": # Unix/Linux/MacOS/BSD/etc
os.system('clear')
elif os.name in ("nt", "dos", "ce"): # DOS/Windows
os.system('CLS')
# mostro all'utente avvio e indirizzo IP del PLC
print ("+--------------------------------------------------+")
print ("| Avvio Lettura/scrittura PLC |")
print ("+--------------------------------------------------+")
print (" ")
print ("IP PLC: ", HOST_PLC)
print (" ")
#------------------------------------------------------------------------------
# loop_plc : Ciclo principale
#------------------------------------------------------------------------------
# input : nulla
# return: nulla
#------------------------------------------------------------------------------
def loop_plc():
aggiorna_schermo()
continua = 1
while continua == 1:
print ("Premere: W per scrittura, R per lettura, S per fermarsi")
# input operatore
comando=input('>> ')
comando=str.lower(comando)
if comando == "s":
continua = 0
elif comando == "r":
esegui_plc("r")
elif comando == "w":
esegui_plc("w")
#------------------------------------------------------------------------------
# esegui_plc : Collegamento con PLC
#------------------------------------------------------------------------------
# input : tipo operazione r=read w=write
# return: nulla
#------------------------------------------------------------------------------
def esegui_plc(tipo):
plc = c.Client()
try:
# Apro canale di comunicazione con PLC
plc.connect(HOST_PLC,0,1)
except:
print("Errore di connessione!!!")
exit(1)
continua = 1
while continua == 1:
# inserire offset
try:
offset=int(input("Inserire offset iniziale >> "))
continua = 0
except ValueError:
print("Inserire un numero intero!!!")
if tipo == 'r':
continua = 1
while continua == 1:
# inserire byte da leggere
try:
nbytes=int(input("Inserire num. byte da leggere >> "))
continua = 0
except ValueError:
print("Inserire un numero intero!!!")
stringa = ReadMemory(plc,offset,nbytes,"Lettura PLC")
print("Lettura=", stringa)
elif tipo == 'w':
# inserire stringa caratteri
s=input('Inserire stringa da scrivere >> ')
l=len(s)
# Leggo dati PLC prima di scrivere
stringa = ReadMemory(plc,offset,l,"Lettura PLC prima Write")
print("Lettura prima write=", stringa)
WriteMemory(plc,offset,s,"Write PLC")
# Verifica dati scritti
stringa = ReadMemory(plc,offset,l,"Lettura PLC dopo Write")
print("Lettura dopo write=", stringa)
plc.disconnect()
#------------------------------------------------------------------------------
# ReadMemory : Legge area di memoria PLC
#------------------------------------------------------------------------------
# input : plc = riferimento alla connessione PLC
# offset = offset iniziale di lettura
# L = lunghezza byte da leggere
# text = testo che indica fase di lettura
# return: stringa di lettura
#------------------------------------------------------------------------------
def ReadMemory(plc,offset,L,text):
try:
result = plc.read_area(areas[DB_PLC],NUM_DB,offset,L)
except:
print("Errore di lettura PLC in fase:",text,"!!!")
exit(1)
return result.decode('utf-8')
#------------------------------------------------------------------------------
# WriteMemory : Scrive area di memoria PLC
#------------------------------------------------------------------------------
# input : plc = riferimento alla connessione PLC
# offset = offset iniziale di lettura
# s = stringa da scrivere
# text = testo che indica fase di scrittura
# return: nulla
#------------------------------------------------------------------------------
def WriteMemory(plc,offset,s,text):
# Trasformo la stringa in un array di byte
b = bytearray()
b.extend(map(ord, s))
# Lancio la scrittura nel PLC
try:
plc.write_area(areas[DB_PLC],NUM_DB,offset,b)
except:
print("Errore di scrittura PLC in fase:",text,"!!!")
exit(1)
#------------------------------------------------------------------------------
# INIZIO PROGRAMMA
#------------------------------------------------------------------------------
if __name__=='__main__':
loop_plc()
Ciao Fabio,
RispondiEliminaho installato SNAP7 su raspberry riesco a comunicare correttamente in TCP con il plc siemens. Dovrei ora indirizzare le mie chiamate sui registri del DB, ma non saprei come reperire questi indirizzi (AREA DI MEMORIA, ecc.). Esiste uno script PYTHON SNAP7 in GET che te li potrebbe elencare?
Se volessi puntare all'area degli ingressi I1,I2,I3,ecc. su che area di memoria,lenght,bit mi dovrei spostare-dirottare la mia chiamata python???
Se potresti aiutarmi te ne sarei grato
Purtroppo le sorgenti del software caricato sul plc LOGO!
Intanto grazie.
Ciao Mike55,
Eliminascusami ma il primo dicembre ero in cina e vedo solo adesso la tua domanda (mi è sfuggita la mail di avviso!...in cina google è del tutto bannato).
Hai già risolto?
L'area di memoria, a suo tempo, me l'ha indicata la persona che ha installato l'impianto, hai modo di contattarli?
Altrimenti prova ad andare per tentativi usando la funzione indicata:
plc.write_area(areas[DB_PLC],NUM_DB,offset,b)
Nel mio caso il DB_PLC era DB e il NUM_DB era 8, fai qualche tentativo.
ciao
fabio