public void LogDataLightSensor(LightSensorReading reading)
        {
            LoggingFields loggingFields = new LoggingFields();

            loggingFields.AddString("Timestamp", reading.Timestamp.ToString());
            loggingFields.AddDouble("IlluminanceInLux", reading.IlluminanceInLux);
            rootPage.loggingChannelView.LogEvent("LightSensorData", loggingFields);
        }
        private void SensorValueupload(object state)
        {
            try
            {
                // Air sensor readings
                double temperatureCelsius    = bme280Sensor.Temperature.DegreesCelsius;
                double temperatureFahrenheit = bme280Sensor.Temperature.DegreesFahrenheit;
                double humidity = bme280Sensor.Humidity;
                double pressure = bme280Sensor.Pressure.Kilopascals;

                double lightLevel    = GroveBaseHatAnalogPorts.Read(AnalogPorts.AnalogPort.A0);
                double soilMoisture1 = GroveBaseHatAnalogPorts.Read(AnalogPorts.AnalogPort.A2);
                double soilMoisture2 = GroveBaseHatAnalogPorts.Read(AnalogPorts.AnalogPort.A4);

                LoggingService.Log(string.Format("C {0:0.0}° F {1:0.0}° H {2:0}% P {3:0.000}kPa L {4:0}%  Soil1 {5:0}% Soil2 {6:0}% ", temperatureCelsius, temperatureFahrenheit, humidity, pressure, lightLevel, soilMoisture1, soilMoisture2));

                // Setup for the the logging of sensor values
                var loggingData = new LoggingFields();

                // Construct Azure IoT Central friendly payload
                var telemetryDataPoint = new TelemetryDataPoint();
                telemetryDataPoint.Celsius = temperatureCelsius;
                loggingData.AddDouble(nameof(telemetryDataPoint.Celsius), temperatureCelsius);
                telemetryDataPoint.Fahrenheit = temperatureFahrenheit;
                loggingData.AddDouble(nameof(telemetryDataPoint.Fahrenheit), temperatureFahrenheit);
                telemetryDataPoint.Humidity = humidity;
                loggingData.AddDouble(nameof(telemetryDataPoint.Humidity), humidity);
                telemetryDataPoint.Pressure = pressure;
                loggingData.AddDouble(nameof(telemetryDataPoint.Pressure), pressure);

                telemetryDataPoint.Light = lightLevel;
                loggingData.AddDouble(nameof(telemetryDataPoint.Light), lightLevel);

                telemetryDataPoint.SoilMoisture1 = soilMoisture1;
                loggingData.AddDouble(nameof(telemetryDataPoint.SoilMoisture1), soilMoisture1);
                telemetryDataPoint.SoilMoisture2 = soilMoisture2;
                loggingData.AddDouble(nameof(telemetryDataPoint.SoilMoisture2), soilMoisture2);

                // Log the sensor values to ETW logging
                LoggingService.LogEvent("Sensor readings", loggingData, LoggingLevel.Verbose);

                using (var message = new Message(Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(telemetryDataPoint))))
                {
                    LoggingService.Log("AzureIoTHubClient SendEventAsync starting");
                    azureIoTHubClient.SendEventAsync(message).Wait();
                    LoggingService.Log("AzureIoTHubClient SendEventAsync finished");
                }
            }
            catch (Exception ex)
            {
                LoggingService.Error("Failed to send telemetry", ex);
            }
        }
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            StorageFolder localFolder = ApplicationData.Current.LocalFolder;

            try
            {
                // see if the configuration file is present if not copy minimal sample one from application directory
                if (localFolder.TryGetItemAsync(ConfigurationFilename).AsTask().Result == null)
                {
                    StorageFile templateConfigurationfile = Package.Current.InstalledLocation.GetFileAsync(ConfigurationFilename).AsTask().Result;
                    templateConfigurationfile.CopyAsync(localFolder, ConfigurationFilename).AsTask();
                }

                // Load the settings from configuration file exit application if missing or invalid
                StorageFile file = localFolder.GetFileAsync(ConfigurationFilename).AsTask().Result;

                applicationSettings = (JsonConvert.DeserializeObject <ApplicationSettings>(FileIO.ReadTextAsync(file).AsTask().Result));
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("JSON configuration file load failed " + ex.Message, LoggingLevel.Error);
                return;
            }

            // Log the Application build, shield information etc.
            LoggingFields appllicationBuildInformation = new LoggingFields();

#if DRAGINO
            appllicationBuildInformation.AddString("Shield", "DraginoLoRaGPSHat");
#endif
#if ELECROW
            appllicationBuildInformation.AddString("Shield", "ElecrowRFM95IoTBoard");
#endif
#if M2M
            appllicationBuildInformation.AddString("Shield", "M2M1ChannelLoRaWanGatewayShield");
#endif
#if ELECTRONIC_TRICKS
            appllicationBuildInformation.AddString("Shield", "ElectronicTricksLoRaLoRaWANShield");
#endif
#if UPUTRONICS_RPIZERO_CS0
            appllicationBuildInformation.AddString("Shield", "UputronicsPiZeroLoRaExpansionBoardCS0");
#endif
#if UPUTRONICS_RPIZERO_CS1
            appllicationBuildInformation.AddString("Shield", "UputronicsPiZeroLoRaExpansionBoardCS1");
#endif
#if UPUTRONICS_RPIPLUS_CS0
            appllicationBuildInformation.AddString("Shield", "UputronicsPiPlusLoRaExpansionBoardCS0");
#endif
#if UPUTRONICS_RPIPLUS_CS1
            appllicationBuildInformation.AddString("Shield", "UputronicsPiPlusLoRaExpansionBoardCS1");
#endif
#if ADAFRUIT_RADIO_BONNET
            appllicationBuildInformation.AddString("Shield", "AdafruitRadioBonnet");
#endif
            appllicationBuildInformation.AddString("Timezone", TimeZoneSettings.CurrentTimeZoneDisplayName);
            appllicationBuildInformation.AddString("OSVersion", Environment.OSVersion.VersionString);
            appllicationBuildInformation.AddString("MachineName", Environment.MachineName);

            // This is from the application manifest
            Package        package   = Package.Current;
            PackageId      packageId = package.Id;
            PackageVersion version   = packageId.Version;

            appllicationBuildInformation.AddString("ApplicationVersion", string.Format($"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}"));
            this.logging.LogEvent("Application starting", appllicationBuildInformation, LoggingLevel.Information);

            // Configure the AdaFruit API client
            LoggingFields adaFruitIOSettings = new LoggingFields();
            if (!string.IsNullOrEmpty(this.applicationSettings.AdaFruitIOBaseUrl))
            {
                this.adaFruitIOClient.BaseUrl = this.applicationSettings.AdaFruitIOBaseUrl;
                adaFruitIOSettings.AddString("BaseURL", this.applicationSettings.AdaFruitIOBaseUrl);
            }

            this.adaFruitIOClient.ApiKey = this.applicationSettings.AdaFruitIOApiKey;

            adaFruitIOSettings.AddString("APIKey", this.applicationSettings.AdaFruitIOApiKey);
            adaFruitIOSettings.AddString("UserName", this.applicationSettings.AdaFruitIOUserName);
            adaFruitIOSettings.AddString("GroupName", this.applicationSettings.AdaFruitIOGroupName);
            this.logging.LogEvent("AdaFruit.IO configuration", adaFruitIOSettings, LoggingLevel.Information);

            rfm9XDevice.OnReceive  += Rfm9XDevice_OnReceive;
            rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;

            rfm9XDevice.Initialise(this.applicationSettings.Frequency,
                                   rxDoneignoreIfCrcMissing: true,
                                   rxDoneignoreIfCrcInvalid: true,
                                   paBoost: this.applicationSettings.PABoost, maxPower: this.applicationSettings.MaxPower, outputPower: this.applicationSettings.OutputPower,
                                   ocpOn: this.applicationSettings.OCPOn, ocpTrim: this.applicationSettings.OCPTrim,
                                   lnaGain: this.applicationSettings.LnaGain, lnaBoost: this.applicationSettings.LNABoost,
                                   bandwidth: this.applicationSettings.Bandwidth, codingRate: this.applicationSettings.CodingRate, implicitHeaderModeOn: this.applicationSettings.ImplicitHeaderModeOn,
                                   spreadingFactor: this.applicationSettings.SpreadingFactor,
                                   rxPayloadCrcOn: true,
                                   symbolTimeout: this.applicationSettings.SymbolTimeout,
                                   preambleLength: this.applicationSettings.PreambleLength,
                                   payloadLength: this.applicationSettings.PayloadLength,
                                   payloadMaxLength: this.applicationSettings.PayloadMaxLength,
                                   freqHoppingPeriod: this.applicationSettings.FreqHoppingPeriod,
                                   lowDataRateOptimize: this.applicationSettings.LowDataRateOptimize,
                                   ppmCorrection: this.applicationSettings.PpmCorrection,

                                   detectionOptimize: this.applicationSettings.DetectionOptimize,
                                   invertIQ: this.applicationSettings.InvertIQ,
                                   detectionThreshold: this.applicationSettings.DetectionThreshold,
                                   syncWord: this.applicationSettings.SyncWord
                                   );

                        #if DEBUG
            rfm9XDevice.RegisterDump();
                        #endif

            rfm9XDevice.Receive(Encoding.UTF8.GetBytes(this.applicationSettings.Address));

            LoggingFields loRaSettings = new LoggingFields();
            loRaSettings.AddString("Address", this.applicationSettings.Address);
            loRaSettings.AddDouble("Frequency", this.applicationSettings.Frequency);
            loRaSettings.AddBoolean("PABoost", this.applicationSettings.PABoost);

            loRaSettings.AddUInt8("MaxPower", this.applicationSettings.MaxPower);
            loRaSettings.AddUInt8("OutputPower", this.applicationSettings.OutputPower);
            loRaSettings.AddBoolean("OCPOn", this.applicationSettings.OCPOn);
            loRaSettings.AddUInt8("OCPTrim", this.applicationSettings.OCPTrim);

            loRaSettings.AddString("LnaGain", this.applicationSettings.LnaGain.ToString());
            loRaSettings.AddBoolean("lnaBoost", this.applicationSettings.LNABoost);

            loRaSettings.AddString("codingRate", this.applicationSettings.CodingRate.ToString());
            loRaSettings.AddString("implicitHeaderModeOn", applicationSettings.ImplicitHeaderModeOn.ToString());
            loRaSettings.AddString("spreadingFactor", this.applicationSettings.SpreadingFactor.ToString());
            loRaSettings.AddBoolean("rxPayloadCrcOn", true);

            loRaSettings.AddUInt8("symbolTimeout", this.applicationSettings.SymbolTimeout);
            loRaSettings.AddUInt8("preambleLength", this.applicationSettings.PreambleLength);
            loRaSettings.AddUInt8("payloadLength", this.applicationSettings.PayloadLength);

            loRaSettings.AddUInt8("payloadMaxLength", this.applicationSettings.PayloadMaxLength);
            loRaSettings.AddUInt8("freqHoppingPeriod", this.applicationSettings.FreqHoppingPeriod);
            loRaSettings.AddBoolean("lowDataRateOptimize", this.applicationSettings.LowDataRateOptimize);
            loRaSettings.AddUInt8("ppmCorrection", this.applicationSettings.PpmCorrection);

            loRaSettings.AddString("detectionOptimize", this.applicationSettings.DetectionOptimize.ToString());
            loRaSettings.AddBoolean("invertIQ", this.applicationSettings.InvertIQ);
            loRaSettings.AddString("detectionThreshold", this.applicationSettings.DetectionThreshold.ToString());
            loRaSettings.AddUInt8("SyncWord", this.applicationSettings.SyncWord);

            this.logging.LogEvent("LoRa configuration", loRaSettings, LoggingLevel.Information);

            this.deferral = taskInstance.GetDeferral();
        }
        private async void Rfm9XDevice_OnReceive(object sender, Rfm9XDevice.OnDataReceivedEventArgs e)
        {
            string addressBcdText;
            string messageBcdText;
            string messageText = "";

            char[] sensorReadingSeparator    = new char[] { ',' };
            char[] sensorIdAndValueSeparator = new char[] { ' ' };

            addressBcdText = BitConverter.ToString(e.Address);

            messageBcdText = BitConverter.ToString(e.Data);
            try
            {
                messageText = UTF8Encoding.UTF8.GetString(e.Data);
            }
            catch (Exception)
            {
                this.logging.LogMessage("Failure converting payload to text", LoggingLevel.Error);
                return;
            }

#if DEBUG
            Debug.WriteLine(@"{0:HH:mm:ss}-RX From {1} PacketSnr {2:0.0} Packet RSSI {3}dBm RSSI {4}dBm = {5} byte message ""{6}""", DateTime.Now, addressBcdText, e.PacketSnr, e.PacketRssi, e.Rssi, e.Data.Length, messageText);
#endif
            LoggingFields messagePayload = new LoggingFields();
            messagePayload.AddInt32("AddressLength", e.Address.Length);
            messagePayload.AddString("Address-BCD", addressBcdText);
            messagePayload.AddInt32("Message-Length", e.Data.Length);
            messagePayload.AddString("Message-BCD", messageBcdText);
            messagePayload.AddString("Nessage-Unicode", messageText);
            messagePayload.AddDouble("Packet SNR", e.PacketSnr);
            messagePayload.AddInt32("Packet RSSI", e.PacketRssi);
            messagePayload.AddInt32("RSSI", e.Rssi);
            this.logging.LogEvent("Message Data", messagePayload, LoggingLevel.Verbose);


            // Check the address is not to short/long
            if (e.Address.Length < AddressLengthMinimum)
            {
                this.logging.LogMessage("From address too short", LoggingLevel.Warning);
                return;
            }

            if (e.Address.Length > MessageLengthMaximum)
            {
                this.logging.LogMessage("From address too long", LoggingLevel.Warning);
                return;
            }

            // Check the payload is not too short/long
            if (e.Data.Length < MessageLengthMinimum)
            {
                this.logging.LogMessage("Message too short to contain any data", LoggingLevel.Warning);
                return;
            }

            if (e.Data.Length > MessageLengthMaximum)
            {
                this.logging.LogMessage("Message too long to contain valid data", LoggingLevel.Warning);
                return;
            }

            // Adafruit IO is case sensitive & onlye does lower case ?
            string deviceId = addressBcdText.ToLower();

            // Chop up the CSV text payload
            string[] sensorReadings = messageText.Split(sensorReadingSeparator, StringSplitOptions.RemoveEmptyEntries);
            if (sensorReadings.Length == 0)
            {
                this.logging.LogMessage("Payload contains no sensor readings", LoggingLevel.Warning);
                return;
            }

            Group_feed_data groupFeedData = new Group_feed_data();

            LoggingFields sensorData = new LoggingFields();
            sensorData.AddString("DeviceID", deviceId);

            // Chop up each sensor reading into an ID & value
            foreach (string sensorReading in sensorReadings)
            {
                string[] sensorIdAndValue = sensorReading.Split(sensorIdAndValueSeparator, StringSplitOptions.RemoveEmptyEntries);

                // Check that there is an id & value
                if (sensorIdAndValue.Length != 2)
                {
                    this.logging.LogMessage("Sensor reading invalid format", LoggingLevel.Warning);
                    return;
                }

                string sensorId = sensorIdAndValue[0].ToLower();
                string value    = sensorIdAndValue[1];

                // Construct the sensor ID from SensordeviceID & Value ID
                groupFeedData.Feeds.Add(new Anonymous2()
                {
                    Key = string.Format("{0}{1}", deviceId, sensorId), Value = value
                });

                sensorData.AddString(sensorId, value);

                Debug.WriteLine(" Sensor {0}{1} Value {2}", deviceId, sensorId, value);
            }

            this.logging.LogEvent("Sensor readings", sensorData, LoggingLevel.Verbose);

            try
            {
                Debug.WriteLine(" CreateGroupDataAsync start");
                await this.adaFruitIOClient.CreateGroupDataAsync(this.applicationSettings.AdaFruitIOUserName, this.applicationSettings.AdaFruitIOGroupName.ToLower(), groupFeedData);

                Debug.WriteLine(" CreateGroupDataAsync finish");
            }
            catch (Exception ex)
            {
                Debug.WriteLine(" CreateGroupDataAsync failed {0}", ex.Message);
                this.logging.LogMessage("CreateGroupDataAsync failed " + ex.Message, LoggingLevel.Error);
            }
        }
Example #5
0
        /// <summary>
        /// This method demonstrates the LoggingChannel and LoggingActivity APIs.
        /// </summary>
        /// <param name="channel">
        /// The channel to use for the demonstration. This channel may have been
        /// constructed using a Windows 8.1 constructor or a Windows 10 constructor.
        /// The same APIs are supported in both cases, but the ETL events will be
        /// formatted a bit differently depending on how the channel was constructed.
        /// </param>
        private void DemonstrateLogging(LoggingChannel channel)
        {
            // Whenever any ETW session changes the way it is listening to this
            // channel, the LoggingEnable event is fired. For example, this might
            // be called when a session begins listening, changes the level at
            // which it is listening, or stops listening.
            channel.LoggingEnabled += this.OnLoggingEnabled;

            // Log simple string events
            channel.LogMessage("Simple message"); // default level is Verbose
            channel.LogMessage("Simple error", LoggingLevel.Error);

            // Log simple string + integer events.
            channel.LogValuePair("Simple message", 123); // default level is Verbose
            channel.LogValuePair("Simple error", 456, LoggingLevel.Error);

            // The channel.Name property returns the name that was used when the
            // channel was constructed. When running in Windows 10 mode, the name
            // is already set as the provider name, so no LoggingChannelName is
            // automatically added to the event.
            channel.LogMessage(channel.Name);

            // The channel.Id property is new to Windows 10.
            channel.LogMessage(channel.Id.ToString());

            // If you want to avoid the overhead of collecting data when nobody is
            // listening to your channel, check the Enabled property before logging.
            if (channel.Enabled)
            {
                channel.LogMessage(this.CollectExpensiveData());
            }

            // The IsEnabled() method is exactly the same as the Enabled property,
            // except that it is a new Windows 10 API.
            if (channel.IsEnabled())
            {
                channel.LogMessage(this.CollectExpensiveData());
            }

            // If you want to only collect data if somebody is listening at a specific
            // level, you need to check both Enabled and Level. Note that the value of
            // the Level is unspecified when Enabled is false.
            if (channel.Enabled && channel.Level <= LoggingLevel.Warning)
            {
                channel.LogMessage(this.CollectExpensiveData(), LoggingLevel.Warning);
            }

            // The IsEnabled(LoggingLevel) method is a bit nicer than checking both
            // Enabled and Level, but it is only available on Windows 10 or later.
            if (channel.IsEnabled(LoggingLevel.Warning))
            {
                channel.LogMessage(this.CollectExpensiveData(), LoggingLevel.Warning);
            }

            // You can also use IsEnabled to check for keywords.
            if (channel.IsEnabled(LoggingLevel.Information, 0x10))
            {
                channel.LogMessage(this.CollectExpensiveData(), LoggingLevel.Information);
            }

            // Use LoggingFields with the LogEvent method to write complex events.
            var fields = new LoggingFields();

            fields.AddDouble("pi", 3.14159);
            channel.LogEvent(
                "ComplexEvent",
                fields,
                LoggingLevel.Verbose,
                new LoggingOptions(0x10)); // Keywords = 0x10

            // You can add any number of name-value pairs to a fields object, though
            // you may encounter ETW limitations if you add too many. For example,
            // ETW is limited to a maximum event size of 64KB, and the current
            // TraceLogging decoder can handle no more than 128 fields.

            // Performance optimization: You can reuse a LoggingFields object to
            // avoid unnecessary allocations. Don't forget to call Clear()
            // between uses, and don't try to share a LoggingFields object between
            // threads.
            fields.Clear();
            fields.AddDateTime("Date", DateTimeOffset.Now);
            channel.LogEvent("Now", fields);

            fields.Clear();

            // You can add a formatting hint to affect the way a value is decoded.
            // Not all combinations are useful, and the hint may be ignored.
            // For example, you can encode an MBCS string by writing a byte array
            // with a String hint.
            fields.AddUInt8Array(
                "AnsiString",
                new byte[] { 65, 66, 67, 49, 50, 51 }, // "ABC123"
                LoggingFieldFormat.String);

            // You can add "tag" bits to a field. These are user-defined bits that
            // can be used to communicate with an event processing tool. For example,
            // you might define a tag bit to indicate that a field contains private
            // data that should not be displayed on-screen.
            fields.AddString("Password", "12345", LoggingFieldFormat.Default, 0x10);

            // You can add a "structure" to an event. A structure is a name for a
            // group of fields. Structures can nest. Call BeginStruct to add a level
            // of nesting, and call EndStruct after the last field of the structure.
            fields.BeginStruct("Nested");
            fields.AddInt16("Nested-1", 1);
            fields.AddInt16("Nested-2", 2);
            fields.BeginStruct("Nested-Nested");
            fields.AddInt16("Nested-Nested-3", 3);
            fields.EndStruct();
            fields.AddInt16("Nested-4", 4);
            fields.EndStruct();

            // Advanced scenarios: you can use a LoggingOptions object to control
            // detailed event settings such as keywords, opcodes, and activity Ids.
            // These have their normal ETW semantics. You can also set event tags,
            // which are bit values that can be used to communicate with the event
            // processor.
            channel.LogEvent(
                "VeryComplexEvent",
                fields,
                LoggingLevel.Information,
                new LoggingOptions {
                Keywords = 0x123, Tags = 0x10
            });

            // Windows 10 introduces the ILoggingTarget interface. LoggingChannel
            // implements this interface. This interface allows components to accept
            // a logger as an parameter.
            this.DoSomething(channel);

            /*
             * If a LoggingActivity is created using a LoggingActivity constructor,
             * it will use Windows 8.1 semantics:
             *
             * - If an activity is destroyed (garbage-collected) without being closed
             * and the associated LoggingChannel is still open, the activity will
             * write a default Stop event.
             * - The default Stop event (written by the destructor or by the Close()
             * method) is encoded as a "simple" event.
             *
             * The 8.1 semantics are deprecated because the automatic generation of
             * a Stop event at garbage-collection can be misleading. The Stop event
             * is intended to mark the a precise point at which an activity is
             * completed, while the garbage-collection of an abandoned activity is
             * inherently imprecise and unpredictable.
             *
             * If a LoggingActivity is created using a StartActivity method, it will
             * use Windows 10 semantics:
             *
             * - If an activity is destroyed (garbage-collected) without being closed,
             * there will be no Stop event for the activity.
             * - The default Stop event (written by the Close() method) is encoded as
             * a TraceLogging event with name "ActivityClosed".
             */

            // This activity is created with Windows 8.1 semantics.
            using (var a1 = new LoggingActivity("Activity1", channel))
            {
                // The activity Start event is written by the LoggingActivity constructor.
                // You would do your activity's work here.
                // The activity Stop event is written when the activity is closed (disposed).

                // The Windows 10 LoggingActivity adds new methods for writing events
                // that are marked as associated with the activity.
                a1.LogEvent("Activity event");

                // LoggingActivity also implements the ILoggingTarget interface, so you can
                // use either a channel or an activity as a logging target.
                this.DoSomething(a1);

                // The Windows 10 LoggingActivity adds new methods for creating nested activities.
                // Note that nested activities are always created with Windows 10 semantics,
                // even when nested under an activity that is using Windows 8.1 semantics.
                using (var a2 = a1.StartActivity("Activity2"))
                {
                    // Nested task occurs here.

                    // The Windows 10 LoggingActivity allows you to customize the Stop event.
                    a2.StopActivity("Activity 2 stop");
                }

                // Because a1 is using Windows 8.1 semantics, if we did not call Dispose(),
                // it would attempt to write a Stop event when it is garbage collected.
                // Writing Stop events during garbage collection is not useful, so be sure
                // to properly stop, close, or dispose activities.
            }

            // The Windows 10 StartActivity method creates a new activity, optionally with
            // specified fields and characteristics.
            // This activity is created with Windows 10 semantics.
            using (var a3 = channel.StartActivity("Activity3"))
            {
                // Because a3 is using Windows 10 semantics, if we did not call Dispose(),
                // there would be no Stop event (not even when the activity is garbage
                // collected). To get a Stop event, be sure to stop, close, or dispose the
                // activity.
            }
        }
Example #6
0
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            StorageFolder localFolder = ApplicationData.Current.LocalFolder;

            try
            {
                // see if the configuration file is present if not copy minimal sample one from application directory
                if (localFolder.TryGetItemAsync(ConfigurationFilename).AsTask().Result == null)
                {
                    StorageFile templateConfigurationfile = Package.Current.InstalledLocation.GetFileAsync(ConfigurationFilename).AsTask().Result;
                    templateConfigurationfile.CopyAsync(localFolder, ConfigurationFilename).AsTask();
                }

                // Load the settings from configuration file exit application if missing or invalid
                StorageFile file = localFolder.GetFileAsync(ConfigurationFilename).AsTask().Result;

                applicationSettings = (JsonConvert.DeserializeObject <ApplicationSettings>(FileIO.ReadTextAsync(file).AsTask().Result));
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("JSON configuration file load failed " + ex.Message, LoggingLevel.Error);
                return;
            }

            // Log the Application build, shield information etc.
            LoggingFields applicationBuildInformation = new LoggingFields();

#if DRAGINO
            applicationBuildInformation.AddString("Shield", "DraginoLoRaGPSHat");
#endif
#if ELECROW
            applicationBuildInformation.AddString("Shield", "ElecrowRFM95IoTBoard");
#endif
#if M2M
            applicationBuildInformation.AddString("Shield", "M2M1ChannelLoRaWanGatewayShield");
#endif
#if ELECTRONIC_TRICKS
            applicationBuildInformation.AddString("Shield", "ElectronicTricksLoRaLoRaWANShield");
#endif
#if UPUTRONICS_RPIZERO_CS0
            applicationBuildInformation.AddString("Shield", "UputronicsPiZeroLoRaExpansionBoardCS0");
#endif
#if UPUTRONICS_RPIZERO_CS1
            applicationBuildInformation.AddString("Shield", "UputronicsPiZeroLoRaExpansionBoardCS1");
#endif
#if UPUTRONICS_RPIPLUS_CS0
            applicationBuildInformation.AddString("Shield", "UputronicsPiPlusLoRaExpansionBoardCS0");
#endif
#if UPUTRONICS_RPIPLUS_CS1
            applicationBuildInformation.AddString("Shield", "UputronicsPiPlusLoRaExpansionBoardCS1");
#endif
#if ADAFRUIT_RADIO_BONNET
            applicationBuildInformation.AddString("Shield", "AdafruitRadioBonnet");
#endif
#if PAYLOAD_TEXT
            applicationBuildInformation.AddString("PayloadProcessor", "Text");
#endif
#if PAYLOAD_TEXT_COMA_SEPARATED_VALUES
            applicationBuildInformation.AddString("PayloadProcessor", "ComaSeperatedValues");
#endif
#if PAYLOAD_BINARY_BINARY_CODED_DECIMAL
            applicationBuildInformation.AddString("PayloadProcessor", "BinaryCodedDecimal");
#endif
#if PAYLOAD_BINARY_CAYENNE_LOW_POWER_PAYLOAD
            applicationBuildInformation.AddString("PayloadProcessor", "CayenneLowPowerPayload");
#endif
            applicationBuildInformation.AddString("Timezone", TimeZoneSettings.CurrentTimeZoneDisplayName);
            applicationBuildInformation.AddString("OSVersion", Environment.OSVersion.VersionString);
            applicationBuildInformation.AddString("MachineName", Environment.MachineName);

            // This is from the application manifest
            Package        package   = Package.Current;
            PackageId      packageId = package.Id;
            PackageVersion version   = packageId.Version;

            applicationBuildInformation.AddString("ApplicationVersion", string.Format($"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}"));
            this.logging.LogEvent("Application starting", applicationBuildInformation, LoggingLevel.Information);

            // Log the MQTT connection string and associated settings
            LoggingFields mqttClientInformation = new LoggingFields();
            mqttClientInformation.AddString("UserName", this.applicationSettings.MqttUserName);
            mqttClientInformation.AddString("Password", this.applicationSettings.MqttPassword);
            mqttClientInformation.AddString("Server", this.applicationSettings.MqttServer);
            mqttClientInformation.AddString("ClientID", this.applicationSettings.MqttClientID);
            mqttClientInformation.AddString("PlatformSpecificConfiguration", this.applicationSettings.PlatformSpecificConfiguration);
            this.logging.LogEvent("MQTT client configuration", mqttClientInformation, LoggingLevel.Information);

            // Connect the MQTT broker so we are ready for messages
            var factory = new MqttFactory();
            this.mqttClient = factory.CreateMqttClient();

            // Wire up a handler for disconnect event for retry
            mqttClient.UseDisconnectedHandler(new MqttClientDisconnectedHandlerDelegate(e => MqttClient_Disconnected(e)));
            mqttClient.UseApplicationMessageReceivedHandler(new MqttApplicationMessageReceivedHandlerDelegate(e => MqttClient_ApplicationMessageReceived(e)));

            try
            {
                this.mqttOptions = new MqttClientOptionsBuilder()
                                   .WithClientId(applicationSettings.MqttClientID)
                                   .WithTcpServer(applicationSettings.MqttServer)
                                   .WithCredentials(applicationSettings.MqttUserName, applicationSettings.MqttPassword)
                                   .WithTls()
                                   .Build();

                this.mqttClient.ConnectAsync(this.mqttOptions).Wait();
            }
            catch (Exception ex)
            {
                mqttClientInformation.AddString("Exception", ex.ToString());
                this.logging.LogMessage("MQTT Connect Async failed" + ex.Message, LoggingLevel.Error);
                return;
            }

            // Load up the message handler assembly
            try
            {
                Assembly assembly = Assembly.Load(applicationSettings.MessageHandlerAssembly);

                messageHandler = (IMessageHandler)assembly.CreateInstance("devMobile.Mqtt.IoTCore.FieldGateway.MessageHandler");
                if (messageHandler == null)
                {
                    this.logging.LogMessage($"MessageHandler assembly {applicationSettings.MessageHandlerAssembly} load failed", LoggingLevel.Error);
                    return;
                }

                messageHandler.Initialise(logging, mqttClient, rfm9XDevice, applicationSettings.PlatformSpecificConfiguration);
            }
            catch (Exception ex)
            {
                mqttClientInformation.AddString("Exception", ex.ToString());
                this.logging.LogMessage("MessageHandler configuration failed" + ex.Message, LoggingLevel.Error);
                return;
            }

            // Configure the LoRa module
            rfm9XDevice.OnReceive  += Rfm9XDevice_OnReceive;
            rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;

            rfm9XDevice.Initialise(this.applicationSettings.Frequency,
                                   rxDoneignoreIfCrcMissing: true,
                                   rxDoneignoreIfCrcInvalid: true,
                                   paBoost: this.applicationSettings.PABoost, maxPower: this.applicationSettings.MaxPower, outputPower: this.applicationSettings.OutputPower,
                                   ocpOn: this.applicationSettings.OCPOn, ocpTrim: this.applicationSettings.OCPTrim,
                                   lnaGain: this.applicationSettings.LnaGain, lnaBoost: this.applicationSettings.LNABoost,
                                   bandwidth: this.applicationSettings.Bandwidth, codingRate: this.applicationSettings.CodingRate, implicitHeaderModeOn: this.applicationSettings.ImplicitHeaderModeOn,
                                   spreadingFactor: this.applicationSettings.SpreadingFactor,
                                   rxPayloadCrcOn: true,
                                   symbolTimeout: this.applicationSettings.SymbolTimeout,
                                   preambleLength: this.applicationSettings.PreambleLength,
                                   payloadLength: this.applicationSettings.PayloadLength,
                                   payloadMaxLength: this.applicationSettings.PayloadMaxLength,
                                   freqHoppingPeriod: this.applicationSettings.FreqHoppingPeriod,
                                   lowDataRateOptimize: this.applicationSettings.LowDataRateOptimize,
                                   ppmCorrection: this.applicationSettings.PpmCorrection,
                                   detectionOptimize: this.applicationSettings.DetectionOptimize,
                                   invertIQ: this.applicationSettings.InvertIQ,
                                   detectionThreshold: this.applicationSettings.DetectionThreshold,
                                   syncWord: this.applicationSettings.SyncWord
                                   );

#if DEBUG
            rfm9XDevice.RegisterDump();
#endif

            rfm9XDevice.Receive(Encoding.UTF8.GetBytes(this.applicationSettings.Address));

            LoggingFields loRaSettings = new LoggingFields();
            loRaSettings.AddString("Address", this.applicationSettings.Address);
            loRaSettings.AddDouble("Frequency", this.applicationSettings.Frequency);
            loRaSettings.AddBoolean("PABoost", this.applicationSettings.PABoost);

            loRaSettings.AddUInt8("MaxPower", this.applicationSettings.MaxPower);
            loRaSettings.AddUInt8("OutputPower", this.applicationSettings.OutputPower);
            loRaSettings.AddBoolean("OCPOn", this.applicationSettings.OCPOn);
            loRaSettings.AddUInt8("OCPTrim", this.applicationSettings.OCPTrim);

            loRaSettings.AddString("LnaGain", this.applicationSettings.LnaGain.ToString());
            loRaSettings.AddBoolean("lnaBoost", this.applicationSettings.LNABoost);

            loRaSettings.AddString("codingRate", this.applicationSettings.CodingRate.ToString());
            loRaSettings.AddString("implicitHeaderModeOn", applicationSettings.ImplicitHeaderModeOn.ToString());
            loRaSettings.AddString("spreadingFactor", this.applicationSettings.SpreadingFactor.ToString());
            loRaSettings.AddBoolean("rxPayloadCrcOn", true);

            loRaSettings.AddUInt8("symbolTimeout", this.applicationSettings.SymbolTimeout);
            loRaSettings.AddUInt8("preambleLength", this.applicationSettings.PreambleLength);
            loRaSettings.AddUInt8("payloadLength", this.applicationSettings.PayloadLength);

            loRaSettings.AddUInt8("payloadMaxLength", this.applicationSettings.PayloadMaxLength);
            loRaSettings.AddUInt8("freqHoppingPeriod", this.applicationSettings.FreqHoppingPeriod);
            loRaSettings.AddBoolean("lowDataRateOptimize", this.applicationSettings.LowDataRateOptimize);
            loRaSettings.AddUInt8("ppmCorrection", this.applicationSettings.PpmCorrection);

            loRaSettings.AddString("detectionOptimize", this.applicationSettings.DetectionOptimize.ToString());
            loRaSettings.AddBoolean("invertIQ", this.applicationSettings.InvertIQ);
            loRaSettings.AddString("detectionThreshold", this.applicationSettings.DetectionThreshold.ToString());
            loRaSettings.AddUInt8("SyncWord", this.applicationSettings.SyncWord);

            this.logging.LogEvent("LoRa configuration", loRaSettings, LoggingLevel.Information);

            this.deferral = taskInstance.GetDeferral();
        }
        /// <summary>
        /// This method demonstrates the LoggingChannel and LoggingActivity APIs.
        /// </summary>
        /// <param name="channel">
        /// The channel to use for the demonstration. This channel may have been
        /// constructed using a Windows 8.1 constructor or a Windows 10 constructor.
        /// The same APIs are supported in both cases, but the ETL events will be
        /// formatted a bit differently depending on how the channel was constructed.
        /// </param>
        private void DemonstrateLogging(LoggingChannel channel)
        {
            // Whenever any ETW session changes the way it is listening to this
            // channel, the LoggingEnable event is fired. For example, this might
            // be called when a session begins listening, changes the level at
            // which it is listening, or stops listening.
            channel.LoggingEnabled += this.OnLoggingEnabled;

            // Log simple string events
            channel.LogMessage("Simple message"); // default level is Verbose
            channel.LogMessage("Simple error", LoggingLevel.Error);

            // Log simple string + integer events.
            channel.LogValuePair("Simple message", 123); // default level is Verbose
            channel.LogValuePair("Simple error", 456, LoggingLevel.Error);

            // The channel.Name property returns the name that was used when the
            // channel was constructed. When running in Windows 10 mode, the name
            // is already set as the provider name, so no LoggingChannelName is
            // automatically added to the event.
            channel.LogMessage(channel.Name);

            // The channel.Id property is new to Windows 10.
            channel.LogMessage(channel.Id.ToString());

            // If you want to avoid the overhead of collecting data when nobody is
            // listening to your channel, check the Enabled property before logging.
            if (channel.Enabled)
            {
                channel.LogMessage(this.CollectExpensiveData());
            }

            // The IsEnabled() method is exactly the same as the Enabled property,
            // except that it is a new Windows 10 API.
            if (channel.IsEnabled())
            {
                channel.LogMessage(this.CollectExpensiveData());
            }

            // If you want to only collect data if somebody is listening at a specific
            // level, you need to check both Enabled and Level. Note that the value of
            // the Level is unspecified when Enabled is false.
            if (channel.Enabled && channel.Level <= LoggingLevel.Warning)
            {
                channel.LogMessage(this.CollectExpensiveData(), LoggingLevel.Warning);
            }

            // The IsEnabled(LoggingLevel) method is a bit nicer than checking both
            // Enabled and Level, but it is only available on Windows 10 or later.
            if (channel.IsEnabled(LoggingLevel.Warning))
            {
                channel.LogMessage(this.CollectExpensiveData(), LoggingLevel.Warning);
            }

            // You can also use IsEnabled to check for keywords.
            if (channel.IsEnabled(LoggingLevel.Information, 0x10))
            {
                channel.LogMessage(this.CollectExpensiveData(), LoggingLevel.Information);
            }

            // Use LoggingFields with the LogEvent method to write complex events.
            var fields = new LoggingFields();
            fields.AddDouble("pi", 3.14159);
            channel.LogEvent(
                "ComplexEvent",
                fields,
                LoggingLevel.Verbose,
                new LoggingOptions(0x10)); // Keywords = 0x10

            // You can add any number of name-value pairs to a fields object, though
            // you may encounter ETW limitations if you add too many. For example,
            // ETW is limited to a maximum event size of 64KB, and the current
            // TraceLogging decoder can handle no more than 128 fields.

            // Performance optimization: You can reuse a LoggingFields object to
            // avoid unnecessary allocations. Don't forget to call Clear()
            // between uses, and don't try to share a LoggingFields object between
            // threads.
            fields.Clear();
            fields.AddDateTime("Date", DateTimeOffset.Now);
            channel.LogEvent("Now", fields);

            fields.Clear();

            // You can add a formatting hint to affect the way a value is decoded.
            // Not all combinations are useful, and the hint may be ignored.
            // For example, you can encode an MBCS string by writing a byte array
            // with a String hint.
            fields.AddUInt8Array(
                "AnsiString",
                new byte[] { 65, 66, 67, 49, 50, 51 }, // "ABC123"
                LoggingFieldFormat.String);

            // You can add "tag" bits to a field. These are user-defined bits that
            // can be used to communicate with an event processing tool. For example,
            // you might define a tag bit to indicate that a field contains private
            // data that should not be displayed on-screen.
            fields.AddString("Password", "12345", LoggingFieldFormat.Default, 0x10);

            // You can add a "structure" to an event. A structure is a name for a
            // group of fields. Structures can nest. Call BeginStruct to add a level
            // of nesting, and call EndStruct after the last field of the structure.
            fields.BeginStruct("Nested");
                fields.AddInt16("Nested-1", 1);
                fields.AddInt16("Nested-2", 2);
                fields.BeginStruct("Nested-Nested");
                    fields.AddInt16("Nested-Nested-3", 3);
                fields.EndStruct();
                fields.AddInt16("Nested-4", 4);
            fields.EndStruct();

            // Advanced scenarios: you can use a LoggingOptions object to control
            // detailed event settings such as keywords, opcodes, and activity Ids.
            // These have their normal ETW semantics. You can also set event tags,
            // which are bit values that can be used to communicate with the event
            // processor.
            channel.LogEvent(
                "VeryComplexEvent",
                fields,
                LoggingLevel.Information,
                new LoggingOptions { Keywords = 0x123, Tags = 0x10 });

            // Windows 10 introduces the ILoggingTarget interface. LoggingChannel
            // implements this interface. This interface allows components to accept
            // a logger as an parameter.
            this.DoSomething(channel);

            /*
            If a LoggingActivity is created using a LoggingActivity constructor,
            it will use Windows 8.1 semantics:

            - If an activity is destroyed (garbage-collected) without being closed
              and the associated LoggingChannel is still open, the activity will
              write a default Stop event.
            - The default Stop event (written by the destructor or by the Close()
              method) is encoded as a "simple" event.

            The 8.1 semantics are deprecated because the automatic generation of
            a Stop event at garbage-collection can be misleading. The Stop event
            is intended to mark the a precise point at which an activity is
            completed, while the garbage-collection of an abandoned activity is
            inherently imprecise and unpredictable.

            If a LoggingActivity is created using a StartActivity method, it will
            use Windows 10 semantics:

            - If an activity is destroyed (garbage-collected) without being closed,
              there will be no Stop event for the activity.
            - The default Stop event (written by the Close() method) is encoded as
              a TraceLogging event with name "ActivityClosed".
            */

            // This activity is created with Windows 8.1 semantics.
            using (var a1 = new LoggingActivity("Activity1", channel))
            {
                // The activity Start event is written by the LoggingActivity constructor.
                // You would do your activity's work here.
                // The activity Stop event is written when the activity is closed (disposed).

                // The Windows 10 LoggingActivity adds new methods for writing events
                // that are marked as associated with the activity.
                a1.LogEvent("Activity event");

                // LoggingActivity also implements the ILoggingTarget interface, so you can
                // use either a channel or an activity as a logging target.
                this.DoSomething(a1);

                // The Windows 10 LoggingActivity adds new methods for creating nested activities.
                // Note that nested activities are always created with Windows 10 semantics,
                // even when nested under an activity that is using Windows 8.1 semantics.
                using (var a2 = a1.StartActivity("Activity2"))
                {
                    // Nested task occurs here.

                    // The Windows 10 LoggingActivity allows you to customize the Stop event.
                    a2.StopActivity("Activity 2 stop");
                }

                // Because a1 is using Windows 8.1 semantics, if we did not call Dispose(),
                // it would attempt to write a Stop event when it is garbage collected.
                // Writing Stop events during garbage collection is not useful, so be sure
                // to properly stop, close, or dispose activities.
            }

            // The Windows 10 StartActivity method creates a new activity, optionally with
            // specified fields and characteristics.
            // This activity is created with Windows 10 semantics.
            using (var a3 = channel.StartActivity("Activity3"))
            {
                // Because a3 is using Windows 10 semantics, if we did not call Dispose(),
                // there would be no Stop event (not even when the activity is garbage
                // collected). To get a Stop event, be sure to stop, close, or dispose the
                // activity.
            }
        }
Example #8
0
        private async void InterruptGpioPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
        {
            DateTime currentTime = DateTime.UtcNow;

            Debug.WriteLine($"Digital Input Interrupt {sender.PinNumber} triggered {args.Edge}");

            if (args.Edge != this.interruptTriggerOn)
            {
                return;
            }

            // Check that enough time has passed for picture to be taken
            if ((currentTime - this.imageLastCapturedAtUtc) < this.debounceTimeout)
            {
                this.displayGpioPin.Write(GpioPinValue.High);
                this.displayOffTimer.Change(this.timerPeriodDetectIlluminated, this.timerPeriodInfinite);
                return;
            }

            this.imageLastCapturedAtUtc = currentTime;

            // Just incase - stop code being called while photo already in progress
            if (this.cameraBusy)
            {
                this.displayGpioPin.Write(GpioPinValue.High);
                this.displayOffTimer.Change(this.timerPeriodDetectIlluminated, this.timerPeriodInfinite);
                return;
            }

            this.cameraBusy = true;

            try
            {
                using (Windows.Storage.Streams.InMemoryRandomAccessStream captureStream = new Windows.Storage.Streams.InMemoryRandomAccessStream())
                {
                    this.mediaCapture.CapturePhotoToStreamAsync(ImageEncodingProperties.CreateJpeg(), captureStream).AsTask().Wait();
                    captureStream.FlushAsync().AsTask().Wait();
                    captureStream.Seek(0);

                    IStorageFile photoFile = await KnownFolders.PicturesLibrary.CreateFileAsync(ImageFilename, CreationCollisionOption.ReplaceExisting);

                    ImageEncodingProperties imageProperties = ImageEncodingProperties.CreateJpeg();
                    await this.mediaCapture.CapturePhotoToStorageFileAsync(imageProperties, photoFile);

                    IList <FaceAttributeType> returnfaceAttributes = new List <FaceAttributeType>();

                    returnfaceAttributes.Add(FaceAttributeType.Gender);
                    returnfaceAttributes.Add(FaceAttributeType.Age);

                    IList <DetectedFace> detectedFaces = await this.faceClient.Face.DetectWithStreamAsync(captureStream.AsStreamForRead(), returnFaceAttributes : returnfaceAttributes);

                    Debug.WriteLine($"Count {detectedFaces.Count}");

                    if (detectedFaces.Count > 0)
                    {
                        this.displayGpioPin.Write(GpioPinValue.High);

                        // Start the timer to turn the LED off
                        this.displayOffTimer.Change(this.timerPeriodFaceIlluminated, this.timerPeriodInfinite);
                    }

                    LoggingFields imageInformation = new LoggingFields();

                    imageInformation.AddDateTime("TakenAtUTC", currentTime);
                    imageInformation.AddInt32("Pin", sender.PinNumber);
                    imageInformation.AddInt32("Faces", detectedFaces.Count);
                    foreach (DetectedFace detectedFace in detectedFaces)
                    {
                        Debug.WriteLine("Face");
                        if (detectedFace.FaceId.HasValue)
                        {
                            imageInformation.AddGuid("FaceId", detectedFace.FaceId.Value);
                            Debug.WriteLine($" Id:{detectedFace.FaceId.Value}");
                        }

                        imageInformation.AddInt32("Left", detectedFace.FaceRectangle.Left);
                        imageInformation.AddInt32("Width", detectedFace.FaceRectangle.Width);
                        imageInformation.AddInt32("Top", detectedFace.FaceRectangle.Top);
                        imageInformation.AddInt32("Height", detectedFace.FaceRectangle.Height);
                        Debug.WriteLine($" L:{detectedFace.FaceRectangle.Left} W:{detectedFace.FaceRectangle.Width} T:{detectedFace.FaceRectangle.Top} H:{detectedFace.FaceRectangle.Height}");
                        if (detectedFace.FaceAttributes != null)
                        {
                            if (detectedFace.FaceAttributes.Gender.HasValue)
                            {
                                imageInformation.AddString("Gender", detectedFace.FaceAttributes.Gender.Value.ToString());
                                Debug.WriteLine($" Gender:{detectedFace.FaceAttributes.Gender.ToString()}");
                            }

                            if (detectedFace.FaceAttributes.Age.HasValue)
                            {
                                imageInformation.AddDouble("Age", detectedFace.FaceAttributes.Age.Value);
                                Debug.WriteLine($" Age:{detectedFace.FaceAttributes.Age.Value.ToString("F1")}");
                            }
                        }
                    }

                    this.logging.LogEvent("Captured image processed by Cognitive Services", imageInformation);
                }
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("Camera photo or save failed " + ex.Message, LoggingLevel.Error);
            }
            finally
            {
                this.cameraBusy = false;
            }
        }
Example #9
0
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            StorageFolder localFolder = ApplicationData.Current.LocalFolder;

            try
            {
                // see if the configuration file is present if not copy minimal sample one from application directory
                if (localFolder.TryGetItemAsync(ConfigurationFilename).AsTask().Result == null)
                {
                    StorageFile templateConfigurationfile = Package.Current.InstalledLocation.GetFileAsync(ConfigurationFilename).AsTask().Result;
                    templateConfigurationfile.CopyAsync(localFolder, ConfigurationFilename).AsTask();
                }

                // Load the settings from configuration file exit application if missing or invalid
                StorageFile file = localFolder.GetFileAsync(ConfigurationFilename).AsTask().Result;

                applicationSettings = (JsonConvert.DeserializeObject <ApplicationSettings>(FileIO.ReadTextAsync(file).AsTask().Result));
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("JSON configuration file load failed " + ex.Message, LoggingLevel.Error);
                return;
            }

            // Log the Application build, shield information etc.
            LoggingFields applicationBuildInformation = new LoggingFields();

#if DRAGINO
            applicationBuildInformation.AddString("Shield", "DraginoLoRaGPSHat");
#endif
#if ELECROW
            applicationBuildInformation.AddString("Shield", "ElecrowRFM95IoTBoard");
#endif
#if M2M
            applicationBuildInformation.AddString("Shield", "M2M1ChannelLoRaWanGatewayShield");
#endif
#if ELECTRONIC_TRICKS
            applicationBuildInformation.AddString("Shield", "ElectronicTricksLoRaLoRaWANShield");
#endif
#if UPUTRONICS_RPIZERO_CS0
            applicationBuildInformation.AddString("Shield", "UputronicsPiZeroLoRaExpansionBoardCS0");
#endif
#if UPUTRONICS_RPIZERO_CS1
            applicationBuildInformation.AddString("Shield", "UputronicsPiZeroLoRaExpansionBoardCS1");
#endif
#if UPUTRONICS_RPIPLUS_CS0
            applicationBuildInformation.AddString("Shield", "UputronicsPiPlusLoRaExpansionBoardCS0");
#endif
#if UPUTRONICS_RPIPLUS_CS1
            applicationBuildInformation.AddString("Shield", "UputronicsPiPlusLoRaExpansionBoardCS1");
#endif
#if ADAFRUIT_RADIO_BONNET
            applicationBuildInformation.AddString("Shield", "AdafruitRadioBonnet");
#endif
#if CLOUD_DEVICE_BOND
            applicationBuildInformation.AddString("Bond", "Supported");
#else
            applicationBuildInformation.AddString("Bond", "NotSupported");
#endif
#if CLOUD_DEVICE_PUSH
            applicationBuildInformation.AddString("Push", "Supported");
#else
            applicationBuildInformation.AddString("Push", "NotSupported");
#endif
#if CLOUD_DEVICE_SEND
            applicationBuildInformation.AddString("Send", "Supported");
#else
            applicationBuildInformation.AddString("Send", "NotSupported");
#endif
#if PAYLOAD_TEXT
            applicationBuildInformation.AddString("PayloadProcessor", "Text");
#endif
#if PAYLOAD_TEXT_COMA_SEPARATED_VALUES
            applicationBuildInformation.AddString("PayloadProcessor", "ComaSeperatedValues");
#endif
#if PAYLOAD_BINARY_BINARY_CODED_DECIMAL
            applicationBuildInformation.AddString("PayloadProcessor", "BinaryCodedDecimal");
#endif
#if PAYLOAD_BINARY_CAYENNE_LOW_POWER_PAYLOAD
            applicationBuildInformation.AddString("PayloadProcessor", "CayenneLowPowerPayload");
#endif

            applicationBuildInformation.AddString("Timezone", TimeZoneSettings.CurrentTimeZoneDisplayName);
            applicationBuildInformation.AddString("OSVersion", Environment.OSVersion.VersionString);
            applicationBuildInformation.AddString("MachineName", Environment.MachineName);

            // This is from the application manifest
            Package        package   = Package.Current;
            PackageId      packageId = package.Id;
            PackageVersion version   = packageId.Version;

            applicationBuildInformation.AddString("ApplicationVersion", string.Format($"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}"));
            this.logging.LogEvent("Application starting", applicationBuildInformation, LoggingLevel.Information);

            try
            {
#if DRAGINO
                rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS0, ChipSelectLine, ResetLine, InterruptLine);
#endif
#if M2M
                rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS0, ChipSelectLine, ResetLine, InterruptLine);
#endif
#if ELECROW
                rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS1, ResetLine, InterruptLine);
#endif
#if ELECTRONIC_TRICKS
                rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS0, ResetLine, InterruptLine);
#endif
#if UPUTRONICS_RPIZERO_CS0
                rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS0, InterruptLine);
#endif
#if UPUTRONICS_RPIZERO_CS1
                rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS1, InterruptLine);
#endif
#if UPUTRONICS_RPIPLUS_CS0
                rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS0, InterruptLine);
#endif
#if UPUTRONICS_RPIPLUS_CS1
                rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS1, InterruptLine);
#endif
#if ADAFRUIT_RADIO_BONNET
                rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS1, ResetLine, InterruptLine);
#endif
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("Hardware initialisation failed " + ex.Message, LoggingLevel.Error);
                return;
            }

            // Log the Azure connection string and associated settings
            LoggingFields azureIoTHubSettings = new LoggingFields();
            azureIoTHubSettings.AddString("DeviceConnectionString", this.applicationSettings.AzureIoTHubDeviceConnectionString);
            azureIoTHubSettings.AddString("TransportType", this.applicationSettings.AzureIoTHubTransportType.ToString());
            azureIoTHubSettings.AddString("SensorIDIsDeviceIDSensorID", this.applicationSettings.SensorIDIsDeviceIDSensorID.ToString());
            this.logging.LogEvent("AzureIoTHub configuration", azureIoTHubSettings, LoggingLevel.Information);

            // Connect the IoT hub first so we are ready for any messages
            try
            {
                this.azureIoTHubClient = DeviceClient.CreateFromConnectionString(this.applicationSettings.AzureIoTHubDeviceConnectionString, this.applicationSettings.AzureIoTHubTransportType);
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("IoT Hub connection failed " + ex.Message, LoggingLevel.Error);
                return;
            }

            try
            {
                TwinCollection reportedProperties;
                reportedProperties = new TwinCollection();

                // This is from the OS
                reportedProperties["Timezone"]    = TimeZoneSettings.CurrentTimeZoneDisplayName;
                reportedProperties["OSVersion"]   = Environment.OSVersion.VersionString;
                reportedProperties["MachineName"] = Environment.MachineName;

                reportedProperties["ApplicationDisplayName"] = package.DisplayName;
                reportedProperties["ApplicationName"]        = packageId.Name;
                reportedProperties["ApplicationVersion"]     = string.Format($"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}");

                // Unique identifier from the hardware
                SystemIdentificationInfo systemIdentificationInfo = SystemIdentification.GetSystemIdForPublisher();
                using (DataReader reader = DataReader.FromBuffer(systemIdentificationInfo.Id))
                {
                    byte[] bytes = new byte[systemIdentificationInfo.Id.Length];
                    reader.ReadBytes(bytes);
                    reportedProperties["SystemId"] = BitConverter.ToString(bytes);
                }

                azureIoTHubClient.UpdateReportedPropertiesAsync(reportedProperties).Wait();
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("IoT Hub updating reported properties failed " + ex.Message, LoggingLevel.Error);
            }

            // Wire up the field gateway restart method handler
            try
            {
                azureIoTHubClient.SetMethodHandlerAsync("Restart", RestartAsync, null);
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("Azure IoT Hub Restart method handler setup failed " + ex.Message, LoggingLevel.Error);
                return;
            }

#if CLOUD_DEVICE_BOND
            // Wire up the bond device method handler
            try
            {
                azureIoTHubClient.SetMethodHandlerAsync("DeviceBond", this.DeviceBondAsync, null);
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("Azure IoT Hub Device Bond method handler setup failed " + ex.Message, LoggingLevel.Error);
                return;
            }
#endif

#if CLOUD_DEVICE_PUSH
            // Wire up the push message to device method handler
            try
            {
                this.azureIoTHubClient.SetMethodHandlerAsync("DevicePush", this.DevicePushAsync, null);
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("Azure IoT Hub DevicePush method handler setup failed " + ex.Message, LoggingLevel.Error);
                return;
            }
#endif

#if CLOUD_DEVICE_SEND
            // Wire up the send message to device method handler
            try
            {
                this.azureIoTHubClient.SetMethodHandlerAsync("DeviceSend", this.DeviceSendAsync, null);
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("Azure IoT Hub client DeviceSend method handler setup failed " + ex.Message, LoggingLevel.Error);
                return;
            }
#endif

            // Configure the LoRa module
            rfm9XDevice.OnReceive  += Rfm9XDevice_OnReceive;
            rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;

            rfm9XDevice.Initialise(this.applicationSettings.Frequency,
                                   rxDoneignoreIfCrcMissing: true,
                                   rxDoneignoreIfCrcInvalid: true,
                                   paBoost: this.applicationSettings.PABoost, maxPower: this.applicationSettings.MaxPower, outputPower: this.applicationSettings.OutputPower,
                                   ocpOn: this.applicationSettings.OCPOn, ocpTrim: this.applicationSettings.OCPTrim,
                                   lnaGain: this.applicationSettings.LnaGain, lnaBoost: this.applicationSettings.LNABoost,
                                   bandwidth: this.applicationSettings.Bandwidth, codingRate: this.applicationSettings.CodingRate, implicitHeaderModeOn: this.applicationSettings.ImplicitHeaderModeOn,
                                   spreadingFactor: this.applicationSettings.SpreadingFactor,
                                   rxPayloadCrcOn: true,
                                   symbolTimeout: this.applicationSettings.SymbolTimeout,
                                   preambleLength: this.applicationSettings.PreambleLength,
                                   payloadLength: this.applicationSettings.PayloadLength,
                                   payloadMaxLength: this.applicationSettings.PayloadMaxLength,
                                   freqHoppingPeriod: this.applicationSettings.FreqHoppingPeriod,
                                   lowDataRateOptimize: this.applicationSettings.LowDataRateOptimize,
                                   ppmCorrection: this.applicationSettings.PpmCorrection,
                                   detectionOptimize: this.applicationSettings.DetectionOptimize,
                                   invertIQ: this.applicationSettings.InvertIQ,
                                   detectionThreshold: this.applicationSettings.DetectionThreshold,
                                   syncWord: this.applicationSettings.SyncWord
                                   );

#if DEBUG
            rfm9XDevice.RegisterDump();
#endif

            rfm9XDevice.Receive(Encoding.UTF8.GetBytes(this.applicationSettings.Address));

            LoggingFields loRaSettings = new LoggingFields();
            loRaSettings.AddString("Address", this.applicationSettings.Address);
            loRaSettings.AddDouble("Frequency", this.applicationSettings.Frequency);
            loRaSettings.AddBoolean("PABoost", this.applicationSettings.PABoost);

            loRaSettings.AddUInt8("MaxPower", this.applicationSettings.MaxPower);
            loRaSettings.AddUInt8("OutputPower", this.applicationSettings.OutputPower);
            loRaSettings.AddBoolean("OCPOn", this.applicationSettings.OCPOn);
            loRaSettings.AddUInt8("OCPTrim", this.applicationSettings.OCPTrim);

            loRaSettings.AddString("LnaGain", this.applicationSettings.LnaGain.ToString());
            loRaSettings.AddBoolean("lnaBoost", this.applicationSettings.LNABoost);

            loRaSettings.AddString("codingRate", this.applicationSettings.CodingRate.ToString());
            loRaSettings.AddString("implicitHeaderModeOn", applicationSettings.ImplicitHeaderModeOn.ToString());
            loRaSettings.AddString("spreadingFactor", this.applicationSettings.SpreadingFactor.ToString());
            loRaSettings.AddBoolean("rxPayloadCrcOn", true);

            loRaSettings.AddUInt8("symbolTimeout", this.applicationSettings.SymbolTimeout);
            loRaSettings.AddUInt8("preambleLength", this.applicationSettings.PreambleLength);
            loRaSettings.AddUInt8("payloadLength", this.applicationSettings.PayloadLength);

            loRaSettings.AddUInt8("payloadMaxLength", this.applicationSettings.PayloadMaxLength);
            loRaSettings.AddUInt8("freqHoppingPeriod", this.applicationSettings.FreqHoppingPeriod);
            loRaSettings.AddBoolean("lowDataRateOptimize", this.applicationSettings.LowDataRateOptimize);
            loRaSettings.AddUInt8("ppmCorrection", this.applicationSettings.PpmCorrection);

            loRaSettings.AddString("detectionOptimize", this.applicationSettings.DetectionOptimize.ToString());
            loRaSettings.AddBoolean("invertIQ", this.applicationSettings.InvertIQ);
            loRaSettings.AddString("detectionThreshold", this.applicationSettings.DetectionThreshold.ToString());
            loRaSettings.AddUInt8("SyncWord", this.applicationSettings.SyncWord);

            this.logging.LogEvent("LoRa configuration", loRaSettings, LoggingLevel.Information);

            this.deferral = taskInstance.GetDeferral();
        }
Example #10
0
        private async void InterruptGpioPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
        {
            DateTime currentTime = DateTime.UtcNow;

            Debug.WriteLine($"Digital Input Interrupt {sender.PinNumber} triggered {args.Edge}");

            if (args.Edge != this.interruptTriggerOn)
            {
                return;
            }

            // Check that enough time has passed for picture to be taken
            if ((currentTime - this.imageLastCapturedAtUtc) < this.debounceTimeout)
            {
                this.displayGpioPin.Write(GpioPinValue.High);
                this.displayOffTimer.Change(this.timerPeriodDetectIlluminated, this.timerPeriodInfinite);
                return;
            }

            this.imageLastCapturedAtUtc = currentTime;

            // Just incase - stop code being called while photo already in progress
            if (this.cameraBusy)
            {
                this.displayGpioPin.Write(GpioPinValue.High);
                this.displayOffTimer.Change(this.timerPeriodDetectIlluminated, this.timerPeriodInfinite);
                return;
            }

            this.cameraBusy = true;

            try
            {
                using (Windows.Storage.Streams.InMemoryRandomAccessStream captureStream = new Windows.Storage.Streams.InMemoryRandomAccessStream())
                {
                    this.mediaCapture.CapturePhotoToStreamAsync(ImageEncodingProperties.CreateJpeg(), captureStream).AsTask().Wait();
                    captureStream.FlushAsync().AsTask().Wait();
                    captureStream.Seek(0);

                    IStorageFile photoFile = await KnownFolders.PicturesLibrary.CreateFileAsync(ImageFilename, CreationCollisionOption.ReplaceExisting);

                    ImageEncodingProperties imageProperties = ImageEncodingProperties.CreateJpeg();
                    await this.mediaCapture.CapturePhotoToStorageFileAsync(imageProperties, photoFile);

                    ImagePrediction imagePrediction = await this.customVisionClient.ClassifyImageAsync(this.projectId, this.publishedName, captureStream.AsStreamForRead());

                    Debug.WriteLine($"Prediction count {imagePrediction.Predictions.Count}");

                    LoggingFields imageInformation = new LoggingFields();

                    imageInformation.AddDateTime("TakenAtUTC", currentTime);
                    imageInformation.AddInt32("Pin", sender.PinNumber);
                    imageInformation.AddInt32("Predictions", imagePrediction.Predictions.Count);

                    foreach (var prediction in imagePrediction.Predictions)
                    {
                        Debug.WriteLine($" Tag:{prediction.TagName} {prediction.Probability}");
                        imageInformation.AddDouble($"Tag:{prediction.TagName}", prediction.Probability);
                    }

                    this.logging.LogEvent("Captured image processed by Cognitive Services", imageInformation);
                }
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("Camera photo or save failed " + ex.Message, LoggingLevel.Error);
            }
            finally
            {
                this.cameraBusy = false;
            }
        }
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            StorageFolder localFolder = ApplicationData.Current.LocalFolder;
            TimeSpan      imageUpdateDue;
            TimeSpan      imageUpdatePeriod;

            this.logging.LogEvent("Application starting");

            // Log the Application build, OS version information etc.
            LoggingFields startupInformation = new LoggingFields();

            startupInformation.AddString("Timezone", TimeZoneSettings.CurrentTimeZoneDisplayName);
            startupInformation.AddString("OSVersion", Environment.OSVersion.VersionString);
            startupInformation.AddString("MachineName", Environment.MachineName);

            // This is from the application manifest
            Package        package   = Package.Current;
            PackageId      packageId = package.Id;
            PackageVersion version   = packageId.Version;

            startupInformation.AddString("ApplicationVersion", string.Format($"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}"));

            try
            {
                // see if the configuration file is present if not copy minimal sample one from application directory
                if (localFolder.TryGetItemAsync(ConfigurationFilename).AsTask().Result == null)
                {
                    StorageFile templateConfigurationfile = Package.Current.InstalledLocation.GetFileAsync(ConfigurationFilename).AsTask().Result;
                    templateConfigurationfile.CopyAsync(localFolder, ConfigurationFilename).AsTask();
                }

                IConfiguration configuration = new ConfigurationBuilder().AddJsonFile(Path.Combine(localFolder.Path, ConfigurationFilename), false, true).Build();

                this.interruptPinNumber = int.Parse(configuration.GetSection("InterruptPinNumber").Value);
                startupInformation.AddInt32("Interrupt pin", this.interruptPinNumber);

                this.interruptTriggerOn = (GpioPinEdge)Enum.Parse(typeof(GpioPinEdge), configuration.GetSection("interruptTriggerOn").Value);
                startupInformation.AddString("Interrupt Trigger on", this.interruptTriggerOn.ToString());

                this.displayPinNumber = int.Parse(configuration.GetSection("DisplayPinNumber").Value);
                startupInformation.AddInt32("Display pin", this.interruptPinNumber);

                this.azureIoTHubConnectionString = configuration.GetSection("AzureIoTHubConnectionString").Value;
                startupInformation.AddString("AzureIoTHubConnectionString", this.azureIoTHubConnectionString);

                this.transportType = (TransportType)Enum.Parse(typeof(TransportType), configuration.GetSection("TransportType").Value);
                startupInformation.AddString("TransportType", this.transportType.ToString());
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("JSON configuration file load or settings retrieval failed " + ex.Message, LoggingLevel.Error);
                return;
            }

            #region AzureIoT Hub connection string creation
            try
            {
                this.azureIoTHubClient = DeviceClient.CreateFromConnectionString(this.azureIoTHubConnectionString, this.transportType);
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("AzureIOT Hub DeviceClient.CreateFromConnectionString failed " + ex.Message, LoggingLevel.Error);
                return;
            }
            #endregion

            #region Report device and application properties to AzureIoT Hub
            try
            {
                TwinCollection reportedProperties = new TwinCollection();

                // This is from the OS
                reportedProperties["Timezone"]    = TimeZoneSettings.CurrentTimeZoneDisplayName;
                reportedProperties["OSVersion"]   = Environment.OSVersion.VersionString;
                reportedProperties["MachineName"] = Environment.MachineName;

                reportedProperties["ApplicationDisplayName"] = package.DisplayName;
                reportedProperties["ApplicationName"]        = packageId.Name;
                reportedProperties["ApplicationVersion"]     = string.Format($"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}");

                // Unique identifier from the hardware
                SystemIdentificationInfo systemIdentificationInfo = SystemIdentification.GetSystemIdForPublisher();
                using (DataReader reader = DataReader.FromBuffer(systemIdentificationInfo.Id))
                {
                    byte[] bytes = new byte[systemIdentificationInfo.Id.Length];
                    reader.ReadBytes(bytes);
                    reportedProperties["SystemId"] = BitConverter.ToString(bytes);
                }

                this.azureIoTHubClient.UpdateReportedPropertiesAsync(reportedProperties).Wait();
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("Azure IoT Hub client UpdateReportedPropertiesAsync failed " + ex.Message, LoggingLevel.Error);
                return;
            }
            #endregion

            #region Retrieve device twin settings
            try
            {
                LoggingFields configurationInformation = new LoggingFields();

                Twin deviceTwin = this.azureIoTHubClient.GetTwinAsync().GetAwaiter().GetResult();

                if (!deviceTwin.Properties.Desired.Contains("ImageUpdateDue") || !TimeSpan.TryParse(deviceTwin.Properties.Desired["ImageUpdateDue"].value.ToString(), out imageUpdateDue))
                {
                    this.logging.LogMessage("DeviceTwin.Properties ImageUpdateDue setting missing or invalid format", LoggingLevel.Warning);
                    return;
                }
                configurationInformation.AddTimeSpan("ImageUpdateDue", imageUpdateDue);

                if (!deviceTwin.Properties.Desired.Contains("ImageUpdatePeriod") || !TimeSpan.TryParse(deviceTwin.Properties.Desired["ImageUpdatePeriod"].value.ToString(), out imageUpdatePeriod))
                {
                    this.logging.LogMessage("DeviceTwin.Properties ImageUpdatePeriod setting missing or invalid format", LoggingLevel.Warning);
                    return;
                }
                configurationInformation.AddTimeSpan("ImageUpdatePeriod", imageUpdatePeriod);

                if (!deviceTwin.Properties.Desired.Contains("ModelType") || (!Enum.TryParse(deviceTwin.Properties.Desired["ModelType"].value.ToString(), out modelType)))
                {
                    this.logging.LogMessage("DeviceTwin.Properties ModelType setting missing or invalid format", LoggingLevel.Warning);
                    return;
                }
                configurationInformation.AddString("ModelType", modelType.ToString());

                if (!deviceTwin.Properties.Desired.Contains("ModelPublishedName") || (string.IsNullOrWhiteSpace(deviceTwin.Properties.Desired["ModelPublishedName"].value.ToString())))
                {
                    this.logging.LogMessage("DeviceTwin.Properties ModelPublishedName setting missing or invalid format", LoggingLevel.Warning);
                    return;
                }
                modelPublishedName = deviceTwin.Properties.Desired["ModelPublishedName"].value.ToString();
                configurationInformation.AddString("ModelPublishedName", modelPublishedName);

                if (!deviceTwin.Properties.Desired.Contains("ProjectID") || (!Guid.TryParse(deviceTwin.Properties.Desired["ProjectID"].value.ToString(), out projectId)))
                {
                    this.logging.LogMessage("DeviceTwin.Properties ProjectId setting missing or invalid format", LoggingLevel.Warning);
                    return;
                }
                configurationInformation.AddGuid("ProjectID", projectId);

                if (!deviceTwin.Properties.Desired.Contains("ProbabilityThreshold") || (!Double.TryParse(deviceTwin.Properties.Desired["ProbabilityThreshold"].value.ToString(), out probabilityThreshold)))
                {
                    this.logging.LogMessage("DeviceTwin.Properties ProbabilityThreshold setting missing or invalid format", LoggingLevel.Warning);
                    return;
                }
                configurationInformation.AddDouble("ProbabilityThreshold", probabilityThreshold);

                if (!deviceTwin.Properties.Desired.Contains("AzureCognitiveServicesEndpoint") || (string.IsNullOrWhiteSpace(deviceTwin.Properties.Desired["AzureCognitiveServicesEndpoint"].value.ToString())))
                {
                    this.logging.LogMessage("DeviceTwin.Properties AzureCognitiveServicesEndpoint setting missing or invalid format", LoggingLevel.Warning);
                    return;
                }
                azureCognitiveServicesEndpoint = deviceTwin.Properties.Desired["AzureCognitiveServicesEndpoint"].value.ToString();
                configurationInformation.AddString("AzureCognitiveServicesEndpoint", modelPublishedName);

                if (!deviceTwin.Properties.Desired.Contains("AzureCognitiveServicesSubscriptionKey") || (string.IsNullOrWhiteSpace(deviceTwin.Properties.Desired["AzureCognitiveServicesSubscriptionKey"].value.ToString())))
                {
                    this.logging.LogMessage("DeviceTwin.Properties AzureCognitiveServicesSubscriptionKey setting missing or invalid format", LoggingLevel.Warning);
                    return;
                }
                azureCognitiveServicesSubscriptionKey = deviceTwin.Properties.Desired["AzureCognitiveServicesSubscriptionKey"].value.ToString();
                configurationInformation.AddString("AzureCognitiveServicesSubscriptionKey", azureCognitiveServicesSubscriptionKey);

                if (!deviceTwin.Properties.Desired.Contains("DebounceTimeout") || !TimeSpan.TryParse(deviceTwin.Properties.Desired["DebounceTimeout"].value.ToString(), out debounceTimeout))
                {
                    this.logging.LogMessage("DeviceTwin.Properties DebounceTimeout setting missing or invalid format", LoggingLevel.Warning);
                    return;
                }
                configurationInformation.AddTimeSpan("DebounceTimeout", debounceTimeout);

                this.logging.LogEvent("Configuration settings", configurationInformation);
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("Azure IoT Hub client GetTwinAsync failed or property missing/invalid" + ex.Message, LoggingLevel.Error);
                return;
            }
            #endregion

            try
            {
                this.customVisionClient = new CustomVisionPredictionClient(new System.Net.Http.DelegatingHandler[] { })
                {
                    ApiKey   = this.azureCognitiveServicesSubscriptionKey,
                    Endpoint = this.azureCognitiveServicesEndpoint,
                };
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("Azure Cognitive Services Custom Vision Client configuration failed " + ex.Message, LoggingLevel.Error);
                return;
            }

            try
            {
                this.mediaCapture = new MediaCapture();
                this.mediaCapture.InitializeAsync().AsTask().Wait();
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("Camera configuration failed " + ex.Message, LoggingLevel.Error);
                return;
            }

            this.displayOffTimer = new Timer(this.TimerCallback, null, Timeout.Infinite, Timeout.Infinite);

            #region Wire up interupt handler for image capture request
            if (this.interruptPinNumber != 0)
            {
                try
                {
                    GpioController gpioController = GpioController.GetDefault();
                    this.interruptGpioPin = gpioController.OpenPin(this.interruptPinNumber);
                    this.interruptGpioPin.SetDriveMode(GpioPinDriveMode.InputPullUp);
                    this.interruptGpioPin.ValueChanged += this.InterruptGpioPin_ValueChanged;

                    this.displayGpioPin = gpioController.OpenPin(this.displayPinNumber);
                    this.displayGpioPin.SetDriveMode(GpioPinDriveMode.Output);
                    this.displayGpioPin.Write(GpioPinValue.Low);
                }
                catch (Exception ex)
                {
                    this.logging.LogMessage("Digital input configuration failed " + ex.Message, LoggingLevel.Error);
                    return;
                }
            }
            #endregion

            #region Wire up command handler for image capture request
            try
            {
                this.azureIoTHubClient.SetMethodHandlerAsync("ImageCapture", this.ImageUpdateHandler, null);
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("Azure IoT Hub client ImageCapture SetMethodHandlerAsync failed " + ex.Message, LoggingLevel.Error);
                return;
            }
            #endregion

            #region Wire up command handler for device reboot request
            try
            {
                this.azureIoTHubClient.SetMethodHandlerAsync("DeviceReboot", this.DeviceRebootAsync, null);
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("Azure IoT Hub client DeviceReboot SetMethodHandlerAsync failed " + ex.Message, LoggingLevel.Error);
                return;
            }
            #endregion

            if ((imageUpdateDue != TimeSpan.MinValue) || (imageUpdatePeriod != TimeSpan.MinValue))
            {
                this.imageUpdatetimer = new Timer(this.ImageUpdateTimerCallback, null, imageUpdateDue, imageUpdatePeriod);
            }

            this.logging.LogEvent("Application started", startupInformation);

            // enable task to continue running in background
            this.backgroundTaskDeferral = taskInstance.GetDeferral();
        }
        private async Task ImageUpdate(bool isCommand)
        {
            DateTime currentTime = DateTime.UtcNow;

            // Just incase - stop code being called while photo already in progress
            if (this.cameraBusy)
            {
                return;
            }
            this.cameraBusy = true;
            this.displayGpioPin.Write(GpioPinValue.High);

            // Check that enough time has passed for picture to be taken
            if ((currentTime - this.imageLastCapturedAtUtc) < this.debounceTimeout)
            {
                this.displayOffTimer.Change(this.timerPeriodDetectIlluminated, this.timerPeriodInfinite);
                return;
            }

            this.imageLastCapturedAtUtc = currentTime;

            try
            {
                ImagePrediction imagePrediction;

                using (Windows.Storage.Streams.InMemoryRandomAccessStream captureStream = new Windows.Storage.Streams.InMemoryRandomAccessStream())
                {
                    this.mediaCapture.CapturePhotoToStreamAsync(ImageEncodingProperties.CreateJpeg(), captureStream).AsTask().Wait();
                    captureStream.FlushAsync().AsTask().Wait();
                    captureStream.Seek(0);

                    IStorageFile photoFile = await KnownFolders.PicturesLibrary.CreateFileAsync(ImageFilename, CreationCollisionOption.ReplaceExisting);

                    ImageEncodingProperties imageProperties = ImageEncodingProperties.CreateJpeg();
                    await this.mediaCapture.CapturePhotoToStorageFileAsync(imageProperties, photoFile);

                    switch (modelType)
                    {
                    case ModelType.Classification:
                        imagePrediction = await this.customVisionClient.ClassifyImageAsync(this.projectId, this.modelPublishedName, captureStream.AsStreamForRead());

                        break;

                    case ModelType.Detection:
                        imagePrediction = await this.customVisionClient.DetectImageAsync(this.projectId, this.modelPublishedName, captureStream.AsStreamForRead());

                        break;

                    default:
                        throw new ArgumentException("ModelType Invalid");
                    }
                    Debug.WriteLine($"Prediction count {imagePrediction.Predictions.Count}");
                }

                JObject       telemetryDataPoint = new JObject();
                LoggingFields imageInformation   = new LoggingFields();

                imageInformation.AddDateTime("TakenAtUTC", currentTime);
                imageInformation.AddBoolean("IsCommand", isCommand);
                imageInformation.AddDouble("Probability threshold", probabilityThreshold);
                imageInformation.AddInt32("Predictions", imagePrediction.Predictions.Count);

                // Display and log the results of the prediction
                foreach (var prediction in imagePrediction.Predictions)
                {
                    Debug.WriteLine($" Tag:{prediction.TagName} {prediction.Probability}");
                    imageInformation.AddDouble($"Tag:{prediction.TagName}", prediction.Probability);
                }

                // Post process the predictions based on the type of model
                switch (modelType)
                {
                case ModelType.Classification:
                    // Use only the tags above the specified minimum probability
                    foreach (var prediction in imagePrediction.Predictions)
                    {
                        if (prediction.Probability >= probabilityThreshold)
                        {
                            // Display and log the individual tag probabilities
                            Debug.WriteLine($" Tag valid:{prediction.TagName} {prediction.Probability:0.00}");
                            imageInformation.AddDouble($"Tag valid:{prediction.TagName}", prediction.Probability);

                            telemetryDataPoint.Add(prediction.TagName, prediction.Probability);
                        }
                    }
                    break;

                case ModelType.Detection:
                    // Group the tags to get the count, include only the predictions above the specified minimum probability
                    var groupedPredictions = from prediction in imagePrediction.Predictions
                                             where prediction.Probability >= probabilityThreshold
                                             group prediction by new { prediction.TagName }
                    into newGroup
                        select new
                    {
                        TagName = newGroup.Key.TagName,
                        Count   = newGroup.Count(),
                    };

                    // Display and log the agregated predictions
                    foreach (var prediction in groupedPredictions)
                    {
                        Debug.WriteLine($" Tag valid:{prediction.TagName} {prediction.Count}");
                        imageInformation.AddInt32($"Tag valid:{prediction.TagName}", prediction.Count);
                        telemetryDataPoint.Add(prediction.TagName, prediction.Count);
                    }
                    break;

                default:
                    throw new ArgumentException("ModelType Invalid");
                }

                this.logging.LogEvent("Captured image processed by Cognitive Services", imageInformation);

                try
                {
                    using (Message message = new Message(Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(telemetryDataPoint))))
                    {
                        Debug.WriteLine(" {0:HH:mm:ss} AzureIoTHubClient SendEventAsync start", DateTime.UtcNow);
                        await this.azureIoTHubClient.SendEventAsync(message);

                        Debug.WriteLine(" {0:HH:mm:ss} AzureIoTHubClient SendEventAsync finish", DateTime.UtcNow);
                    }
                    this.logging.LogEvent("SendEventAsync payload", imageInformation, LoggingLevel.Information);
                }
                catch (Exception ex)
                {
                    imageInformation.AddString("Exception", ex.ToString());
                    this.logging.LogEvent("SendEventAsync payload", imageInformation, LoggingLevel.Error);
                }
            }
            catch (Exception ex)
            {
                this.logging.LogMessage("Camera photo or save failed " + ex.Message, LoggingLevel.Error);
            }
            finally
            {
                this.displayGpioPin.Write(GpioPinValue.Low);
                this.cameraBusy = false;
            }
        }