I tried a lot already, would appreciate any help.
I built a state machine Dashboard with pytransitions and a dashboard with pywebio. Now im trying to have two buttons:
def btn_click(btn_val):
global Lang
if btn_val=='English':
Lang=1
Dashboard.to_Start()
if btn_val=='German':
Lang=2
Dashboard.to_Start()
while True:
match Dashboard.state:
case 'Start':
clear()
put_buttons(['German', 'English'], onclick=btn_click)
match Lang:
case 1:
put_text("some english text")
case 2:
put_text("some german text")
Dashboard.ok()
case 'State2':
#do this and that
Dashboard.ok()
case 'State3':
#do this and that
match Lang:
case 1:
name = input("What's your name")
case 2:
name = input("Wie heisst du?")
which call a function, in which the variable for the different languages of my dashboard can be set. I also want the dashboard to completely refresh when the language is changed aka i want the state machine to change its state to the initial state Start. This should also happen, when the Dashboard is waiting for human text input like in state3. The buttons stay on screen the whole time, even in state2 and state3.
Unfortunately, when i try to do that (the method to_Start()) in the function, it doesnt work. Funny enough, when i print out state machine state in the function, it prints its in the Start state, but not anymore after we are leaving the function. Which i understand has something to do with the fact that only local stuff is processed in functions (except for when defining global variables).
I also tried onclick=Dashboard.to_Start(), it doesnt work that way and i would run into the next problem, which would be that my variable Lang needs to be changed.
I guess it would be best if i could call the function btn_click AND Dashboard.to_Start() from onclick. Or maybe my whole approach is bad and i would be happy to hear some advice.
Can someone please explain what i am missing here?
Edit: Im not getting an error message at all or pywebio tells me on the dashboard that an "internal error occured". In the comments my code example was critiziced, i did my best to improve on that.
Im getting mad crazy over this, please help. Cheers and Regards
Welcome to Stack Overflow, Toben.
Since I cannot test your code directly I went ahead and created a MRE myself. But first, some remarks:
One argument for state machines is to get rid of complex switch/case statements. If you track your machines states with (polling) switch/case statements, either the machine or switch/case might not be the right choice here.
Many networking and GUI frameworks handle user or network input asynchronously (e.g. in threads) and use/block the main thread for changes to the GUI. Furthermore, most state machine implementations -- transitions' Machine
included -- are not thread-safe. Transitions features LockedMachine
for threaded and AsyncMachine
for async/await use cases.
For my minimal example I will ignore B) since it depends on the chosen framework whether threading or asynchronous event processing is the more suitable approach. But let's face A) now: Instead of polling the machine's state, we use an event-driven approach where create a controller to handle GUI changes based on user input and pass this to the state machine as a state model. The separation of machine and stateful model is the recommended way of working with transitions. Machines can be considered rulebooks that can be used for more than one stateful object.
from pywebio.output import put_text, put_buttons, clear
from pywebio import start_server
from transitions import Machine
# Our GUI controller/stateful model contains all methods that will
# alter the GUI and process input events. Each method may receive
# input information.
class WebIOModel:
def on_enter_Start(self, inp=None):
# We bind the button's onclick callback to a yet
# to be defined "clicked" method.
put_buttons(["German", "English"], onclick=self.clicked)
put_text("Choose a language")
def on_enter_Clicked(self, inp=None):
put_text(f"You chose {inp}")
def clear_canvas(self, inp=None):
clear()
# Our simple machine has three states
# Pay attention to the state names and the method names of our
# WebIOModel: transitions will automatically bind methods to
# state events that match the pattern "on_enter_<state>"
# and "on_exit_<state>".
states = ["Init", "Start", "Clicked"]
# We create a transition event named "clicked".
# When the state machine is
# in state "Start" and "clicked" happens, the machine will transition
# to state "Clicked" and also execute callbacks associated with
# exiting "Start" and entering "Clicked".
transitions = [["clicked", "Start", "Clicked"]]
model = WebIOModel()
# We tell our machine that it should always execute the model's
# callback "clear_canvas" before a state transition.
# We use this callback to clear the GUI and reduce redundant code.
# Note that for "enter_<state>" callbacks to be called, a state
# must be actually entered. However, an "initial" state is not
# entered. We create a dummy "Init" state to deal with this.
machine = Machine(
model,
states=states,
transitions=transitions,
before_state_change="clear_canvas",
initial="Init",
)
def main():
# Note that events and trigger functions are bound to the model
# not the machine! 'to_Start' will enter this state and draw the
# buttons
model.to_Start()
start_server(main, 80)
When the user clicks a button the onclick
callback is forwarded to our model's clicked
event. Since onclick
returns a value, our callback chain needs to be able to handle inputs. This is way each callback in WebIOModel
has an optional input
argument. The value passed to clicked
will be forwarded to all callbacks in the processing chain. Callbacks can be called at any state of a transition/event processing chain. The complete list and their execution order can be found in the documentation.
This is not production code since it ignore B) as mentioned before. Whether you queue user input events and process state changes in the main thread or whether you queue GUI events with a thread-safe LockedMachine
or via async/await depends on your architecture and PyWebIO
.