I state that this is the first time I use Kivy.
The code I attached works, the only problem is that the lbl label does not update automatically but only if I press the update button.
In practice, if I call the update_lbl function via the "update" button it works, when it is called automatically by unpacking_msg it does nothing.
from email import message
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty, StringProperty
from kivy.lang import Builder
from numpy import empty
import paho.mqtt.client as mqttClient
from queue import Queue
import threading
q=Queue()
incoming_message =''
incoming_topic =''
class Manager(ScreenManager):
pass
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
class HomeScreen(Screen):
pass
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
class MsgDecoder ():
def __init__(self,msg):
self.msg = msg
def unpacking_msg(self):
global incoming_message
global incoming_topic
incoming_topic = str(self.msg.topic)
incoming_message = str(self.msg.payload.decode("utf-8"))
MainApp().update_lbl()
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
class MqttApp():
def __init__(self,broker_address,port,user,password):
self.password = password
self.user = user
self.port = port
self.broker_address = broker_address
broker_address = "broker.hivemq.com"
port = 1883
user = ""
password = ""
try:
client = mqttClient.Client(clean_session=True, userdata=True)
client.username_pw_set(user , password)
client.connect(broker_address,port)
client.loop_start()
except:
pass
def on_connect(client, userdata, flags, rc):
client.subscribe("kivy")
def on_message(client, userdata, msg):
q.put(msg)
client.on_connect = on_connect
client.on_message = on_message
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
class MainApp(App):
lbl_txt = StringProperty()
def __init__(self):
super(MainApp, self).__init__()
self.lbl_txt = ("No message")
def switch_callback(self, switchObject, switchValue):
if(switchValue):
MqttApp.client.publish("kivy", "ON")
else:
MqttApp.client.publish("kivy", "OFF")
def update_lbl(self, *args):
self.lbl_txt=incoming_message
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
def get_msg ():
threading.Timer(1.0, get_msg).start()
while not q.empty():
msg = q.get()
if msg is None:
continue
md= MsgDecoder(msg)
md.unpacking_msg()
get_msg()
if __name__ == '__main__':
MainApp().run()
Here is the Kivy file:
ScreenManager:
HomeScreen:
id: 'homescreen'
<HomeScreen>:
swc: swc
BoxLayout:
orientation: 'vertical'
spacing: 50
padding: 100
Label:
text: 'Remote Lamp'
Switch:
id:swc
on_active: app.switch_callback(*args)
Button:
text: "update"
on_press: app.update_lbl()
Label:
id: lbl
text: app.lbl_txt
I really appreciate any other helpful advice! I am not an expert as you can see.
The problem is that your code:
MainApp().update_lbl()
is creating a new instance of MainApp and calling its update)lbl() method. However, that new instance of MainApp is not the instance that you see on the screen. You must call the update_lbl() method of the running App. You can do that by using the get_running_app() method. See the documentation. Try this replacement for the above line:
App.get_running_app().update_lbl()
Related
I'm working on a program with a GUI interface, which I've implemented using a kivy app. My end-goal is to enter a desired voltage in the kivy textbox and for it to be set using an Arduino + a DAC converter. For now I'm just trying to access the voltage from the text input from outside the kivy app in my main, so that I can separate the GUI cleanly from communication with the arduino, and other necessary calculations.
The problem is that the program doesn't continue after App.run() until the kivy app has been closed and the entered voltage has been lost. I've tried using the multiprocessing library, but the start() function also runs as long as the app is running. I'm also not sure what the best way is to access the voltage, I tried having it as a class member of the GUI, but maybe that's not a good idea.
This is my GUI.py code:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.properties import StringProperty, NumericProperty
from kivy.uix.button import Button
class GUI_GridLayout(GridLayout):
voltage_label_1 = StringProperty("Voltage 1?")
voltage_label_2 = StringProperty("Voltage 2?")
voltage_input_1 = TextInput()
voltage_input_2 = TextInput()
submit_button_label_1 = StringProperty("Submit 1")
submit_button_label_2 = StringProperty("Submit 2")
voltage_output_1 = NumericProperty(0)
voltage_output_2 = NumericProperty(0)
def press_1(self):
voltage_1 = self.voltage_input_1.text
self.voltage_output_1 = float(voltage_1)
def press_2(self):
voltage_2 = self.voltage_input_2.text
self.voltage_output_2 = float(voltage_2)
def build(self):
self.add_widget(Label(text=self.voltage_label_1))
self.add_widget(self.voltage_input_1)
self.add_widget(Label(text=self.voltage_label_2))
self.add_widget(self.voltage_input_2)
self.submit_button_1 = Button(text=self.submit_button_label_1)
self.submit_button_1.bind(on_press=self.press_1)
self.add_widget(self.submit_button_1)
self.submit_button_2 = Button(text=self.submit_button_label_2)
self.submit_button_2.bind(on_press=self.press_2)
self.add_widget(self.submit_button_2)
class apason_GUIApp(App):
def build(self):
return GUI_GridLayout()
The corresponding kv file:
#:kivy 1.0.9
<GUI_GridLayout>:
cols: 3
voltage_input_1: input_1
voltage_input_2: input_2
Label:
font_size: 50
text: str(root.voltage_label_1)
TextInput:
id: input_1
font_size: 50
multiline: False
text: root.voltage_input_1.text
Button:
font_size: 50
text: str(root.submit_button_label_1)
on_press: root.press_1()
Label:
font_size: 50
center_x: root.width / 4
text: str(root.voltage_label_2)
TextInput:
id: input_2
font_size: 50
multiline: False
text: root.voltage_input_2.text
Button:
font_size: 50
text: str(root.submit_button_label_2)
on_press: root.press_2()
And here's my main:
import GUI.GUI as gui
import multiprocessing as multiproc
import time
class GetVoltage:
def __init__(self):
self.voltage_1 = 0
self.voltage_2 = 0
def fetchVoltage(self, interface):
self.voltage_1 = interface.voltage_output_1
self.voltage_2 = interface.voltage_output_2
def run(self, interface):
while (True):
self.fetchVoltage(interface)
print(self.voltage_1)
print(self.voltage_2)
time.sleep(1)
if __name__ == '__main__':
interface = gui.apason_GUIApp()
interface_process = multiproc.Process(target=interface.run())
checker = GetVoltage()
checker_process = multiproc.Process(target=checker.run(interface))
interface_process.start()
checker_process.start()
Since you already have one process when you run main.py, you really only need to start the second process. And you can communicate between the processes using a Queue. Here is a modified version of your main.py that uses this approach:
import GUI as gui
import multiprocessing as multiproc
class GetVoltage:
def run(self, queue):
while True:
voltage = queue.get() # get the data from the other process by using the Queue
print(voltage)
if __name__ == '__main__':
q = multiproc.Queue()
# start the GetVoltage process
checker = GetVoltage()
checker_process = multiproc.Process(target=checker.run, args=(q,), daemon=True)
checker_process.start()
# start the App
interface = gui.apason_GUIApp()
interface.q = q
interface.run() # this does not return until the App closes
# kill the GetVoltage process after the App exits
checker_process.kill()
And a couple minor changes in the App code:
def press_1(self):
voltage_1 = self.voltage_input_1.text
self.voltage_output_1 = float(voltage_1)
App.get_running_app().q.put('voltage_1 = ' + voltage_1) # put the data in the Queue
def press_2(self):
voltage_2 = self.voltage_input_2.text
self.voltage_output_2 = float(voltage_2)
App.get_running_app().q.put('voltage_2 = ' + voltage_2) # put the data in the Queue
As a test, I prepared the following code,
Firstly, I set the button text from two function names add_front and add_back
Then, I get the function handle from the name and make a partial function to bind it to Buttons.
Though the binding seems ok, the results are random.
Anybody can help me out?
"""
#author:
#file:test-bind.py
#time:2022/01/3111:42
#file_desc
"""
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivy.clock import Clock
import random
from functools import partial
Builder.load_string("""
<MButton#Button>:
_cb:[]
<TestWin>:
inp1:inp1_
lbl1:lbl1_
btn1:btn1_
btn2:btn2_
orientation: 'vertical'
BoxLayout:
orientation:'horizontal'
TextInput:
id:inp1_
readonly: True
Label:
id:lbl1_
MButton:
id:btn1_
text: 'add_front'
MButton:
id:btn2_
text: 'add_back'
Button:
id:btn_cls_
text:'clear'
on_press:root.clear_elements()
Button:
id:btn_shuffle_
text:'Shuffle'
on_press:root.shuffle_btn()
TextInput:
multiline: True
text:'Usage: press <Shuffle> to randomize button function and set a random number in [0,9], press <add_front> or <add_back> buttons to insert to list'
""")
class Box:
def __init__(self):
self.elements =[]
def add(self,e,front=False):
if front:
self.elements.insert(0,e)
else:
self.elements.append(e)
def add_front(self,e):
print("add_front",e)
self.add(e,front=True)
def add_back(self,e):
print("add_back",e)
self.add(e,front=False)
class TestWin(BoxLayout):
inp1 = ObjectProperty()
lbl1 = ObjectProperty()
btn1 = ObjectProperty()
btn2 = ObjectProperty()
btn_bind = ObjectProperty()
def __init__(self, **kwargs):
super(TestWin, self).__init__(**kwargs)
self.box = Box()
Clock.schedule_interval(self.update_elements_display, 0.5)
def update_elements_display(self,*args):
self.lbl1.text = "%s"%str(self.box.elements)
pass
def clear_elements(self):
self.box.elements=[]
def shuffle_btn(self):
btn_txt_ = ["add_front", "add_back"]
random.shuffle(btn_txt_)
self.btn1.text = btn_txt_[0]
self.btn2.text = btn_txt_[1]
v = random.randint(0,9)
self.inp1.text= "%d"%v
# bind func
for btn in [self.btn1,self.btn2]:
# clear old bind firstly
for cb in btn._cb:
btn.funbind("on_press",cb)
btn._cb = []
# The following codes give wrong result
#foo_ = getattr(self.box, btn.text)
#foo = lambda elem, instance: foo_(elem)
#call_back_ = partial(foo, self.inp1.text)
# The following codes give correct result
if btn.text=="add_back":
call_back_ = partial(self.box.add_back, self.inp1.text)
elif btn.text =="add_front":
call_back_ = partial(self.box.add_front, self.inp1.text)
btn._cb.append(call_back_)
btn.fbind('on_press',call_back_)
print("bind to",call_back_)
class TestApp(App):
def build(self):
return TestWin()
if __name__ == '__main__':
TestApp().run()
Edit:
Modifying the following codes may give me correct result,
but I wonder why
# The following codes give wrong result
#foo_ = getattr(self.box, btn.text)
#foo = lambda elem, instance: foo_(elem)
#call_back_ = partial(foo, self.inp1.text)
# The following codes give correct result
if btn.text=="add_back":
call_back_ = partial(self.box.add_back, self.inp1.text)
elif btn.text =="add_front":
call_back_ = partial(self.box.add_front, self.inp1.text)
# The following doesn't work
# foo_ = getattr(self.box, btn.text)
# foo = lambda elem, instance: foo_(elem)
# call_back_ = partial(foo, self.inp1.text)
#this is ok
call_back_ = partial(getattr(self.box, btn.text), self.inp1.text)
intro
im building a p2pvideo call app.
from the side that gets the call
i have the kivy main loop running as a thread with a another thread slistner_bind_thread.start() (a socket.bind function) that listens to calls and returns a pickup quote or a decline quote, if its picked up quote it starts another thread sstream_bind_thread.start() which cv2capture and pack send the feed (socket.bind).
from the side that calls
i have the same kivy main loop running as a thread with another thread scall_CON_thread.start() (socket.connect) that starts with a call button and waits for either a pickup quote or a decline from the other side if its picked up quote it calls for another threads feed_CON_thread.start() that revieves an opencv2 data packets.
Problem
when i make the call everything works as expected but when hanging up and calling again i understood that threads cant be started more than once. if i attempt to use anything else the gui gets bugged obviously.
i have some ideas but i dont know if they will work or not, if anyone can suggest something PLEASE at least a comment.
here is the main.py
import socket
import gi
gi.require_version('Gst', '1.0')
import kivy
from kivy.uix.gridlayout import GridLayout
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.textinput import TextInput
from kivy.clock import Clock
from kivy.uix.image import Image
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.core.window import Window
from kivy.config import Config
from kivy.lang import Builder
from kivy.properties import StringProperty, ObjectProperty, NumericProperty, ReferenceListProperty
from kivy.graphics.texture import Texture
from kivy.core.camera import Camera
from kivy.graphics import *
import time
import os
from pathlib import Path
import cv2
import struct
import threading
import pickle
Builder.load_file('the.kv')
##########go to line 233 and 240
################################ GLOBAL VARIABLAS BEGIN #######################
######## PORTS ############
sfeed_CON_port = 2430
slistner_bind_port = 1330
sstream_bind_port = 2430
scall_CON_port = 1330
###################### ################# #########
action = ''
endcall = False
endcall_string = ''
myip = ''
sfeed_cv_frame_recieve = None
incall_add = 'nocalls'
outcall_add = ''
################################ GLOBAL VARIABLS END #########################
def feedconnect():
global outcall_add, sfeed_CON_port, endcall ########### addr conn from listenr
global sfeed_cv_frame_recieve
try:
sfeed_CON_ad = outcall_add
sfeed_CON = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sfeed_CON.connect((sfeed_CON_ad, sfeed_CON_port))
data = b""
payload_size = struct.calcsize("Q")
while endcall == False:
while len(data) < payload_size:
packet = sfeed_CON.recv(4*1024)
if not packet: break
data+=packet
packed_msg_size = data[:payload_size]
data =data[payload_size:]
msg_size = struct.unpack("Q", packed_msg_size)[0]
while len(data) < msg_size:
data+=sfeed_CON.recv(4*2024)
frame_data = data[:msg_size]
data =data[msg_size:]
frame = pickle.loads(frame_data)
sfeed_cv_frame_recieve = frame
key = cv2.waitKey(1) & 0xFF
if endcall == True:
print(endcall_string + ' closing feedconnect() socket')
sfeed_CON.close()
break
sfeed_CON.close()
except Exception as e:
print("client: " + outcall_add + 'disconnected ')
sfeed_CON_thread = threading.Thread(target = feedconnect) ####### THREAD
def callconnect():
global outcall_add, scall_CON_port
scall_CON_ad = outcall_add
scall_CON = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
scall_CON.connect((scall_CON_ad, scall_CON_port))
print('callconnect is waiting for an answer from :' + scall_CON_ad + ' on port: ' + str(scall_CON_port))
time.sleep(15)
answer = scall_CON.recv(4*1024)
decoded_answer = answer.decode("utf-8")
if decoded_answer == 'decilned':
print(decoded_answer)
elif decoded_answer == 'picked up' :
print(decoded_answer)
f = True ############ to check
#feedconnect()
sfeed_CON_thread.start()
print(f)
else:
print(decoded_answer)
scall_CON.close()
scall_CON_thread = threading.Thread(target = callconnect) #### THREAD
def streambind():
global myip, sstream_bind_port
sstream_bind_ad = myip
sstream_bind = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sstream_bind.bind((sstream_bind_ad, sstream_bind_port))
print('myip is : ' + myip + ' from streambind() ' )
sstream_bind.listen(5)
sstream_bind_sclient, sstream_bind_sclient_ad = sstream_bind.accept()
if sstream_bind_sclient:
opcv_vid = cv2.VideoCapture(0)
while (opcv_vid.isOpened()):
try:
img, frame = opcv_vid.read()
pickled_frame = pickle.dumps(frame)
struct_pickled_frame = struct.pack("Q", len(pickled_frame)) + pickled_frame
sstream_bind_sclient.sendall(struct_pickled_frame)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
slistner_bind_sclient.close()
except:
endcall = True
endcall_string ='endcall'
print(endcall_string, endcall)
sstream_bind.close()
break
#sstream_bind_sclient.close() ############ hint i am closing only the client
sstream_bind_thread = threading.Thread(target = streambind) #### THREAD
def listnerbind():
global myip, slistner_bind_port, incall_add
global action
host_name = socket.gethostname()
myip = socket.gethostbyname(host_name + ".local")
slistner_bind_ad = myip
slistner_bind = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
slistner_bind.bind((slistner_bind_ad, slistner_bind_port))
slistner_bind.listen(10)
print('listnerbind() on thread')
print('listning for calls in: ' + slistner_bind_ad + ' on port: ' + str(slistner_bind_port))
while True:
slistner_bind_sclient, slistner_bind_sclient_ad = slistner_bind.accept()
###### block note start ###
#### the incall_add should be a list for multiple incalls at once #####
incall_add = str(slistner_bind_sclient_ad[0])
###### block note end ####
print('incall from: ' + incall_add + ' at port: ' + str(slistner_bind_port))
time.sleep(15) ##### needs to be edited later #########
if action == '':
slistner_bind_sclient.send(bytes('no answer',"utf-8"))
elif action == 'picked up':
sstream_bind_thread.start()
slistner_bind_sclient.send(bytes(action,"utf-8"))
elif action == 'declined':
slistner_bind_sclient.send(bytes(action,"utf-8"))
slistner_bind_thread = threading.Thread(target = listnerbind)
class fscreen(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def on_touch_down(self, touch):
if touch.x !=0:
theapp.screenm.current = "secondscreen"
class secscreen(Widget):
waiter = StringProperty()
callerid = StringProperty()
def __init__(self,**kwargs):
super().__init__(**kwargs)
global outcall_add
Clock.schedule_interval(self.callcheck, 0.5)
def callcheck(self, *args):
global incall_add
self.waiter = incall_add
self.callerid = incall_add
def decline(self):
global action
action = "declined"
def pickup(self):
global action
action = "picked up"
theapp.screenm.current = "thirdscreen"
def call(self):
global callconnect
global outcall_add
self.ip = self.ids.ipcall.text
outcall_add = self.ip
theapp.screenm.current = "thirdscreen"
if outcall_add != '':
print('')
#### note start sstreamer_bind_thread should also start but i dont have another camera###
#callconnect()
scall_CON_thread.start()
########## note end ########
class thscreen(Widget):
def __init__(self,**kwargs):
super().__init__(**kwargs)
global sfeed_cv_frame_recieve, endcall
Config.set('graphics', 'resizable', True)
Config.set('graphics', 'width', '800')
Config.set('graphics', 'height', '800')
#######################
Clock.schedule_interval(self.feedupdate, 0.01)
def feedupdate(self, dt): # MULTIPLE USERS NEDSS MORE WORK OF POS AND SIZE AFTER SOCKETS
global sfeed_cv_frame_recieve, endcall
############## note begin sfeed_cv_frame_recieve is converted to texture ############
if sfeed_cv_frame_recieve is not None and endcall == False:
self.frame = sfeed_cv_frame_recieve
buf1 = cv2.flip(self.frame, 0)
buf = buf1.tostring()
image_texture = Texture.create(
size=(self.frame.shape[1], self.frame.shape[0]), colorfmt='bgr')
image_texture.blit_buffer(buf, colorfmt='bgr', bufferfmt='ubyte')
self.texture = image_texture
with self.canvas:
Rectangle(texture=self.texture, pos=(self.pos[0], self.pos[1]+(self.size[1]*0.1)), size=(self.size[0],self.size[1]*0.9))
###################### note end ##############
elif endcall == True:
theapp.screenm.current = "secondscreen"
def hangup(self):
global endcall
endcall = True
endcall_string = 'endcall'
print(endcall_string)
theapp.screenm.current = "secondscreen"
class theapp(App):
def build(self):
self.screenm = ScreenManager() #(transition=FadeTransition())
self.fscreen = fscreen()
screen = Screen(name = "first screen")
screen.add_widget(self.fscreen)
self.screenm.add_widget(screen)
self.secscreen = secscreen()
screen = Screen(name = "secondscreen")
screen.add_widget(self.secscreen)
self.screenm.add_widget(screen)
self.thscreen = thscreen()
screen = Screen(name = "thirdscreen")
screen.add_widget(self.thscreen)
self.screenm.add_widget(screen)
return self.screenm
if __name__ == "__main__":
theapp = theapp()
slistner_bind_thread.start() ########### LISTING THREAD
threading.Thread(target = theapp.run()) ################ GUI APP THREAD
and here is the.kv file
<fscreen>
Label:
text: '0I Mechanics'
font_size: root.width*0.05
pos: root.width*0.4, root.height*0.8
<secscreen>
TextInput:
id: ipcall
hint_text: 'Ip'
width: root.width*0.3
height: root.height*0.05
pos: root.width*0.4, root.height*0.8
Button:
text: 'call'
width: root.width*0.3
height: root.height*0.05
pos: root.width*0.4, root.height*0.7
on_press: root.call()
Label:
text: root.callerid
font_size: root.width*0.02
pos: root.width*0.4, root.height*0.4
Button:
text: 'pickup'
width: root.width*0.2
height: root.height*0.05
disabled: True if root.waiter == 'nocalls' else False
pos: root.width*0.5, root.height*0.3
on_press: root.pickup()
Button:
text: 'decline'
width: root.width*0.2
height: root.height*0.05
disabled: True if root.waiter == 'nocalls' else False
pos: root.width*0.3, root.height*0.3
on_press: root.decline()
<thscreen>
Widget:
Button:
text: 'hangup'
width: root.width*0.2
height: root.height*0.05
pos: root.width*0.45, root.height*0.01
on_press: root.hangup()
I'm new to python and kivy and try to learn from code snippets and trial and error. But now I'm stuck.
To displays weather and garbage information on an raspberry I used kivy.
To grab these information I use the function URLRequest. This function needs the clock-function
while not req.is_finished:
Clock.tick()
return req.result
So the program works, displays the infos but crashed regularly after some 20 minutes or so wit hthe error "RuntimeError: maximum recursion depth exceeded
But I don't understand how I can get rid of the recursion by still getting things working :(
Here's mor of the code in context. Can anyone help?
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.label import Label
from kivy.clock import Clock
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.uix.textinput import TextInput
from kivy.network.urlrequest import UrlRequest
from time import gmtime, strftime, localtime, sleep
class garbage:
def garbage_text(garbage):
req = UrlRequest('http://192.168.1.1:8083/fhem?cmd={ReadingsVal(%22ABFALL%22,%22next_text%22,0)}&XHR=1&fwcsrf=password')
while not req.is_finished:
Clock.tick()
return req.result
class weather:
def weather_db1(weather):
req = UrlRequest('http://192.168.1.1:8083/fhem?cmd={ReadingsVal(%22netatmo_M01_00_00_3f_1d_1a%22,%22temperature%22,0)}&XHR=1&fwcsrf=password')
while not req.is_finished:
Clock.tick()
return req.result
class MyBox(BoxLayout):
def update(self, *args):
uweather = weather()
aktw = uweather.weather_db1()
ggarbage = garbage()
garbagetext = ggarbage.garbage_text()
self.ids.temp_ist.text = str(aktw)
self.ids.uhrzeit_top.text = strftime("%H:%M", localtime())
self.ids.datum_top.text = strftime("%d.%m.%Y", localtime())
self.ids.garbage_std.text = garbagetext+" rausstellen "
class ControlApp(App):
def build(self):
actclock = MyBox()
Clock.schedule_interval(actclock.update, 1)
return actclock
if __name__ == "__main__":
ControlApp().run()
Here is a modified version of your code that uses callbacks instead of looping or waiting:
from kivy.app import App
from kivy.lang import Builder
from kivy.clock import Clock, mainthread
from kivy.uix.boxlayout import BoxLayout
from kivy.network.urlrequest import UrlRequest
from time import strftime, localtime
class garbage:
def garbage_text(self, *args):
req = UrlRequest('http://192.168.1.1:8083/fhem?cmd={ReadingsVal(%22netatmo_M01_00_00_3f_1d_1a%22,%22temperature%22,0)}&XHR=1&fwcsrf=password',
on_success=self.update, on_failure=self.failure)
#mainthread
def update(self, req, result):
# update the GUI
garbage_std = App.get_running_app().root.ids.garbage_std
garbage_std.text = str(result)+" rausstellen "
# schedule the next update
Clock.schedule_once(self.garbage_text, 1)
def failure(self):
print('garbage failed')
class weather:
def weather_db1(self, *args):
req = UrlRequest('http://192.168.1.1:8083/fhem?cmd={ReadingsVal(%22netatmo_M01_00_00_3f_1d_1a%22,%22temperature%22,0)}&XHR=1&fwcsrf=password',
on_success=self.update, on_failure=self.failure)
#mainthread
def update(self, req, result):
# update the GUI
temp_ist = App.get_running_app().root.ids.temp_ist
temp_ist.text = str(result)
# schedule the next update
Clock.schedule_once(self.weather_db1, 1)
def failure(self):
print('weather failed')
class MyBox(BoxLayout):
def update(self, *args):
self.ids.uhrzeit_top.text = strftime("%H:%M", localtime())
self.ids.datum_top.text = strftime("%d.%m.%Y", localtime())
class ControlApp(App):
def build(self):
actclock = MyBox()
self.weather = weather()
self.garbage = garbage()
# start the time updates
Clock.schedule_interval(actclock.update, 1)
# start the other updates
Clock.schedule_once(self.weather_update)
Clock.schedule_once(self.garbage_update)
return actclock
def weather_update(self, dt):
self.weather.weather_db1()
def garbage_update(self, dt):
self.garbage.garbage_text()
Builder.load_string('''
<MyBox>:
orientation: 'vertical'
Label:
id: temp_ist
Label:
id: uhrzeit_top
Label:
id: datum_top
Label:
id: garbage_std
''')
if __name__ == "__main__":
ControlApp().run()
I would like to create two labels in Kivy that update their text with sensor data from temp sensors.
Temp sensors are connected to an Arduino, which prints their values to serial in the example format every two seconds or so:
A 82.4 (on line 1)
B 80.6 (on line 2)
The A/B is included in each print as an identifier that python could pick up to differentiate between the two.
The issue is importing this data into python and attaching it to labels.
Here is the existing .py:
import kivy
kivy.require('1.10.0')
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.stacklayout import StackLayout
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy.properties import StringProperty, NumericProperty, ObjectProperty
from digitalclock import DigitalClock
from kivy.animation import Animation
import serial
import time
import opc
class IntroScreen(Screen):
pass
class ContScreen(Screen):
pass
class ScreenManagement(ScreenManager):
pass
#Disregard this, a timer is included in layout
class Timer(Label):
a = NumericProperty() # seconds
def __init__(self, root, instance, duration, bg_color, **kwargs):
super(Timer, self).__init__(**kwargs)
self.obj = instance
self.a = duration
self.root = root
self.obj.disabled = True # disable widget/button
self.obj.background_color = bg_color
self.root.add_widget(self) # add Timer/Label widget to screen, 'cont'
def animation_complete(self, animation, widget):
self.root.remove_widget(widget) # remove Timer/Label widget to screen, 'cont'
self.obj.background_color = [1, 1, 1, 1] # reset to default colour
self.obj.disabled = False # enable widget/button
def start(self):
Animation.cancel_all(self) # stop any current animations
self.anim = Animation(a=0, duration=self.a)
self.anim.bind(on_complete=self.animation_complete)
self.anim.start(self)
def on_a(self, instance, value):
self.text = str(round(value, 1))
class Status(FloatLayout):
_change = StringProperty()
_tnd = ObjectProperty(None)
def update(self, *args):
self.time = time.asctime()
self._change = str(self.time)
self._tnd.text = str(self.time)
print (self._change)
#Here is where I start referencing Serial Comms, this line is to identify where
#to *send* commands to via a separate identifier.
bone = serial.Serial('/dev/ttyACM0', 9600)
class XGApp(App):
time = StringProperty()
sensor1 = NumericProperty(0)
sensor2 = NumericProperty(0)
def update(self, *args):
self.time = str(time.asctime())
arduino = self.arduino
data = arduino.read(arduino.inWaiting())
for line in data.split('\n'):
try:
sensor, value = line.strip().split(' ')
except:
print("parse error!")
continue
if sensor == 'A':
self.sensor1 = float(value)
elif sensor == 'B':
self.sensor2 = float(value)
else:
print("unknown data! {}".format(line))
def build(self):
try:
self.arduino = serial.Serial('/dev/ttyACM0', 9600)
except Exception as e: print(e)
Clock.schedule_interval(self.update, 1)
return Builder.load_file("main.kv")
xApp = XGApp()
if __name__ == "__main__":
xApp.run()
and the .kv:
<ContScreen>:
FloatLayout
orientation: 'vertical'
padding: [10,50,10,50]
spacing: 50
Label:
id: 'TempLabel1'
text: str(app.sensor1)
color: 1,1,1,1
font_size: 80
pos_hint: {'center_x':0.2, 'center_y':0.6}
Label:
id: 'TempLabel2'
text: str(app.sensor1)
color: 1,1,1,1
font_size: 80
pos_hint: {'center_x':0.5, 'center_y':0.6}
later in the .kv:
StackLayout
orientation: "tb-rl"
spacing: 15
Button:
text: "1"
size_hint: None, .16
width: 225
on_press:
Timer(root, self, 10, [100, 0, 100, 1.75]).start()
bone.Write('j'.encode())
print("One Executed")
TempLabel1 and TempLabel2 are the two labels i'd like updated from the sensors.
It's totally possible. But you are missing a few things.
You are trying to connect to the serial port after running your app, that won't work as your app will be stopped when you arrive there. Instead, you want to do this part while your app runs. I would do the try/except to connect to arduino in app.build.
def build (self):
try:
self.arduino = serial.Serial('/dev/ttyACM0')
exept:
print("unable to connect to arduino :(")
Clock.schedule_interval(self.update, 1)
return Builder.load_file("main.kv")
then, you want to check for messages in the update method, but you don't want to block, so you only read the amount of data that is waiting in the buffer.
def update(self, *args):
arduino = self.arduino
data = arduino.read(arduino.inWaiting())
then you do your processing, i assume your data is something like:
A value
B value
in such case, you can parse it and update the corresponding variable, something like:
def update(self, *args):
arduino = self.arduino
data = arduino.read(arduino.inWaiting())
for line in data.split('\n'):
try:
sensor, value = line.strip().split(' ')
except:
print("parse error!")
continue
if sensor == 'A':
self.sensor1 = float(value)
elif sensor == 'B':
self.sensor2 = float(value)
else:
print("unknown data! {}".format(line))
would do the job, it's a bit simplistic, as it assumes you always get full lines, but it can be improved later if needed (and it seems enough for a lot of cases in my experience).
Now, we need to make sure that our labels notice the changes of values, for this, kivy uses properties, which are smarter attributes, you need to declare them on the app class.
class XGApp(App):
sensor1 = NumericProperty(0)
sensor2 = NumericProperty(0)
now, you can make your update display the value directly, through the app instance.
<ContScreen>:
FloatLayout
orientation: 'vertical'
padding: [10,50,10,50]
spacing: 50
Label:
id: 'TempLabel1'
text: str(app.sensor1)
color: 1,1,1,1
font_size: 80
pos_hint: {'center_x':0.2, 'center_y':0.6}
Label:
id: 'TempLabel2'
text: str(app.sensor2)
color: 1,1,1,1
font_size: 80
pos_hint: {'center_x':0.5, 'center_y':0.6}