So I wanted to read data from my Arduino through serial port, and update the data I read to a label text to display. It is working when I only have simple code to just read and update, but when I add in the ScreenManager and Screen, it stops updating the text.
Eventually I will need to have different animation according to the data I received, this is more on a testing if this function works
Thanks in Advance!
Here's my entire code
import os
os.environ['KIVY_GL_BACKEND'] ='gl'
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.properties import (NumericProperty, StringProperty, ReferenceListProperty, ObjectProperty, ListProperty)
from kivy.clock import Clock
from kivy.vector import Vector
from kivy.core.text import LabelBase
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
LabelBase.register(name='Sans',fn_regular="Sansation-Regular.ttf")
import serial
kivy = Builder.load_string("""
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
<MyManager>:
transition: FadeTransition()
MainScreen:
OperationScreen:
<MainScreen>:
name: 'main'
Label:
text: 'Welcome'
font_size: 40
on_touch_up : app.root.current = 'operation'
Label:
text: 'dafault'
font_size: 20
pos: -200,-100
id: data_label
<OperationScreen>:
name: 'operation'
Label:
text: 'Youre in'
font_size: 40
""")
class OperationScreen(Screen):
pass
class MainScreen(Screen):
def __init__(self,**kwargs):
super(MainScreen,self).__init__(**kwargs)
def Read(self,dt):
Clock.unschedule(self.Read)
data = arduino.readline()
if data != '':
self.ids.data_label.text = data
Clock.schedule_once(self.Read)
pass
class MyManager(ScreenManager):
pass
class mainApp(App):
Main = MainScreen()
def build(self):
Clock.schedule_once(self.Main.Read)
return MyManager()
if __name__ == '__main__':
try:
arduino = serial.Serial('/dev/ttyACM0', 9600, timeout=1)
except:
print("failed to connect")
mainApp().run()
I expect the label with text 'default' to be changing accordingly but it just froze with the 'default'
Problem
When your app runs, there are two instances of class MainScreen. One was instantiated in kv file. The other one was instantiated manually, Main = MainScreen() in class mainApp.
The scheduling of method Read() is in the instance created manually, Main = MainScreen() and there is no modal view associated with this one.
Solution
In kv file, add id: main_screen for MainScreen:
Remove Main = MainScreen() in class mainApp
Implement a constructor for class MyManager()
Move the scheduling from class mainApp into the constructor of class MyManager()
In your case, it is better to use Clock.create_trigger() instead of Clock.schedule_once()
The correct way to cancel a Clock event is either event.cancel() or Clock.unschedule(event)
Example
main.py
import os
os.environ['KIVY_GL_BACKEND'] = 'gl'
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.properties import (NumericProperty, StringProperty, ReferenceListProperty, ObjectProperty, ListProperty)
from kivy.clock import Clock
from kivy.vector import Vector
from kivy.core.text import LabelBase
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
# LabelBase.register(name='Sans', fn_regular="Sansation-Regular.ttf")
import serial
kivy = Builder.load_string("""
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
<MyManager>:
transition: FadeTransition()
MainScreen:
id: main_screen
OperationScreen:
<MainScreen>:
name: 'main'
Label:
text: 'Welcome'
font_size: 40
on_touch_up : app.root.current = 'operation'
Label:
text: 'dafault'
font_size: 20
pos: -200,-100
id: data_label
<OperationScreen>:
name: 'operation'
Label:
text: 'Youre in'
font_size: 40
""")
class OperationScreen(Screen):
pass
class MainScreen(Screen):
def Read(self, dt):
data = str(dt)
# data = arduino.readline()
if data != '':
self.ids.data_label.text = data
self.manager.event_trigger()
else:
self.manager.event_trigger.cancel()
class MyManager(ScreenManager):
event_trigger = ObjectProperty(None)
def __init__(self, **kwargs):
super(MyManager, self).__init__(**kwargs)
self.event_trigger = Clock.create_trigger(self.ids.main_screen.Read)
self.event_trigger()
class mainApp(App):
def build(self):
return MyManager()
if __name__ == '__main__':
# try:
# arduino = serial.Serial('/dev/ttyACM0', 9600, timeout=1)
# except:
# print("failed to connect")
mainApp().run()
Output
Related
I've just started my first kivy app. The app is intended to start with button "Start Scan" and then show up the QR scanner built with ZBarCam.
I'm using Screens with the ScreenManager to change from the button view to the camera view (with zbarcam), the problem is that I realized that the camera is initialized from the beginning , so before clicking on the button the camera is already on (I know it because the led from the camera is on).
I don't know if Screen should not be used in this case, or if is there a way to tell the app to do not initialize all screens.
The code I'm using is the following:
QrApp.py:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
class QrReader(Screen):
pass
class ScanButton(Screen):
pass
class QrApp(App):
pass
if __name__ == '__main__':
QrApp().run()
qrapp.kv:
ScreenManager:
id: screen_manager
ScanButton:
id: scan_btn
name: 'scan_btn'
manager: 'screen_manager'
QrReader:
id: qr_reader
name: 'qr_reader'
manager: 'screen_manager'
<ScanButton>:
BoxLayout:
orientation: 'vertical'
Button:
text:'Start Scan'
font_size:"50sp"
color: [0, 255, 255, .67]
on_press: app.root.current = 'qr_reader'
<QrReader>:
#:import ZBarCam kivy_garden.zbarcam.ZBarCam
BoxLayout:
orientation: 'vertical'
ZBarCam:
id:qrcodecam
Label:
size_hint: None, None
size: self.texture_size[0], 50
text: ' '.join([str(symbol.data) for symbol in qrcodecam.symbols])
Thanks!
==== ALTERNATIVE BASED IN A COMMENT (still fails) ====
Based on the comment from n4321d I tried to add the ZBarCam as widget in the QrReader Screen. While I can now initiate the camera when the widget is added, I don't see how I can get the symbols that is, the text read from the QR.
This alternative code is the following:
QrApp.py:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen
class QrReader(Screen):
def on_enter(self):
from kivy_garden.zbarcam import ZBarCam
zbarcam = ZBarCam()
self.add_widget(zbarcam)
self.add_widget(Label(
text='PRINT SYMBOLS', #' '.join([str(symbol.data) for symbol in zbarcam.symbols] does not work
size_hint=(None,None),
size=(Window.width*0.1, Window.height*0.1),
center=(Window.width*0.3, Window.height*0.5)))
class ScanButton(Screen):
pass
class QrApp(App):
pass
if __name__ == '__main__':
QrApp().run()
qrapp.kv
ScreenManager:
id: screen_manager
ScanButton:
id: scan_btn
name: 'scan_btn'
manager: 'screen_manager'
QrReader:
id: qr_reader
name: 'qr_reader'
manager: 'screen_manager'
<ScanButton>:
BoxLayout:
orientation: 'vertical'
Button:
text:'Start Scan'
font_size:"50sp"
color: [0, 255, 255, .67]
on_press:
app.root.current = 'qr_reader'
<QrReader>:
BoxLayout:
orientation: 'vertical'
====== SOLUTION ========
My workaround solution is posted as an answer to this question here
First:
I would also add an on_leave function where you remove your cam widget otherwise you will keep adding new widgets every time you load it.
I dont have a working cam now , so i cannot test your code. Looking at your code I think you have to bind the text in your Label to the text in zbarcam.symbols with a function: self.label = Label(....); zbarcam.bind(symbols=lambda *x: setattr(self.label, "text", str(x[1]))) or somethign like that.
Here is an example using a random text generator instead of ZBarCam (since i cannot run that).
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ListProperty
import random
from kivy.clock import Clock
kv_str = """
ScreenManager:
id: screen_manager
ScanButton:
id: scan_btn
name: 'scan_btn'
manager: 'screen_manager'
QrReader:
id: qr_reader
name: 'qr_reader'
manager: 'screen_manager'
<ScanButton>:
BoxLayout:
orientation: 'vertical'
Button:
text:'Start Scan'
font_size:"50sp"
color: [0, 255, 255, .67]
on_press:
app.root.current = 'qr_reader'
<QrReader>:
BoxLayout:
orientation: 'vertical'
"""
class ZBarCam(Label):
symbols = ListProperty([])
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_interval(self.gen_rand_text, 1)
def gen_rand_text(self, *args):
self.text = random.choice(['aaaaa', 'bbbbb', 'ccccc'])
self.symbols.append(self.text)
if len(self.symbols) > 3:
del self.symbols[0]
class QrReader(Screen):
def on_enter(self):
self.zbarcam = ZBarCam()
self.add_widget(self.zbarcam)
self.label = Label(
text='PRINT SYMBOLS', #' '.join([str(symbol.data) for symbol in zbarcam.symbols] does not work
size_hint=(None,None),
size=(Window.width*0.1, Window.height*0.1),
center=(Window.width*0.3, Window.height*0.5))
self.add_widget(self.label)
self.zbarcam.bind(symbols = lambda *x: setattr(self.label, "text", str(x[1])))
def on_leave(self, *args):
self.remove_widget(self.zbarcam)
self.remove_widget(self.label)
class ScanButton(Screen):
pass
class QrApp(App):
def build(self):
return Builder.load_string(kv_str)
if __name__ == '__main__':
QrApp().run()
if it still does not work you might have to call self.zbarcam.start() too in the on_enter method after adding it
hope this helps
Thanks to answer from n4321d I finally found a solution.
I also struggled releasing the camera, as zbarcam.stop() stops zbarcam but camera was apparently still open (led was on). After digging a lot, I found this workaround which seems to work well to release the camera.
I also merged the button and qr reader in the same Class and I no longer use kv file, in this example. The final code is the following:
QrApp.py:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy_garden.zbarcam import ZBarCam
class QrScanner(BoxLayout):
def __init__(self, **kwargs):
super(QrScanner, self).__init__(**kwargs)
btn1 = Button(text='Scan Me', font_size="50sp")
btn1.bind(on_press=self.callback)
self.add_widget(btn1)
def callback(self, instance):
"""On click button, initiate zbarcam and schedule text reader"""
self.remove_widget(instance) # remove button
self.zbarcam = ZBarCam()
self.add_widget(self.zbarcam)
Clock.schedule_interval(self.read_qr_text, 1)
def read_qr_text(self, *args):
"""Check if zbarcam.symbols is filled and stop scanning in such case"""
if(len(self.zbarcam.symbols) > 0): # when something is detected
self.qr_text = self.zbarcam.symbols[0].data # text from QR
Clock.unschedule(self.read_qr_text, 1)
self.zbarcam.stop() # stop zbarcam
self.zbarcam.ids['xcamera']._camera._device.release() # release camera
class QrApp(App):
def build(self):
return QrScanner()
if __name__ == '__main__':
QrApp().run()
There is no way to lazy-load kivy Screens. Try this as a workaround: Do not let ScreenManager know about all Screens. Use switch_to to remove the current screen and load the desired one instead. Check Kivy docs to learn how to use it.
Maybe so?
from kivy_garden.zbarcam import ZBarCam
from kivymd.uix.textfield import MDTextField
class MenuScreen(MDScreen):
zbarcam: ZBarCam = ObjectProperty()
qrfield: MDTextField = ObjectProperty()
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.__cam_release()
def scan(self, instance):
self.__cam_release()
def on_touch_down(self, touch):
if self.zbarcam.collide_point(*touch.pos):
self.__cam_open()
return super().on_touch_down(touch)
def __cam_release(self):
if self.zbarcam.xcamera._camera._device.isOpened():
self.zbarcam.stop()
self.zbarcam.xcamera._camera._device.release()
def __cam_open(self):
if not self.zbarcam.xcamera._camera._device.isOpened():
self.zbarcam.xcamera._camera._device.open(0)
self.zbarcam.start()
Basically i'm trying to take some input from a user, perform some actions, and then output the result in a new screen (i'm thinking as the label of the new screen). I've managed to switch between screens but i cannot figure out how to output the input from the first screen to the second screen. I've tried to make the input data a global variable so i can assign to the text of the label of the output screen, but it didn't work. Here's my python file:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen,ScreenManager
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen,ScreenManager
from kivy.core.window import Window
from kivymd.app import MDApp
from kivy.uix.image import Image
from kivy.animation import Animation
from kivy.clock import Clock
from kivy.properties import ColorProperty
from kivy.uix.popup import Popup
from kivy.uix.floatlayout import FloatLayout
from plyer import filechooser
from kivymd.uix.dialog import MDDialog
Window.clearcolor = (1,1,1,1)
g_data = 'xstring'
class MainWindow(Screen):
def get_data(self):
global g_data
g_data = self.ids.user_input.text
class OutputScreen(Screen):
def ex(self):
self.ids.output_label.text = g_data
class mainApp(MDApp):
def __init__(self):
super().__init__()
def change_screen(self):
screemanager = self.root.ids['screenmanager']
screemanager.current = 'output'
def change(self):
self.change_screen()
if __name__ == '__main__':
mainApp().run()
and my kv file:
#:import utils kivy.utils
GridLayout:
cols:1
ScreenManager:
id: screenmanager
MainWindow:
id: main
name: 'main'
OutputScreen:
id: output
name: 'output'
<MainWindow>:
FloatLayout:
TextInput:
id: user_input
pos_hint:{"x" : 0.05, "top" : 0.9}
size_hint: 0.9, 0.37
Button:
pos_hint:{"top" : 0.51, "x" : 0.05}
size_hint: (None,None)
width : 150
height : 40
font_size : 23
text:'Submit'
on_release: app.change()
<OutputScreen>:
FloatLayout:
Label:
id: output_label
text: root.ex()
color: 0,0,0,1
Thank you very much.
I can't get your example to work, but you would need to do this:
g_data = '' # new global variable defined
class MainWindow(Screen):
def get_data(self):
global g_data # global goes here
g_data = self.ids.user_input.text
class OutputScreen(Screen):
def ex(self):
self.ids.output_label.text = g_data
i have an app, that in the future, will be a cards game counter, but right now its nowhere near that stage, anyways, i have 3 files, main.py, screens.kv,screens2.kv(which doesnt have anything in it) now i also got a picture, and its a button, each time its pressed, for test it writes down "pressed", i've been on this for at least 2 hours and cant figure out how to make the button appear when i press "Start Pasmar" (Pasmar is the name of the game in future), all it brings up is a blank screen after i press the button, but i did replace them and it only showed the button, nothing else.
This my current code:
#-*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition
from kivy.properties import ObjectProperty
from kivy.uix.tabbedpanel import TabbedPanel
from kivy.uix.tabbedpanel import TabbedPanelHeader
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from functools import partial
from kivy.base import runTouchApp
from kivy.uix.image import Image
from kivy.uix.widget import Widget
from kivy.uix.popup import Popup
import sys
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.uix.behaviors import ButtonBehavior
from kivy.lang import Builder
from kivy.base import runTouchApp
import random
import time
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen, NoTransition
from kivy.properties import ObjectProperty, NumericProperty
import time
import threading
class ScreensApp(App):
def build(self):
return RootWidget()
class ScreenOne(Screen):
pass
class ScreenTwo(Screen):
def __init__(self, **kwargs):
super(ScreenTwo, self).__init__(**kwargs)
class RootWidget(FloatLayout):
pass
class ImageButton(ButtonBehavior, Image):
def on_press(self):
print ('pressed')
def displayScreenThenLeave(self):
self.changeScreen()
def changeScreen(self):
if self.manager.current == 'screen1':
self.manager.current = 'screen2'
else:
self.manager.current = 'screen1'
class Manager(ScreenManager):
screen_one = ObjectProperty(None)
screen_two = ObjectProperty(None)
class ScreensApp(App):
def build(self):
m = Manager(transition=NoTransition())
return m
if __name__ == "__main__":
ScreensApp().run()
class The_AssignmentApp(App):
def build(self):
return RootWidget()
if __name__ == "__main__":
The_AssignmentApp().run()
This is the kv file:
#:kivy 1.10.1
<ScreenOne>:
BoxLayout:
orientation: "vertical"
size: root.size
spacing: 15
padding: 200
Label:
font_size: 34
text: "Pasmar"
Button:
background_color: 0, 255, 0, .255
text: "Start Pasmar"
on_release: root.manager.current = "screen2"
<ScreenTwo>:
BoxLayout:
orientation: "vertical"
size: root.size
spacing: 20
padding: 20
<Manager>:
id: screen_manager
screen_one: screen_one
screen_two: screen_two
ScreenOne:
id: screen_one
name: "screen1"
manager: screen_manager
ScreenTwo:
id: screen_two
name: "screen2"
manager: screen_manager
I would like to get the text of my TextInput via a StringProperty, but it does not work. I get an empty string. In the second example, I am declaring the whole TextInput as an ObjectProperty and then it does work. What is wrong with my first example?
How can a StringProperty be used to define the text inside a TextInput?
First example does not print text of TextInput
example1.py
from kivy.app import App
from kivy.base import Builder
from kivy.properties import StringProperty
from kivy.uix.boxlayout import BoxLayout
Builder.load_string("""
<rootwi>:
orientation: 'vertical'
Button:
on_press: root.print_txt()
TextInput:
text: root.textinputtext
""")
class rootwi(BoxLayout):
textinputtext = StringProperty()
def print_txt(self):
print(self.textinputtext)
class MyApp(App):
def build(self):
return rootwi()
if __name__ == '__main__':
MyApp().run()
Second example does print text of TextInput, but uses a ObjectProperty not StringProperty example2.py
from kivy.app import App
from kivy.base import Builder
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
Builder.load_string("""
<rootwi>:
txt: txt
orientation: 'vertical'
Button:
on_press: root.print_txt()
TextInput:
id: txt
""")
class rootwi(BoxLayout):
txt = ObjectProperty()
def print_txt(self):
print(self.txt.text)
class MyApp(App):
def build(self):
return rootwi()
if __name__ == '__main__':
MyApp().run()
If I set the text to sth specific, it shows up in the TextInput. (But still, cannot be printed)
from kivy.app import App
from kivy.base import Builder
from kivy.properties import StringProperty
from kivy.uix.boxlayout import BoxLayout
Builder.load_string("""
<rootwi>:
orientation: 'vertical'
Button:
on_press: root.print_txt()
TextInput:
text: root.textinputtext
""")
class rootwi(BoxLayout):
textinputtext = StringProperty()
def __init__(self, **kwargs):
self.textinputtext = 'palim'
super(rootwi, self).__init__(**kwargs)
def print_txt(self):
print(self.textinputtext)
class MyApp(App):
def build(self):
return rootwi()
if __name__ == '__main__':
MyApp().run()
If you want set and get the text using the StringProperty then you should create a bidirectional bind:
from kivy.app import App
from kivy.base import Builder
from kivy.properties import StringProperty, ObjectProperty
from kivy.uix.boxlayout import BoxLayout
Builder.load_string("""
<rootwi>:
orientation: 'vertical'
textinputtext: txt.text
Button:
on_press: root.print_txt()
TextInput:
id: txt
text: root.textinputtext
""")
class rootwi(BoxLayout):
textinputtext = StringProperty()
def __init__(self, **kwargs):
super(rootwi, self).__init__(**kwargs)
self.textinputtext = 'palim'
def print_txt(self):
print(self.textinputtext)
class MyApp(App):
def build(self):
return rootwi()
if __name__ == '__main__':
MyApp().run()
Output:
I'm semi-OK with Python but brand new to Kivy, I know my problem is referencing the label ID but I can't seem to solve it and searching doesn't seem to give me what I need.
I'm trying to get a label to display the current time, so I know I have the right framework in terms of updating etc but I'm sure its all down to referencing the label ID somehow and its that I'm struggling with?
The following code runs fine, displays the labels etc until I try to update the label_text.text at which point i get an error: AttributeError: 'float' object has no attribute 'lbl_time'. I've tried str(time.strftime("%H:%M:%S")) but that doesn't solve it.
from kivy.app import App
from kivy.base import runTouchApp
from kivy.lang import Builder
from kivy.properties import ListProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivy.uix.screenmanager import ScreenManager, Screen, WipeTransition, FadeTransition
from kivy.uix.anchorlayout import AnchorLayout
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
import time
from datetime import datetime
class MainScreen(Screen):
def update_time(self):
lbl_time = ObjectProperty()
MyTime = time.strftime("%H:%M:%S")
self.lbl_time.text = MyTime
class DetailScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class MyScreenManager(ScreenManager):
pass
root_widget = Builder.load_string('''
MyScreenManager:
MainScreen:
DetailScreen:
SettingsScreen:
<MainScreen>:
name: 'main'
BoxLayout:
orientation: 'vertical'
Label:
id: lbl_time
text: 'Time'
font_size: 60
Label:
text: 'Main2'
font_size: 30
GridLayout:
cols: 2
Label:
text: 'Bottom'
font_size: 30
Label:
text: 'Bottom1'
font_size: 30
<DetailScreen>:
name: 'details'
<SettingsScreen>:
name: 'settings'
''')
class ScreenManagerApp(App):
def build(self):
return root_widget
def on_start(self):
Clock.schedule_interval(MainScreen.update_time, 1)
ScreenManagerApp().run()
This was more of a Python problem rather than a Kivy one. You were calling the update_time of the class MainScreen class, not of the object/instance of the MainScreen. Basically, you would need to save a reference to the object (self.main_screen) in the build method, and then use it in the on_start.
class ScreenManagerApp(App):
def build(self):
self.main_screen = MainScreen()
return self.main_screen
def on_start(self):
Clock.schedule_interval(self.main_screen.update_time, 1)
Also you cannot access id outside of the kv language, i.e. in the python. You have to reference the id by adding a property, e.g. the_time:
<MainScreen>:
name: 'main'
the_time: _id_lbl_time
BoxLayout:
orientation: 'vertical'
Label:
id: _id_lbl_time
A minor problem is that the update_time() receives to parameters. Also, there were quite a few weird things in your code, so I couldn't run the code. I fixed all the above in the code below:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from kivy.properties import ObjectProperty, StringProperty
from kivy.clock import Clock
import time
from datetime import datetime
Builder.load_string('''
<MainScreen>:
name: 'main'
the_time: _id_lbl_time
BoxLayout:
orientation: 'vertical'
Label:
id: _id_lbl_time
text: 'Time'
font_size: 60
''')
class MainScreen(Screen):
def update_time(self, sec):
MyTime = time.strftime("%H:%M:%S")
self.the_time.text = MyTime
class ScreenManagerApp(App):
def build(self):
self.main_screen = MainScreen()
return self.main_screen
def on_start(self):
Clock.schedule_interval(self.main_screen.update_time, 1)
ScreenManagerApp().run()