0

I'm working on some simple automation for a haunted house. The idea is to have 3 inputs (will be capacitive sensors, right now I'm using buttons), an led, a button, and a relay. When all three capacitive sensors have objects placed on them and are all giving input to the gpio, it will light up an led and "activate" the button which previously did nothing. The newly active button can then be pressed to turn on the relay. Using the following code I have gotten to the point where all 3 sensors are activated and the led turns on, but I can't figure how to "activate" the button. I'm guessing maybe use a separate function which gets called when the led goes on? In an ideal world the button press would also play a sound effect and go inactive again.

import RPi.GPIO as GPIO

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)

GPIO.setup(11, GPIO.OUT)
GPIO.setup(13, GPIO.IN, pull_up_down = GPIO.PUD_UP)
GPIO.setup(15, GPIO.IN, pull_up_down = GPIO.PUD_UP)
GPIO.setup(19, GPIO.IN, pull_up_down = GPIO.PUD_UP)

try:
    while True:
            GPIO.output(11, GPIO.LOW)
            if (GPIO.input(13) == False) and (GPIO.input(15) == False) and (GPIO.input(19) == False):
                    GPIO.output(11, GPIO.HIGH)
except KeyboardInterrupt:
    GPIO.cleanup()

GPIO.cleanup()

2 Answers 2

1

There are several fairly straightforward solutions to this.

Will all three items still be on the pads when the fourth button is to be enabled? If so, all you need to do is check for all four conditions:

....
while True:
        GPIO.output(11, GPIO.LOW)
        if (GPIO.input(13) == False) and (GPIO.input(15) == False) and (GPIO.input(19) == False):
                GPIO.output(11, GPIO.HIGH)

        if (GPIO.input(13) == False) and (GPIO.input(15) == False) and (GPIO.input(19) == False) and (GPIO.input(xx) == False):
                # Do whatever you have to do
....

Alternatively, if you want the player to have to place the three items, and then allow him to remove them and keep the fourth button active, you can set a flag:

....
button_active = False
while True:
        GPIO.output(11, GPIO.LOW)
        if (GPIO.input(13) == False) and (GPIO.input(15) == False) and (GPIO.input(19) == False):
                GPIO.output(11, GPIO.HIGH)
                button_active = True

        if (button_active == True) and (GPIO.input(xx) == False):
                # Do whatever you have to do
 ....

There are more solutions - for example you could have your fourth switch connected between an output (which you'd set low/high to enable/disable it) and an input (which you'd read). That seems a bit of a faff, though.

2
  • @Chester While this approach is definitely simpler/straightforward than what i had proposed, there are a few issues that are likely to come up later - polling the gpio pins in round robin fashion can result in poor responsiveness, missed button pushes, inefficient use of the CPU time to name some. handling the gpio pin transitions allows you to do other interesting stuff instead of periodically sampling them in a while lop. I had considered these common problems while framing my answer which is why it looks/is more complicated than this answer. just my 2 cents. Commented Oct 10, 2016 at 19:14
  • @Chester might want have a look at these ... this, this and this to understand more. Commented Oct 10, 2016 at 19:22
1

Welcome!

I find your question quite interesting as it requires careful modelling of the different states and transitions between them. I feel your question only partially related to the Raspberry PI and is more specifically related to modelling states, events, state transitions and behaviors which might make some moderators mark it as off-topic.

Anyway, based on my understanding of your requirement, I have written up a python script (not tested at all) which I hope will help you in some way. if you find any errors or require any clarifications, please leave a comment below

import RPi.GPIO as GPIO
import time
import threading

from subprocess import Popen

GPIO_LED = 11
GPIO_SENSORS = [13, 15, 19]
GPIO_BUTTON = 21 # change 21 to match your requirement
GPIO_RELAY = 23  # change 23 to match your requirement

# models the button behavior
# button keeps track of whether it is enabled or not
# it gets enabled when all 3 sensors inputs are low
# it gets disabled when one of the following happens
#   one or more sensor inputs goes high
#   after being enabled, it was pushed by user which caused a relay be activated, a song played after which the relay was deactivated (see below)
# if the button is physically pushed when disabled, the push is ignored
# if the button is physically pushed when enabled, 
#    the relay is turned, 
#    a song is played
#    at the end of the song
#       relay is turned off
#       button gets disabled
# if the button is physically pushed when the song is being played the additional push is ignored
# button state can be reset either internally or externally
# internal reset happens when the background thread launched in response to the button push exits
# external reset happens when one of the sensor state goes high
# internal reset has higher priority than external
# (i.e) if a song was being played in response to the button push and the sensor input goes high
# we wait until the song finishes and then reset the button state
# while the song is being played if sensor inputs transition back to ALL LOW, the button will still
# be in enabled state and have a consistent state
class Button(object):
  def __init__(self):
    self.enabled = False
    self.pushCount = 0
    self.thread = None

  # ugly workaround since the API does provide a way to setup a callback when a subprocess exits
  # we first launch a background thread, 
  # within which we launch the subprocess 
  # wait for the subprocess to exit
  # execute our callback
  # and then exit the background thread
  # see http://stackoverflow.com/questions/2581817/python-subprocess-callback-when-cmd-exits
  def playInBackground(self):
    def threadFunc():
      # turn on relay
      GPIO.output(GPIO_RELAY, HIGH)
      # play some ♬, change to match your requirement
      player = subprocess.Popen(["/usr/bin/mpg123", "/home/pi/sound.mp3"])
      # wait for player process to finish
      player.wait()
      # turn off relay
      GPIO.output(GPIO_RELAY, LOW)
      # reset internal state, True indicates internal reset
      self.reset(True)

    thread = threading.Thread(target=runInThread, args=(onExit, popenArgs))
    thread.start()
    # returns immediately after the thread starts
    return thread


  def push(self):
    # ignore push if disabled
    if not self.enabled:
      return

    # if already pushed the button once, ignore the push
    if self.pushCount != 0:
      return

    self.pushCount = 1
    # hold on to the background thread so that the GC doesnt pick it up
    self.thread = self.playInBackground()

  def reset(self, isInternalReset):
    if not isInternalReset: # if external reset
      # if we have a background thread running, 
      # let that thread's exit reset the state
      if self.thread is not None: 
        return

    self.thread = None
    self.pushCount = 0
    self.enabled = False

  def enable():
    GPIO.output(GPIO_LED, HIGH)

class SystemState(object):
  def __init__(self):
    self.button = Button()
    self.channels = {}
    for sensor in GPIO_SENSORS:
      self.channels[sensor] = True # should match the GPIO.PUD_UP below

  def onSensorFallingEdge(self, channel):
    self.channels[channel] = False
    if all(not x for x in self.channels):
      self.button.enabled = True

  def onSensorRaisingEdge(self, channel):
    self.channels[channel] = False    
    if any(x for x in self.channels):
      self.button.reset(False) # False indicates external reset

  def onButtonPush(self):
    button.push()

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)

GPIO.setup(GPIO_LED, GPIO.OUT)
GPIO.setup(GPIO_RELAY, GPIO.OUT)
GPIO.setup(GPIO_SENSORS, GPIO.IN, pull_up_down = GPIO.PUD_UP)
GPIO.setup(GPIO_BUTTON, GPIO.IN, pull_up_down = GPIO.PUD_UP)

systemState = SystemState()

def onSensorChanged(channel):
  if GPIO.input(channel): # if raising edge
    systemState.onSensorRaisingEdge(channel)
  else
    systemState.onSensorFallingEdge(channel)

GPIO.add_event_detect(SENSORS, GPIO.BOTH,    callback=onSensorChanged)
GPIO.add_event_detect(BUTTON,  GPIO.FALLING, callback=lambda: systemState.onButtonPush())

try:
  while True:
    time.sleep(0.1)

  GPIO.cleanup()
except KeyboardInterrupt:
    GPIO.cleanup()

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.