Vai al contenuto
PLC Forum


Omron-Fins su Python


Vaas98

Messaggi consigliati

Buongiorno,
Sto cercando di connettermi a un plc Omron-Fins con uno script in python. Ho trovato su internet una libreria che mi permette di connettermi con successo al plc e anche di leggere qualcosa, purtroppo incomprensibile (b'\xc0\x00\x02\x00\x1e\x00\x00\xd3\x00`\x01\x01\x00\x00\x00\x00'). 
Per ottenere tale risultato ho utilizzato questo codice

 

import fins.udp
import time


fins_instance = fins.udp.UDPFinsConnection()
fins_instance.connect('192.168.1.211')
fins_instance.dest_node_add=211
fins_instance.srce_node_add=30

mem_area = fins_instance.memory_area_read(fins.FinsPLCMemoryAreas().CIO_WORD,b'\x00\x51\x06')
print(mem_area)

 

Purtroppo penso di aver sbagliato le configurazioni in quanto non conoscendo bene i plc omron-fins non so cosa io debba mettere in dest_node e in srce_node.
Qualcuno sa dirmi qualcosa in più ? 

Link al commento
Condividi su altri siti


Se ti può servire ti posso girare il codice di una implementazione che ho realizzato in C++/Qt, la uso con i CP1 etc... Potresti cercare di portartelo su python leggendoti il codice.
Nei manuali alcune cose se vuoi implementarlo sono un po' fuorvianti e legate a vecchie implementazioni, a me spiegò molte cose direttamente un ingegnere di OMRON Milano. È un protocollo abbastanza semplice se lo usi solo per scambiare dati.

Modificato: da Marco Mondin
Link al commento
Condividi su altri siti

Quote

Se ti può servire ti posso girare il codice di una implementazione che ho realizzato in C++/Qt, la uso con i CP1 etc... Potresti cercare di portartelo su python leggendoti il codice.
Nei manuali alcune cose se vuoi implementarlo sono un po' fuorvianti e legate a vecchie implementazioni, a me spiegò molte cose direttamente un ingegnere di OMRON Milano. È un protocollo abbastanza semplice se lo usi solo per scambiare dati.

 

Se puoi mi saresti molto di aiuto. Grazie mille

Link al commento
Condividi su altri siti

Eccolo.
Lo metto qua. Mai servisse ad altri.
È LGPL, quindi linkabile dinamicamente a applicativi closed.
È il codice grezzo della libreria.
Se ti serve un esempio su come funziona e come interagiscono tra loro gli oggetti ti chiedo di avere un po' di pazienza in quanto devo preparartene uno.


https://drive.google.com/file/d/15ugA9uLOwilb1tKjnEhfi51xiIszOERo/view?usp=sharing

Ha un oggetto per la gestione del "socket" (che non sono socket essendo UDP) FINS QFinsUdpSocket.
Ha un oggetto QFinsDataUnit, per comporre le richieste e con cui ricevere le risposte. (Funziona sulla falsa riga del QModbusDataUnit che esiste nelle librerie Qt)
Ha un oggetto QFinsReply, che viene rilasciato dal socket ad ogni richiesta e che emette un signal quando la risposta dal device è arrivata.
La reply possiede una QFinsDataUnit con i dati ricevuti.
Ha giù tutta una serie di method per la gestione dei tipi di dato comprese stringa. Converte già tutto dal formato OMRON a quello PC e viceversa, senza dover pensare a gestire nulla di raw.
 

Link al commento
Condividi su altri siti

Grazie mille ho già iniziato a dargli una occhiata, ti posso chiedere comunque, se hai il tempo, un piccolo esempio del funzionamento.
Veramente gentile. 
Posso chiederti anche un altra cosa....in che classe fai la "traduzione" dei byte che leggi da fins

Modificato: da Vaas98
Link al commento
Condividi su altri siti

 

2 ore fa, Vaas98 ha scritto:

Grazie mille ho già iniziato a dargli una occhiata, ti posso chiedere comunque, se hai il tempo, un piccolo esempio del funzionamento.
Veramente gentile. 
Posso chiederti anche un altra cosa....in che classe fai la "traduzione" dei byte che leggi da fins

È passato molto tempo da quando lo scrissi.
Mi pare che la lettura cominci sul metodo setRawPacket del QFinsDataUnit, per poi passare alla QFinsReply etc...
Il QFinsDataMapper permette di fare il lavoro sporco del mappare i dati raw da vector di WORDS ai dati leggibili.

Link al commento
Condividi su altri siti

Eccomi:

Cominciamo... Io uso un QFinsDataMapper, lo ho creato per togliermi tanto lavoro sporco. Si smazza tutta la roba RAW lui.

Questo è il costruttore della classe che uso per uno specifico "Cabinet", macchina da monitorare:

Cabinet1::Cabinet1(QObject *parent) : Cabinet(parent),
    finsDataMapper(new QFinsDataMapper(this)),
    watchDogTimer(new QTimer(this)),
    oldWatchDog(0),
    numAlarms(16)
{
    cleanData();
    setAlarmList();
    setWarningList();
    QSettings setup;
    setId(1);
    setMethods();
    setup.beginGroup(QString("MACHINE").append(QString::number(id())));
    setName(setup.value("NAME").toString());
    
    //Imposto i parametri di connessione alla macchina
    //Nel mio caso li pesco da un file ".ini" con il QSettings
    quint8 dna = quint8(setup.value("DNA").toUInt());
    quint8 da1 = quint8(setup.value("DA1").toUInt());
    quint8 da2 = quint8(setup.value("DA2").toUInt());
    quint8 sna = quint8(setup.value("SNA").toUInt());
    quint8 sa1 = quint8(setup.value("SA1").toUInt());
    quint8 sa2 = quint8(setup.value("SA2").toUInt());
    QHostAddress local = QHostAddress(setup.value("LISTEN_IP").toString());
    QHostAddress plc = QHostAddress(setup.value("IP").toString());
    quint16 port = quint16(setup.value("LISTEN_PORT").toUInt());

    //Imposto un QFinsDataMapper
    finsDataMapper->bind(local, port, plc, dna, da1, da2, sna, sa1, sa2,
                         QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
    
    setup.endGroup();
    watchDogTimer->setSingleShot(true);
    
    //Collego i segnali alle rispettive callaback
    connect(watchDogTimer, SIGNAL(timeout()), this, SLOT(watchDogCaptured()));
    connect(finsDataMapper, SIGNAL(readDone(int)), this, SLOT(checkReceived(int)));
    connect(finsDataMapper, SIGNAL(readFailure(int)), this, SLOT(checkFailureReceived(int)));
    connect(finsDataMapper, SIGNAL(writeDone(int)), this, SLOT(checkWrite(int)));
    connect(finsDataMapper, SIGNAL(writeFailure(int)), this, SLOT(checkFailureWrite(int)));
    mapData();
    watchDogTimer->start(TM_C1_DISCONNECT);
}


Qua mappo i dati da monitorare, ne definisco i tipi nomi mnemonici etc...:

void Cabinet1::mapData()
{
    //Tutte le variabili sono semplici INT a cui appoggio un ID del datamapper.
    //Nel datamapper posso mettere range di dati da monitorare/leggere/scrivere
    h0_alarms = finsDataMapper->addDataRange(QFins::AreaHR, 0, 1);

    //Definisco nomi di comodo mnemonici per specifiche variabili e ne dichiaro il tipo.
    finsDataMapper->mapData("alarms", QFins::Word, QFins::AreaHR, 0);
   
    c0_inputs1 = finsDataMapper->addDataRange(QFins::AreaCIO, 0, 1);
    finsDataMapper->mapData("inputs1", QFins::Word, QFins::AreaCIO, 0);
    w0_10_vars = finsDataMapper->addDataRange(QFins::AreaWR, 0, 10);
    finsDataMapper->mapData("phase", QFins::Word, QFins::AreaWR, 0);
    finsDataMapper->mapData("autoOn", QFins::Word, QFins::AreaWR, 2);
    d100_d250 = finsDataMapper->addDataRange(QFins::AreaDM, 100, 150);
    finsDataMapper->mapData("setWashing", QFins::UInt, QFins::AreaDM, 100);
    finsDataMapper->mapData("setSteam", QFins::UInt, QFins::AreaDM, 101);
    finsDataMapper->mapData("setCondensation", QFins::UInt, QFins::AreaDM, 102);
    finsDataMapper->mapData("cycleTx", QFins::UInt, QFins::AreaDM, 150);
    finsDataMapper->mapData("cycleActive", QFins::UInt, QFins::AreaDM, 160);
    finsDataMapper->mapData("readWashing", QFins::UInt, QFins::AreaDM, 200);
    finsDataMapper->mapData("readSteam", QFins::UInt, QFins::AreaDM, 201);
    finsDataMapper->mapData("readCondensation", QFins::UInt, QFins::AreaDM, 202);
    d704 = finsDataMapper->addDataRange(QFins::AreaDM, 700, 10);
    finsDataMapper->mapData("cycleCounter", QFins::UDInt, QFins::AreaDM, 700);
    d1000_watchDog = finsDataMapper->addDataRange(QFins::AreaDM, 1000, 1);
    finsDataMapper->mapData("watchdog", QFins::UInt, QFins::AreaDM, 1000);
    
    //Qua dichiaro alcuni dati che voglio che il datamapper controlli in modo ciclico
    //Emetterà un segnale per indicarmi se uno di questi è cambiato
    //Gli altri li lascio in gestione manuale su richiesta
    finsDataMapper->addCyclicDataRange(d1000_watchDog);
    finsDataMapper->addCyclicDataRange(h0_alarms);
    finsDataMapper->addCyclicDataRange(c0_inputs1);
    finsDataMapper->addCyclicDataRange(w0_10_vars);
    finsDataMapper->addCyclicDataRange(d100_d250);
    finsDataMapper->addCyclicDataRange(d704);
    finsDataMapper->setCyclicTime();
    
    //Ancora un paio di mappature
    productCode = finsDataMapper->mapData("productCode", QFins::String, QFins::AreaDM, 1500, 64);
    lotNumber = finsDataMapper->mapData("lotNumber", QFins::ULInt, QFins::AreaDM, 2000, 1);
    
    //Il segnale cyclicDataChanged, mi restituirà l'ID del range che ha subito un cambio
    connect(finsDataMapper, SIGNAL(cyclicDataChanged(int)), this, SLOT(checkDataChanged(int)));
}


Qua faccio i controlli sull'evento ciclico di dato cambiato:

void Cabinet1::checkDataChanged(int index)
{
    if (index == h0_alarms) {
        checkAlarms();
    }
    if (index == c0_inputs1) {
        checkInputs();
    }
    if (index == w0_10_vars) {
        checkWords();
    }
    if (index == d100_d250) {
        checkData();
    }
    if (index == d704) {
        checkData704();
    }
    if (index == d1000_watchDog) {
        int watchDog = finsDataMapper->value("watchdog").toInt();
        if (oldWatchDog != watchDog) {
            oldWatchDog = watchDog;
            if (!m_connected) {
                m_connected = true;
                emit connected();
                checkAlarms();
                sendLot();
                sendProduction();
            }
            watchDogTimer->start(TM_C1_DISCONNECT);
            finsDataMapper->setValue("watchdog", watchDog + 1);
            finsDataMapper->syncToPlc(d1000_watchDog);
        }
    }
}


Questo è un esempio di scrittura asincrona (Nel mio caso quando da un ERP mi mandano un lotto lo scrivo in macchina).
Vedi sopra come ho mappato il dato:

void Cabinet1::sendLot()
{
    finsDataMapper->setValue("lotNumber", lot);
    finsDataMapper->syncToPlc(lotNumber);
}


Questo è un esempio di lettura asincrona:

	//Leggo il dato autoOn e lo faccio diventare unsigned int del C/C++.
    quint16 word = quint16(finsDataMapper->value("autoOn").toUInt());

    //Uso un metodo statico di comodo che mi estrae bit da una word
    // (NON MI UCCIDERE SE NON HO MESSO ENUM NON È DA ME)
    m_run = QFinsDataMapper::bitValue(word, 0);
    word = quint16(finsDataMapper->value("phase").toUInt());
    m_initPhase = QFinsDataMapper::bitValue(word, 0);
    m_phase1 = QFinsDataMapper::bitValue(word, 1);
    m_phase2 = QFinsDataMapper::bitValue(word, 2);
    m_phase3 = QFinsDataMapper::bitValue(word, 3);
    m_phase4 = QFinsDataMapper::bitValue(word, 4);
    m_endPhase = QFinsDataMapper::bitValue(word, 5);


Altri esempi di lettura:

void Cabinet1::checkData()
{
    m_setWashing = finsDataMapper->value("setWashing").toInt();
    m_setSteam = finsDataMapper->value("setSteam").toInt();
    m_setCondensation = finsDataMapper->value("setCondensation").toInt();
    m_cycleTx = finsDataMapper->value("cycleTx").toInt();
    m_currentCycle = finsDataMapper->value("cycleActive").toInt();
    m_getWashing = finsDataMapper->value("readWashing").toInt();
    m_getSteam = finsDataMapper->value("readSteam").toInt();
    m_getCondensation = finsDataMapper->value("readCondensation").toInt();
}

void Cabinet1::checkData704()
{
    m_counterCycle = finsDataMapper->value("cycleCounter").toInt();
}

 

Link al commento
Condividi su altri siti

Il 15/1/2021 alle 11:38 , Vaas98 ha scritto:

Grazie mille ho già iniziato a dargli una occhiata, ti posso chiedere comunque, se hai il tempo, un piccolo esempio del funzionamento.
Veramente gentile. 
Posso chiederti anche un altra cosa....in che classe fai la "traduzione" dei byte che leggi da fins

L'esempio che ho postato è di molto tempo fa. Non usavo ancora cose come le lambda functions di C++11 e usavo ancora la notazione dei segnali delle librerie Qt4, mantenuta nelle Qt5 (anche se sarebbe stato meglio usare quella nuova). Oggi lo scriverei in modo molto diverso e più compatto.

Link al commento
Condividi su altri siti

 

Il 16/1/2021 alle 14:54 , Marco Mondin ha scritto:

L'esempio che ho postato è di molto tempo fa. Non usavo ancora cose come le lambda functions di C++11 e usavo ancora la notazione dei segnali delle librerie Qt4, mantenuta nelle Qt5 (anche se sarebbe stato meglio usare quella nuova). Oggi lo scriverei in modo molto diverso e più compatto.

Grazie mille lo stesso 

 

Link al commento
Condividi su altri siti

Buongiorno, scusami se ti riscrivo dopo parecchio tempo ma ero impegnato con altro al lavoro e ho dovuto accantonare un attimo il discorso.
Da ignorante in materia perchè la prima volta che comunque mi affaccio non solo a un plc omron ma a un plc in generale, dove tu imposti i parametri di configurazione .....giusto per intenderci dna,da1,da2,sna,sa1,sa2,local cosa ci devo inserire per configurarlo ?  C'è quei valori cosa sono nella pratica/dove li riperisco sul plc
Grazie in anticipo

Link al commento
Condividi su altri siti

Sono i parametri che definiscono la rete fins. Sono legati soprattutto a quando FINS non era su UDP e TCP. Oggi con UDP perdono parte del loro significato.
Potrebbero ancora essere utili per instradare pacchetti su reti ibride usando un PLC come gateway, tuttavia non credo sia molto probabile.

DNA e SNA sono le reti sorgenti e di destinazione FINS per rete locale (link diretto) vanno sempre a 00 o 01 non ricordo ma mi pare che in UDP sia 01.
DA1 e SA1 sono gli indirizzi FINS sorgente e di destinazione del nodo. DA1 mi pare che su UDP vada preso l'ultimo byte dell'indirizzo IP del plc, SA1 del PC o viceversa.
DA2 e SA2 sono gli indirizzi FINS sorgente e di destinazione dell'unità sul nodo. In UDP prova a mettere 00 su DA2 e 00 su SA2 ignora il manuale FINS.

Uso la mia libreria raramente, ci avrò fatto 5-6 applicazioni per adattare macchine ad un 4.0, alla fine metto quei parametri in un file .ini e li testo sul campo. Con UDP hanno valori che non si riferiscono ad un tubo con quelli dei manuali e i manuali sono fatti bene per la vecchia versione non ethernet, ma male per quella ethernet.

Quei dati devono essere presenti nell'header di ogni pacchetto se non ci sono non funziona.

Il SID è solo un contatore pacchetti per controllare a livello applicativi quanti ne perdi etc...

(P.S. sei per caso di Milano?)

Modificato: da Marco Mondin
Link al commento
Condividi su altri siti

Appena il sito di qt decide di ripartire installo qt e inizio a testare
comunque non sono proprio di Milano ma nelle zone limitrofe

 

Modificato: da Vaas98
Link al commento
Condividi su altri siti

1 ora fa, Vaas98 ha scritto:

Appena il sito di qt decide di ripartire installo qt e inizio a testare
comunque non sono proprio di Milano ma nelle zone limitrofe

 

Ti consiglio Qt tra 5.9 e 5.12, lo ho testato su tutte, mentre non sono ancora passato a versioni più recenti e non posso garantire che compili correttamente.
Puoi usare sia MSVC che MinGW per windows o sia gcc che clang per Linux, non fa grande differenza, solo su MSVC è un po' macchinoso configurare CDB per Qt, ma nulla di impossibile.

Noi attualmente in produzione siamo a Qt 5.12.2 sia su Linux che su Windows che su MAC(Dove abbiamo solo un applicativo per il settore odontoiatrico).
Prima di passare a versioni nuove dedichiamo sempre un po' di tempo ai test e cerchiamo solo di passare a versioni LTS.

Con MSVC siamo fermi alla 2015 per via della compatibilità con Qt5.12.2

Per scopo personale su linux uso un po' tutte le versioni e sovente me le compilo a partire dai sorgenti per ottimizzare alcune cose.

Modificato: da Marco Mondin
Link al commento
Condividi su altri siti

  • 1 year later...
  • Livio Orsini locked this discussione
Ospite
Questa discussione è chiusa alle risposte.
×
×
  • Crea nuovo/a...