I've created a program that streams through twitter and based on the result of generated by the tweets it plays music using the pygame library. Below is a sample of my code.
class listener(StreamListener):
def on_status(self, status):
global mood_happy, mood_sad, mood_angry, mood_shocked, mood_romantic
try:
# print status
tweet_text = status.text
for mood_n_score in [[happy, 'mood_happy'], [sad, 'mood_sad'], [angry, 'mood_angry'],
[shocked, 'mood_shocked'], [romantic, 'mood_romantic']]:
lst_mood = mood_n_score[0]
type_mood = mood_n_score[1]
for mood in lst_mood:
if mood in tweet_text:
if type_mood == 'mood_happy':
mood_happy += 1
elif type_mood == 'mood_sad':
mood_sad += 1
elif type_mood == 'mood_angry':
mood_angry += 1
elif type_mood == 'mood_shocked':
mood_shocked += 1
else:
mood_romantic += 1
break
print('\n----------------')
print 'mood_happy:', mood_happy
print 'mood_sad:', mood_sad
print 'mood_angry:', mood_angry
print 'mood_shocked:', mood_shocked
print 'mood_romantic:', mood_romantic
top_mood=max(mood_happy,mood_sad,mood_angry,mood_shocked,mood_romantic)
if top_mood==mood_happy:
print "the mood is: happy"
pygame.mixer.music.load(file.mp3)
pygame.mixer.music.play()
As you can see, I have a streamer class which streams through twitter continously and prints the top mood. When I run my code to play the mp3 file, the streaming stops and only the music plays. How can I make my program stream through twitter and play music at the same time?
Thank you!
I have never used pygame, but based on what it does I think I can probably assume it's not thread-safe.
What I would do is have the streaming code in a thread using the threading module and have the music playing logic constantly wait on the main thread for a threading.Event to be set.
import threading
import pygame
new_mood_event = threading.Event()
class TwitterStreamer(StreamListener):
def run(self):
while True: # keep the streamer going forever
pass # define your code here
def on_status(self, status):
# ... Define your code here
if top_mood == mood_happy:
new_mood_event.mp3_file_path = 'happy_file.mp3'
new_mood_event.set() # alert the main thread we have a new mood to play
if __name__ == '__main__':
twitter_streamer = TwitterStreamer()
streaming_thread = threading.Thread(target=twitter_streamer.run) # creates a thread that will call `twitter_streamer.run()` when executed
streaming_thread.start() # starts the thread
# everything from here will be run in the main thread
while True: # creates an "event loop"
new_mood_event.wait() # blocks the main thread until `new_mood_event.set()` is called by `on_status`
new_mood_event.clear() # clears the event. if we don't clear the event, then `new_mood_event.wait()` will only block once
pygame.mixer.music.load(new_mood_event.mp3_file_path)
pygame.mixer.music.play()
Related
How to indeterminately record user's audio, if and only if when the user press ctrl key and shut down the recording loop when the user press ctrl+c keys? So far based on some online examples build this script:
from pynput import keyboard
import time, os
import pyaudio
import wave
import sched
import sys
from playsound import playsound
CHUNK = 8192
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
WAVE_OUTPUT_FILENAME = "mic.wav"
p = pyaudio.PyAudio()
frames = []
def callback(in_data, frame_count, time_info, status):
frames.append(in_data)
return (in_data, pyaudio.paContinue)
class MyListener(keyboard.Listener):
def __init__(self):
super(MyListener, self).__init__(self.on_press, self.on_release)
self.key_pressed = None
self.wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
self.wf.setnchannels(CHANNELS)
self.wf.setsampwidth(p.get_sample_size(FORMAT))
self.wf.setframerate(RATE)
def on_press(self, key):
try:
if key.ctrl:
self.key_pressed = True
return True
except AttributeError:
sys.exit()
def on_release(self, key):
if key.ctrl:
self.key_pressed = False
return True
listener = MyListener()
listener.start()
started = False
stream = None
def recorder():
global started, p, stream, frames
while True:
try:
if listener.key_pressed and not started:
# Start the recording
try:
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK,
stream_callback = callback)
print("Stream active:", stream.is_active())
started = True
print("start Stream")
except KeyboardInterrupt:
print('\nRecording finished: ' + repr(WAVE_OUTPUT_FILENAME))
quit()
elif not listener.key_pressed and started:
print("Stop recording")
listener.wf.writeframes(b''.join(frames))
listener.wf.close()
print("You should have a wav file in the current directory")
print('-> Playing recorded sound...')
playsound(str(os.getcwd())+'/mic.wav')
os.system('python "/Users/user/rec.py"')
except KeyboardInterrupt:
print('\nRecording finished: ' + repr(WAVE_OUTPUT_FILENAME))
quit()
except AttributeError:
quit()
print ("-> Press and hold the 'ctrl' key to record your audio")
print ("-> Release the 'ctrl' key to end recording")
recorder()
The problem is that it is really inefficient, for example the computer starts heating up. The only way I found to make the program keep running and recording different audio samples was with: os.system('python "/Users/user/rec.py"'). For finishing the program I tried to either catch the exception with:
except AttributeError:
sys.exit()
or with the user input:
if key.ctrl_c:
sys.exit()
Based on pyinput docs, I tried to make effective usage of the listeners. However, for this specific scenario which is the recommended way of using those listeners?
As to the primary concern of your computer seeming to work terribly hard, that's because you use a while loop to constantly check for when the record key is released. Within this loop, the computer will loop around as fast as it can without ever taking a break.
A better solution is to use event driven programming where you let the OS inform you of events periodically, and check if you want to do anything when they happen. This may sound complicated, but fortunately pynput does most of the hard work for you.
If you keep track of the state of the recording or playback, it is also fairly simple to start a new recording the next time a control key down event happens without needing the "hack" of calling an entire new process recursively for each new recording. The event loop inside the keyboard listener will keep on going until one of the callback functions returns False or raises self.stopException().
I have created a simple listener class similar to your initial attempt that calls on a recorder or player instance (which I'll get to later) to start and stop. I also have to agree with Anwarvic that <ctl-c> is supposed to be reserved as an emergency way of stopping a script, so I have changed the stop command to the letter q.
class listener(keyboard.Listener):
def __init__(self, recorder, player):
super().__init__(on_press = self.on_press, on_release = self.on_release)
self.recorder = recorder
self.player = player
def on_press(self, key):
if key is None: #unknown event
pass
elif isinstance(key, keyboard.Key): #special key event
if key.ctrl and self.player.playing == 0:
self.recorder.start()
elif isinstance(key, keyboard.KeyCode): #alphanumeric key event
if key.char == 'q': #press q to quit
if self.recorder.recording:
self.recorder.stop()
return False #this is how you stop the listener thread
if key.char == 'p' and not self.recorder.recording:
self.player.start()
def on_release(self, key):
if key is None: #unknown event
pass
elif isinstance(key, keyboard.Key): #special key event
if key.ctrl:
self.recorder.stop()
elif isinstance(key, keyboard.KeyCode): #alphanumeric key event
pass
if __name__ == '__main__':
r = recorder("mic.wav")
p = player("mic.wav")
l = listener(r, p)
print('hold ctrl to record, press p to playback, press q to quit')
l.start() #keyboard listener is a thread so we start it here
l.join() #wait for the tread to terminate so the program doesn't instantly close
With that structure, we then need a recorder class with a start and stop function which will not block (asynchronous) the listener thread from continuing to receive key events. The documentation for PyAudio gives a pretty good example for asynchronous output, so I simply applied it to an input. A little bit of re-arranging, and a flag to let our listener know when we're recording later, and we have a recorder class:
class recorder:
def __init__(self,
wavfile,
chunksize=8192,
dataformat=pyaudio.paInt16,
channels=2,
rate=44100):
self.filename = wavfile
self.chunksize = chunksize
self.dataformat = dataformat
self.channels = channels
self.rate = rate
self.recording = False
self.pa = pyaudio.PyAudio()
def start(self):
#we call start and stop from the keyboard listener, so we use the asynchronous
# version of pyaudio streaming. The keyboard listener must regain control to
# begin listening again for the key release.
if not self.recording:
self.wf = wave.open(self.filename, 'wb')
self.wf.setnchannels(self.channels)
self.wf.setsampwidth(self.pa.get_sample_size(self.dataformat))
self.wf.setframerate(self.rate)
def callback(in_data, frame_count, time_info, status):
#file write should be able to keep up with audio data stream (about 1378 Kbps)
self.wf.writeframes(in_data)
return (in_data, pyaudio.paContinue)
self.stream = self.pa.open(format = self.dataformat,
channels = self.channels,
rate = self.rate,
input = True,
stream_callback = callback)
self.stream.start_stream()
self.recording = True
print('recording started')
def stop(self):
if self.recording:
self.stream.stop_stream()
self.stream.close()
self.wf.close()
self.recording = False
print('recording finished')
Finally, we create an audio player for audio playback when you press p. I threw the PyAudio example into a thread which is created everytime you press the button so that multiple players could be created which overlap eachother. We also keep track of how many players are playing so we don't try to record while the file is already in use by a player. (I also have included my imports at the top)
from threading import Thread, Lock
from pynput import keyboard
import pyaudio
import wave
class player:
def __init__(self, wavfile):
self.wavfile = wavfile
self.playing = 0 #flag so we don't try to record while the wav file is in use
self.lock = Lock() #muutex so incrementing and decrementing self.playing is safe
#contents of the run function are processed in another thread so we use the blocking
# version of pyaudio play file example: http://people.csail.mit.edu/hubert/pyaudio/#play-wave-example
def run(self):
with self.lock:
self.playing += 1
with wave.open(self.wavfile, 'rb') as wf:
p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True)
data = wf.readframes(8192)
while data != b'':
stream.write(data)
data = wf.readframes(8192)
stream.stop_stream()
stream.close()
p.terminate()
wf.close()
with self.lock:
self.playing -= 1
def start(self):
Thread(target=self.run).start()
I can't guarantee this is perfectly free of bugs, but if you have any questions on how it works / how to get it working, feel free to comment.
I really don't recommend using ctrl + c for anything rather than interrupting. Also, I don't recommend to keep pressing on a certain button to keep recording. What I suggest is to use a key to record and another to stop; That's why in the following code I've used s to start recording and q to quit. It's totally configurable and you can change based on your preference:
Install Dependencies
pip install pyaudio numpy scipy
sudo pip install keyboard
Recorder
A simple class to record from your mic:
import pyaudio
import keyboard
import numpy as np
from scipy.io import wavfile
class Recorder():
def __init__(self, filename):
self.audio_format = pyaudio.paInt16
self.channels = 1
self.sample_rate = 44100
self.chunk = int(0.03*self.sample_rate)
self.filename = filename
self.START_KEY = 's'
self.STOP_KEY = 'q'
def record(self):
recorded_data = []
p = pyaudio.PyAudio()
stream = p.open(format=self.audio_format, channels=self.channels,
rate=self.sample_rate, input=True,
frames_per_buffer=self.chunk)
while(True):
data = stream.read(self.chunk)
recorded_data.append(data)
if keyboard.is_pressed(self.STOP_KEY):
print("Stop recording")
# stop and close the stream
stream.stop_stream()
stream.close()
p.terminate()
#convert recorded data to numpy array
recorded_data = [np.frombuffer(frame, dtype=np.int16) for frame in recorded_data]
wav = np.concatenate(recorded_data, axis=0)
wavfile.write(self.filename, self.sample_rate, wav)
print("You should have a wav file in the current directory")
break
def listen(self):
print(f"Press `{self.START_KEY}` to start and `{self.STOP_KEY}` to quit!")
while True:
if keyboard.is_pressed(self.START_KEY):
self.record()
break
To use this class, simply call the listen() method like so:
recorder = Recorded("mic.wav") #name of output file
recorder.listen()
Try this:
import sys
try:
#Your code here
except KeyboardInterrupt:
sys.exit()
I am looking to create a UI that displays an animated popup while another task is being carried out. That will exit upon completion. I am using PYSimpleGUI and am using the example listed here to base my work off. I can get a single frame of the animation to display once I start the code and exit upon completion of the task but can't get it to play the entire gif. Code:
import queue
import threading
import time
import PySimpleGUI as sg
# ############################# User callable CPU intensive code #############################
# Put your long running code inside this "wrapper"
# NEVER make calls to PySimpleGUI from this thread (or any thread)!
# Create one of these functions for EVERY long-running call you want to make
def long_function_wrapper(work_id, gui_queue):
# LOCATION 1
# this is our "long running function call"
#time.sleep(10) # sleep for a while as a simulation of a long-running computation
x = 0
while True:
print(x)
time.sleep(0.5)
x = x + 1
if x == 5:
break
# at the end of the work, before exiting, send a message back to the GUI indicating end
gui_queue.put('{} ::: done'.format(work_id))
# at this point, the thread exits
return
def the_gui():
gui_queue = queue.Queue() # queue used to communicate between the gui and long-running code
layout = [[sg.Text('Multithreaded Work Example')],
[sg.Text('This is a Test.', size=(25, 1), key='_OUTPUT_')],
[sg.Button('Go'), sg.Button('Exit')], ]
window = sg.Window('Multithreaded Window').Layout(layout)
# --------------------- EVENT LOOP ---------------------
work_id = 0
while True:
event, values = window.Read(timeout=100) # wait for up to 100 ms for a GUI event
if event is None or event == 'Exit':
#sg.PopupAnimated(None)
break
if event == 'Go': # clicking "Go" starts a long running work item by starting thread
window.Element('_OUTPUT_').Update('Starting long work %s'%work_id)
# LOCATION 2
# STARTING long run by starting a thread
thread_id = threading.Thread(target=long_function_wrapper, args=(work_id, gui_queue,), daemon=True)
thread_id.start()
#for i in range(200000):
work_id = work_id+1 if work_id < 19 else 0
#while True:
sg.PopupAnimated(sg.DEFAULT_BASE64_LOADING_GIF, background_color='white', time_between_frames=100)
#if message == None:
#break
# --------------- Read next message coming in from threads ---------------
try:
message = gui_queue.get_nowait() # see if something has been posted to Queue
except queue.Empty: # get_nowait() will get exception when Queue is empty
message = None # nothing in queue so do nothing
# if message received from queue, then some work was completed
if message is not None:
# LOCATION 3
# this is the place you would execute code at ENDING of long running task
# You can check the completed_work_id variable to see exactly which long-running function completed
completed_work_id = int(message[:message.index(' :::')])
sg.PopupAnimated(None)
#window['_GIF_'].update_animation(sg.DEFAULT_BASE64_LOADING_GIF, time_between_frames=100)
#window.read(timeout = 1000)
# if user exits the window, then close the window and exit the GUI func
window.Close()
############################# Main #############################
if __name__ == '__main__':
the_gui()
print('Exiting Program'
)
You've got your call to popup_animated inside of an "if" statement that is only executed once.
You must call popup_animated for every frame you wish to show. It's not spun off as a task that works in the background.
This change to your code will keep the animation going as long as there as background tasks running.
import queue
import threading
import time
import PySimpleGUI as sg
# ############################# User callable CPU intensive code #############################
# Put your long running code inside this "wrapper"
# NEVER make calls to PySimpleGUI from this thread (or any thread)!
# Create one of these functions for EVERY long-running call you want to make
def long_function_wrapper(work_id, gui_queue):
# LOCATION 1
# this is our "long running function call"
# time.sleep(10) # sleep for a while as a simulation of a long-running computation
x = 0
while True:
print(x)
time.sleep(0.5)
x = x + 1
if x == 5:
break
# at the end of the work, before exiting, send a message back to the GUI indicating end
gui_queue.put('{} ::: done'.format(work_id))
# at this point, the thread exits
return
def the_gui():
gui_queue = queue.Queue() # queue used to communicate between the gui and long-running code
layout = [[sg.Text('Multithreaded Work Example')],
[sg.Text('This is a Test.', size=(25, 1), key='_OUTPUT_')],
[sg.Text(size=(25, 1), key='_OUTPUT2_')],
[sg.Button('Go'), sg.Button('Exit')], ]
window = sg.Window('Multithreaded Window').Layout(layout)
# --------------------- EVENT LOOP ---------------------
work_id = 0
while True:
event, values = window.Read(timeout=100) # wait for up to 100 ms for a GUI event
if event is None or event == 'Exit':
# sg.PopupAnimated(None)
break
if event == 'Go': # clicking "Go" starts a long running work item by starting thread
window.Element('_OUTPUT_').Update('Starting long work %s' % work_id)
# LOCATION 2
# STARTING long run by starting a thread
thread_id = threading.Thread(target=long_function_wrapper, args=(work_id, gui_queue,), daemon=True)
thread_id.start()
# for i in range(200000):
work_id = work_id + 1 if work_id < 19 else 0
# while True:
# if message == None:
# break
# --------------- Read next message coming in from threads ---------------
try:
message = gui_queue.get_nowait() # see if something has been posted to Queue
except queue.Empty: # get_nowait() will get exception when Queue is empty
message = None # nothing in queue so do nothing
# if message received from queue, then some work was completed
if message is not None:
# LOCATION 3
# this is the place you would execute code at ENDING of long running task
# You can check the completed_work_id variable to see exactly which long-running function completed
completed_work_id = int(message[:message.index(' :::')])
window.Element('_OUTPUT2_').Update('Finished long work %s' % completed_work_id)
work_id -= 1
if not work_id:
sg.PopupAnimated(None)
if work_id:
sg.PopupAnimated(sg.DEFAULT_BASE64_LOADING_GIF, background_color='white', time_between_frames=100)
# window['_GIF_'].update_animation(sg.DEFAULT_BASE64_LOADING_GIF, time_between_frames=100)
# window.read(timeout = 1000)
# if user exits the window, then close the window and exit the GUI func
window.Close()
############################# Main #############################
if __name__ == '__main__':
the_gui()
print('Exiting Program')
I'm reading tweets from Twitter Streaming API. After connecting to the API, I'm getting a generator.
I'm looping through each tweet received but I want to exit from the iterator, say, at 18PM. After receiving each tweet, I'm checking if it's later than the specified timestamp and stopping.
The issue is that I'm not receiving tweets frequently enough. So, I could receive one at 17:50 and the next one at 19PM. That's when I'll find out that the time has passed and I need to stop.
Is there a way to force the stop at 18PM exactly?
Here's a high-level view of my code:
def getStream(tweet_iter):
for tweet in tweet_iter:
#do stuff
if time_has_passed():
return
tweet_iter = ConnectAndGetStream()
getStream(tweet_iter)
Create a separate thread for the producer and use a Queue to communicate. I also had to use a threading.Event for stopping the producer.
import itertools, queue, threading, time
END_TIME = time.time() + 5 # run for ~5 seconds
def time_left():
return END_TIME - time.time()
def ConnectAndGetStream(): # stub for the real thing
for i in itertools.count():
time.sleep(1)
yield "tweet {}".format(i)
def producer(tweets_queue, the_end): # producer
it = ConnectAndGetStream()
while not the_end.is_set():
tweets_queue.put(next(it))
def getStream(tweets_queue, the_end): # consumer
try:
while True:
tweet = tweets_queue.get(timeout=time_left())
print('Got', tweet)
except queue.Empty:
print('THE END')
the_end.set()
tweets_queue = queue.Queue() # you might wanna use the maxsize parameter
the_end = threading.Event()
producer_thread = threading.Thread(target=producer,
args=(tweets_queue, the_end))
producer_thread.start()
getStream(tweets_queue, the_end)
producer_thread.join()
Your problem could be resolved by splitting the functionality of your design into two separated processes:
A twitter process that acts as wrapper to Twitter API and
A monitor process that is able to terminate the twitter process when the exit time is reached.
The following piece of code prototypes the functionality described above using Python's multiprocessing module:
import multiprocessing as mp
import time
EXIT_TIME = '12:21' #'18:00'
def twitter():
while True:
print 'Twittttttttttt.....'
time.sleep(5)
def get_time():
return time.ctime().split()[3][:5]
if __name__ == '__main__':
# Execute the function as a process
p = mp.Process( target=twitter, args=() )
p.start()
# Monitoring the process p
while True:
print 'Checking the hour...'
if get_time() == EXIT_TIME:
p.terminate()
print 'Current time:', time.ctime()
print 'twitter process has benn terminated...'
break
time.sleep(5)
Of course you can use p.join(TIMEOUT) instead of using the while True loop presented in my example as pointed here.
Here is an example with threading and python scheduler:
import threading
import time
import os
import schedule
def theKillingJob():
print("Kenny and Cartman die!")
os._exit(1)
schedule.every().day.at("18:00").do(theKillingJob,'It is 18:00')
def getStream(tweet_iter):
for tweet in tweet_iter:
#do stuff
def kenny():
while True:
print("Kenny alive..")
schedule.run_pending()
time.sleep(1)
def cartman():
while True:
print("Cartman alive..")
tweet_iter = ConnectAndGetStream()
getStream(tweet_iter)
# You can change whenever you want to check for tweets by changing sleep time here
time.sleep(1)
if __name__ == '__main__':
daemon_kenny = threading.Thread(name='kenny', target=kenny)
daemon_cartman = threading.Thread(name='cartman', target=cartman)
daemon_kenny.setDaemon(True)
daemon_cartman.setDaemon(True)
daemon_kenny.start()
daemon_cartman.start()
daemon_kenny.join()
daemon_cartman.join()
Having a lot of trouble adding an additional thread to this program'. As it sits it accesses an API and executes trades on an account. All fine and good until I try and pass a streaming rate to the stopLoss order. Once I run it this way the code runs a loop blocking out everything else and just prints the latest price. How can I set this thing to run concurrently with the other two threads?
Main trading program:
import Queue
import threading
import time
import json
import streamer
from execution import Execution
from settings import STREAM_DOMAIN, API_DOMAIN, ACCESS_TOKEN, ACCOUNT_ID
from strategy import TestRandomStrategy
from streaming import StreamingForexPrices
from event import TickEvent
from streamer import demo
stop = demo(0)
def trade(events, strategy, execution):
"""
Carries out an infinite while loop that polls the
events queue and directs each event to either the
strategy component of the execution handler. The
loop will then pause for "heartbeat" seconds and
continue.
"""
while True:
try:
event = events.get(False)
except Queue.Empty:
pass
else:
if event is not None:
if event.type == 'TICK':
strategy.calculate_signals(event)
elif event.type == 'ORDER':
print "Executing order!"
execution.execute_order(event)
elif event.type == 'stopLoss':
print "StOP LOSS HERE!!!!"
execution.execute_order(event)
time.sleep(heartbeat)
if __name__ == "__main__":
heartbeat = 0 # Half a second between polling
events = Queue.Queue()
# Trade 1000 unit of EUR/USD
instrument = "EUR_USD"
units = 1
stopLoss = stop
# Create the OANDA market price streaming class
# making sure to provide authentication commands
prices = StreamingForexPrices(
STREAM_DOMAIN, ACCESS_TOKEN, ACCOUNT_ID,
instrument, events
)
#handle stopLoss price
stop = demo(0)
# Create the execution handler making sure to
# provide authentication commands
execution = Execution(API_DOMAIN, ACCESS_TOKEN, ACCOUNT_ID)
# Create the strategy/signal generator, passing the
# instrument, quantity of units and the events queue
strategy = TestRandomStrategy(instrument, units, events, stopLoss)
# Create two separate threads: One for the trading loop
# and another for the market price streaming class
trade_thread = threading.Thread(target=trade, args=(events, strategy, execution))
price_thread = threading.Thread(target=prices.stream_to_queue, args=[])
stop_thread = threading.Thread(target=stop, args=[])
# Start both threads
trade_thread.start()
price_thread.start()
stop_thread.start()
Any help is greatly appreciated!
Edit:
Ok I have drilled down to the problem I think. This code here executes the trades and when I try and post "prices" to the stopLoss order the script gives me error. example:
stopLoss = prices NameError: name 'prices' is not defined
On the other hand, in trying to simplify the problem to post here I solved another one I was having!
code:
def trade(events, strategy, execution):
while True:
prices = demo(0)
print prices
while True:
try:
event = events.get(False)
except Queue.Empty:
pass
else:
if event is not None:
if event.type == 'TICK':
strategy.calculate_signals(event)
elif event.type == 'ORDER':
print "Executing order!"
execution.execute_order(event)
time.sleep(heartbeat)
if __name__ == "__main__":
heartbeat = 0 # Half a second between polling
events = Queue.Queue()
# Trade 1000 unit of EUR/USD
instrument = "EUR_USD"
units = 1
stopLoss = prices
Thanks to those who helped me figure out I needed to use threading to run a loop in a control script I have run, I now have an issue to try and control the thread - by starting or stopping it based on a function:
I want to start a process to get a motor to cycle through a movement based on a 'start' parameter sent to the controlling function, also I want to send a 'stop' parameter to stop the thread too - here's where I got to:
def looper():
while True:
print 'forward loop'
bck.ChangeDutyCycle(10)
fwd.ChangeDutyCycle(0)
time.sleep(5)
print 'backwards loop'
bck.ChangeDutyCycle(0)
fwd.ChangeDutyCycle(20)
time.sleep(5)
def looper_control(state):
t = threading.Thread(target=looper)
if state == 'start':
t.start()
elif state == 'stop':
t.join()
print 'looper stopped!!'
This starts the thread okay when I call looper_control('start') but throws an error when looper_control('stop'):
File "/usr/lib/python2.7/threading.py", line 657, in join
raise RuntimeError("cannot join thread before it is started")
RuntimeError: cannot join thread before it is started
EDIT: looper_control called from here
if "motor" in tmp:
if tmp[-1:] == '0':
#stop both pin
MotorControl('fwd',0,0)
print 'stop motors'
looper_control('stop')
elif tmp[-1:] == '2':
#loop the motor
print 'loop motors'
looper_control('start')
UPDATE: Ive not been able to stop the thread using the method suggested - I thought I had it!
here's where I am:
class sliderControl(threading.Thread):
def __init__(self,stop_event):
super(sliderControl,self).__init__()
self.stop_event = stop_event
def run(self):
while self.stop_event:
print 'forward loop'
bck.ChangeDutyCycle(10)
fwd.ChangeDutyCycle(0)
time.sleep(5)
print 'backwards loop'
bck.ChangeDutyCycle(0)
fwd.ChangeDutyCycle(20)
time.sleep(5)
def looper_control(state,stop_event):
if state == 'start':
t = sliderControl(stop_event=stop_event)
t.start()
elif state == 'stop':
#time.sleep(3)
stop_event.set()
#t.join()
print 'looper stopped!!'
called via:
if tmp[-1:] == '0':
#stop both pin
MotorControl('fwd',0,0)
print 'stop motors'
#stop_thread_event = threading.Event()
print 'stopping thread'
print stop_thread_event
looper_control('stop',stop_thread_event)
elif tmp[-1:] == '2':
#loop the motor
print 'loop motors'
global stop_thread_event
stop_thread_event = threading.Event()
print stop_thread_event
looper_control('start', stop_thread_event)
It looked like a separate thread event was being called by loop and stop, so I thought a global would sort it out but its just not playing ball. When I start the loop - it runs, but when I try to stop it, I get looper stopped!! , but the process just keeps running
Your top-level thread routine will need to become an event handler that listens to a Queue object (as in from Queue import Queue) for messages, then handles them based on state. One of those messages can be a shutdown command, in which case the worker thread function simply exits, allowing the main thread to join it.
Instead of time.sleep, use threading.Timer with the body of the timer sending a message into your event queue.
This is a substantial refactoring. But especially if you plan on adding more conditions, you'll need it. One alternative is to use a package that handles this kind of thing for you, maybe pykka.
To stop a python thread you can use threading.Event()
try this:
class YourClass(threading.Thread):
def __init__(self, stop_event):
super(YourClass, self).__init__()
self.stop_event = stop_event
def run(self):
while not self.stop_event.is_set():
# do what you need here (what you had in looper)
def looper_control(state, stop_event):
if state == 'start':
t = YourClass(stop_event=stop_event)
t.start()
elif state == 'stop':
stop_event.set()
and call to looper_control:
stop_thread_event = threading.Event()
looper_control(state, stop_thread_event)
you only can "start" once a thread
but you can lock and unlock the thread.
the best way to stop and start a thread is with mutex, Example:
#!/usr/bin/python
import threading
from time import sleep
mutex2 = threading.Lock()
#This thread add values to d[]
class Hilo(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
while True:
mutex2.acquire()
#Add values to d[]
d.append("hi from Peru")
mutex2.release()
sleep(1)
d=[];
hilos = [Hilo()]
#Stop Thread
#If you have more threads you need make a mutex for every thread
mutex2.acquire()
#Start treades, but the thread is lock
for h in hilos:
h.start()
#so you need do
#unlock THREAD<
mutex2.release()
#>START THREAD
#Sleep for 4 seconds
sleep(4)
#And print d[]
print d
print "------------------------------------------"
#WAIT 5 SECONDS AND STOP THE THREAD
sleep(5)
try:
mutex2.acquire()
except Exception, e:
mutex2.release()
mutex2.acquire()
#AND PRINT d[]
print d
#AND NOW YOUR TRHEAD IS STOP#
#When the thread is lock(stop), you only need call: mutex2.release() for unlock(start)
#When your thread is unlock(start) and you want lock(stop):
#try:
# mutex2.acquire()
#except Exception, e:
# mutex2.release()
# mutex2.acquire()