Hi All
I have made an app that monitors a product over serial communication it mostly works fine but I get an error when closing but only once I have connected / disconnected to the serial. I am very new to kivy so I probably have a number of mistakes.
Any help would be greatly appreciated
Thanks
GibbsyFPV
Code below
.py
from kivy.lang import Builder
from kivy.metrics import dp
from kivymd.app import MDApp
from kivymd.uix.menu import MDDropdownMenu
from kivy.core.window import Window
from kivy.clock import Clock, mainthread
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.dialog import MDDialog
from kivymd.uix.button import MDFlatButton, MDRectangleFlatButton
import serial.tools.list_ports
import serial
import time
from threading import Thread
import threading
import re
class MenuHeader(MDBoxLayout):
'''An instance of the class that will be added to the menu header.'''
class KFSMonitor(MDApp):
def __init__(self, serialPort = None, serialBaud = 2400, **kwargs):
super(KFSMonitor, self).__init__(**kwargs)
self.dialog = 0
self.port = serialPort
self.baud = serialBaud
self.runcheck = 0
self.closethread = None
self.data = 0
self.thread = None
self.threadKill = None
self.start = 0
self.port_list = []
ports = serial.tools.list_ports.comports()
#scan for ports
for port, desc, hwid in sorted(ports):
self.port_list.append(port)
self.screen = Builder.load_file('kfsmonitor.kv')
items_d = self.port_list
menu_items = [
{
"text": f"{i}",
"viewclass": "OneLineListItem",
"height": dp(56),
"on_release": lambda x=f"{i}": self.menu_callback(x),
} for i in items_d
]
self.menu = MDDropdownMenu(
header_cls=MenuHeader(),
caller=self.screen.ids.toolBarTop,
items=menu_items,
width_mult=2,
position="bottom",
)
def showAlertBox(self):
if not self.dialog:
self.dialog = MDDialog(
title = "Port Error",
text = "Please try another Port",
buttons = [
MDRectangleFlatButton(
text = "OK", text_color=self.theme_cls.primary_color, on_release = self.close_dialog
)
],
)
self.dialog.open()
def close_dialog(self, obj):
self.dialog.dismiss()
def menu_callback(self, text_item):
self.serialPort = text_item
self.menu.dismiss()
try:
self.serialConnection = serial.Serial(self.serialPort, self.baud, timeout=1)
self.root.ids.toolBarBottom.title = self.serialPort
self.root.ids.mainToolBarBottom.disabled = False
except serial.serialutil.SerialException:
#print("no port")
self.root.ids.mainToolBarBottom.disabled = True
self.root.ids.toolBarBottom.title = ""
try:
self.serialConnection.close()
except:
pass
self.showAlertBox()
#print(self.serialPort)
def runchecker(self):
if self.runcheck == 0:
self.runcheck = 1
#print(self.runcheck)
self.root.ids.toolBarBottom.icon = "pause"
self.readSerialStart(1)
else:
self.runcheck = 0
#print(self.runcheck)
self.root.ids.toolBarBottom.icon = "play"
self.StopSerial()
def StopSerial(self):
if self.stop == 0:
Clock.unschedule(self.readSerialStart)
self.serialConnection.reset_input_buffer()
self.threadKill = 1
self.thread.join()
self.thread = None
while(self.stop == 0):
if self.thread == None:
self.serialConnection.write(b'999999')
serial_data = self.serialConnection.readline()
serial_data = serial_data.decode()
#print(serial_data)
if "DONE. Resetting....." in serial_data:
#print("here")
self.start = 0
self.stop = 1
self.StopSerial()
def readSerialStart(self, dt):
self.threadKill = None
if self.thread == None:
#self.ids.clear_button.disabled = True
self.serialConnection.reset_input_buffer()
if self.start == 0:
#Put SEISMO In test mode
while(self.start == 0):
self.serialConnection.write(b'\r\r\r\r\r\r\r\r\r\r\r\r\r\r\r')
serial_data = self.serialConnection.readline()
serial_data = serial_data.decode()
#print(serial_data)
if "Factory Mode Entered" in serial_data:
#print("here")
self.start = 1
self.stop = 0
self.thread = Thread(target=self.backgroundThread, args = (1, ))
self.thread.daemon=True
self.thread.start()
# Set the timer for redrawing the screen
refresh_time = 0.3
Clock.schedule_interval(self.readSerialStart, refresh_time)
def backgroundThread(self, dt): # retrieve data
while(self.threadKill == None):
time.sleep(.001) # delay of 1ms
serial_data = self.serialConnection.readline() # read complete line from serial output
while not '\\n'in str(serial_data): # check if full data is received.
# This loop is entered only if serial read value doesn't contain \n
# which indicates end of a sentence.
# str(serial_data) - serial_data is byte where string operation to check `\\n`
# can't be performed
time.sleep(.001) # delay of 1ms
temp = self.serialConnection.readline() # check for serial output.
if not not temp.decode(): # if temp is not empty.
serial_data = (serial_data.decode()+temp.decode()).encode()
# requrired to decode, sum, then encode because
# long values might require multiple passes
serial_data = serial_data.decode() # decoding from bytes
serial_data = serial_data.strip() # stripping leading and trailing spaces.
if self.threadKill == None:
#Debug
#print(serial_data)
if "SEISMO" in serial_data:
# Update a widget property in the main thread by decorating the
# called function with @mainthread.
self.update_model_label_text(serial_data)
#print(serial_data)
elif "Pulse Count = " in serial_data:
# Update a widget property in the main thread by decorating the
# called function with @mainthread.
self.update_pulse_count_label_text(re.sub('\D', '', serial_data))
elif "Detection Mode =" in serial_data:
if "Detection Mode = SHOCK ONLY" in serial_data:
serial_data = "SHOCK ONLY"
# Update a widget property in the main thread by decorating the
# called function with @mainthread.
self.update_detection_mode_label_text(serial_data)
elif "Detection Mode = MOTION ONLY" in serial_data:
serial_data = "MOTION ONLY"
# Update a widget property in the main thread by decorating the
# called function with @mainthread.
self.update_detection_mode_label_text(serial_data)
elif "Detection Mode = SHOCK AND MOTION" in serial_data:
serial_data = "SHOCK AND MOTION"
# Update a widget property in the main thread by decorating the
# called function with @mainthread.
self.update_detection_mode_label_text(serial_data)
elif "Detection Mode = SHOCK OR MOTION" in serial_data:
serial_data = "SHOCK OR MOTION"
# Update a widget property in the main thread by decorating the
# called function with @mainthread.
self.update_detection_mode_label_text(serial_data)
elif "Sensitivity Pot =" in serial_data:
# Update a widget property in the main thread by decorating the
# called function with @mainthread.
self.update_sensitivity_pot_label_text(re.sub('\D', '', serial_data))
elif "LEDs Enabled =" in serial_data:
if "LEDs Enabled = Enabled" in serial_data:
serial_data = "Enabled"
# Update a widget property in the main thread by decorating the
# called function with @mainthread.
self.update_leds_enabled_label_text(serial_data)
elif "LEDs Enabled = Disabled" in serial_data:
serial_data = "Disabled"
# Update a widget property in the main thread by decorating the
# called function with @mainthread.
self.update_leds_enabled_label_text(serial_data)
elif "Latch =" in serial_data:
if "Latch = 0" in serial_data:
serial_data = "Enabled"
# Update a widget property in the main thread by decorating the
# called function with @mainthread.
self.update_latch_alarm_label_text("Disabled")
elif "Latch = 1" in serial_data:
serial_data = "Disabled"
# Update a widget property in the main thread by decorating the
# called function with @mainthread.
self.update_latch_alarm_label_text("First to Alarm")
elif "Latch = 2" in serial_data:
serial_data = "Disabled"
# Update a widget property in the main thread by decorating the
# called function with @mainthread.
self.update_latch_alarm_label_text("Latched")
def clear_results(self):
self.root.ids.model.secondary_text = "-"
self.root.ids.pulse_count.secondary_text = "-"
self.root.ids.detection_mode.secondary_text = "-"
self.root.ids.sensitivity_pot.secondary_text = "-"
self.root.ids.leds_enabled.secondary_text = "-"
self.root.ids.latch_alarm.secondary_text = "-"
def build(self):
self.title = "KFS SEISMO Monitor"
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "DeepPurple"
Window.bind(on_request_close=self.on_request_close)
return self.screen
def on_request_close(self, *args):
self.get_running_app().stop()
# removing window
Window.close()
@mainthread
def update_model_label_text(self, new_text):
#self.ids.model.text = new_text
self.root.ids.model.secondary_text = new_text
@mainthread
def update_pulse_count_label_text(self, new_text):
#self.ids.pulse_count.text = new_text
self.root.ids.pulse_count.secondary_text = new_text
@mainthread
def update_detection_mode_label_text(self, new_text):
#self.ids.detection_mode.text = new_text
self.root.ids.detection_mode.secondary_text = new_text
@mainthread
def update_sensitivity_pot_label_text(self, new_text):
#self.ids.sensitivity_pot.text = new_text
self.root.ids.sensitivity_pot.secondary_text = new_text
@mainthread
def update_leds_enabled_label_text(self, new_text):
#self.ids.leds_enabled.text = new_text
self.root.ids.leds_enabled.secondary_text = new_text
@mainthread
def update_latch_alarm_label_text(self, new_text):
#self.ids.latch_alarm.text = new_text
self.root.ids.latch_alarm.secondary_text = new_text
if __name__ == "__main__":
KFSMonitor().run()
.kv
MDBoxLayout:
orientation:'vertical'
MDToolbar:
id:toolBarTop
title:'KFS SEISMO Monitor'
type:'top'
pos_hint:{'top':1}
right_action_items : [["usb-port", lambda x: app.menu.open()],["eraser", lambda x: app.clear_results()]]
#MDLabel:
# text:"Some stuff"
# halign:"center"
ScrollView:
MDList:
TwoLineAvatarListItem:
id: model
text: "SEISMO Type"
secondary_text: "-"
IconLeftWidget:
icon: "alpha-m-box"
TwoLineAvatarListItem:
id: pulse_count
text: "Pulse Count"
secondary_text: "-"
IconLeftWidget:
icon: "pulse"
TwoLineAvatarListItem:
id: detection_mode
text: "Detection Mode"
secondary_text: "-"
IconLeftWidget:
icon: "alarm-light"
TwoLineAvatarListItem:
id: sensitivity_pot
text: "Sensitivity Pot"
secondary_text: "-"
IconLeftWidget:
icon: "contrast"
TwoLineAvatarListItem:
id: leds_enabled
text: "LEDs Enabled"
secondary_text: "-"
IconLeftWidget:
icon: "led-variant-on"
TwoLineAvatarListItem:
id: latch_alarm
text: "Latch Alarm"
secondary_text: "-"
IconLeftWidget:
icon: "download-lock"
MDBottomAppBar:
disabled: True
id:mainToolBarBottom
MDToolbar:
id:toolBarBottom
icon: 'play'
type:'bottom'
mode:'end'
#mode:'center'
title:''
left_action_items: [["lan-connect"]]
on_action_button: app.runchecker()
error =
Traceback (most recent call last):
File "main.py", line 315, in <module>
KFSMonitor().run()
File "C:\KivyGui\kivy_venv\lib\site-packages\kivy\app.py", line 955, in run
runTouchApp()
File "C:\KivyGui\kivy_venv\lib\site-packages\kivy\base.py", line 574, in runTouchApp
EventLoop.mainloop()
File "C:\KivyGui\kivy_venv\lib\site-packages\kivy\base.py", line 341, in mainloop
self.window.mainloop()
File "C:\KivyGui\kivy_venv\lib\site-packages\kivy\core\window\window_sdl2.py", line 569, in mainloop
if self.dispatch('on_request_close'):
File "kivy\_event.pyx", line 727, in kivy._event.EventDispatcher.dispatch
File "kivy\_event.pyx", line 1307, in kivy._event.EventObservers.dispatch
File "kivy\_event.pyx", line 1231, in kivy._event.EventObservers._dispatch
File "main.py", line 278, in on_request_close
self.get_running_app().stop()
TypeError: 'int' object is not callable
Glad to hear you solved it!
Here is a tip:
Under the class App or MDApp, self is the running app so:
self.get_running_app().stop()
is the same as:
self.stop()
def on_request_close(self, *args):
self.get_running_app().stop()
# removing window
Window.close()
Additionally, if you are not doing an processing in on_request_close, you do not need to include it. Kivy will close the window and call stop for you with the window close button is pressed. I often use on_request_close to save window coordinates to a config file or remind a user to save prior to closing. Here is an example that saves the window size and position: https://github.com/ElliotGarbus/KivyWindowSize
--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/bcd85c5f-85b1-4981-a596-1657499a4905n%40googlegroups.com.