Search code examples
kivymdmodbusmodbus-tcpscadapymodbustcp

Problem with kivyMD TCPModbus app not reading from the server


I created an app, based on a teacher's videoclass program. It turned out I had some issues, returning me the following exceptions and Id badly apreciate your attention:

Error during data status reading.
Error during data setpoint reading.
Error during data tempOven reading.
Error during data tempOven reading.
Error during data tempOven reading.

I know it's a theoric script, but I'd appreciate If u guys could run in your computers, because I hand-copied the teacher's scripts but It simply doesnt work althought it has no errors. As long as I post here the codes, Ill keep writting here its overviews and the files will be 3 (the kivyMD .kv, the main.py and the datacards). The app appearence is like that:CONFIGURATION SCREEN The issues trigger exactly on the page below (data acquisition) because theonnection step is succesful. DATA SCREEN

Starting with the main, this code instantiates the MDApp and sequentially the Screen. It has a button, as seen on the picture1, for connecting. The connecting method himself instantiates the clock scheduler for eventual updates on the holding card and on the coil card.

from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from datacard import CardCoil, CardHoldingRegister, CardInputRegister
from pyModbusTCP.client import ModbusClient
from kivymd.uix.snackbar import Snackbar
from kivy.clock import Clock

class MyWidget(MDScreen):
    """
    Contructor
    """
    def __init__(self,tags,**kwargs):
        self._clientMOD= ModbusClient()
        super().__init__(**kwargs)
        self._tags=tags 
        self._ev=[]   #pt4 min 58
        for tag in self._tags:
            if tag["type"]=="input":
                self.ids.modbus_data.add_widget(CardInputRegister(tag,self._clientMOD))
            elif tag["type"]== "holding":
                self.ids.modbus_data.add_widget(CardHoldingRegister(tag,self._clientMOD))
            elif tag["type"]=="coil":
                self.ids.modbus_data.add_widget(CardCoil(tag,self._clientMOD))
    
    def connect(self):
        if self.ids.bt_con.text== "CONNECT":
            try:
                self.ids.bt_con.text= "DISCONNECT"
                self._clientMOD.host= self.ids.hostname.text
                self._clientMOD.port = int(self.ids.port.text)
                self._clientMOD.open()
                Snackbar(text="Connected succesfully",bg_color=(0,1,0,1)).open()
                self._ev=[]
                for card in self.ids.modbus_data.children:
                    if card.tag['type'] == "holding" or card.tag['type']== "coil":
                        self._ev.append(Clock.schedule_once(card.update_card))
                    else:
                        self._ev.append(Clock.schedule_interval(card.update_card,1))
            except Exception as e:
                print("Erro de conexao com servidor: ", e.args)  
        else:
            self.ids.bt_con.text="CONNECT"
            for event in self._ev:
                event.cancel()
                self._clientMOD.close()
            Snackbar(text="Disconnected",bg_color=(1,0,0,1)).open()


class BasicApp(MDApp):

    
    __tags=  [{'name':'tempOven','description':'Oven Tempture','unit':'ºC','address':1000,'type':"input"},
              {'name':'setpoint','description':'Desired Tempture','unit':'ºC','address':2000,'type': "holding"},
              {'name':'status','description':'Actuator state','address':1000,'type':"coil"},

    ]
    def build(self):
        self.theme_cls.primary_palette= "Orange"
        self.theme_cls.primary_hue= "700"
        self.theme_cls.accent_palette="Orange"
         
        return MyWidget(self.__tags)

if __name__=='__main__':
    BasicApp().run()

The second file and next step is the datacard module. Its goal is to define 2 classes the MDcard one, and the 3 different MDcard children. Actually the 3 of them are repreenting the methodology differences between Mbus functions Holding, Input or Coil. Their goil is to interface with the server:

from kivymd.uix.card import MDCard,MDCard
from pyModbusTCP.client import ModbusClient

class DataCard(MDCard):             
    title= "Data Card"
    def __init__(self,tag,mbusClient,**kwargs):
        self.tag= tag                                          
        self.title=self.tag['description']          
        self._mbusclient = mbusClient
        super().__init__(**kwargs)  

    def update_card(self,dt):
        try:
            if self._mbusclient.is_open():
                self.set_data(self._read_data(self.tag['address'],1)[0])
        except Exception as e:
            print("Error during data",self.tag['name'] ,"reading.")

    def write_card(self):
        try:
            if self._mbusclient.is_open():
                self._write_data_fcn(self.tag['address'],self._get_data())
        except Exception as e:
            print("Error during data" ,self.tag['name'] ,"writting.")
    
class CardHoldingRegister(DataCard):
    def __init__(self,tag,mbusClient,**kwargs):
        super().__init__(tag,mbusClient,**kwargs)
        self._read_data=self._mbusclient.read_holding_registers
        self._write_data_fcn= self._mbusclient.write_single_register
    
    def set_data(self, data):
        self.ids.textfield.text= str(data)

    def get_data(self):
        return int(self.ids.textfield.text)
        

class CardInputRegister(DataCard):
    def __init__(self,tag,mbusClient,**kwargs):
        super().__init__(tag,mbusClient,**kwargs)
        self._read_data= self._mbusclient.read_input_registers
    
    def set_data(self, data):
        self.ids.label.text= str(data)
    

class CardCoil(DataCard):
    def __init__(self,tag,mbusClient,**kwargs):
        super().__init__(tag,mbusClient,**kwargs)
        self._read_data= self._mbusclient.read_coils
        self._write_data_fcn= self._mbusclient.write_single_coil

    
    def set_data(self, data):
        self.ids.switch.active= data    #True / False
    
    def get_data(self):
        return self.ids.switch.active                      #RETURN = ajuste

At last, we have the kivy .kv module, it diallogues with the datacrd module (e.g. when u want to show on the MDcard boxes the data exchanges between server and client) and with the main one (e.g connection and MD)

#:kivy 1.11.1
<MyWidget>:
    MDBoxLayout:
        orientation:'vertical'
        MDTopAppBar:
            title: "Informatica industrial"
        MDBottomNavigation:
            panel_color: app.theme_cls.accent_color
            text_color_normal:0.4,0.4,0.4,1
            text_color_active:0.8,0.8,0.8,1
            MDBottomNavigationItem:
                name:"config"
                text:"CONFIGURATION"
                icon: "cog"
                MDBoxLayout:
                    orientation: 'vertical'
                    padding: "20p"
                    spacing: "50dp"
                    Image:
                        source:"imgs/modbus.png"
                        pos_hint: {"center_x":0.5,"center_y":0.5}   
                        size_hint: {1,0.2}    
                    MDTextField:
                        id:hostname
                        text:"127.0.0.1"
                        hint_text: "IP Address"
                        size_hint: {0.3, None}
                        height: "60dp"        #tentatativa e erro
                        pos_hint: {"center_x":0.5,"center_y":0.5}
                    MDTextField:
                        id:port
                        text:"502"
                        hint_text: "Port"
                        size_hint: {0.3, None}
                        height: "60dp"       
                        pos_hint: {"center_x":0.5,"center_y":0.4}                        
                    MDRoundFlatIconButton:
                        id: bt_con
                        text:"CONNECT"
                        icon:'connection'
                        pos_hint:{"center_x":0.5,"center_y":0.3} 
                        on_release: root.connect()
                   
            MDBottomNavigationItem:
                name:"data"
                text:"DADOS" 
                icon:"chart-donut"
                ScrollView:
                    size_hint: (1,None)
                    size: 800,600     #define o tamanho como o da janela 800x600
                    bar_pos_y: 'left'
                    bard_width: 20
                    effect_cls: 'ScrollEffect'
                    MDStackLayout:
                        id: modbus_data
                        size_hint: (1, None)
                        padding: 0.05*600,"150dp"
                        spacing: (800/5 - 2*0.05*800)/3
                        adaptive_height: True

<DataCard>:
    orientation: 'vertical'
    padding: '10dp'
    size_hint:None,None
    size: 600/5 , "90dp"
    pos_hint:{"center_x": 0.5, "center_y":0.5}
    MDLabel:
        text: root.title 
        size_hint_y: None
        height:self.texture_size[1]
        pos_hint: {'top':1} 
    MDSeparator:
        height: "1dp"

<CardHoldingRegister>:
    MDTextField:
        id:textfield
        helper_text: "Pressione Enter para enviar os dados"
        helper_text_mode:'persistent'
        multiline: False
        on_text_validate: root.write_card

<CardInputRegister>:
    MDLabel:
        id:label

<CardCoil>:
    MDSwitch:
        id: switch
    

Im sorry if the question went too long, but that's it. If u prefer, I leave here the Youtube playlist which I'm basing on (https://www.youtube.com/watch?v=DqO-KJXv6UE&list=PLDBnf2G73PkBqYVoxUoGQe7htYYE4fIsX&index=20). The teacher also shared the server module and It can be downloaded on the Youtube link above (on video descriptions). I appreciate any kind of tips and I thank u all beforehand.


Solution

  • As per the docs ModbusClient.is_open is a boolean (not a function). This means that attempting to use it as a function if self._mbusclient.is_open() will trigger the exception you are seeing.

    To fix replace if self._mbusclient.is_open() with if self._mbusclient.is_open (i.e. remove the (). I'm not guaranteeing that is the only issue :-)...