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

