0

i want to run my pi headless, and only wire up some switches via gpio to operate it. i want it to monitor 3 switches, and tell if theyre high or low, and react accordingly. it should be launched every minute through cron, but i want its responsiveness to be around the 2 second mark.

so, not running constantly(multiple times per second) and not running sluggish either(flip the switch, wait 5 seconds, doesnt seem to work, switch it the other way, repeat). i feel the best idea for this is to launch a script to only read gpios, and change environment variables accordingly. then when the 3 other scripts are launched, they will only read the variable, and do their task accordingly.

lets get this out of the way, i dont want to use a duino. i need it to play music through speakers as one of the switches, its just easier to hook in a headphone jack and play it like that than to fumble with extra components to store/play mp3s.

i was looking into using environment variables but they dont persist across each script (4 being launched each minute). ive gotten as far as dropping some into my env/environment file and my .profile but they need to relogin to reset, and again arent persistent.

the question is does anyone know how i can do this? im trying to just have 3 rocker switches and the components hooked into it so i just apply power and it works

best diagram i can come up with right now:

Switch1=on ->play music Switch1=off ->kill music

Switch2=on ->turn motor on Switch2=off ->turn motor off

Switch3=on ->turn led on Switch3=off ->you guessed it, turn led off

nothing more is asked of this pi right now but i cant even get this simple set up to work

Heres led.py

import os
import sys
import RPi.GPIO as gpio

r = 14
g = 15
b = 18
initime = time.strftime('%M')

gpio.setmode(gpio.BCM)

gpio.setup(r,gpio.OUT)
gpio.setup(g,gpio.OUT)
gpio.setup(b,gpio.OUT)

gpio.output(r,0)
gpio.output(g,0)
gpio.output(b,0)

def lightDazzle():

    gpio.output(r,1)
    gpio.output(g,0)
    gpio.output(b,0)
    sleep(1)#red1sec
    gpio.output(r,0)
    gpio.output(g,1)
    gpio.output(b,0)
    sleep(1)#green1sec
    gpio.output(r,0)
    gpio.output(g,0)
    gpio.output(b,1)
    sleep(1)#blue1sec
    gpio.output(r,1)
    gpio.output(g,1)
    gpio.output(b,0)
    sleep(1)#yellow1sec
    gpio.output(r,1)
    gpio.output(g,0)
    gpio.output(b,1)
    sleep(1)#violet1sec
    gpio.output(r,0)
    gpio.output(g,1)
    gpio.output(b,1)
    sleep(1)#teal1sec
    gpio.output(r,1)
    gpio.output(g,1)
    gpio.output(b,1)
    sleep(1)#white1sec
    for a in range(4):
        gpio.output(r,0)
        gpio.output(g,0)
        gpio.output(b,0)
        sleep(.125)
        gpio.output(r,0)
        gpio.output(g,0)
        gpio.output(b,1)
        sleep(.125)#blue
    for a in range(4):
        gpio.output(r,0)
        gpio.output(g,0)
        gpio.output(b,0)
        sleep(.125)
        gpio.output(r,1)
        gpio.output(g,1)
        gpio.output(b,0)
        sleep(.125)#yellow
    for a in range(4):
        gpio.output(r,0)
        gpio.output(g,0)
        gpio.output(b,0)
        sleep(.125)
        gpio.output(r,0)
        gpio.output(g,1)
        gpio.output(b,1)
        sleep(.125)#teal
    gpio.output(r,0)
    gpio.output(g,0)
    gpio.output(b,0)


while True:
    switchState = os.environ.get['LEDSWITCH']
    if switchState = 'ON':
        lightDazzle()
    else: sleep(2.5)
    now = time.strftime('%M')
    if now != initime:
        sys.exit()

Heres music.py:

import os
import sys
import subprocess
#import RPi.GPIO as gpio

initime = time.strftime('%M')

#gpio.setmode(gpio.BCM)

def chkNrunMusic():
    #run ps and grep for vlc and job number
    musicPlaying = subprocess.check_output(['/home/anonymous/Documents/NurseryPi/startmusic.sh'])
    #if found, do nothing
    #if nothing, PlayDisney

def chkNkillMusic():
    #run ps and grep for vlc and job number
    musicPlaying = subprocess.check_output(['/home/anonymous/Documents/NurseryPi/stopmusic.sh'])
    #if found, kill
    #if nothing, do nothing
while True:
    switchState = os.environ.get('MUSICSWITCH', '-1')
    if switchState == 'ON':
        chkNrunMusic()
    else: 
        chkNkillMusic()
        time.sleep(2.5)
        print("cycled")
    now = time.strftime('%M')
    if now != initime:
        sys.exit()

heres the readgpio.py

import os
import sys
#import RPi.GPIO as gpio

initime = time.strftime('%M')
mobileSwitch = 24
ledSwitch = 23
musicSwitch = 21

'''gpio.setmode(gpio.BCM)

gpio.setup(ledSwitch,gpio.IN)
gpio.setup(mobileSwitch,gpio.IN)
gpio.setup(musicSwitch,gpio.IN)
'''
while True:

    '''if GPIO.input(mobileSwitch):
        os.environ['MOBILESWITCH'] = 'ON'
    else:
        os.environ['MOBILESWITCH'] = 'OFF'


    if GPIO.input(ledSwitch):
        os.environ['LEDSWITCH'] = 'ON'
    else:
        os.environ['LEDSWITCH'] = 'OFF'


    if GPIO.input(musicSwitch):
        os.environ['MUSICSWITCH'] = 'ON'
    else:
        os.environ['MUSICSWITCH'] = 'OFF'
    sleep(2.5)
    now = time.strftime('%M')
    if now != initime:
        sys.exit()'''
    switchState = os.environ.get('MYSWITCH', '-1')
    print(switchState)
    os.environ['MYSWITCH'] = 'ON'
    switchState = os.environ.get('MUSICSWITCH', '-1')
    print(switchState)
    time.sleep(10)
    os.environ['MYSWITCH'] = 'OFF'
    switchState = os.environ.get('MYSWITCH', '-1')
    print(switchState)
    time.sleep(10)
    now = time.strftime('%M')
    if now != initime:
        sys.exit()

this is the jist of the scripts called in music.py. theres a start and stop and theyre the same, but different.


#search all running processes for a vlc job number
MUSICA=$( ps -e | grep "vlc"| cut -d " " -f 2 )
#kill that job number
kill $MUSICA
echo "stopping"

im hacking at this still, so ignore unrelated inconsistencies.

  • Could you provide the scripts you came up with? – Paradox Mar 7 at 4:34
  • Could you explain why you have rejected just using one script? Properly checking the GPIO even hundreds of times a second should use negligible (probably unmeasureable) load. My pigpio reads 32 GPIO @ 200 thousand times a second for about 8% CPU load. – joan Mar 7 at 8:18
  • i added it, also im not only dodging the load, but i want them to shut down on a dime when it checks the variable, or keep going uninterrupted. lights could be going while music and motor scripts are going too and i dont want to loop through a led script for 10sec-1min between each variable check. just loop it independently in its own thread i guess – Guy Fawkes Mar 9 at 17:44
  • I don't quite get why you can't, for example, replace switchState = os.environ.get('MUSICSWITCH', '-1') with switchState = GPIO.input(musicSwitch) or the equivalent in each of your three scripts so each script checks it's own switch directly? – Roger Jones Mar 9 at 18:15
1

I believe the problem lies with the way that the scripts are run within their own environment, copied when they are launched, so don't share changes made to that environment between them once they are launched.

To get your inter-process communication to work the way you want there are a few options you can try such as using files or pipes; message queues; shared memory or even TCP/IP messaging. I'm no Python expert so I'm in no position to explain how any of these might work (if at all) in that language and choosing the right one for your project might be better addressed as the subject for an edited/another question.

  • If i have to pick an answer I'd say this is it because file seems to be the closest answer. I was just trying to use environment variables to do what writing in a file does but cleaner i guess. – Guy Fawkes Mar 10 at 17:58
1

In your first paragraph, you state some simple goals... some of which are not wanting it to be constantly looping, but also be responsive. You mention using cron to launch once per minute. If the code only checks the state of the GPIO pins, performs some work based on their state, then exits ... then the state of the GPIO pins could possibly be waiting up to a full minute to notice the change.

An alternative would be to use "interrupt-driven" handling (more on that later). I also wanted to address your question about environment variables.

Environment Variables and Inheritance

Environment variables are "local" by default. This means a variable (let's call it TESTVAR) is only available in the same process in which it was set.

e.g. if I set: TESTVAR=one then later in the same process I can retrieve the value of TESTVAR and it will be 'one'.

But if I launch a child process and attempt to fetch the value of TESTVAR, it won't exist. Also, other (independent) processes can have their own instance of TESTVAR (it would not be a shared variable).

If you export a variable... e.g. either: $ export TESTVAR=one or $ TESTVAR=one; export TESTVAR then "TESTVAR" will be inherited by child processes.

In this case if your python script has a line that reads: print("TESTVAR is set to: " + os.environ['TESTVAR'] you'll see that it will print "TESTVAR is set to: one" ... because it inherited the value from the parent.

Note that inheritance is one-way... a child process can inherit from a parent but not the other way around.

This is probably ok for you since your child processes only need to inherit... they don't need to modify the value and return it.

Here's some sample code:

I'll start by setting (and exporting) TESTVAR

$ export TESTVAR=one

Then I'll run my first python script, testvar1.py. I'll have testvar1.py launch testvar2.py as a sub-process.

testvar1.py:

import os

if os.environ.has_key('TESTVAR'):
    print("TESTVAR is currently set to: " + os.environ['TESTVAR'])
else:
    print("TESTVAR is not set.")

os.environ['TESTVAR'] = "two"

os.system('python testvar2.py')

Note that at this point, testvar1 has "inherited" the value of TESTVAR from the bash shell that launched it. testvar1 modifies the value and calls testvar2

testvar2.py:

import os

if os.environ.has_key('TESTVAR'):
    print("TESTVAR is currently set to: " + os.environ['TESTVAR'])
else:
    print("TESTVAR is not set.")

os.environ['TESTVAR'] = "three"

At this point, testvar2.py runs and prints the inherited value of TESTVAR and then modifies it's value to "three".

Let's run it and see what happens:

pi@timspi:~/Python $ python testvar1.py; echo $TESTVAR
TESTVAR is currently set to: one
TESTVAR is currently set to: two
one
pi@timspi:~/Python $ 

You can see that testvar1.py inherited the value from the bash shell (one) You can see that testvar2.py inherited the modified value from testvar1.py (two) Then testvar2.py exits (returning to testvar1.py) which also exits (returning to the bash shell). Finally, I asked the bash shell to print the value of TESTVAR ... it has the original value of "one" (because parents don't inherit from children).

Hopefully that helps you with the idea of environment variables and how to allow them to be inherited by child processes.

Interrupt Driven Code

You did mention wanting to do all this from a cron job that you would run once per minute. Was this to avoid having a process run in an infinite loop?

If so, there's a more elegant method to handle this via "interrupt" driven code. This would result in code which is much more responsive (basically instant) while, at the same time, using less processor time and not requiring a crontab entry.

With this approach you define subroutines (which are ordinary subroutines) that have the code which should only be executed if the interrupt is encountered. These subroutines could be your three python scripts.

Then you use GPIO.add_event_detect(...) to establish the interrupt and what to do when it occurs. It takes a few arguments such as:

  1. The GPIO pin for this interrupt (e.g. 24 if watching pin #24)
  2. The type of event or "edge" (voltage rising on the pin or voltage falling on the pin. In other words is it a button "press" or a button "release" that triggers the action. This will take the value GPIO.RISING, GPIO.FALLING, or GPIO.BOTH (rising or falling triggers the event).
  3. The callback function that should be invoked when our event occurs on the pin we care about. e.g. "callback=my_function")
  4. There is a problem of flakey contact switches on buttons ... you press the button once, but the Pi think you pressed it a few times. The old solution was to record the time of each button press and ignore any additional events if they occurred within a few milliseconds of a previous event. But this is now simplified in the more recent versions of the GPIO library. You can add the parameter "bouncetime=250" and any subsequent events that occur with 250ms will be ignored.

Putting it together the interrupt definition looks like this: GPIO.add_event_detect(24, GPIO.RISING, callback=my_function, bouncetime=250)

That would define an interrupt event driven callback that fires whenever voltage is "rising" (button press, not button release) on pin 24. It would invoke the function "my_function" as the interrupt handler. It would ignore any additional button presses if they occur within 250ms.

Once you've write the code that is to be invoked when the event occurs (the event handlers), you use the GPIO.add_event_detect(...) to define the interrupts and tie it to your handler, then ... just wait.

You would want to import time so you can use sleep to sleep the process while it waits for input.

The basic structure would look something like this (incomplete code with the essential bits):

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO.setup(<pin>, GPIO.IN)
# repeat for each pin in use)

def led_handler()
# your led handler code goes here

def music_handler()
# your music handler code goes here

def motor_handler()
# your motor handler code goes here

# now define the interrupt events and time them to the handlers. I used <pin> to
# refer to the pin number (you would supply the actual pin number or a variable
# that holds its value.

GPIO.add_event_detect(<pin>, GPIO.RISING, callback=led_handler, bouncetime=250)
GPIO.add_event_detect(<pin>, GPIO.RISING, callback=music_handler, bouncetime=250)
GPIO.add_event_detect(<pin>, GPIO.RISING, callback=motor_handler, bouncetime=250)

try:
        while (True):
            time.sleep(300)

except: KeyboardInterrupt:
        print("Keyboard interrupt detected.")

finally:
        GPIO.cleanup()
        print("Exiting cleanly.")

That's the general idea.

Note: I also added a keyboard interrupt handler (watches for ctrl+c) and will gracefully exit and clean up the GPIO as it exits.

A web search such as "raspberry pi inerrupt driven gpio" should reveal some tutorial articles.

  • indeed. i was kinda looking for a "use the whole pi as a program" approach i guess and using environment variables to speak across scripts. the problem occurred not from child/parent processes, but simple sibling processes. i define a variable globally, they can all pull the original value, but none of them can modify for the others to see. im launching a series of minute long scripts to prevent long running scripts bugging out so your interrupt idea is basically an infinite loop, no? and would it still work with flipping off? like, cut the 50 second led sequence off immediately at 15 secs? – Guy Fawkes Mar 9 at 22:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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