You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

KivyMD TCP Modbus客户端应用无法从服务器读取数据求助

KivyMD TCP Modbus客户端数据读取失败问题排查与解决

我基于教学视频开发了一款KivyMD TCP Modbus客户端应用,服务器连接步骤成功,但进入数据采集页面时出现数据读取失败异常:

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.

手动复刻教学代码,无语法错误但功能异常,以下是完整代码模块,求排查与解决建议:

main.py

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()

datacard.py

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

.kv布局文件

#: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
    

问题排查与解决建议

  1. Modbus地址偏移修正
    pyModbusTCP默认使用0起始地址,但多数Modbus服务器用1起始地址。你的标签地址设置为1000、2000,需要将地址减1后传入读取函数:
    修改DataCard的update_card方法:
read_result = self._read_data(self.tag['address'] - 1, 1)
  1. 空读取结果处理
    当读取失败时,_read_data会返回None,直接取[0]会报错,需先校验结果:
def update_card(self,dt):
    try:
        if self._mbusclient.is_open():
            read_result = self._read_data(self.tag['address'] - 1, 1)
            if read_result and len(read_result) > 0:
                self.set_data(read_result[0])
            else:
                print(f"读取{self.tag['name']}失败:无返回数据")
    except Exception as e:
        print(f"读取{self.tag['name']}出错:{str(e)}")
  1. 方法名拼写错误修复
    DataCard的write_card方法中调用了不存在的_get_data(),应改为子类定义的get_data():
def write_card(self):
    try:
        if self._mbusclient.is_open():
            self._write_data_fcn(self.tag['address'] - 1, self.get_data())
    except Exception as e:
        print(f"写入{self.tag['name']}出错:{str(e)}")
  1. 调度延迟调整
    schedule_once默认立即执行,可能在连接未完全建立时触发读取,添加微小延迟:
self._ev.append(Clock.schedule_once(card.update_card, 0.1))
  1. 服务器端验证
  • 确认Modbus服务器运行正常,IP/端口正确,无防火墙拦截
  • 用Modbus Poll等工具验证:1000(或999)地址的输入寄存器、2000(或1999)地址的保持寄存器、1000(或999)地址的线圈寄存器是否可正常访问

内容的提问来源于stack exchange,提问作者caio_alBR

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 17:35:16