erickcantona
Posts: 4
Joined: Wed Oct 08, 2014 2:06 pm

Threading Cancel

Wed Oct 08, 2014 2:12 pm

Hi everyone!

New to the forum. I am having problems with the following code, getting errors. I am trying to send an email every 10 seconds when the user presses the button on the Pibrella connected to RPI. Now what I want it to do, is stop the next time the button is pressed. I do not have much experience with Python, but was able to put this together:

Code: Select all

import pibrella, signal
import threading
import time
import smtplib

server = smtplib.SMTP("smtp.gmail.com",587)
server.ehlo()
server.starttls()
server.ehlo()

server.login("email@gmail.com", "EmailPassword")

isPressed = 0

def pressed(pin):
    global isPressed
    if isPressed == 0:
        isPressed = 1
        sendMail()
    else:
        pibrella.light.off()
        t.cancel()
        isPressed = 0

def sendMail():
    pibrella.light.pulse()
    server.sendmail("email@gmail.com", "receiver@gmail.com", "Hello World")
    t.start()

t = threading.Timer(10, sendMail).start()

pibrella.button.pressed(pressed)
pibrella.pause()

server.close()
The error I am getting is:

Code: Select all

Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/pibrella.py", line 262, in handle_callback
    callback(self)
  File "sendText.py", line 27, in pressed
    sendMail()
  File "sendText.py", line 36, in sendMail
    t.start()
AttributeError: 'NoneType' object has no attribute 'start'
Exception in thread Thread-14:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 552, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 760, in run
    self.function(*self.args, **self.kwargs)
  File "sendText.py", line 36, in sendMail
    t.start()
AttributeError: 'NoneType' object has no attribute 'start'
Can someone help me out, and show me what I am doing wrong?

Thanks in advance!

User avatar
elParaguayo
Posts: 1943
Joined: Wed May 16, 2012 12:46 pm
Location: London, UK

Re: Threading Cancel

Wed Oct 08, 2014 5:37 pm

You've defined t as:

Code: Select all

t = threading.Timer(10, sendMail).start()
which will give t a value of None as this is what start() returns rather than being the timer object.

So when you call

Code: Select all

t.start()
in sendMail you're really calling None.start().

Is the idea that the timer resets itself?
RPi Information Screen: plugin based system for displaying weather, travel information, football scores etc.

erickcantona
Posts: 4
Joined: Wed Oct 08, 2014 2:06 pm

Re: Threading Cancel

Thu Oct 09, 2014 12:32 pm

No, that was a mistake on my part. I removed the .start() part. Now it is:

Code: Select all

def pressed(pin):
    global isPressed
    if isPressed == 0:
        isPressed = 1
        t.start()
    else:
        pibrella.light.off()
        t.cancel()
        isPressed = 0

def sendMail():
    pibrella.light.pulse()
    server.sendmail("email@gmail.com", "receiver@gmail.com", "Hello World")

t = threading.Timer(10, sendMail)
But now the first time the button is pressed, nothing happens. the second time it is pressed I get this error:

Code: Select all

Traceback (most recent call last):
  File "/usr/local/lib/python2.7/dist-packages/pibrella.py", line 262, in handle_callback
    callback(self)
  File "sendText.py", line 27, in pressed
    t.start()
  File "/usr/lib/python2.7/threading.py", line 489, in start
    raise RuntimeError("threads can only be started once")
RuntimeError: threads can only be started once

User avatar
elParaguayo
Posts: 1943
Joined: Wed May 16, 2012 12:46 pm
Location: London, UK

Re: Threading Cancel

Thu Oct 09, 2014 2:12 pm

Can you paste your full code please. At the moment there's nothing in there to call the "pressed" function.

I'm not convinced the threading timer is the best way to be doing this. If you're using a global variable "ispressed" (I don't like global variables much, but they could work here) then I don't see why you couldn't do

Code: Select all

while True:
  while isPressed:
    sendMail()
    time.sleep(10)
  sleep(0.1)
This way you could stay in an infinite loop but the sendMail function would only be called when the isPressed variable is true.

Assuming you've used an interrupt to call a function on the press of a button, then that function can change the value of isPressed.
RPi Information Screen: plugin based system for displaying weather, travel information, football scores etc.

erickcantona
Posts: 4
Joined: Wed Oct 08, 2014 2:06 pm

Re: Threading Cancel

Thu Oct 09, 2014 2:30 pm

Code: Select all

import pibrella, signal
import threading
import time
import smtplib

server = smtplib.SMTP("smtp.gmail.com",587)
server.ehlo()
server.starttls()
server.ehlo()

server.login("email@gmail.com", "EmailPassword")

isPressed = 0

def pressed(pin):
    global isPressed
    if isPressed == 0:
        isPressed = 1
        t.start()
    else:
        pibrella.light.off()
        t.cancel()
        isPressed = 0

def sendMail():
    pibrella.light.pulse()
    server.sendmail("email@gmail.com", "receiver@gmail.com", "Hello World")

t = threading.Timer(10, sendMail)

pibrella.button.pressed(pressed)
pibrella.pause()

server.close()
Here is the full code.

User avatar
Douglas6
Posts: 4861
Joined: Sat Mar 16, 2013 5:34 am
Location: Chicago, IL

Re: Threading Cancel

Thu Oct 09, 2014 4:04 pm

Sometimes error messages can be cryptic, but often enough they point you directly to the problem. In this case, "threads can only be started once". You are trying to start the same thread on each button press, but threads can only be started once.

Python Timers are best used for one-shot operations. I agree with elParaguayo that a single long-running thread is the best idea. Otherwise you'll need to create a new thread and start it (if needed) for each button press, and managing them may be confusing.

erickcantona
Posts: 4
Joined: Wed Oct 08, 2014 2:06 pm

Re: Threading Cancel

Wed Oct 29, 2014 2:00 pm

That is what I am trying to achieve. A single long-running thread, that is canceled on the second press of the button.

So first press starts it, second press stops it, third press starts it again, and so on.

Is there a way to do this?

User avatar
paddyg
Posts: 2541
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Threading Cancel

Wed Oct 29, 2014 9:11 pm

I probably wouldn't use threading but assuming you do go down this route then you can pass a variable to it when you start it that you can change from your main thread. NB this only works for non-simple objects i.e list, class instances etc. Or you use a global variable. Something along the lines of..

Code: Select all

def mailer(flag=[False]):
  while True:
    if flag[0]:
      sever.sendmail(...)
    time.sleep(10)
...
aflag = [True]
t = threading.Thread(target=mailer, args=(aflag,)) #nb extra comma to make sure it's a tuple with only one entry
t.start()
...
if button_pressed():
  aflag[0] = not aflag[0]
PS but as elP said above you can do this more simply with a main while loop and not get yourself confused by threading!
PPS in your example code you have to put in a while True loop to stop the main program just stopping! So you need to go round and round a loop checking the buttons then sleeping for a short while. If you want to do more in the while loop then you can't do the sleep(10) system; you have to keep a record of when you last sent an email and resend if that's more than 10s ago and the button flag is True. Virtually this exact process has been discussed on this forum previously (probably several times), it's just a question of being able to find it!
also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

ground_
Posts: 17
Joined: Tue Oct 14, 2014 6:13 am

Re: Threading Cancel

Thu Oct 30, 2014 3:20 am

I had a similar problem last week., and i solved it like this (very similar to the solution above me):

Code: Select all

isPressed = 0

def pressed(pin):
    global isPressed
    if isPressed == 0:
        isPressed = 1
        sendMail()
    else:
        pibrella.light.off()
        isPressed = 0

def sendMail():
    if pressed == 1:
        threading.Timer(10, sendMail).start()
        pibrella.light.pulse()
         server.sendmail("email@gmail.com", "receiver@gmail.com", "Hello World")
    Else:
        return

pibrella.button.pressed(pressed)
pibrella.pause()

server.close()

what this does:
it checks at the start of the function if the value for the pressed button is still "on". If so, it will start the timer again for the same function (including the value control), if not it will stop repeating.

at least this worked for me (except i had a different function ;) )

Return to “Python”