In kv-File auf Zustand eines Objekts abfragen?

Hier werden alle anderen GUI-Toolkits sowie Spezial-Toolkits wie Spiele-Engines behandelt.
Antworten
Benutzeravatar
Dennis89
User
Beiträge: 1123
Registriert: Freitag 11. Dezember 2020, 15:13

Guten Abend zusammen,

ich muss mich wieder an euch wenden. Ich habe ein GUI in Kivy erstellt, allerdings kann das nur arbeiten, wenn meine Cloud gemountet ist. Das GUI läuft auf einem Raspberry Pi und dieser ist über eine W-lan Verbindung im Netz. Das GUI startet automatisch beim hochfahren, zu dieser Zeit ist das Netzwerk wohl noch nicht so weit, so dass die 'fstab' abgearbeitet wird ohne die Cloud einzubinden.
Ich dachte mir, mit dem Problem kann ich was neues in Python lernen. Also habe ich mir gedacht, ich starte eine Animation und im Hintergrund wird für eine bestimmte Zeit 'mount -a' abgearbeitet. Wenn das eine gewisse Zeit fehlschlägt soll der Pi wieder herunterfahren. (Da habe ich aber viel "gedacht" :D )
Ich habe einen ersten Ansatz, den ich auch am Ende meines Textes poste. Mein Problem ist gerade, dass ich bei erfolgreicher mount-Operation die Animation verlassen will und der eigentliche 'main'-Screen soll angezeigt werden.
Ich will also in der kv-Datei abfragen ob 'mounted' True ist und dann den Bildschirm wechseln. Aber ich weis nicht wie ich in diesem Fall die Kommunikation zwischen .py und .kv Datei aufbaue.
Kann mir hier bitte jemand weiter helfen? Ich vermute dass das 'return' so nicht den Zustand an die kv-Datei übermittelt. Das ist auch meine erste Berührung mit Threading, da fehlt mir vermutlich noch etwas Basiswissen.
Ob die Funktionen, wie sie im folgenden Code geschrieben sind funktionieren weis ich noch nicht. Ich weis aber, dass die Abfrage in der kv-Datei nicht funktioniert. Und erst wenn ich das raus habe, kann ich den Rest mener Meinung nach sinnvoll testen.

Hier poste ich euch mal beide vollständigen Dateien:

Code: Alles auswählen

#!/usr/bin/env python3

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.dropdown import DropDown
from kivy.uix.slider import Slider
from kivy.uix.button import Button
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.image import Image
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.factory import Factory
from kivy.animation import Animation
from kivy.clock import Clock
from InternetRadio import Radio
from MusicPlayer import PlaylistPlayer
from pathlib import Path
from subprocess import run
import threading
from time import sleep


RADIOSENDER_PATH = Path('/media/MusicBox/Senderliste.csv')
PLAYLIST_PATH = Path('/media/MusicBox/Playlist/')


class WaitForWlan(Screen):
    def start_second_thread(self):
        threading.Thread(target=self.check_wlan).start()

    def check_wlan(self):
        Clock.schedule_once(self.waiting_animation, 0)
        sleep(0.5)
        mounted = False
        for _ in range(20):
            if not mounted:
                mounted = run(['sudo', 'mount', '-a'], check=True)
                return mounted
            sleep(1)
        if not mounted:
            run(['sudo', 'systemctl', '-h', '0'], check=True)

    def waiting_animation(self, *args):
        anim_bar = Factory.AnimWidget()
        self.anim_box.add_widget(anim_bar)
        anim = Animation(opacity=0.3, width=50, duration=0.6)
        anim += Animation(opacity=1, width=400, duration=0.8)
        anim.repeat = True
        anim.start(anim_bar)


class MainScreen(Screen):
    pass


class StartScreen(Screen):
    pass


class RadioScreen(Screen):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.radiostation = Radio()

    def create_radioplayer(self):
        try:
            for sender in self.radiostation.read_station(RADIOSENDER_PATH):
                sender_button = Button(text=sender, size_hint_y=None, height=44)
                sender_button.bind(on_release=lambda sender_button: self.ids.DropDownMenu.select(sender_button.text))
                #
                # Check to add only once the radiostation to DropDownMenu.
                #
                if self.ids.sender_button.text not in self.radiostation.read_station(RADIOSENDER_PATH):
                    self.ids.DropDownMenu.add_widget(sender_button)
                else:
                    break
        except:
            pass

    def set_sender(self, sendername):
        self.radiostation.switch_station(sendername)

    def radio_play(self):
        self.radiostation.play_music()

    def radio_stop(self):
        self.radiostation.radio_stop()

    def set_volume(self, volume):
        self.radiostation.set_volume(volume)


class PlaylistScreen(Screen):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.player = PlaylistPlayer()

    def load_playlist(self):
        #
        #       Check to add only once the playlistobjects to DropDownMenu.
        #
        try:
            check_new_folder = Path(f"{PLAYLIST_PATH}/{self.ids.playlist_button.text}")
            if check_new_folder.is_dir():
                pass
            else:
                for playlist in PLAYLIST_PATH.iterdir():
                    playlist_button = Button(text=playlist.name, size_hint_y=None, height=44)
                    playlist_button.bind(
                        on_release=lambda playlist_button:
                        self.ids.DropDownPlaylist.select(playlist_button.text)
                    )
                    self.ids.DropDownPlaylist.add_widget(playlist_button)
        except:
            pass

    def playlist_player(self, music_folder):
        music_path = Path(f"{PLAYLIST_PATH}/{music_folder}/")
        self.player.addPlaylist(music_path)

    def player_play(self):
        self.player.play()

    def player_next(self):
        self.player.next()

    def player_pause(self):
        self.player.pause()

    def player_previous(self):
        self.player.previous()

    def player_stop(self):
        self.player.stop()

    def player_volume(self, volume):
        self.player.set_volume(volume)


class BluetoothScreen(Screen):
    def bluetooth_on(self):
        try:
            run(["rfkill unblock bluetooth"], shell=True, check=True)
        except:
            pass

    def bluetooth_off(self):
        try:
            run(["rfkill block bluetooth"], shell=True, check=True)
        except:
            pass

class ShutdownScreen(Screen):
    def musicbox_off(self):
        run(["sudo shutdown -h 0"], shell=True, check=True)

class AnimWidget(Widget):
    pass


class ImageButton(ButtonBehavior, Image):
    pass


class VolumeSlider(Slider):
    pass


class DropDownMenu(DropDown):
    pass


class DropDownPlaylist(DropDown):
    pass


kv = Builder.load_file("musicbox.kv")


class MusicBox(App):
    def build(self):
        screenmanager = ScreenManager()
        screenmanager.add_widget(WaitForWlan(name='wlan'))
        screenmanager.add_widget(MainScreen(name='main'))
        screenmanager.add_widget(RadioScreen(name='internetradio'))
        screenmanager.add_widget(PlaylistScreen(name='playlist'))
        screenmanager.add_widget(BluetoothScreen(name='bluetooth'))
        screenmanager.add_widget(ShutdownScreen(name='shutdown'))
        return screenmanager


if __name__ == '__main__':
    run(["rfkill block bluetooth"], shell=True, check=True)
   # Window.fullscreen = 'auto'
    MusicBox().run()

Code: Alles auswählen

# Filename: musicbox.kv

<AnimWidget@Widget>:
    canvas:
        Color:
            rgba: 0.7, 0.3, 0.9, 1
        Rectangle:
            size: self.size
            pos: self.pos
    size_hint: None, None
    size: 400, 30

<WaitforWlan>:
    name: "wlan"
    anim_box: anim_box

    GridLayout:
        cols: 1
        Image:
            source: 'hugo.png'
            size: self.size
        AnchorLayout:
            id: anim_box
            returns: root.start_second_thread()
            if mounted:
                app.root.current = "main"
                root.manager.transition.direction = "up"



<MainScreen>:
    name: "main"

    GridLayout:
        cols: 2

        ImageButton:
            id: button_radio
            source: 'icon/radio.png'
            pos_hint: {"x":0, "top":1}
            on_press:
                app.root.current = "internetradio"
                root.manager.transition.direction = "up"

        ImageButton:
            id: button_playlist
            source: 'icon/playlist.png'
            pos_hint: {"x":0, "top":1}
            on_press:
                app.root.current = "playlist"
                root.manager.transition.direction = "up"

        ImageButton:
            id: button_bluetooth
            source: 'icon/bluetooth.png'
            pos_hint: {"x":0, "top":1}
            on_press:
                app.root.current = "bluetooth"
                root.manager.transition.direction = "up"

        ImageButton:
            id: button_shutdown
            source: 'icon/shutdown.png'
            pos_hint: {"x":0, "top":1}
            on_press:
                app.root.current = "shutdown"
                root.manager.transition.direction = "up"


<RadioScreen>:
    name: "internetradio"

    GridLayout:
        rows: 2

        GridLayout:
            cols: 2

            Image:
                size_hint_x: 0.1
                source: 'icon/volume+.png'
                allow_stretch: True

            Slider:
                id: slider
                value_track: True
                value_track_color: (1,0.2,0.7,1)
                min:0
                max:100
                value: 30
                step: 1
                on_value:
                    root.set_volume(int(self.value))

        GridLayout:
            cols: 4

            ImageButton:
                id: button_back_main
                size_hint_x: 1
                allow_stretch: True
                source: 'icon/home_radio.png'
                on_press:
                    app.root.current = "main"
                    root.radio_stop()
                    root.manager.transition.direction = "down"

            ImageButton:
                id: button_play
                source: 'icon/play_radio.png'
                on_press:
                    root.radio_play()

            ImageButton:
                id: button_pause
                source: 'icon/break_radio.png'
                on_press:
                    root.radio_stop()

            ImageButton:
                id: sender_button
                text: ''
                source: 'icon/liste_radio.png'
                on_parent: DropDownMenu.dismiss()
                on_release:
                    root.create_radioplayer()
                    DropDownMenu.open(self)

            DropDownMenu:
                id: DropDownMenu
                on_select:
                    sender_button.text = '{}'.format(args[1])
                    root.set_sender(sender_button.text)

<PlaylistScreen>:
    name: "playlist"

    GridLayout:
        rows: 3

        GridLayout:
            cols: 2

            Image:
                size_hint_x: 0.1
                source: 'icon/volume+.png'
                allow_stretch: True

            Slider:
                id: volume_slider
                value_track: True
                value_track_color: (0,245,255,1)
                min:0
                max:100
                value: 30
                step: 1
                on_value: root.player_volume(int(self.value))

        GridLayout:
            cols: 5

            ImageButton:
                id: button_previous
                source: 'icon/backward.png'
                on_press:
                    root.player_previous()

            ImageButton:
                id: button_play
                source: 'icon/play_playlist.png'
                on_press:
                    root.player_play()

            ImageButton:
                id: button_pause
                source: 'icon/break_playlist.png'
                on_press:
                    root.player_pause()

            ImageButton:
                id: button_stop
                source: 'icon/stop_playlist.png'
                on_press:
                    root.player_stop()

            ImageButton:
                id: button_next
                source: 'icon/forward.png'
                on_press:
                    root.player_next()

        GridLayout:
            cols: 3

            ImageButton:
                id: button_back_main
                source: 'icon/home_playlist.png'
                on_press:
                    root.player_stop()
                    app.root.current = "main"
                    root.manager.transition.direction = "down"

            ImageButton:
                id: playlist_button
                text: ' '
                source: 'icon/liste_playlist.png'
                on_parent: DropDownPlaylist.dismiss()
                on_release:
                    root.load_playlist()
                    DropDownPlaylist.open(self)

            DropDownPlaylist:
                id: DropDownPlaylist
                on_select:
                    playlist_button.text = '{}'.format(args[1])
                    root.playlist_player(playlist_button.text)
                    root.player_stop()

<BluetoothScreen>:
    name: "bluetooth"
    GridLayout:
        cols: 2

        ImageButton:
            id: button_on
            source: 'icon/bluetooth.png'
            on_press:
                root.bluetooth_on()

        ImageButton:
            id: button_back_main
            source: 'icon/home_bluetooth.png'
            on_press:
                app.root.current = "main"
                root.manager.transition.direction = "down"
                root.bluetooth_off()

<ShutdownScreen>:
    name: "shutdown"
    GridLayout:
        cols: 2

        ImageButton:
            id: button_back_main
            source: 'icon/home_shutdown.png'
            on_press:
                app.root.current = "main"
                root.manager.transition.direction = "down"

        ImageButton:
            id: button_shutdown
            source: 'icon/shutdown.png'
            on_press: root.musicbox_off()
Vielen Dank schon mal und Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
Dennis89
User
Beiträge: 1123
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

ich konnte das Problem leider noch nicht lösen. Es funktioniert auch gar nicht so wie ich es mir gedacht habe. Ich brauche ja eine Art Callback-Funktion oder eine Schleife in der *.kv File, damit diese reagiert, wenn meine Cloud gemounted wurde. Allerdings stehe ich jetzt gerade vor dem Nichts. Ich habe keine Idee wie ich das umsetzen könnte.
Nachfolgend noch meine aktuelle Klasse und der Ausschnitt aus der *.kv-File.

Code: Alles auswählen

class WaitForWlan(Screen):
    def start_second_thread(self):
        return threading.Thread(target=self.check_wlan).start()

    def check_wlan(self):
        Clock.schedule_once(self.waiting_animation, 0)
        sleep(0.5)
        mounted = False
        for _ in range(20):
            if not mounted:
                mounted = run(['sudo', 'mount', '-a'], check=True)
                return mounted
            sleep(1)
        if not mounted:
            run(['sudo', 'systemctl', '-h', '0'], check=True)

    def waiting_animation(self, *args):
        anim_bar = Factory.AnimWidget()
        self.anim_box.add_widget(anim_bar)
        anim = Animation(opacity=0.3, width=50, duration=0.6)
        anim += Animation(opacity=1, width=400, duration=0.8)
        anim.repeat = True
        anim.start(anim_bar)

Code: Alles auswählen

<AnimWidget@Widget>:
    canvas:
        Color:
            rgba: 0.7, 0.3, 0.9, 1
        Rectangle:
            size: self.size
            pos: self.pos
    size_hint: None, None
    size: 400, 30

<WaitforWlan>:
    name: "wlan"
    anim_box: anim_box

    GridLayout:
        cols: 1
        Image:
            source: 'hugo.png'
            size: self.size
        AnchorLayout:
            id: anim_box
            returns: root.start_second_thread()
    if root.start_second_thread:
        app.root.current = "main"
        root.manager.transition.direction = "up"
Bei ausführen erhalte ich die Meldung:

Code: Alles auswählen

  File "/home/dennis/PycharmProjects/MusicBox/Kivy_lernen/MusicBox_GUI.py", line 178, in <module>
     kv = Builder.load_file("musicbox.kv")
   File "/home/dennis/PycharmProjects/MusicBox/venv/lib64/python3.9/site-packages/kivy/lang/builder.py", line 306, in load_file
     return self.load_string(data, **kwargs)
   File "/home/dennis/PycharmProjects/MusicBox/venv/lib64/python3.9/site-packages/kivy/lang/builder.py", line 373, in load_string
     parser = Parser(content=string, filename=fn)
   File "/home/dennis/PycharmProjects/MusicBox/venv/lib64/python3.9/site-packages/kivy/lang/parser.py", line 402, in __init__
     self.parse(content)
   File "/home/dennis/PycharmProjects/MusicBox/venv/lib64/python3.9/site-packages/kivy/lang/parser.py", line 511, in parse
     objects, remaining_lines = self.parse_level(0, lines)
   File "/home/dennis/PycharmProjects/MusicBox/venv/lib64/python3.9/site-packages/kivy/lang/parser.py", line 614, in parse_level
     _objects, _lines = self.parse_level(
   File "/home/dennis/PycharmProjects/MusicBox/venv/lib64/python3.9/site-packages/kivy/lang/parser.py", line 591, in parse_level
     raise ParserException(self, ln, 'Invalid class name')
 kivy.lang.parser.ParserException: Parser: File "/home/dennis/PycharmProjects/MusicBox/Kivy_lernen/musicbox.kv", line 25:
 ...
      23:            id: anim_box
      24:            returns: root.start_second_thread()
 >>   25:    if root.start_second_thread:
      26:        app.root.current = "main"
      27:        root.manager.transition.direction = "up"
 ...
 Invalid class name

Process finished with exit code 1
Hoffe jemand hat eine Idee wie ich mein Problem umsetzen könnte. Bin für jede Hilfe sehr dankbar.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
poldi
User
Beiträge: 20
Registriert: Sonntag 19. April 2020, 08:35

Also root wurde in deinem Code nicht wirklich definiert. Wenn du aus einer Klasse heraus im kv file etwas beschreibst:

MeineKlasse:

self.meinobjekt

muss es sich im Code genauso Wiederspiegeln:

MeineKlasse:
def __init__(self, obj):
self.meinobjekt = obj

bzw:

MeineApp(App):
def __init__(self):
root = MeineKlasse()
Antworten