Topic: [RISOLTO]Condividere dizionari tra due processi  (Letto 89 volte)

0 Utenti e 1 Visitatore stanno visualizzando questo topic.

Offline Anakin73

  • python unicellularis
  • *
  • Post: 10
  • Punti reputazione: 0
    • Mostra profilo
[RISOLTO]Condividere dizionari tra due processi
« il: Gennaio 29, 2021, 19:51 »
Buonasera a tutti,
volevo sottoporvi un problema che mi sta assillando da una settimana.
Sto realizzando una domotica per la mia casa, ho acquistato un pc all-in-one sul quale ho installato Ubuntu 20.04 il pc ha un processore i7 quadcore 16G RAM.
Ho realizzato il pannello di controllo e programmazione della domotica utilizzando Python3.8 e come libreria GUI PyQt5
Il programma realizzato ha anche la funzione di chiamare, con protocollo Modbus utilizzando la libreria Modbus_tk, dei dispositivi suddivisi su due linee RS485 native del pc. Una linea ha le seguenti caratteristiche 115200 ,N,8,1. L'altra 9600,N,8,1.
Su quella più veloce leggo e scrivo i dispositivi di I/O sull'altra i "servizi" (un analizzatore di rete, l'inverter del FV  e la pompa di calore).
Tutto funziona meravigliosamente ma, ecco l'assillo, eseguendo le letture delle due linee rs485 in sequenza, risulta che la linea più lenta infici le prestazioni dell'altra e cosa peggiore, quando (e purtroppo capita sovente) l'inverter FV è in spegnimento o in accensione e non comunica, il timeout di "non risposta" blocca il processo di lettura di entrambe le linee per 500 mS sembra una banalità, ma vi assicuro che è veramente noiso il ritardo di accensione o quello di lettura dei pulsanti.
Ho cercato di risolvere con QRunnable o con QThread, la cosa migliora in caso di "non risposta" ma non risolve il problema del conflitto (se così si può definire) tra le due linee. L'unica soluzione funzionante che ho trovato è stata quella di creare un thread contenente  due QApplication contenenti i "loop" della lettura continua della linea a 115200 e di quella a 9600 e una QApplication contenete il programma principale. Il problema che avrete già intuito e che non riesco a "prendere" i dati letti ne ad inviare dati da scrivere, pertanto il mio assillo sta nel poter condividere tra i due "loop" e il programma principale il dizionario contenente le informazioni di I/O.
Avete dei suggerimenti?
Grazie mille!
« Ultima modifica: Febbraio 03, 2021, 09:09 da Anakin73 »

Offline Anakin73

  • python unicellularis
  • *
  • Post: 10
  • Punti reputazione: 0
    • Mostra profilo
Re:Condividere dizionari tra due processi
« Risposta #1 il: Gennaio 30, 2021, 12:59 »
Se può aiutare vi mando la stampa dei due oggetti (COM0 e COM1) che come si può vedere sembrano condividere la stessa area di memoria (?)
E' possibile?
<__main__.ComunicationRS485 object at 0x7f1d429829d0> COM1
<__main__.ComunicationRS485 object at 0x7f1d429829d0> COM0

Questi due oggetti sono stati creati in questo modo:
def process1():
    Thread1 =  QApplication([])
    COM1 = ComunicationRS485('COM1')
    print(COM1,COM1.objectName())
    COM1.show()
    sys.exit(Thread1.exec_())


def process0():
    Thread0 = QApplication([])
    COM0 = ComunicationRS485('COM0')
    print(COM0,COM0.objectName())
    COM0.show()
    sys.exit(Thread0.exec_())


if __name__ == "__main__":

    processo0 = Process(target=process0)
    processo1= Process(target=process1)

    processo0.start()
    processo1.start()

Offline Anakin73

  • python unicellularis
  • *
  • Post: 10
  • Punti reputazione: 0
    • Mostra profilo
Re:Condividere dizionari tra due processi
« Risposta #2 il: Febbraio 03, 2021, 09:09 »
Buongiorno a tutti.
Finalmente ho risolto il problema, lascio qui la soluzione sperando possa essere utile a qualcun altro della Community.  ;)
La soluzione l'ho trovata con Ray https://ray.io/, un pacchetto che offre, con pochi comandi, la gestione dei subprocessi in modo eccellente.
Oltre ad importarlo con il classico import ray, bisogna inserire all'inizio del codice il comando ray.init() e inserire il decoratore @ray.remote prima della classe o della funzione che si vuole "mandare" nel sotto processo.
La cosa più complessa è stata, per me, gestire il flusso dei dati di ritorno perchè nelle istruzioni non era ben chiaro il meccanismo, mi spiego meglio:
se ho due sotto processi di durata differente la funzione ray.get(miaFunzione) preleva i dati aspettando che tutti i sottoprocessi siano completati, per questo va utilizzata la funzione ray.wait(miaFunzione) che restituisce due liste (qui sono impazzito fin quando non ho trovato questo articolo https://medium.com/distributed-computing-with-ray/ray-tips-and-tricks-part-i-ray-wait-9ed7a0b9836d), la prima contiene lo/gli oggetto/i di tipo Actors o Functions risolti, la seconda quelli non ancora pronti. Si deve eseguire la ray.get() solo sulla prima lista. Un altro consiglio che mi sento di dare è: se avete necessità, come ne mio caso, di eseguire questi processi all'infinito, bisogna rimuovere dalla lista delle funzioni quelle risolte perchè non vengono rimosse in automatico (questo mi è sembrato strano, ma non sono riuscito a fare di meglio che toglierle manualmente).
Inserisco qui sotto il mio prog. di prova, ci sono delle classi modificate da me quindi non vi funzionerà però spero che il listato possa aiutare nella comprensione. Ah dimenticavo, questi sono i tempi di esecuzione delle letture:
Ready length, values:  1 ('/dev/ttyUSB1', [[0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
Not Ready length: 1
duration com= 0.017377614974975586
Ready length, values:  1 ('/dev/ttyUSB0', [[218.3000030517578], [0.06800000369548798], [6.300000190734863], [6.4808149337768555], [0.0], [0.9767923951148987], [50.0], [6.712806701660156], [1.7009999752044678]])
Not Ready length: 1
duration com= 0.0005311965942382812
comunicazione0 = {"port":"/dev/ttyUSB1","baudrate":115200,"parity":"N","bytesize":8,"stopbits":1,"method":"rtu","timeout":0.005}
comunicazione1 = {"port":"/dev/ttyUSB0","baudrate":9600,"parity":"N","bytesize":8,"stopbits":1,"method":"rtu","timeout":0.5}

data0 = [{"unit":1,"funz":0x01,"address":0x0000,"count":0x08,"tipo":"OU"},
         {"unit":2,"funz":0x02,"address":0x0000,"count":0x10,"tipo":"DI"},
        #  {"unit":3,"funz":0x03,"address":0x0BB8,"count":0x04,"tipo":"AO","timeout":0.05},
         {"unit":4,"funz":0x02,"address":0x0000,"count":0x10,"tipo":"DI"},
         {"unit":5,"funz":0x02,"address":0x0000,"count":0x10,"tipo":"DI"},
         {"unit":6,"funz":0x02,"address":0x0000,"count":0x10,"tipo":"OU"}]

data1 = [{"unit":1,"funz":0x04,"count":0x02,"address":  0,"typeVal":"float"},
         {"unit":1,"funz":0x04,"count":0x02,"address":  6,"typeVal":"float"},
         {"unit":1,"funz":0x04,"count":0x02,"address": 12,"typeVal":"float"},
         {"unit":1,"funz":0x04,"count":0x02,"address": 18,"typeVal":"float"},
         {"unit":1,"funz":0x04,"count":0x02,"address": 24,"typeVal":"float"},   
         {"unit":1,"funz":0x04,"count":0x02,"address": 30,"typeVal":"float"},
         {"unit":1,"funz":0x04,"count":0x02,"address": 70,"typeVal":"float"},
         {"unit":1,"funz":0x04,"count":0x02,"address": 86,"typeVal":"float"},
         {"unit":1,"funz":0x04,"count":0x02,"address":342,"typeVal":"float"}]

import MJTool
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import ray
import time

class MainWindow(QWidget):
    def __init__(self):
        super().__init__()
        self.setFixedSize(400,400)
        self.mainLayout = QVBoxLayout(self)
        self.layout0 = QHBoxLayout()
        self.layout1 = QVBoxLayout()

        self.lbl1 = QLabel()
        self.lbl2 = QLabel()
        self.btnCnt = QPushButton('Cnt1')
        self.btnCnt.setCheckable(True)
 
        self.layout0.addWidget(self.btnCnt)

        self.layout1.addWidget(self.lbl1)
        self.layout1.addWidget(self.lbl2)
       
        self.mainLayout.addLayout(self.layout0)
        self.mainLayout.addLayout(self.layout1)     

    def setText(self,val1=None,val2=None):
        if val1 != None:
            self.lbl1.setText(str(val1))
        if val2 !=None:
            self.lbl2.setText(str(val2))

    def closeEvent(self, event):
        self.close()
        print("main process finished")


ray.init()
@ray.remote
class Com0():
    def __init__(self,com,dati):
        self.com  = com
        self.dati = dati
        self.client = MJTool.MJModbus_tk()  #Pacchetto MOdbus_tk modificato (esegue anche la funzione 17)
        self.val = []

    def comunicaCOM(self):
        self.val.clear()
        for dato in self.dati:
            self.client.comunica(dato)
            self.val.append(self.client.valore)   
        return self.client.client._serial.port, self.val  #aggiungo il valore della porta seriale per riconoscere la funzione di ritorno

    def com_avvio(self):
        self.client.connetti(self.com)



f=[]

if __name__ == "__main__":
    import sys
   
    app = QApplication(sys.argv)

    main = MainWindow()

    com0 = Com0.remote(comunicazione0,data0)
    com0.com_avvio.remote()

    com1 = Com0.remote(comunicazione1,data1)
    com1.com_avvio.remote()

    timer = QTimer(main)
    timer.start()

    def setTimer():
        global f
        if main.btnCnt.isChecked():
            start = time.time()

            if len(f) == 0: #inizializzo la lista funzioni
                f.append(com1.comunicaCOM.remote())
                f.append(com0.comunicaCOM.remote())

            ready, not_ready = ray.wait(f)
            valore = ray.get(ready)[0]
            print('Ready length, valori in Ready: ', len(ready), valore)
            print('Not Ready length:', len(not_ready))
            f.pop(f.index(ready[0]))   

            if valore[0] == comunicazione1["port"]:
                main.setText(val2=valore)
                f.append(com1.comunicaCOM.remote())

            elif valore[0] == comunicazione0["port"]:
                main.setText(valore)   
                f.append(com0.comunicaCOM.remote())
               
            print("duration com=", time.time() - start)
       
           
    timer.timeout.connect(setTimer)

    main.show()

    sys.exit(app.exec_())
« Ultima modifica: Febbraio 03, 2021, 10:24 da Anakin73 »