Looping a function until another function is called - python

I am making a menu that runs on an LCD screen powered by a Raspberry Pi. I am trying to use the threading module to make the text, on the LCD, update until the menu position changes.
The menu is made up of a list of functions that are called when the menu position is changed. The switch_menu() function is called from outside the class, using an event handler, and is used to call the correct menu function. With some of these functions(item2); I want them to loop, and with others(item1); just display static text. The important thing is that they stop looping when switch_menu() is called again. How can I do this?
(here is a simplified version of my code)
class Menu:
def __init__(self):
self.LCD = Adafruit_CharLCD()
self.m_pos = 0
self.items = [self.item1,self.item2]
self.switch_menu(0)
def switch_menu(self,operation):
# 2. And here I want to stop it.
m_pos = self.m_pos
pos = m_pos
max_pos = len(self.items) - 1
m_pos = self.loop_selection(pos,max_pos,operation)
# 1. Here I want to start looping the function below.
self.items[m_pos]()
self.m_pos = m_pos
def loop_selection(self,pos,max_pos,operation):
if pos >= max_pos and operation == 1:
pos = 0
elif pos <= 0 and operation == -1:
pos = max_pos
else:
pos += operation
return pos
def item1(self):
self.LCD.clear()
text = "item1"
self.LCD.message(text)
def item2(self):
while True:
self.LCD.clear()
text = "item2"
self.LCD.message(text)
time.sleep(10)

There are many ways to achieve this, one simple way is to make the while loop in a variable and then set it to False outside the loop (for example, when calling switch_menu) once you want to stop it. Just beware of any race conditions that may be caused, of which I can't talk much more about since I don't know the rest of your code.

Typical, I have been trying to get this to work for days and as soon as I post a question; I find the answer.
Here is where I found my answer:
Stopping a thread after a certain amount of time
And this is what I did to make it work:
class Menu:
def __init__(self):
self.LCD = Adafruit_CharLCD()
self.m_pos = 0
self.items = [self.item1,self.item2]
self.switch_menu(0)
def switch_menu(self,operation):
try:
self.t_stop.set()
except:
pass
m_pos = self.m_pos
pos = m_pos
max_pos = len(self.items) - 1
m_pos = self.loop_selection(pos,max_pos,operation)
item = self.items[m_pos][0]
self.t_stop = threading.Event()
self.t = threading.Thread(target=item,args=(1,self.t_stop))
self.t.start()
self.m_pos = m_pos
def loop_selection(self,pos,max_pos,operation):
if pos >= max_pos and operation == 1:
pos = 0
elif pos <= 0 and operation == -1:
pos = max_pos
else:
pos += operation
return pos
def item1(self,arg):
while not stop_event.is_set():
text = "item1"
self.LCD.clear()
if not stop_event.is_set(): self.LCD.message(text)
stop_event.wait(10)
def item2(self,arg):
while not stop_event.is_set():
text = "item2"
self.LCD.clear()
if not stop_event.is_set(): self.LCD.message(text)
stop_event.wait(10)
I used a try/except to bypass the initial execution of switch_menu():
try:
self.t_stop.set()
except:
pass
I check the condition a second time as a workaround, to prevent race conditions:
if not stop_event.is_set(): self.LCD.message(text)
And I don't know why I had to pass in an argument when creating a thread, but it gave me errors when I didn't:
self.t = threading.Thread(target=item,args=(1,self.t_stop))
I know it needs some tidying up, but it works. If anyone has a more elegant solution feel free to post it.

Related

Using Python and Open CV to match images and execute commands in another code

So fair warning, I am new to Python. Basically This is a script that communicates to another script. The py script compares stored images to a live feed for a reasonable match to a specific location in the live feed. While the other just interprets the results and sends them to a device.
Most of the time the images are pretty consistent and it will only print once. But some on the other hand, cause it to "flood" the output as it is matching a different similarity in the same defined location. I am trying to figure out the best way to suppress multiple print notifications from that.
This is all there is to the Python side of the code, removing print statements from the Python code remedies the print flood issue, but takes away the only means to know what the state is.
import os
import cv2
import time
class GCVWorker:
def __init__(self, width, height):
os.chdir(os.path.dirname(__file__))
self.gcvdata = bytearray(255)
self.User_defined_event_1 = cv2.imread('Images/Event_Data/xy.png')
self.User_defined_event_1_b = cv2.imread('Images/Event_Data_b/xy.png')
self.User_defined_event_2 = cv2.imread('Images/Event_Data/xy.pmg')
self.User_defined_event_2_b = cv2.imread('Images/Event_Data_b/xy.png')
self.Found_User_defined_event_1 = True
self.Found_User_defined_event_1_b = True
self.Found_User_defined_event_2 = True
self.Found_User_defined_event_2_b = True
def __del__(self):
del self.gcvdata
del self.User_defined_event_1
del self.User_defined_event_1_b
del self.User_defined_event_2
del self.User_defined_event_2_b
def process(self, frame):
self.gcvdata[0] = False
self.gcvdata[1] = False
User_defined_event_1 = frame[y1:y2, x1:x2]
similar = cv2.norm(self.User_defined_event_1, User_defined_event_1)
if similar <= 3000.0 and self.User_defined_event_1:
print('User defined event 1 Detected')
self.Found_User_defined_event_1 = False
self.gcvdata[0] = True
elif similar <= 3000.0 and self.User_defined_event_1_b:
print('User defined event 1 Detected')
self.Found_User_defined_event_1_b = False
self.gcvdata[0] = True
elif similar <= 3000.0:
pass
else:
self.Found_User_defined_event_1 = True
User_defined_event_2 = frame[y1:y2, x1:x2]
similar = cv2.norm(self.User_defined_event_2, User_defined_event_2)
if similar <= 3000.0 and self.User_defined_event_2:
print('User defined event 2 Detected')
self.gcvdata[1] = True
self.User_defined_event_2_a = False
elif similar <= 3000.0 and self.User_defined_event_2_b:
print('User defined event 2 Detected')
self.gcvdata[1] = True
self.User_defined_event_2_b = False
elif similar <= 3000.0:
pass
else:
self.Found_User_defined_event_2 = True
return frame, self.gcvdata

Python self.attribute cannot be seen when calling class method

I have a problem related to a TKinter GUI I am creating, but the problem is not necessarily specific to this library.
Background
I am currently in the advanced stage of a python self-learning course. The learning module I am on is covering TKinter for creating interactive GUI's. I am making a game whereby randomly generated numbered buttons must be clicked in succession in the quickest time possible.
Brief: https://edube.org/learn/pcpp1-4-gui-programming/lab-the-clicker
Problem
Under my class, game_grid, I have created an instance variable; 'self.holder', a 25 entry dictionary of {Key : TkinterButtonObject} form
When calling this instance variable for use in a class method, I get the following error:
AttributeError: 'game_grid' object has no attribute 'holder'
I have a print statement under class init which proves this attribute has been successfully created. I have made sure my spacing and tabs are all OK, and have tried every location for this variable, including using as a class variable, and a global variable to no avail - as it is an semi-complex object. I don't see what difference it should make, but any ideas would be much appreciated. I am also aware this could be done without classes, but I am trying to adopt DRY principles and orthogonality in all of my programs.
Thanks in advance.
Full Code:
import tkinter as tk
from tkinter import*
import random
from tkinter import messagebox
import time
win = tk.Tk()
class game_grid:
def __init__(self, win):
self.last_number = 0
self.number_buttons = {}
self.row_count = 0
self.column_count = 0
#Generate a list of 25 random numbers
self.number_list = random.sample(range(0, 999), 25)
#Puts the numbers in a dictionary (number : buttonobject)
self.holder = {i: tk.Button(win, text = str(i), command = game_grid.select_button(self, i)) for i in self.number_list}
#pack each object into window by iterating rows and columns
for key in self.holder:
self.holder[key].grid(column = self.column_count, row = self.row_count)
if self.column_count < 4:
self.column_count += 1
elif self.column_count == 4:
self.column_count = 0
self.row_count += 1
print(self.holder)
def select_button(self, number):
if number > self.last_number:
self.holder[number].config(state=tk.DISABLED)
self.last_number = number
else:
pass
class stopclock():
def __init__(self):
#Stopclock variable initialisation
self.time_begin = 0
self.time_end = 0
self.time_elapsed= 0
def start(self):
if self.time_begin == 0:
self.time_begin = time.time()
return("Timer started\nStart time: ", self.time_begin)
else:
return("Timer already active")
def stop(self):
self.time_end = time.time()
self.time_elapsed = time_end - time_begin
return("Timer finished\nEnd time: ", time_begin,"\nTime Elapsed: ", time_elapsed)
play1 = game_grid(win)
win.mainloop()
Perhaps you meant:
command = self.select_button(self, i)
Update:
Though from research:How to pass arguments to a Button command in Tkinter?
It should be:
command = lambda i=i: self.select_button(i)
You call select_button from inside the dict comprehension of holder. select_button then tries to use holder, but it is not yet defined. You don't want to actually call select_button, but assign a function to the button, like that:
self.holder = {i: tk.Button(window, text=str(i), command=lambda i=i: self.select_button(i)) for i in self.number_list}

Python CodeSkulptor Pause Drawing from Inside For Loop

I want to build some visualizations for searching algorithms (BFS, A* etc.) within a grid.
My solution should show each step of the algorithm using CodeSkulptor simplegui (or the offline version using SimpleGUICS2Pygame.)
I've made a version which highlights all the cells visited by changing their color, but I've run into trouble trying to make the path display step-by-step with a time delay between each step.
I've extracted the essence of the problem and created a minimal example representing it in the code below, also run-able online here: http://www.codeskulptor.org/#user47_jB2CYfNrH2_2.py
What I want is during the change_colors() function, for there to be a delay between each iteration.
CodeSkulptor doesn't have time.sleep() available, and I don't think it would help anyway.
CodeSkulptor does have timers available, which might be one solution, although I can't see how to use one in this instance.
Code below:
import time
try:
import simplegui
except ImportError:
import SimpleGUICS2Pygame.simpleguics2pygame as simplegui
simplegui.Frame._hide_status = True
TITLE = "TEST"
FRAME_WIDTH = 400
FRAME_HEIGHT = 400
DELAY = 10
class Square:
"""This class represents a simple Square object."""
def __init__(self, size, pos, pen_size=2, pen_color="red", fill_color="blue"):
"""Constructor - create an instance of Square."""
self._size = size
self._pos = pos
self._pen_size = pen_size
self._pen_color = pen_color
self._fill_color = fill_color
def set_color(self, color):
self._fill_color = color
def get_color(self):
return self._fill_color
def is_in(self, pos):
"""
Determine whether coordinates are within the area of this Square.
"""
return self._pos[0] < pos[0] < self._pos[0] + self._size and self._pos[1] < pos[1] < self._pos[1] + self._size
def draw(self, canvas):
"""
calls canvas.draw_image() to display self on canvas.
"""
points = [(self._pos[0], self._pos[1]), (self._pos[0] + self._size, self._pos[1]),
(self._pos[0] + self._size, self._pos[1] + self._size), (self._pos[0], self._pos[1] + self._size)]
canvas.draw_polygon(points, self._pen_size, self._pen_color, self._fill_color)
def __str__(self):
return "Square: {}".format(self._pos)
def draw(canvas):
for square in squares:
square.draw(canvas)
def change_colors():
for square in squares:
# time.sleep(1) # Not implemented in CodeSkulptor and would'nt work anyway
square.set_color("green")
frame = simplegui.create_frame(TITLE, FRAME_WIDTH, FRAME_HEIGHT)
frame.set_draw_handler(draw)
width = 20
squares = []
for i in range(10):
squares.append(Square(width, (i * width, 0)))
change_colors()
frame.start()
Any help appreciated.
Yes, you need to use a timer. Something like this:
I = 0
def change_next_color():
if I < len(squares):
squares[I].set_color("green")
global I
I += 1
timer = simplegui.create_timer(1000, change_next_color)
timer.start()
http://www.codeskulptor.org/#user47_udyXzppCdw2OqdI.py
I also replaced
simplegui.Frame._hide_status = True
by simplegui.Frame._hide_controlpanel = True
https://simpleguics2pygame.readthedocs.io/en/latest/simpleguics2pygame/frame.html#SimpleGUICS2Pygame.simpleguics2pygame.frame.Frame._hide_controlpanel
See also _keep_timers option of SimpleGUICS2Pygame to help you:
https://simpleguics2pygame.readthedocs.io/en/latest/simpleguics2pygame/frame.html#SimpleGUICS2Pygame.simpleguics2pygame.frame.Frame._keep_timers
Possible improvements:
Find a better solution that don't use a global counter.
Stop timer when all work is finished.

python threading for elevator simulation

I am trying to make an elevator simulation because of an interesting problem I saw on CareerCup. My problem is that I want the elevator to "take time" to move from one floor to another. Right now it just instantly moves to the next floor in its "to visit" list. I'm not sure how to program it so that "pickup requests" can be coming in while the elevator is moving. I think this may require threading, and the time.sleep() function. How do I make one thread that makes random requests to the elevator, and another thread that has the elevator trying to meet all of the requests? This is what I have so far:
import time
from random import *
import math
class Elevator:
def __init__(self, num_floors):
self.going_up = False
self.going_down = False
self.docked = True
self.curr_floor = 0
self.num_floors = num_floors
self.floors_to_visit = []
self.people_waiting = []
def print_curr_status(self):
for i in range(self.num_floors):
if i == self.curr_floor:
print('. []')
else:
print('.')
print ("to_visit: ", self.floors_to_visit)
def handle_call_request(self, person):
if not self.going_up and not self.going_down:
self.floors_to_visit = [person.curr_floor] + self.floors_to_visit
self.going_up = True
self.docked = False
self.people_waiting.append(person)
else:
self.floors_to_visit.append(person.curr_floor)
self.people_waiting.append(person)
def handle_input_request(self, floor_num):
self.floors_to_visit.append(floor_num)
def go_to_next(self):
if not self.floors_to_visit:
self.print_curr_status()
return
self.curr_floor = self.floors_to_visit.pop(0)
for i,person in enumerate(self.people_waiting):
if person.curr_floor == self.curr_floor:
person.riding = True
person.press_floor_num()
self.people_waiting.pop(i)
return
class Person:
def __init__(self, assigned_elevator, curr_floor):
self.curr_floor = curr_floor
self.desired_floor = math.floor(random() * 10)
self.assigned_elevator = assigned_elevator
self.riding = False
def print_floor(self):
print(self.desired_floor)
def call_elevator(self):
self.assigned_elevator.handle_call_request(self)
def press_floor_num(self):
self.assigned_elevator.handle_input_request(self.desired_floor)
my_elevator = Elevator(20)
while True:
for i in range(3):
some_person = Person(my_elevator, math.floor(random() * 10))
some_person.call_elevator()
my_elevator.go_to_next()
my_elevator.print_curr_status()
time.sleep(1)
No threding is neccessary. You can introduce 2 new variables: one keeping track on the time the elevator started and one for the time an elevator ride should take. Then just just check when the elevator has run long enough. You can do this calling the function time.time(); it'll return the time in seconds since January 1, 1970 (since you're only interested in the difference it doesn't matter; you just need a function that increment in time). Although, this function often can't give a more accurate time period than 1 second. If you feel it's to inaccurate on your machine then you could use datetime.
class Elevator:
def __init__(self, num_floors):
self.start_time = 0
self.ride_duration = 1
...
def call_elevator(self):
self.start_time = time.time()
self.assigned_elevator.handle_call_request(self)
def go_to_next(self):
if time.time() - self.start_time < self.ride_duration:
return # Do nothing.
else:
...
You'll probably need to refactor the code to suit your needs and add some logic on what to do when the elevator is in use, etc.

wxPython Rendering issues, very slow and crashes, not sure why

I have been creating a simple tile based game to help me learn python and wx python. For starters I wanted to create my own 'world' and to test the simple map generator I made, I bound the return key to generate a new map and display it.
That is when I ran into this problem. It slows down a lot every time you click return, renders each tile line by line (which is obviously slow and inefficient) and eventually just freezes.
I am quite a novice programmer and have never dealt with any form of GUI so this is all very new to me, please bear with me! I can guess that the way I have things set up is very taxing for the machine, and that perhaps I'm causing a lot of recursion. I'm simply unaware. Also I'm not too familiar with OOP so I just follow examples to create my classes (hence why I only have 1 massive class that handles everything, Im not too sure on all the '__ something__' functions.)
Here is all the code I have written so far, please ignore commented-out sections, they are for future functions etc:
import wx
import random
#main screen class, handles all events within the main screen
class MainScreen(wx.Frame):
hMap = []
tTotalX = 50
tTotalY = 50
def __init__(self, *args, **kwargs):
#This line is equivilant to wx.Frame.__init__('stuff')
super(MainScreen, self).__init__(None, -1, 'You shouldnt see this', style = wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX)
self.renderScreen()
def genMap(self,tTotalX,tTotalY):
count1 = 0
count2 = 0
self.hMap = []
while count1 < tTotalY:
count2 = 0
newrow = []
while count2 < tTotalX:
newrow.append(random.randint(1,120))
count2 += 1
self.hMap.append(newrow)
count1 += 1
self.smooth(tTotalX, tTotalY)
self.smooth(tTotalX, tTotalY)
def smooth(self, tTotalX, tTotalY):
countx = 0
county = 0
while county < tTotalY:
countx = 0
while countx < tTotalX:
above = county - 1
below = int(county + 1)
east = int(countx + 1)
west = int(countx - 1)
if east >= tTotalX:
east = 0
if west < 0:
west = tTotalX -1
teast = self.hMap[county][east]
twest = self.hMap[county][west]
if above < 0 or below >= tTotalY:
smooth = (self.hMap[county][countx] + teast + twest)/3
else:
tabove = self.hMap[above][countx]
tbelow = self.hMap[below][countx]
smooth = (self.hMap[county][countx] + tabove + tbelow + teast + twest)/5
self.hMap[countx][county] = int(smooth)
countx += 1
county += 1
def getTileType(self, coordX, coordY, totalX, totalY):
#this is the part of map creation, getting tile type based on tile attributes
tType = ''
height = self.hMap[coordX][coordY]
#the below values are all up to tweaking in order to produce the best maps
if height <= 55:
tType = 'ocean.png'
if height > 55:
tType = 'coast.png'
if height > 60:
tType = 'grassland.png'
if height > 75:
tType = 'hills.png'
if height > 80:
tType = 'mountain.png'
if tType == '':
tType = 'grassland.png'
return tType
#render the main screen so that it dislays all data
def renderScreen(self):
frameSize = 810 #Size of the game window
tTotalX = self.tTotalX #the dimensions of the tile display, setting for in-game coordinates
tTotalY = self.tTotalY
#tsTiny = 1 #ts = Tile Size
#tsSmall = 4
tsMed = 16
#tsLrg = 32
#tsXlrg = 64
tsCurrent = tsMed #the currently selected zoom level, for now fixed at tsMed
pposX = 0 #ppos = Pixel Position
pposY = 0
tposX = 0 #tpos = tile position, essentially the tile co-ordinates independent of pixel position
tposY = 0
#The below is just an example of how to map out the grid, it should be in its own function in due time
self.genMap(tTotalX, tTotalY)
while tposY < tTotalY: #loops through all y coordinates
tposX = 0
while tposX < tTotalX: #loops through all x coordinates
pposX = tposX*tsCurrent
pposY = tposY*tsCurrent
tiletype = self.getTileType(tposX,tposY,tTotalX,tTotalY)
img = wx.Image(('F:\First Pass\\' + str(tiletype)), wx.BITMAP_TYPE_ANY).ConvertToBitmap()
wx.StaticBitmap(self, -1, img, (pposX, pposY))#paints the image object (i think)
tposX += 1
tposY += 1
self.Bind(wx.EVT_KEY_DOWN, self.onclick)
self.SetSize((frameSize-4, frameSize+16))
self.SetBackgroundColour('CYAN')
self.Centre()
self.SetTitle('Nations First Pass')
#string = wx.StaticText(self, label = 'Welcome to Nations, First Pass', pos = (tTotalX*tsCurrent/2,tsCurrent*tTotalY/2))
#string.SetFont(wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT))
self.Show()
def onclick(self, e):
key = e.GetKeyCode()
if key == wx.WXK_RETURN:
self.renderScreen()
#game loop
def main():
app = wx.App()
MainScreen(None)
app.MainLoop()
if __name__ == '__main__':
main()
You will need to fabricate your own 'ocean.png' 'coast.png' 'grassland.png' 'hills.png' and 'mountain.png' (they need to be 16x16 pixels) or you can use mine from the Imgur link:
http://imgur.com/a/uFxfn
Also please change the file path in the img code as appropriate. I need to figure out how to set that to do it itself as well but thats another challenge for another day.
If you have any insight into this I'd be very appreciative.
You are creating a new set of wx.StaticBitmaps each time renderScreen is called, and you are not deleting them before creating a new set. After a while you'll have gobs of widgets stacked up with the old ones no longer visible, but still there consuming resources. At a minimum you should change things so your program makes only one set of wx.StaticBitmaps, keeps track of them, and then call their SetBitmap method when you want to change them.
For even better performance you should forget about the StaticBitmaps and draw the images yourself in an EVT_PAINT handler. StaticBitmaps are intended to be "static", IOW not changing very much. Instead you can implement an EVT_PAINT handler for the window and it will be called whenever the window needs to be redrawn, and you can trigger new redraws simply by calling the window's Refresh method.

Categories

Resources