I'm trying to simulate different tasks running in parallel in Python, and each parallel process is run at different frequencies (e.g. 200 Hz, 100 Hz and 50 Hz). I used code from this question to make a Timer class to run these processes in "Real-Time", but the processes de-synchronize over time (e.i., three 200 Hz tasks can sometimes run in between two 100 Hz tasks).
To synchronize my processes I make tick counters in their shared memory. Every iteration of the 200 Hz process increments a counter, then waits for it to be reset to 0 when the counter reaches 2, while every iteration of 100 Hz process waits for that counter to reach 2 before resetting it. Same thing for the 50 Hz process, but with another counter. I use a while/pass method for the waiting.
Here is the code :
from multiprocessing import Process, Event, Value
import time
# Add Timer class for multiprocessing
class Timer(Process):
def __init__(self, interval, iteration, function, args=[], kwargs={}):
super(Timer, self).__init__()
self.interval = interval
self.iteration = iteration
self.iterationLeft = self.iteration
self.function = function
self.args = args
self.kwargs = kwargs
self.finished = Event()
def cancel(self):
"""Stop the timer if it hasn't finished yet"""
self.finished.set()
def run(self):
startTimeProcess = time.perf_counter()
while self.iterationLeft > 0:
startTimePeriod = time.perf_counter()
self.function(*self.args, **self.kwargs)
# print(self.interval-(time.clock() - startTimePeriod))
self.finished.wait(self.interval-(time.perf_counter() - startTimePeriod))
self.iterationLeft -= 1
print(f'Process finished in {round(time.perf_counter()-startTimeProcess, 5)} seconds')
def func0(id, freq, tick_p1):
# Wait for 2 runs of Process 1 (tick_p1)
while tick_p1.value < 2:
pass
tick_p1.value = 0 # Reset tick_p1
# Add fake computational time depending on the frequency of the process
print(f'id: {id} at {freq} Hz')
if freq == 400:
time.sleep(0.002)
elif freq == 200:
time.sleep(0.003)
elif freq == 100:
time.sleep(0.007)
elif freq == 50:
time.sleep(0.015)
def func1(id, freq, tick_p1, tick_p2):
# Wait for tick_p1 to have been reset by Process0
while tick_p1.value >= 2:
pass
# Wait for 2 runs of Process 2 (tick_p2)
while tick_p2.value < 2:
pass
tick_p2.value = 0 # Reset tick_p2
# Add fake computational time depending on the frequency of the process
print(f'id: {id} at {freq} Hz')
if freq == 400:
time.sleep(0.002)
elif freq == 200:
time.sleep(0.003)
elif freq == 100:
time.sleep(0.007)
elif freq == 50:
time.sleep(0.015)
# Increment tick_p1
tick_p1.value += 1
def func2(id, freq, tick_p2):
# Wait for tick_p2 to have been reset by Process1
while tick_p2.value >= 2:
pass
# Add fake computational time depending on the frequency of the process
print(f'id: {id} at {freq} Hz')
if freq == 400:
time.sleep(0.002)
elif freq == 200:
time.sleep(0.003)
elif freq == 100:
time.sleep(0.007)
elif freq == 50:
time.sleep(0.015)
# Increment tick_p2
tick_p2.value += 1
if __name__ == '__main__':
freqs = [50,100,200]
# freqs = [0.25,0.5,1]
Tf = 10
tick_p1 = Value('i', 1)
tick_p2 = Value('i', 1)
processes = []
p0 = Timer(interval=1/freqs[0], iteration=round(Tf*freqs[0]), function = func0, args=(0,freqs[0], tick_p1))
p1 = Timer(interval=1/freqs[1], iteration=round(Tf*freqs[1]), function = func1, args=(1,freqs[1], tick_p1, tick_p2))
p2 = Timer(interval=1/freqs[2], iteration=round(Tf*freqs[2]), function = func2, args=(2,freqs[2], tick_p2))
processes.append(p0)
processes.append(p1)
processes.append(p2)
start = time.perf_counter()
for process in processes:
process.start()
for process in processes:
process.join()
finish = time.perf_counter()
print(f'Finished in {round(finish-start, 5)} seconds')
As you can see, I've added sleep time within the processes, to simulate computational time. When I remove the print commands in the processes, the script requires 10.2 seconds of runtime to simulate 10 seconds of "real-time" calculations (2% increase, which is acceptable).
My question is, is this the best way to achieve what I'm trying to do? Is there a better/faster way?
Thanks!
I figured out a cleaner way to do this, but I'm still open to other suggestions.
Basically, instead of waiting for the moment to execute the next iteration, I've created a scheduler that flags when to run a cycle on each process (using shared Values).
There is a fast processes (lets say p2 running at 400 Hz) and all other processes must be a slower multiple of the frequency (lets say p1 and p0 200 and 100 Hz).
Instead of waiting for the right moment to raise execution flag (with wait() or sleep()), the scheduler loops with a while loop and checks if a period of p2 has ended. If the condition is met, it raised p2Flag and restart the period. Each process has its own flag, and the slower processes' flags get raised according to a counter which is incremented every period of p2. If 2 timesteps of p2 have run since the last time p1 was called, this scheduler will "wait" for p1 to be completed before raising p2's and p1's flag.
It's a bit complicated, but this ensures slower machines will obtain the same results as machines who can run this in "real-time".
from multiprocessing import Process, Value
import time
def func0(id, freq, endFlag, p0Flag, runIdx, Ts):
while (endFlag.value == 0):
if (p0Flag.value == 1):
t = round(runIdx.value*Ts, 4)
# Add fake computational time depending on the frequency of the process
# print(f'id: {id} at {freq} Hz at {t}s')
if freq == 400:
time.sleep(0.002)
elif freq == 200:
time.sleep(0.003)
elif freq == 100:
time.sleep(0.007)
elif freq == 50:
time.sleep(0.015)
# Lower flag to confirm completion of cycle
p0Flag.value = 0
def func1(id, freq, endFlag, p1Flag, runIdx, Ts):
while (endFlag.value == 0):
if (p1Flag.value == 1):
t = round(runIdx.value*Ts, 4)
# Add fake computational time depending on the frequency of the process
# print(f'id: {id} at {freq} Hz at {t}s')
if freq == 400:
time.sleep(0.002)
elif freq == 200:
time.sleep(0.003)
elif freq == 100:
time.sleep(0.007)
elif freq == 50:
time.sleep(0.015)
# Lower flag to confirm completion of cycle
p1Flag.value = 0
def func2(id, freq, endFlag, p2Flag, runIdx, Ts):
while (endFlag.value == 0):
if (p2Flag.value == 1):
t = round(runIdx.value*Ts, 4)
# Add fake computational time depending on the frequency of the process
# print(f'id: {id} at {freq} Hz at {t}s')
if freq == 500:
time.sleep(0.0015)
elif freq == 400:
time.sleep(0.002)
elif freq == 200:
time.sleep(0.003)
elif freq == 100:
time.sleep(0.007)
elif freq == 50:
time.sleep(0.015)
# Update time for next iteration
runIdx.value += 1
# Lower flag to confirm completion of cycle
p2Flag.value = 0
if __name__ == '__main__':
# Set frequencies of processes
# Last value of freqs is the fastest one, for process p2
freqs = [50,100,200] # Hz
freqs = [100,200,400] # Hz
# freqs = [0.25,0.5,1] # Hz
Tf = 10
Ts = round(1/freqs[-1], 4)
# Create shared values for time index (runIdx)
# Various flags to trigger the execution of the code in each process (p0Flag, ...)
# A flag to en all processes
runIdx = Value('I',0)
p0Flag = Value('b', 0)
p1Flag = Value('b', 0)
p2Flag = Value('b', 0)
endFlag = Value('b', 0)
# How many times the fastest process has to run before flagging the slower processes
p0_counter_exe = freqs[-1]/freqs[0]
p1_counter_exe = freqs[-1]/freqs[1]
if (not(freqs[-1] % freqs[0] == 0) or not(freqs[-1] % freqs[1] == 0)):
raise Exception("Update rates for processes must be a multiple of the dynamic's update rate.")
if (freqs[-1] < freqs[0]) or (freqs[-1] < freqs[1]):
raise Exception("Dynamics update rate must be the fastest.")
# p2 is at fastest frequency, p1 and p0 at lower frequencies
p0 = Process(target=func0, args=(0, freqs[0], endFlag, p0Flag, runIdx, Ts))
p1 = Process(target=func1, args=(1, freqs[1], endFlag, p1Flag, runIdx, Ts))
p2 = Process(target=func2, args=(2, freqs[2], endFlag, p2Flag, runIdx, Ts))
processes = []
processes.append(p0)
processes.append(p1)
processes.append(p2)
for process in processes:
process.start()
time.sleep(0.5)
# Start subprocesse's counters to execute directly at the first timestep
p0_counter = p0_counter_exe
p1_counter = p1_counter_exe
# Scheduler
#------------
startTime = time.perf_counter()
periodEnd = time.perf_counter()
while (runIdx.value*Ts < Tf):
periodTime = time.perf_counter()-periodEnd
do_p2 = False
# Wait for new timestep AND completion of p2
if (periodTime >= Ts and p2Flag.value == 0):
# If p0 or p1 are expected to finish before the new timestep, wait for their completion
# Depending on the situation, if slower processes have finished their cycle, make do_p2 True
if (p1_counter == p1_counter_exe) and (p0_counter == p0_counter_exe):
if (p1Flag.value == 0) and (p0Flag.value == 0):
do_p2 = True
elif (p1_counter == p1_counter_exe):
if (p1Flag.value == 0):
do_p2 = True
elif (p0_counter == p0_counter_exe):
if (p0Flag.value == 0):
do_p2 = True
else:
do_p2 = 1
# If do_p2 is True, raise p2Flag for the p2 process
if (do_p2):
periodEnd = time.perf_counter()
p2Flag.value = 1
# If it's time to start a cycle for the slower processes, raise their flag and reset their counter
if (p1_counter == p1_counter_exe):
p1Flag.value = 1
p1_counter = 0
if (p0_counter == p0_counter_exe):
p0Flag.value = 1
p0_counter = 0
# Increment slower processes counter
p1_counter += 1
p0_counter += 1
# Close all processes
endFlag.value = 1
for process in processes:
process.join()
print(f'Finished in {round(time.perf_counter()-startTime, 5)} seconds')
print(Ts*runIdx.value)
Related
The code has successfully able to compute same value of arrival times given by user input. However, its calculations are inaccurate. I do think the code messed up its computation in the initialisation part of priority queue. Please help me to modify it where it can accurately solve/compute with same value of arrival times as well as burst times.
Here is the code:
import sys
import pandas as pd
def srtf():
num_processes = []
# Get number of processes from user (between 5 and 10)
while True:
try:
num_processes = int(input("Enter the number of processes (minimum of 5 and maximum of 10): "))
if 5 <= num_processes <= 10:
break
else:
print("Invalid input. Number of processes must be between 5 and 10.")
except ValueError:
print("Invalid input. Only integers are allowed.")
print("\n")
# Get burst times and arrival times for each process
burst_times = []
for i in range(num_processes):
while True:
try:
burst_time = int(input("Enter burst time for process {} (minimum of 5 and maximum of 15): ".format(i+1)))
if 5 <= burst_time <= 15:
burst_times.append(burst_time)
break
else:
print("\nInvalid input. Burst time must be between 5 and 15.")
except ValueError:
print("\nInvalid input. Only integers are allowed.")
print("\n")
arrival_times = []
for i in range(num_processes):
while True:
try:
arrival_time = int(input("Enter arrival time for process {} (minimum of 0 and maximum of 12): ".format(i+1)))
if 0 <= arrival_time <= 12:
arrival_times.append(arrival_time)
break
else:
print("\nInvalid input. Arrival time must be between 0 and 12.")
except ValueError:
print("\nInvalid input. Only integers are allowed.")
# Initialize variables
process_id = [i+1 for i in range(num_processes)]
remaining_time = burst_times.copy()
completion_time = [0 for i in range(num_processes)]
waiting_time = [0 for i in range(num_processes)]
turnaround_time = [0 for i in range(num_processes)]
response_time = [0 for i in range(num_processes)]
current_time = 0
completed_processes = 0
import heapq
...
# Initialize the priority queue
priority_queue = []
...
while completed_processes != num_processes:
# Add all processes that have arrived to the priority queue
for i in range(num_processes):
if arrival_times[i] <= current_time and remaining_time[i] > 0:
heapq.heappush(priority_queue, (remaining_time[i], i))
if priority_queue:
remaining_time[priority_queue[0][1]] -= 1
current_time += 1
if response_time[priority_queue[0][1]] == 0: # first time process runs
response_time[priority_queue[0][1]] = current_time - arrival_times[priority_queue[0][1]]
if remaining_time[priority_queue[0][1]] == 0:
completed_processes += 1
completion_time[priority_queue[0][1]] = current_time
heapq.heappop(priority_queue)
else:
current_time += 1
# Calculate waiting time and turnaround time
for i in range(num_processes):
waiting_time[i]= completion_time[i] - burst_times[i] - arrival_times[i]
turnaround_time[i] = completion_time[i] - arrival_times[i]
# Calculate average waiting time, average turnaround time and average response time
avg_waiting_time = sum(waiting_time) / len(waiting_time)
avg_turnaround_time = sum(turnaround_time) / len(turnaround_time)
# Create a table with the results
results = {"Process ID": process_id, "Burst Time": burst_times, "Arrival Time": arrival_times,
"Completion Time": completion_time, "Waiting Time": waiting_time, "Turnaround Time": turnaround_time, "Response Time": response_time}
df = pd.DataFrame(results)
# Print the table
print("\n", df)
# Print the average turnaround time, waiting time and response time
print("\n")
print("Average Turnaround Time: ", "{:.2f}".format(avg_turnaround_time))
print("Average Waiting Time: ", "{:.2f}".format(avg_waiting_time))
return df, avg_turnaround_time, avg_waiting_time
srtf()
This is an example of the output(inaccurate) from the terminal:
I'd like to code a bot, that is able to play a game, which is called "piano Tiles". Basically there are four vertical lanes, and there are black tiles falling down in each lane. The player needs to click them before they reach the End of the screen. Its getting faster while playing.
My goal is to get the highest score possible. The world record is currently at 17 clicks/second(tiles/second). I am not able to get any higher than 15 clicks/second but i am just not able to tell where i slow my script down.
My first approach was to check one single pixel/lane with pyautogui.pixel(x,y) and if its rgb value == the color of the tile - click on that position. Score with that variant ~10 clicks/second.
After that i calculated an offset to keep on with the acceleration, basically i added an increasing number to the y position of that click, which left me at arround 12 clicks/second.
I recorded it and watched it fail frame by frame. What happend is, that eventually the game got so fast that the script for an example is not able to detect pixel in "lane 1" while click happens in "lane 4"
The solution, i came up with, is multiprocessing and pypy.
import pyautogui
import multiprocessing
import time
time.sleep(2)
print("READYprint")
def Lane1():
a = 0
b = 0
pyautogui.PAUSE = 0
while True:
if pyautogui.pixel(800, 520) [0] == 0:
pyautogui.click(x=800, y=520 + b)
a = a + 1
b = a // 15
def Lane2():
a = 0
b = 0
pyautogui.PAUSE = 0
while True:
if pyautogui.pixel(902, 520) [0] == 0:
pyautogui.click(x=902, y=520 + b)
a = a + 1
b = a // 15
def Lane3():
a = 0
b = 0
pyautogui.PAUSE = 0
while True:
if pyautogui.pixel(1033, 520) [0] == 0:
pyautogui.click(x=1033, y=520 + b)
a = a + 1
b = a // 15
def Lane4():
a = 0
b = 0
pyautogui.PAUSE = 0
while True:
if pyautogui.pixel(1134, 520) [0] == 0:
pyautogui.click(x=1134, y=520 + b)
a = a + 1
b = a // 15
p1 = multiprocessing.Process(target=Lane1)
p2 = multiprocessing.Process(target=Lane2)
p3 = multiprocessing.Process(target=Lane3)
p4 = multiprocessing.Process(target=Lane4)
if __name__ == "__main__":
p1.start()
p2.start()
p3.start()
p4.start()
i also did run a test, to benchmark the performance of the script.
import time
import pyautogui
start_time = time.time()
def test():
a = 0
b = 0
pyautogui.PAUSE = 0
while a < 100:
if pyautogui.pixel(970, 208) [0] == 255:
pyautogui.click(x=970, y=208 + b)
a = a + 1
b = a // 15
test()
print("--- %s seconds ---" % (time.time() - start_time))
Output: --- 1.7149109840393066 seconds ---, which is for 100 times "get rgb value + click, if its white."
I have no idea why the bot is that "slow" then. When it fails its far away from that 0,017... seconds/checked_click. Could the reason for this be the multiprocessing? While it did get a bit faster, it should have been a lot faster. I also did run it without pypy. Without the pypy JIT its ~13 clicks/second.
Not a direct answer to your question but it might be faster to get a screenshot for all four lanes at once. pyautogui.pixel() combines the screenshot and getpixel functions. So your code gets a screenshot individually for each lane. A faster way might be to do this:
xlocations= [800,902,1033,1134]
yloc=520
while True:
im = pyautogui.screenshot()
for xloc in xlocations:
if im.getpixel((xloc, yloc)) [0] == 0:
pyautogui.click(x=xloc, y=yloc+ b)
a = a + 1
b = a // 15
The "final" solution i came up with was the following:
import time
import win32api, win32con, win32ui
import multiprocessing
time.sleep(1)
print("RDY")
def Lane1():
window_name = "App Player"
wd = win32ui.FindWindow(None, window_name)
while True:
dc = wd.GetWindowDC()
try:
j = dc.GetPixel (35,230)
if j == 0:
win32api.SetCursorPos((35, 230))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
time.sleep(0.01)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0)
dc.DeleteDC()
except win32ui.error:
print("we got an error 1")
continue
def Lane2():
window_name = "App Player"
wd = win32ui.FindWindow(None, window_name)
while True:
dc = wd.GetWindowDC()
try:
j = dc.GetPixel (60,240)
if j == 0:
win32api.SetCursorPos((60, 240))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
time.sleep(0.01)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0)
dc.DeleteDC()
except win32ui.error:
print("we got an error 2")
continue
def Lane3():
window_name = "App Player"
wd = win32ui.FindWindow(None, window_name)
while True:
dc = wd.GetWindowDC()
try:
j = dc.GetPixel (120,250)
if j == 0:
win32api.SetCursorPos((120, 250))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
time.sleep(0.01)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0)
dc.DeleteDC()
except win32ui.error:
print("we got an error 3")
continue
def Lane4():
window_name = "App Player"
wd = win32ui.FindWindow(None, window_name)
while True:
dc = wd.GetWindowDC()
try:
j = dc.GetPixel (180,260)
if j == 0:
win32api.SetCursorPos((180, 260))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
time.sleep(0.01)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0)
dc.DeleteDC()
except win32ui.error:
print("we got an error 4")
continue
p1 = multiprocessing.Process(target=Lane1)
p2 = multiprocessing.Process(target=Lane2)
p3 = multiprocessing.Process(target=Lane3)
p4 = multiprocessing.Process(target=Lane4)
if __name__ == "__main__":
p1.start()
p2.start()
p3.start()
p4.start()
p1.join()
p2.join()
p3.join()
p4.join()
i checked pixels on different y positions to not have this race condition between the different processes, which did lead to missklicks. Also it helped a lot to keep the game window as small as possible because emulators tend to be laggy.
With this version of the bot the score is about 22 clicks/second.
I am trying to make a timer that counts down to 0, then starts counting up. I am using the time and keyboard modules.
The keyboard module from PyPi.
Everything works as expected, and I am able to press a button to close the program, but it only works at the beginning of each iteration. Is there a way for it to check for a key press at any point while the loop is running? Do I need to be using a different module?
This is my code:
import time
import keyboard
m = 2
s = 0
count_down = True
while True:
if keyboard.is_pressed('q'):
break
print(f"{m} minutes, {s} seconds")
if count_down:
if s == 0:
m -= 1
s = 60
s -= 1
elif not count_down:
s += 1
if s == 60:
m += 1
s = 0
if m == 0 and s == 0:
count_down = False
time.sleep(1)
Using callback is common approach in such case, here is solution:
import time
import keyboard
m = 2
s = 0
count_down = True
break_loop_flag = False
def handle_q_button():
print('q pressed')
global break_loop_flag
break_loop_flag = True
keyboard.add_hotkey('q', handle_q_button)
while True:
if break_loop_flag:
break
print(f"{m} minutes, {s} seconds")
if count_down:
if s == 0:
m -= 1q
s = 60
s -= 1
elif not count_down:
s += 1
if s == 60:
m += 1
s = 0
if m == 0 and s == 0:
count_down = False
time.sleep(1)
If you want to do any two things in parallel, independently of another, you need to consider using multiprocessing. However, even if you do, your loop will either still need to check if a key has been registered in the other process, or you need to terminate the process running the loop forcefully, which may result in unexpected outcomes.
However, in your case, since there are no side effects like files being written, this would work:
import time
import keyboard
from multiprocessing import Process
def print_loop():
m = 2
s = 0
count_down = True
while True:
print(f"{m} minutes, {s} seconds")
if count_down:
if s == 0:
m -= 1
s = 60
s -= 1
elif not count_down:
s += 1
if s == 60:
m += 1
s = 0
if m == 0 and s == 0:
count_down = False
time.sleep(1)
def main():
p = Process(target=print_loop)
p.start()
# this loop runs truly in parallel with the print loop, constantly checking
while True:
if keyboard.is_pressed('q'):
break
# force the print loop to stop immediately, without finishing the current iteration
p.kill()
if __name__ == '__main__':
main()
I am trying to run multiProcessing in python for the first time but when I debug in PyCharm I see that Thread 8 is waiting for a lock and I believe this is where my code is slowing down. I thought about mapping process pool in the while loop but this seems redundant to do this. Am i on the right track with this?
import random
import time
import math
import multiprocessing as mp # alias
# from multiprocessing.dummy import Pool as ThreadPool
# initial sequence:
# HHTHTHHHTHHHTHTH
coinChoices = ["H", "T"] # choices
idealFlip = "HHTHTHHH" # string to match
flip = "" # resets flip
margin_error = 0.1 # accuracy
num_matches = 0.0 # matched strings
probability = 0 # calc. probability
counter = 0 # iterations
flag = 1 # exit token
secondFlag = 1
check = math.ceil(pow(2, len(idealFlip))/2) # used for printing prob
# flips a coin *NOT USED*
def flip_coin(coins):
return str(random.choice(coins))
# requests (num) tasks to be completed
def flip_coin_num(num):
return str(random.choice(coinChoices))
# theoretical probability
def compute_probability():
size = len(idealFlip)
return math.pow(0.5, size)
# actual probability
def empirical_probability(count, num_flips):
return count / num_flips
# TODO: implement multiprocessing
if __name__ == "__main__":
# print("# cores: %d" % mp.cpu_count())
probability = compute_probability()
print("\nInitial probability of landing on the sequence: " + str(probability) + "\n")
actualProb = 0
empiricalProb = 0
tasks = range(len(idealFlip))
pool = mp.Pool(processes=4)
while flag != 0 or counter == 1000:
temp = pool.map(flip_coin_num, tasks)
# add other processes?
# handles close / join
flip = "".join(temp)
# print(temp)
# print(flip)
if counter != 0:
empiricalProb = empirical_probability(num_matches, counter)
if flip == idealFlip:
num_matches += 1
counter += 1
flip = ""
if counter % check is 0:
print("Probability" + str(empricalProb))
I have the following code which turns an outlet on/off every 3 seconds.
start_time = time.time()
counter = 0
agent = snmpy4.Agent("192.168.9.50")
while True:
if (counter % 2 == 0):
agent.set("1.3.6.1.4.1.13742.6.4.1.2.1.2.1.1",1)
else:
agent.set("1.3.6.1.4.1.13742.6.4.1.2.1.2.1.1", 0)
time.sleep(3- ((time.time()-start_time) % 3))
counter = counter + 1
Is there a way I can have the loop terminate at any given point if something is entered, (space) for example... while letting the code above run in the mean time
You can put the loop in a thread and use the main thread to wait on the keyboard. If its okay for "something to be entered" can be a line with line feed (e.g., type a command and enter), then this will do
import time
import threading
import sys
def agent_setter(event):
start_time = time.time()
counter = 0
#agent = snmpy4.Agent("192.168.9.50")
while True:
if (counter % 2 == 0):
print('agent.set("1.3.6.1.4.1.13742.6.4.1.2.1.2.1.1",1)')
else:
print('agent.set("1.3.6.1.4.1.13742.6.4.1.2.1.2.1.1", 0)')
if event.wait(3- ((time.time()-start_time) % 3)):
print('got keyboard')
event.clear()
counter = counter + 1
agent_event = threading.Event()
agent_thread = threading.Thread(target=agent_setter, args=(agent_event,))
agent_thread.start()
for line in sys.stdin:
agent_event.set()