Initiating image capture in response to a trigger was the next step, my plan is to use a button, or a proximity sensor like the passive infrared (PIR) module in the second image to trigger a photo.


For my test rig (in addition to a RaspberryPI & generic USB Web camera) I’m using some Seeedstudio gear
- Grove Base Hat for Raspberry PI USD9.90
- Grove – Button USD1.90
- Grove – PIR Motion Sensor USD7.90
- Grove – Touch Sensor USD3.90
The first step was to write an interrupt handler for the digital input, I figured triggering on the button push rather than release would make device more responsive.
/* Copyright ® 2019 Feb devMobile Software, All Rights Reserved MIT License ... */ namespace devMobile.Windows10IotCore.IoT.DigitalInputTrigger { using System; using System.Diagnostics; using Windows.ApplicationModel.Background; using Windows.Devices.Gpio; public sealed class StartupTask : IBackgroundTask { private BackgroundTaskDeferral backgroundTaskDeferral = null; private GpioPin InterruptGpioPin = null; private const int InterruptPinNumber = 5; public void Run(IBackgroundTaskInstance taskInstance) { Debug.WriteLine("Application startup"); try { GpioController gpioController = GpioController.GetDefault(); InterruptGpioPin = gpioController.OpenPin(InterruptPinNumber); InterruptGpioPin.SetDriveMode(GpioPinDriveMode.InputPullUp); InterruptGpioPin.ValueChanged += InterruptGpioPin_ValueChanged; Debug.WriteLine("Digital Input Interrupt configuration success"); } catch (Exception ex) { Debug.WriteLine($"Digital Input Interrupt configuration failed " + ex.Message); return; } //enable task to continue running in background backgroundTaskDeferral = taskInstance.GetDeferral(); } private void InterruptGpioPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args) { Debug.WriteLine($"{DateTime.UtcNow.ToLongTimeString()} Digital Input Interrupt {sender.PinNumber} triggered {args.Edge}"); } } }
Then I added in the camera functionality and made the interrupt handler async and await the camera and file system calls.
/* Copyright ® 2019 Feb devMobile Software, All Rights Reserved MIT License ... */ namespace devMobile.Windows10IotCore.IoT.PhotoDigitalInputTrigger { using System; using System.Diagnostics; using Windows.ApplicationModel.Background; using Windows.Devices.Gpio; using Windows.Foundation.Diagnostics; using Windows.Media.Capture; using Windows.Media.MediaProperties; using Windows.Storage; public sealed class StartupTask : IBackgroundTask { private readonly LoggingChannel logging = new LoggingChannel("devMobile Photo Digital Input Trigger demo", null, new Guid("4bd2826e-54a1-4ba9-bf63-92b73ea1ac4a")); private BackgroundTaskDeferral backgroundTaskDeferral = null; private GpioPin InterruptGpioPin = null; private const int InterruptPinNumber = 5; private MediaCapture mediaCapture; private const string ImageFilenameFormat = "Image{0:yyMMddhhmmss}.jpg"; private volatile bool CameraBusy = false; public void Run(IBackgroundTaskInstance taskInstance) { LoggingFields startupInformation = new LoggingFields(); this.logging.LogEvent("Application starting"); try { mediaCapture = new MediaCapture(); mediaCapture.InitializeAsync().AsTask().Wait(); Debug.WriteLine("Camera configuration success"); GpioController gpioController = GpioController.GetDefault(); InterruptGpioPin = gpioController.OpenPin(InterruptPinNumber); InterruptGpioPin.SetDriveMode(GpioPinDriveMode.InputPullUp); InterruptGpioPin.ValueChanged += InterruptGpioPin_ValueChanged; Debug.WriteLine("Digital Input Interrupt configuration success"); } catch (Exception ex) { this.logging.LogMessage("Camera or digital input configuration failed " + ex.Message, LoggingLevel.Error); return; } startupInformation.AddString("PrimaryUse", mediaCapture.VideoDeviceController.PrimaryUse.ToString()); startupInformation.AddInt32("Interrupt pin", InterruptPinNumber); this.logging.LogEvent("Application started", startupInformation); //enable task to continue running in background backgroundTaskDeferral = taskInstance.GetDeferral(); } private async void InterruptGpioPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args) { DateTime currentTime = DateTime.UtcNow; Debug.WriteLine($"{DateTime.UtcNow.ToLongTimeString()} Digital Input Interrupt {sender.PinNumber} triggered {args.Edge}"); if (args.Edge == GpioPinEdge.RisingEdge) { return; } // Just incase - stop code being called while photo already in progress if (CameraBusy) { return; } CameraBusy = true; try { string filename = string.Format(ImageFilenameFormat, currentTime); IStorageFile photoFile = await KnownFolders.PicturesLibrary.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting); ImageEncodingProperties imageProperties = ImageEncodingProperties.CreateJpeg(); await mediaCapture.CapturePhotoToStorageFileAsync(imageProperties, photoFile); LoggingFields imageInformation = new LoggingFields(); imageInformation.AddDateTime("TakenAtUTC", currentTime); imageInformation.AddString("Filename", filename); imageInformation.AddString("Path", photoFile.Path); this.logging.LogEvent("Captured image saved to storage", imageInformation); } catch (Exception ex) { this.logging.LogMessage("Camera photo or save failed " + ex.Message, LoggingLevel.Error); } CameraBusy = false; } } }
I found that contactor bounce was an issue (Grove- Touch Sensor OK) with larger mechanical buttons so I added the CameraBusy boolean flag to try and prevent re-entrancy problems. I’ll trial some other types of proximity and beam based on real-world student projects.

The code is available on GitHub and is a bit of a work in progress.
Pingback: Windows 10 IoT Core Cognitive Service Face API | devMobile's blog