How to automatically re-run a state if transition condition fails? - python

Im writing screen scraping application in python, using transitions to handle the state machine.
The initial state is looking for a GUI window. When the window has been found, the state machine changes to next state.
Please consider the following code:
class ScreenScrapper(object):
window = None
def is_window_found(self):
return bool(self.window)
def state_look_for_window(self):
window = get_window() # Returns a bitmap object or None if window is not found
self.do_work()
def state_do_work(self):
print('Do some work!')
print('Window er: ', self.window)
states = ['dummy', 'state_look_for_window', 'state_do_work']
transitions = [
{'trigger': 'start', 'source': 'dummy', 'dest': 'state_look_for_window', 'after': 'state_look_for_window'},
{'trigger': 'do_work', 'source': 'state_look_for_window', 'dest': 'state_do_work', 'conditions': 'is_window_found', 'after': 'state_do_work'},
]
screen_scrapper = ScreenScrapper()
Machine(model=screen_scrapper, states=states, transitions=transitions, initial='dummy')
screen_scrapper.start()
In this simple example, the start changes states from dummy to state_look_for_window. The after callback will look for the window and afterwards change state to state_do_work. This transition has the condition is_window_found
Question: How can state_look_for_window be executed again as long as the transition condition is_window_found return False? Please note: I'm only interested in a solution that can be contained within the state machine. In other words, the only code outside must remain screen_scrapper.start().

Since you just need to transition from one state to another you can just do a state transition after checking is_window_found
I think it should look like this
def state_look_for_window(self):
if not is_window_found:
self.state_look_for_window()
else:
window = get_window() # Returns a bitmap object or None if window is not found
self.do_work()

I think you are asking for a 'reflexive transition', which is a trigger having the same State as source and destination.
I would replace your current diagram by another State called 'Window Ready', and specify an internal transition for that state, where you remain cycling inside the same, until the desired Window GUI is found.

Related

Tkinter using mainloop and another loop

I'm doing a project where I read info from a socket and then intend to display it on a gui using tkinter. The thing is, my read info from socket is a loop and for the gui I need another loop.
I'm pretty inexperienced with both Python and Tkinter, which probably explains my mistake here.
The fd_dict is a dictionary with the properties and respective values of a car ex: gear, power, speed, etc (theme of my project).
The main problem is either I get the values from the socket or I display the gui, never both obviously, since it stays on the earlier loop.
while True:
# UDP server part of the connection
message, address = server_socket.recvfrom(1024)
del address
fdp = ForzaDataPacket(message)
fdp.wall_clock = dt.datetime.now()
# Get all properties
properties = fdp.get_props()
# Get parameters
data = fdp.to_list(params)
assert len(data) == len(properties)
# Zip into a dictionary
fd_dict = dict(zip(properties, data))
# Add timestamp
fd_dict['timestamp'] = str(fdp.wall_clock)
# Print of various testing values
print('GEAR: ', fd_dict['gear'])
print('SPEED(in KMH): ', fd_dict['speed'] * 3.6) #speed in kph
print('POWER(in HP): ', fd_dict['power'] * 0.0013596216173) #power in hp
#print('PERFORMANCE INDEX: ', fd_dict['car_performance_index'])
print('\n')
The tkinter code:
window = Tk()
window.title('Forza Horizon 5 Telemetry')
window.geometry("1500x800")
window.configure(bg="#1a1a1a")
frame = Frame(window)
frame.pack()
label_gear = Label(text = '0')
label_gear.configure(bg="darkgrey")
label_gear.pack()
I read about using after() and using classes, but I've never used them, and can't figure out how to apply them here.
Thanks in advance.

get_triggers() in pytransitions not returning expected output

I am using the pytransitions library (documented here) to implement a Finite State Machine. One of the features outlined is the ability to get a list of the triggers for a specific state. Here is the example as per the documentation:
transitions = [
{ 'trigger': 'melt', 'source': 'solid', 'dest': 'liquid' },
{ 'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas' },
{ 'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas' },
{ 'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma' }
]
machine = Machine(model=Matter(), states=states, tansitions=transitions)
m.get_triggers('solid')
>>> ['melt', 'sublimate']
Here is a sample of my code that I am trying to run:
from transitions import Machine
states = ['changes ongoing', 'changes complete', 'changes pushed', 'code reviewed', 'merged']
triggers = ['git commit', 'git push', 'got plus2', 'merged']
# Initialize the state machine
git_user = Machine(states=states, initial=states[0], ignore_invalid_triggers=True, ordered_transitions=True)
# Create the FSM using the data provided
for i in range(len(triggers)):
git_user.add_transition(trigger=triggers[i], source=states[i], dest=states[i+1])
print(git_user.get_triggers(states[0]))
Expected Output:
['git commit']
Obtained output:
['to_code reviewed', 'to_changes ongoing', 'git commit', 'to_changes pushed', 'to_merged', 'to_changes complete', 'next_state']
Looking at the given example in the documentation, I should only get back 'git commit'. And that is the functionality I am looking for.
Thanks in advance for the help!
Machine.get_triggers returns all possible transitions. This also includes auto transitions which are added by default. Additionally, the constructor keyword ordered_transitions=True (which is equivalent to calling Machine.add_ordered_transitions()) will add transitions from each state to the next one passed in your states array with the trigger name next_state.
So you end up with a) all auto transitions plus b) one next_state and c) one of your added transitions.
To get your desired result you should disable auto_transitions and also omit the ordered_transitions keyword:
from transitions import Machine
states = ['changes ongoing', 'changes complete', 'changes pushed',
'code reviewed', 'merged']
triggers = ['git commit', 'git push', 'got plus2', 'merged']
# Initialise the state machine
git_user = Machine(states=states, initial=states[0],
ignore_invalid_triggers=True, auto_transitions=False)
# Create the FSM using the data provided
for i in range(len(triggers)):
git_user.add_transition(trigger=triggers[i], source=states[i],
dest=states[i+1])
print(git_user.get_triggers(states[0])) # >>> ['git commit']
You might also want to rethink your trigger and state names if you want to make use of transitions' convenience functions which do not work with names with spaces.
For instance, 'changes_ongoing' would allow you to use git_user.is_changes_ongoing() to check the current state where a trigger named 'git_commit' could also be called directly on the model with git_user.git_commit().

Automatically enabling different QPushbuttons according to QRadioButton options in PyQt

I am very new to PyQt, so I am not even sure where to start searching for this.
So I have two different options for QRadioButtons which ideally will correspond to two QPushButtons, one each.
Basically, I have the following code, where i tried to achieve this by using if statements:
def tab1UI(self):
mytabfont = QFont('Lucida Sans Unicode', 9)
layout = QFormLayout()
#self.setTabText(0,"My Data")
self.tab1.setLayout(layout)
tabdescription = 'To obtain or generate data choose an option below:'
# radio options
label1 = QLabel(tabdescription)
label1.setFont(mytabfont)
layout.addWidget(label1)
radiobtn1 = QRadioButton('Load data from file')
radiobtn1.setChecked(True)
#why does my resize not work?
radiobtn1.resize(100,100)
radiobtn1.setFont(mytabfont)
layout.addWidget(radiobtn1)
loadbtn = QPushButton('Open CSV file...')
layout.addWidget(loadbtn)
radiobtn2 = QRadioButton('Generate data')
radiobtn2.setFont(mytabfont)
genbtn= QPushButton('Generating matrix...')
layout.addWidget(radiobtn2)
layout.addWidget(genbtn)
if radiobtn1.isChecked():
# if this option is clicked then this button needs to be activated else it must be de-activated
loadbtn.setEnabled(True)
genbtn.setEnabled(False)
elif radiobtn2.isChecked():
loadbtn.setEnabled(False)
genbtn.setEnabled(True)
else:
loadbtn.setEnabled(False)
genbtn.setEnabled(False)
So, whenever I click one radio-button option I would like one pushbutton to become automatically active or inactive when the other option is checked instead.
There must be some sort of Action to be connected but not sure how to go about this.
You're only running the if statement once, when the buttons are first created. In order for this to work, you need to evaluate those if statements every time the radio button check state is changed. Qt allows you to do this with Signals and Slots. The QRadioButton will emit a signal when you change the check state. You can connect to this signal and run a function that updates the enabled state of the other buttons.
def tab1UI(self):
mytabfont = QFont('Lucida Sans Unicode', 9)
layout = QFormLayout()
self.tab1.setLayout(layout)
tabdescription = 'To obtain or generate data choose an option below:'
# radio options
self.label1 = QLabel(tabdescription)
self.label1.setFont(mytabfont)
layout.addWidget(self.label1)
self.radiobtn1 = QRadioButton('Load data from file')
self.radiobtn1.setChecked(True)
self.radiobtn1.setFont(mytabfont)
layout.addWidget(self.radiobtn1)
self.loadbtn = QPushButton('Open CSV file...')
layout.addWidget(self.loadbtn)
self.radiobtn2 = QRadioButton('Generate data')
self.radiobtn2.setFont(mytabfont)
self.genbtn= QPushButton('Generating matrix...')
layout.addWidget(self.radiobtn2)
layout.addWidget(self.genbtn)
self.radiobtn1.toggled.connect(self.refresh_button_state)
self.radiobtn2.toggled.connect(self.refresh_button_state)
self.refresh_button_state()
def refresh_button_state(self):
if self.radiobtn1.isChecked():
self.loadbtn.setEnabled(True)
self.genbtn.setEnabled(False)
elif self.radiobtn2.isChecked():
self.loadbtn.setEnabled(False)
self.genbtn.setEnabled(True)
else:
self.loadbtn.setEnabled(False)
self.genbtn.setEnabled(False)

Refreshing window content

I am developing a demo program based on PyGobject that has parts in its interface like stocks management that should refresh every few seconds. The program has the need to be able to run 24/7 while keeping the displayed information correct.
I have a container which has its content in stacks(and separately there are tabs to change between them):
def main_content(self):
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.stack = Gtk.Stack()
self.stack.get_style_context().add_class("main-container")
self.stack.props.margin = 20
self.child1_child = self.child1()
self.child1_child.set_visible(True)
self.stack.add_named(self.child1_child, "Child1")
self.child2_child = self.child2()
self.child2_child.set_visible(True)
self.stack.add_named(self.child2_child, "Child2")
self.child3_child = self.child2()
self.child3_child.set_visible(True)
self.stack.add_named(self.child3_child, "Child3")
box.pack_start(self.stack, True, True, 0)
if self.redraw:
print("Redrawing")
self.redraw = True
return box
So, there are functions to generate each stack member, they are assigned to variables, and then set as visible and added to the stack.
Lets suppose that the stack Child1 has a list of stocks, and i don't want it to have more than 5 minutes of delay. I tried to do a redraw function where a list is making the tabs, and this redraw function is either evoked by a timeout_add or a tab click :
def redraw(self):
selected_stack = self.listbox.get_selected_row().get_index()
# **Possible** solution:
stack_elements = {0: "Child1", 1: "Child2", 2: "Child3"}
variables = {0: "child1_child", 1: "child2_child", 2: "stock_child"}
self.variables[janela].destroy()
self.variables[janela] = self.stack_elements[janela].lower()
self.variables[janela].set_visible(True)
self.stack.add_named(self.variables[janela], stack_elements[janela])
# Problems: Doesn't works as python code. Just an idea
# **Possible** solution 2:
self.stack.destroy()
self.stack = Gtk.Stack()
self.stack_content = self.main_content()
self.main_box.pack_start(self.stack_content, True, True, 0)
These were just some try-error ideas, i never made an interface with the need to be kept updated, is there any proper way to do so, or a way to make one of my ways work?

Can I create OSC message handlers containing wildcards?

I am trying to create an OSC msg handler, using pyosc, which can listen to incoming multitoggle messages from TouchOSC.
The multitoggle is a grid of toggleswitches. The incoming messages are in the form "/1/multitoggle1/5/8" or "/1/multitoggle1/x/y" where x and y are integers corresponding to the grid position.
server.addMsgHandler( "/1/multitoggle1/5/8", toggle_callback ) works fine but I need the 5 and the 8 to be arguments read in the handler so I can get at them without having to add a separate handler for each individual toggle.
s.addMsgHandler( "/1/multitoggle1/", toggle_callback ) does not seem to work.
It is a similar problem to this one but I can't implement the implied solution.
I had the same problem and this was my solution:
for x in range(1,9):
for y in range(1,6):
s.addMsgHandler("/Channels/toggleChannels/"+`y`+"/"+`x`, toggleChannels)
def toggleChannels(addr,tags,data,source):
split = addr.split("/")
x = split.pop()
y = split.pop()
I registered all handlers but used only one callback, worked great
Or better yet, extracting things and preventing hardcoding:
# this is put into a config file for easy mod'ing
OSCPATH = {
# Incoming OSC from the tracking subsys
'ping': "/ping",
'start': "/pf/started",
'entry': "/pf/entry",
'exit': "/pf/exit",
'update': "/pf/update",
'frame': "/pf/frame",
'stop': "/pf/stopped",
'set': "/pf/set/",
'minx': "/pf/set/minx",
'maxx': "/pf/set/maxx",
'miny': "/pf/set/miny",
'maxy': "/pf/set/maxy",
'npeople': "/pf/set/npeople",
# Outgoing OSC updates from the conductor
'attribute': "/conducter/attribute",
'rollcall': "/conducter/rollcall",
'event': "/conducter/event",
'conx': "/conducter/conx",
}
class OSCHandler(object):
"""Set up OSC server and other handlers."""
def __init__(self, field):
self.m_server = OSCServer( (OSCHOST, OSCPORT) )
self.EVENTFUNC = {
'ping': self.event_tracking_ping,
'start': self.event_tracking_start,
'stop': self.event_tracking_stop,
'entry': self.event_tracking_entry,
'exit': self.event_tracking_exit,
'update': self.event_tracking_update,
'frame': self.event_tracking_frame,
'minx': self.event_tracking_set,
'miny': self.event_tracking_set,
'maxx': self.event_tracking_set,
'maxy': self.event_tracking_set,
'npeople': self.event_tracking_set,
}
for i in self.EVENTFUNC:
self.m_server.addMsgHandler(OSCPATH[i], self.EVENTFUNC[i])
You'll see that several paths, including minx, miny, etc, map to the same function. These use the path param to take specific actions to handle this data.
OSC does support wildcards in the Address Pattern of Methods (OSC speak for what you call Handlers).
They work similar to windows or unix command-line file name wildcards, not like regular expressions. For details, check out OSC Message Dispatching and Pattern Matching in the OSC 1.0 specifications.
In your example, you could define an address pattern "/1/multitoggle1/*/*",
which would allow you to receive "/1/multitoggle1/5/8" and similar messages.

Categories