Problem with kivyMD TCPModbus app not reading from the server - kivymd

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:
The issues trigger exactly on the page below (data acquisition) because theonnection step is succesful.
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.

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 :-)...

Related

Get the status of a checkbox from a Custom List in Kivy/KivyMD

I'm trying a custom List item based on the KivyMD's documentation examples, but I'm having a problem identifying which of my chechkboxes has been activated.
With * args I can access its state and which object it is but it prints like this:
{<__ main __. RightCheckbox object at 0x000001B1A62E6970>, False}.
The problem is that "0x000001B1A62E6970" is not a constant value (this value may change with the execution of the code) that represents the checkbox as its id does.
My minimal code KV:
KV = '''
MDCard:
orientation : 'vertical'
size_hint : (0.8,0.3)
pos_hint : {"center_x":.5,"center_y":.5}
elevation : 15
padding : 20
spacing : 30
id: box
MDList:
id: scroll
<ListItemWithCheckbox>:
IconLeftWidget:
icon: root.icon
RightCheckbox:
'''
And the MainApp and Custom Class definitions:
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivymd.app import MDApp
from kivymd.uix.list import IRightBodyTouch, OneLineAvatarIconListItem
from kivymd.uix.selectioncontrol import MDCheckbox
from kivymd.icon_definitions import md_icons
class ListItemWithCheckbox(OneLineAvatarIconListItem):
'''Custom list item.'''
icon = StringProperty("android")
def on_press(self):
print(self.text)
class RightCheckbox(IRightBodyTouch, MDCheckbox):
'''Custom right container.'''
def on_active(self, *args):
print(args)
class MainApp(MDApp):
def build(self):
return Builder.load_string(KV)
def on_start(self):
icons = list(md_icons.keys())
for i in range(5):
self.root.ids.scroll.add_widget(
ListItemWithCheckbox(text=f"Item {i}", icon=icons[i])
)
MainApp().run()
What I have tried:
I have tried to give a default id for the RightCheckbox to later change it to a unique one when putting the widgets in the list and thus access their states in something like "root.ids.checkboxid" but I don't know how to put it when doing self.root.ids.scroll.add_widget (ListItemWithCheckbox (text = f "Item {i}", icon = icons [i]))
Also in the on_active method (when any checkbox is selected) of the RightCheckbox class I have tried to print various attributes such as MDCheckBox.active .icon .ids. .text but none of them prints anything to help me identify which specific checkbox has been selected
I would really appreciate if anyone can help Thanks
One way to do this is to create a reference to the ListItemWithCheckbox within the RightCheckbox like this:
<ListItemWithCheckbox>:
IconLeftWidget:
icon: root.icon
RightCheckbox:
listItem: root
Then your on_active() method can use that reference:
class RightCheckbox(IRightBodyTouch, MDCheckbox):
'''Custom right container.'''
def on_active(self, rcb, value):
print(rcb.listItem.text, 'is', value)

kivyMD HotReloadViewer error: NoneType' object has no attribute 'fbind'

I am trying to run the kivyMD HotReloadViewer script and when I run this code I get an error message inside the HotReloadViewer widget that says: 'NoneType' object has no attribute 'fbind'. I have researched the error and it is explained that somewhere in the code a variable is not defined or a function is returning None. I am wondering if there is something I am missing here, or advice on debugging where undefined variable or function is located. I am running kivymd-0.104.2.dev0 and have watchdog installed.
from kivy.lang import Builder
from kivymd.app import MDApp
KV = '''
#:import KivyLexer kivy.extras.highlight.KivyLexer
#:import HotReloadViewer kivymd.utils.hot_reload_viewer.HotReloadViewer
BoxLayout:
CodeInput:
lexer: KivyLexer()
style_name: "native"
on_text: app.update_kv_file(self.text)
size_hint_x: .7
HotReloadViewer:
size_hint_x: .3
path: app.path_to_kv_file
errors: True
errors_text_color: 1, 1, 0, 1
errors_background_color: app.theme_cls.bg_dark
'''
class Example(MDApp):
path_to_kv_file = "kv_file.kv"
def build(self):
self.theme_cls.theme_style = "Dark"
return Builder.load_string(KV)
def update_kv_file(self, text):
with open(self.path_to_kv_file, "w") as kv_file:
kv_file.write(text)
Example().run()
class Example(MDApp):
path_to_kv_file = "kv_file.kv" # path to your KV file

Problem in creating forgot-password screen in python using kivy

I'm creating a new application called "MyApp".
I am creating a forgot-password-screen where I will ask the user to enter the last password he/she remembers, then a new password and finally to confirm the password.
Here is the code and an image of the login-screen and forgot-password-screen:
main.py file:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from difflib import get_close_matches
Builder.load_file('design.kv')
class LoginScreen(Screen):
def forgot_pwd(self):
self.manager.current = "forgotpwd_screen"
class RootWidget(ScreenManager):
pass
class ForgotPwdScreen(Screen):
def reset_pwd(self, lastpwd, newpwd, confirmpwd):
npwd = newpwd
cnfpwd = confirmpwd
lstpwd = lastpwd
with open("passwords.txt", 'w+') as file:
oldpassword = file.read()
if len(get_close_matches(lstpwd, oldpassword)) > 0 :
if npwd == cnfpwd:
with open("passwords.txt", 'w+') as file:
for line in file:
file.write(line.replace(oldpassword, npwd))
else:
print("Invalid Pwd!")
else:
print("Invalid Password.")
class MainApp(App):
def build(self):
return RootWidget()
if __name__ == "__main__":
MainApp().run()
design.kv file:
<LoginScreen>:
GridLayout:
cols: 1
GridLayout:
cols: 4
rows: 1
Label:
text: "MyApp"
Button:
text: "LOGIN"
Button:
text: "CLOSE"
Label:
text: "Version 1.0"
GridLayout:
cols: 1
rows: 2
Label:
text: "User Image"
TextInput:
hint_text: "Enter Password"
GridLayout:
cols: 1
Button:
text: "Forgot Password"
on_press: root.forgot_pwd()
<ForgotPwdScreen>:
GridLayout:
cols:1
GridLayout:
cols: 4
rows: 1
Label:
text: "MyApp"
Button:
text: "LOGIN"
Button:
text: "CLOSE"
Label:
text: "Version 1.0"
GridLayout:
rows: 1
Label:
text: "FORGOT PASSWORD"
GridLayout:
cols: 2
rows: 3
Label:
text: "Enter last password :"
TextInput:
id: lastpwd
Label:
text: "New Password : "
TextInput:
id: newpwd
Label:
text: "Confirm New Password :"
TextInput:
id: confirmpwd
GridLayout:
rows: 1
cols: 1
Button:
text: "DONE"
on_press: root.reset_pwd(root.ids.lastpwd.text, root.ids.newpwd.text, root.ids.confirmpwd.text)
<RootWidget>:
LoginScreen:
name: "login_screen"
ForgotPwdScreen:
name: "forgotpwd_screen"
passwords.txt file:
admin
screen_image:
LoginScreen
Please click this link to view screenshot of login screen.
Forgot Password Screen
Please click this link to view screenshot of forgot password screen.
Problem:
I want that when user gives input in all three inputboxes and clicks done, then the program should use the get_close_matches function to compare the lastpwd(i,e Enter last password) with the text in the passwords.txt file which I have created and I have also given a default password as 'admin' in the txt file. So, If it's len value is greater than 0, then it should check whether npwd(new password) and cnfpwd(confirm password) are same, if they are same, then it should replace the original text in the passwords.txt file with the npwd(newpassword) that user has entered.
But when I execute the program and give correct lastpassword and enter new and confirm password and click done, it removes the original password(i.e 'admin') from the passwords.txt file but it doesn't add the new password in the file.
I haven't got any errors. Please execute this code to see more clearly what I'm trying to tell.
Please help.
Thanks.
The problem is that you are opening the passwords.txt file with mode of w+. From the documentation:
'w' open for writing, truncating the file first
So, the entire contents of the file are truncated before you try to read it. I suggest modifying the reset_pwd() method to something like:
def reset_pwd(self, lastpwd, newpwd, confirmpwd):
npwd = newpwd
cnfpwd = confirmpwd
lstpwd = lastpwd
with open("passwords.txt", 'r') as file:
oldpassword = file.read()
if len(get_close_matches(lstpwd, [oldpassword])) > 0 :
if npwd == cnfpwd:
with open("passwords.txt", 'w+') as file:
file.write(npwd)
else:
print("Invalid Pwd!")
else:
print("Invalid Password.")
This uses r for the mode when reading the file.
Note that the second arg to get_close_matches() must be a list. If you pass a string (like just using oldpassword), each letter in the old password will get compared to lstpwd.
Also, logic of comparing passwords does not need to be within the first with open() code block, since you are reading the entire file at once.
The above code should work OK for just a single password in the file. But, will require modification to handle a file with more than one password.

Kivy .kv file passing argument

OK so what I want to do is have a FloorWidget with a option to add rooms dynamically with a press of a button while the app is running. I want to load specific graphic based on the room number and I don't know how to achive that in .kv file
<RoomWidget>:
id: room_widget
room_num: # would be great if you could pass this as an argument
Image:
source: os.path.join(GRAPHICS_DIR_PATH, "room" + str(room_widget.room_num))
Is there a way to do this? Could I somehow initilize room_num in python file by passing it into the RoomWidget constructor and access it in .kv file?
class RoomWidget(Screen, Widget):
def __init__(self, room_num, **kwargs):
super().__init__(**kwargs)
self.room_num = room_num
<RoomWidget>:
size_hint: (.2, .35)
source: os.path.join(GRAPHICS_DIR_PATH, "room" + str(self.room_num))
Image:
source: source
If I do sth like this I get an error: AttributeError: 'RoomWidget' object has no attribute 'room_num'.
You can do that by making a property of RoomWidget Like this:
class RoomWidget(Screen):
room_num = NumericProperty()
def __init__(self, room_num, **kwargs):
super().__init__(**kwargs)
self.room_num = room_num
Then, in your 'kv':
#:import os os
#:set GRAPHICS_DIR_PATH '.'
<RoomWidget>:
size_hint: (.2, .35)
source: os.path.join(GRAPHICS_DIR_PATH, "room" + str(self.room_num) + '.png')
Image:
source: root.source
Note that you do not need to extend both Screen and Widget, because Screen is a Widget.

How to dynamically update label texts in kivy that is imported from excel file?

I am creating a questionnaire form in kivy. I have added few label widgets in my GUI. I don't want to define label texts statically in my code, instead my objective is to dynamically update label texts that is fetched from an excel file.
For example: my excel file has 2 questions:
Name of the company?
Department?
I have 2 label widgets in my GUI, and the text of widgets should be:
Name of the company?
Department?
respectively and has to be dynamically fetched from the excel file.
I encountered an error when i tried to run my code.
Questionnaire.py
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
import pandas as pd
class FetchData():
file = pd.read_excel("Questionnaire.xlsx")
Quest = file['QUESTIONS']
class Questions(Widget):
Data = FetchData().Quest
qvars =[]
company = ObjectProperty(None)
department = ObjectProperty(None)
qvars.append(company)
qvars.append(department)
def validate(self):
for i in range(len(self.qvars)):
self.qvars[i].text = self.Data[i]
class QuestionnaireApp(App):
def build(self):
return Questions()
if __name__=="__main__":
QuestionnaireApp().run()
Questionnaire.kv
<Questions>:
company:company
department:department
GridLayout:
cols:1
size:root.width, root.height
GridLayout:
cols:1
Label:
id:company
TextInput:
Label:
id:department
TextInput:
Button:
text:"process"
on_release: root.validate()
I am getting the following error:
File "C:/Users/pavan m sunder/virtual environments/android/Questionnaire.py", line 23, in validate
self.qvars[i].text = self.Data[i]
AttributeError: 'kivy.properties.ObjectProperty' object has no attribute 'text'
I referred to similar questions that had the same error but none matches specifically to my problem.
Using your qvars list messes things up because it's a list of the property objects, which doesn't have the right behaviour - Kivy Properties are descriptors, they only work at class level.
Instead, just access self.company or self.department in your methods.

Resources