/// <summary>
        /// Creates the set of tasks that will send data to the IoT hub.
        /// </summary>
        /// <returns></returns>
        private static async Task SetupDeviceRunTasks()
        {
            var       deviceTasks         = new Dictionary <string, Task>();
            var       config              = ParseConfiguration();
            const int sampleSize          = 10000;
            const int failOverXIterations = 625;

            Console.WriteLine("Setting up simulated pump devices and generating random sample data. This may take a while...");

            // Add a pump that gradually fails.
            devices.Add(new PumpDevice(1, config.Device1Key, config.IdScope, config.DpsEndpoint, "DEVICE001", "192.168.1.1", new Location(35.815090, -101.043192),
                                       GenerateData.GeneratePumpTelemetry(sampleSize, true, failOverXIterations)));
            // Add a pump that never fails.
            devices.Add(new PumpDevice(2, config.Device2Key, config.IdScope, config.DpsEndpoint, "DEVICE002", "192.168.1.2", new Location(35.815862, -101.042167),
                                       GenerateData.GeneratePumpTelemetry(sampleSize + failOverXIterations, false, 0)));
            // Add a pump that immediately fails after a period of time.
            devices.Add(new PumpDevice(3, config.Device3Key, config.IdScope, config.DpsEndpoint, "DEVICE003", "192.168.1.3", new Location(35.815743, -101.048099),
                                       GenerateData.GeneratePumpTelemetry(sampleSize + failOverXIterations, true, 0)));

            foreach (var device in devices)
            {
                await device.RegisterAndConnectDeviceAsync(); //fire and forget

                deviceTasks.Add(device.DeviceId, device.RunDeviceAsync());
                device.PumpPowerStateChanged += Device_PumpPowerStateChanged;
            }

            _runningDeviceTasks = deviceTasks;
        }
        /// <summary>
        /// Creates the set of tasks that will send data to the IoT hub.
        /// </summary>
        /// <returns></returns>
        private static Dictionary <string, Task> SetupDeviceRunTasks()
        {
            var       deviceTasks         = new Dictionary <string, Task>();
            var       config              = ParseConfiguration();
            const int sampleSize          = 10000;
            const int failOverXIterations = 625;

            Console.WriteLine("Setting up simulated pump devices and generating random sample data. This may take a while...");

            // Add a pump that gradually fails.
            devices.Add(new PumpDevice(1, config.Device1ConnectionString, "DEVICE001", "192.168.1.1", new Location(35.815090, -101.043192),
                                       GenerateData.GeneratePumpTelemetry(sampleSize, true, failOverXIterations)));
            // Add a pump that never fails.
            devices.Add(new PumpDevice(2, config.Device2ConnectionString, "DEVICE002", "192.168.1.2", new Location(35.815862, -101.042167),
                                       GenerateData.GeneratePumpTelemetry(sampleSize + failOverXIterations, false, 0)));
            // Add a pump that immediately fails after a period of time.
            devices.Add(new PumpDevice(3, config.Device3ConnectionString, "DEVICE003", "192.168.1.3", new Location(35.815743, -101.048099),
                                       GenerateData.GeneratePumpTelemetry(sampleSize + failOverXIterations, true, 0)));

            foreach (var device in devices)
            {
                device.SendDevicePropertiesAndInitialState();
                deviceTasks.Add(device.DeviceId, device.RunDeviceAsync());
                device.PumpPowerStateChanged += Device_PumpPowerStateChanged;
            }

            return(deviceTasks);
        }
        /// <summary>
        /// Generates anomaly model training data to new CSV files.
        /// </summary>
        private static void GenerateTrainingData()
        {
            var sampleSize = 10000;

            Console.WriteLine("Generating data for ML model training. This may take a while...");

            // Generate with no failures:
            Console.WriteLine("\r\nGenerating data with no failures...");
            GenerateData.GenerateModelTrainingData(sampleSize, false, 0, false, true);
            // Generate with immediate failures:
            Console.WriteLine("\r\nGenerating data with immediate failures...");
            GenerateData.GenerateModelTrainingData(sampleSize, true, 0, false, true);
            // Generate with gradual failures:
            Console.WriteLine("\r\nGenerating data with gradual failures...");
            GenerateData.GenerateModelTrainingData(sampleSize, true, 2500, false, true);

            Console.WriteLine("\r\n---------------------------\r\nGeneration complete.");
        }