Hi Terry,

> Those are one of several places that I looked.  What I haven't found
> is a definitive list of methods that RPi.GPIO supports.

This looks like a good start.
https://sourceforge.net/p/raspberry-gpio-python/code/ci/default/tree/source/py_gpio.c#l954
You can also call dir() on an object.

> > >     c_file = 'Wimborne_Minster_StedmanCinques_16-10-2016.mp3' # Change
> > >     rings.
> > >     c_path = os.path.join(chr_subdir, c_file)
> > >     blackhole = open(os.devnull, 'w')
> > 
> > I'd open this once as a global variable.  Python will garbage
> > collect and close the file descriptors over time as all the
> > references die out, but that's assuming they do die out.  If they
> > don't, the process will use up ever more file descriptors and
> > there's a couple of limits it could reach that would make open()
> > fail.
> 
> This part has me somewhat confused.  When you say 'I'd open this once
> as a global variable', you surely don't mean the function?

No, I meant the os.devnull file.  I should have written `open()', sorry.

> What I believe that you are saying is that c_player should be made
> into a global variable and the callback in the GPIO event should
> perform the terminate().  Is that what you were leading up to above?

Yes.  The play_bells() callback kicks off the mpg321 with
subprocess.Pipe() and returns.  The stop_bells() callback uses the
return value from Pipe(), if it's been called, to terminate mpg321.  The
playing may have already finished, but the zombie mpg321 process will
still be present to terminate and reap by wait(2)ing.

> Assuming this is possible, would it be something like:
> 
>     GPIO.add_event_detect(20, GPIO.RISING,
>         callback=c_player.terminate(), bouncetime=500)

It could be, but you don't want the `()' at the end of terminate since
that's calling the function to use its return value as a parameter to
add_event_detect().  `callback=c_player.terminate' would pass in a
reference to that function to be called later.

But, c_player probably won't be a subprocess.Popen object at this point
so there isn't a terminate function to reference.  And you probably need
to do your own internal housekeeping on the switch event, like reap the
zombie mpg321 with wait(), log trace data, and update your variables,
e.g. `c_player', or `bell_player' might be better as a global, to track
whether it's currently running or not.

> Can that be done (or something like it)?

Yes.  I think you're familiar with co-operative multi-tasking?  You can
imagine your Python program comprises the kernel that's passing control
to a callback on the event, and the callback then runs for a little
before co-operatively returning to give something else a chance.

> The are four functions that are controlled by the toggle switches on
> the front of the Control Box:

And the switches can be held up or down and spring back to the centre
"off" position.

Taking them out of order...

> 2.  Change Rings Control - The simplest function.  It either plays the
> bells or it stops them.  No extra feedback needed for the user.  I
> could seen that your suggestion could work here.

    bell_player = None

    def start_bell_player(gpio):
        global bell_player

        if bell_player is None:
            bell_player = subprocess.Pipe(...mpg321...)

    def stop_bell_player(gpio):
        global bell_player

        if bell_player is None:
            return

        bell_player.terminate()
        r = bell_player.wait():
        if r:
            print 'bell_player: mpg321 failed: %#x\n' % r
        bell_player = None

> 1.  Extended Hours Control - Change the 'Opening Hours' that determine
> when the hours, Quarter Jack chimes and other playing functions are
> allowed to sound.  This needs to set / unset a variable and play a
> short message to inform the user of the current state, eg 'Extended
> Hours Enabled'.  The variable is then used to inhibit playback outside
> opening hours.

    extended_hours = False

    def extend_hours(gpio):
        global extended_hours

        extended_hours = True
        r = subprocess.call(...mpg321 plays "enabled"...)
        if r:
            print 'extended_hours: mpg321 failed: %s %#x\n' % (extended_hours, 
r)

    def contract_hours(gpio):
        global extended_hours

        extended_hours = False
        r = subprocess.call(...mpg321 plays "disabled"...)
        if r:
            print 'extended_hours: mpg321 failed: %s %#x\n' % (extended_hours, 
r)

Here call() is used as the audio is short and we let it complete,
blocking all other event callbacks.

Notice how similar the two functions are?  As gpio is passed in, they
can be merged.

    extension_enabled = {False: 'disabled', True: 'enabled'}

    def change_hours(gpio):
        global extended_hours

        extended_hours = gpio == GPIO_EXTEND_HOURS
        r = subprocess.call(...mpg321..., extension_enabled[extended_hours])
        if r:
            print 'extended_hours: mpg321 failed: %s %#x\n' % (extended_hours, 
r)

> 3.  MP3  Control - The most complicated.  Depressing the switch
> increments a variable, plays a user message to indicate which Playlist
> is selected and then fires off the player whaich will then loop
> through the Playlist until stopped by an upwards click.

This is a merge of the two so far.  I've assumed moving to the next
playlist implicitly stops the current one.

    playlists = ('daisy', 'peal', 'vikings', 'march', 'fugue')
    playlist_index = 0

    def cycle_playlist(gpio):
        global playlist_index, playlist_player

        stop_playlist(None)

        playlist_index += 1
        playlist_index %= len(playlists)

        r = subprocess.call(...mpg321..., 'announce-' + 
playlists[playlist_index])
        if r:
            print 'cycle_playlist: mpg321 failed: %d %#x\n' % (playlist_index, 
r)

        playlist_player = subprocess.Pipe(...mpg321..., 
playlists[playlist_index])

    def stop_playlist(gpio):
        global playlist_player

        if playlist_player is None:
            return

        playlist_player.terminate()
        r = playlist_player.wait()
        if r:
            print 'playlist_player: mpg321 failed: %#x\n' % r
        playlist_player = None

> 4.  Wedding Sequence - A kind of extended Change Rings, except that
> there are three tracks to play.

Once started, it plays three tracks in a row?  Or it plays the first
track on the first switch on, second on the next, etc?  There's probably
enough example above to see how it's done if the second.

> There are generally at least one other thing that needs doing; hence
> my query about threaded or multi-processed function calls.

Still don't think you need it.  The Pi GPIO library takes care of your
second thread to call your registered event callbacks.  The subprocess
module handles starting child processes running external programs.  You
don't want any more parallelism that this else you have to worry about
things happening at the same time and communicating between parts to
stop it going wrong, e.g. a quick switch up then down could have two
callbacks running at once as a race.

What's the main bit of Python code do once it's registered the
callbacks?  Just sit there somehow and let it all go on in the
"background"?

You might want to consider playing a brief sound on every switch
activation as user feedback to show the program's operating and seen the
movement.  Two tones could match the up and down, on- and off-ness.  So
stopping would terminate, then play the pong.  Starting would play the
ping and then subprocess.Pipe to start the long-running mpg321.  Given
the two audio channels, one could stress test by multiple hands flicking
the switches about for a minute.

Cheers, Ralph.

-- 
Next meeting:  Bournemouth, Tuesday, 2017-03-07 20:00
Meets, Mailing list, IRC, LinkedIn, ...  http://dorset.lug.org.uk/
New thread:  mailto:[email protected] / CHECK IF YOU'RE REPLYING
Reporting bugs well:  http://goo.gl/4Xue     / TO THE LIST OR THE AUTHOR

Reply via email to