diff --git a/README.md b/README.md index e53fb67..bd94d18 100644 --- a/README.md +++ b/README.md @@ -27,13 +27,13 @@ A single AMQP connection can handle approximately 995 devices. The quickest way to generate telemetry is using Docker with the following command: ```bash -docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=your-iothub-key" mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator:latest +docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=your-iothub-key" mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator ``` **The simulator expects the devices to already exist in Azure IoT Hub**. If you need help creating simulation devices in an Azure IoT Hub use the included project IotSimulatorDeviceProvisioning or the Docker image: ```bash -docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=registryReadWrite;SharedAccessKey=your-iothub-key" -e DeviceCount=1000 mcr.microsoft.com/oss/azure-samples/azureiot-simulatordeviceprovisioning:latest +docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=registryReadWrite;SharedAccessKey=your-iothub-key" -e DeviceCount=1000 mcr.microsoft.com/oss/azure-samples/azureiot-simulatordeviceprovisioning ``` ## Simulator input parameters @@ -60,6 +60,8 @@ The amount of devices, their names and telemetry generated can be customized usi |PartitionKey|optional [partition key](https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-features#partitions) template for Event Hubs (see telemetry template and [Advanced options](#advanced-options))| |Variables|telemetry variables (see telemetry template)| |DuplicateEveryNEvents|if > 0, send duplicates of the given fraction of messages. See [Advanced options](#advanced-options) (default = 0)| +|File|Defines a json file where templates, variables and device based intervals can be defined. File and environment variable configuration can be used in conjunction.| +|Intervals|Allows customizing the message interval per device| ## Telemetry template @@ -99,6 +101,8 @@ Customizable variables can be created with the following properties: |max|The maximum value generated| |values|Defines an array of possible values. Example ["on", "off"]| |customlengthstring|Creates a random string of n bytes. Provide n as parameter| +|sequence|Create a sequence of values as defined in `values` property, producing one after the other. Values can reference other non-sequence variables| + #### Example 1: Telemetry with temperature between 23 and 25 and a counter starting from 100 @@ -125,13 +129,13 @@ Output: Running with Docker: ```powershell -docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=your-iothub-key" -e Template="{ \"deviceId\": \"$.DeviceId\", \"temp\": $.Temp, \"Ticks\": $.Ticks, \"Counter\": $.Counter, \"time\": \"$.Time\" }" -e Variables="[{name: \"Temp\", \"random\": true, \"max\": 25, \"min\": 23}, {\"name\":\"Counter\", \"min\":100} ]" mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator:latest +docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=your-iothub-key" -e Template="{ \"deviceId\": \"$.DeviceId\", \"temp\": $.Temp, \"Ticks\": $.Ticks, \"Counter\": $.Counter, \"time\": \"$.Time\" }" -e Variables="[{name: \"Temp\", \"random\": true, \"max\": 25, \"min\": 23}, {\"name\":\"Counter\", \"min\":100} ]" mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator ``` calling from PowerShell: ```powershell -docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=your-iothub-key" -e Template="{ \"""deviceId\""": \"""$.DeviceId\""", \"""temp\""": $.Temp, \"""Ticks\""": $.Ticks, \"""Counter\""": $.Counter, \"""time\""": \"""$.Time\""", \"""engine\""": \"""$.Engine\""" }" -e Variables="[{name: \"""Temp\""", \"""random\""": true, \"""max\""": 25, \"""min\""": 23}, {\"""name\""":\"""Counter\""", \"""min\""":100}, {name:\"""Engine\""", values: [\"""on\""", \"""off\"""]}]" -e DeviceCount=1 -e MessageCount=3 mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator:latest +docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=iothubowner;SharedAccessKey=your-iothub-key" -e Template="{ \"""deviceId\""": \"""$.DeviceId\""", \"""temp\""": $.Temp, \"""Ticks\""": $.Ticks, \"""Counter\""": $.Counter, \"""time\""": \"""$.Time\""", \"""engine\""": \"""$.Engine\""" }" -e Variables="[{name: \"""Temp\""", \"""random\""": true, \"""max\""": 25, \"""min\""": 23}, {\"""name\""":\"""Counter\""", \"""min\""":100}, {name:\"""Engine\""", values: [\"""on\""", \"""off\"""]}]" -e DeviceCount=1 -e MessageCount=3 mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator ``` #### Example 2: Adding the engine status ("on" or "off") to the telemetry @@ -158,7 +162,60 @@ Output: Running with Docker: ```bash -docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=your-iothub-key" -e Template="{ \"deviceId\": \"$.DeviceId\", \"temp\": $.Temp, \"Ticks\": $.Ticks, \"Counter\": $.Counter, \"time\": \"$.Time\", \"engine\": \"$.Engine\" }" -e Variables="[{name: \"Temp\", \"random\": true, \"max\": 25, \"min\": 23}, {\"name\":\"Counter\", \"min\":100}, {name:\"Engine\", values: [\"on\", \"off\"]}]" mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator:latest +docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=your-iothub-key" -e Template="{ \"deviceId\": \"$.DeviceId\", \"temp\": $.Temp, \"Ticks\": $.Ticks, \"Counter\": $.Counter, \"time\": \"$.Time\", \"engine\": \"$.Engine\" }" -e Variables="[{name: \"Temp\", \"random\": true, \"max\": 25, \"min\": 23}, {\"name\":\"Counter\", \"min\":100}, {name:\"Engine\", values: [\"on\", \"off\"]}]" mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator +``` + +#### Example 3: Using a configuration file to customize simulation + +```bash +docker run -it -e "IotHubConnectionString=HostName=your-iothub-name.azure-devices.net;SharedAccessKeyName=device;SharedAccessKey=your-iothub-key" -e "File=/config_files/test4-config-multiple-internals-per-device.json" -e DeviceCount=3 --mount type=bind,source=$pwd\test\IotTelemetrySimulator.Test\test_files,target=/config_files,readonly mcr.microsoft.com/oss/azure-samples/azureiot-telemetrysimulator +``` + +Where the file content is: + +```plain +{ + "Variables": [ + { + "name": "DeviceSequenceValue1", + "sequence": true, + "values": [ "$.Counter", "$.Counter", "$.Counter", "$.Counter", "$.Counter", "true", "false", "$.Counter" ] + }, + { + "name": "Device1Tags", + "sequence": true, + "values": [ "['ProducedPartCount']", "['ProducedPartCount']", "['ProducedPartCount']", "['ProducedPartCount']", "['ProducedPartCount']", "['Downtime']", "['Downtime']", "['ProducedPartCount']" ] + }, + { + "name": "Device1Downtime", + "values": [ "true", "true", "true", "true", "false" ] + }, + { + "name": "Counter" + } + ], + "Intervals": { + "sim000001": 10000, + "sim000002": 100 + }, + "Payloads": [ + { + "type": "template", + "deviceId": "sim000001", + "template": "{\"device\":\"$.DeviceId\",\"value\":\"$.DeviceSequenceValue1\",\"tags\": $.Device1Tags}" + }, + { + "type": "fix", + "deviceId": "sim000002", + "value": "{\"value\":\"myfixvalue\"}" + }, + { + "type": "template", + "deviceId": "sim000003", + "template": "{\"device\":\"$.DeviceId\",\"a\":\"b\",\"value\":\"$.DeviceSequenceValue1\"}" + } + ] +} ``` ## Generating high volume of telemetry diff --git a/src/IotTelemetrySimulator/Constants.cs b/src/IotTelemetrySimulator/Constants.cs index 9faa713..732c233 100644 --- a/src/IotTelemetrySimulator/Constants.cs +++ b/src/IotTelemetrySimulator/Constants.cs @@ -14,10 +14,11 @@ public static class Constants public const string DeviceIdValueName = "DeviceId"; public const string GuidValueName = "Guid"; public const string MachineNameValueName = "MachineName"; + public const string IterationNumberValueName = "IterationNumber"; public static readonly string[] AllSpecialValueNames = { TimeValueName, LocalTimeValueName, EpochValueName, TicksValueName, - DeviceIdValueName, GuidValueName, MachineNameValueName, + DeviceIdValueName, GuidValueName, MachineNameValueName, IterationNumberValueName }; } } diff --git a/src/IotTelemetrySimulator/FixPayload.cs b/src/IotTelemetrySimulator/FixPayload.cs index 0157040..df8dff9 100644 --- a/src/IotTelemetrySimulator/FixPayload.cs +++ b/src/IotTelemetrySimulator/FixPayload.cs @@ -7,7 +7,12 @@ public class FixPayload : PayloadBase public byte[] Payload { get; } public FixPayload(int distribution, byte[] payload) - : base(distribution) + : this(distribution, null, payload) + { + } + + public FixPayload(int distribution, string deviceId, byte[] payload) + : base(distribution, deviceId) { this.Payload = payload; } diff --git a/src/IotTelemetrySimulator/IotHubSender.cs b/src/IotTelemetrySimulator/IotHubSender.cs index da14484..b710076 100644 --- a/src/IotTelemetrySimulator/IotHubSender.cs +++ b/src/IotTelemetrySimulator/IotHubSender.cs @@ -11,7 +11,7 @@ internal class IotHubSender : SenderBase const string ApplicationJsonContentType = "application/json"; const string Utf8Encoding = "utf-8"; - private DeviceClient deviceClient; + private readonly DeviceClient deviceClient; public IotHubSender(DeviceClient deviceClient, string deviceId, RunnerConfiguration config) : base(deviceId, config) diff --git a/src/IotTelemetrySimulator/PayloadBase.cs b/src/IotTelemetrySimulator/PayloadBase.cs index 40a88ba..b787725 100644 --- a/src/IotTelemetrySimulator/PayloadBase.cs +++ b/src/IotTelemetrySimulator/PayloadBase.cs @@ -7,12 +7,20 @@ public abstract class PayloadBase { public int Distribution { get; set; } - public PayloadBase(int distribution) + public string DeviceId { get; set; } + + protected PayloadBase(int distribution) + : this(distribution, null) + { + } + + protected PayloadBase(int distribution, string deviceId) { if (distribution < 1 || distribution > 100) throw new ArgumentOutOfRangeException(nameof(distribution), "Distribution must be between 1 and 100"); this.Distribution = distribution; + this.DeviceId = deviceId; } public abstract (byte[], Dictionary) Generate(Dictionary variableValues); diff --git a/src/IotTelemetrySimulator/PayloadGenerator.cs b/src/IotTelemetrySimulator/PayloadGenerator.cs index 3fc43cf..1bcea5b 100644 --- a/src/IotTelemetrySimulator/PayloadGenerator.cs +++ b/src/IotTelemetrySimulator/PayloadGenerator.cs @@ -10,6 +10,8 @@ public class PayloadGenerator public PayloadBase[] Payloads { get; } + private readonly Dictionary payloadsPerDevice; + public PayloadGenerator(IEnumerable payloads, IRandomizer randomizer) { this.randomizer = randomizer ?? throw new ArgumentNullException(nameof(randomizer)); @@ -19,13 +21,19 @@ public PayloadGenerator(IEnumerable payloads, IRandomizer randomize } this.Payloads = payloads.OrderByDescending(x => x.Distribution).ToArray(); + this.payloadsPerDevice = this.Payloads + .Where(x => !string.IsNullOrEmpty(x.DeviceId)) + .ToDictionary(x => x.DeviceId); } - public (byte[], Dictionary) Generate(Dictionary variableValues) + public (byte[], Dictionary) Generate(string deviceId, Dictionary variableValues) { if (this.Payloads.Length == 1) return this.Payloads[0].Generate(variableValues); + if (deviceId != null && this.payloadsPerDevice.TryGetValue(deviceId, out var payloadForDevice)) + return payloadForDevice.Generate(variableValues); + var random = this.randomizer.Next(1, 101); var currentPercentage = 0; foreach (var payload in this.Payloads) diff --git a/src/IotTelemetrySimulator/Program.cs b/src/IotTelemetrySimulator/Program.cs index 2972ed9..6f7618b 100644 --- a/src/IotTelemetrySimulator/Program.cs +++ b/src/IotTelemetrySimulator/Program.cs @@ -1,11 +1,7 @@ namespace IotTelemetrySimulator { using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Linq; - using System.Threading; - using System.Threading.Tasks; + using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -22,6 +18,20 @@ public static IHostBuilder CreateHostBuilder(string[] args) #if DEBUG .UseEnvironment("Development") #endif + .ConfigureAppConfiguration(builder => + { + var tempConfig = builder.Build(); + var fileConfig = tempConfig["File"]; + if (!string.IsNullOrEmpty(fileConfig)) + { + builder.AddJsonFile(fileConfig, optional: false, reloadOnChange: false); + } + + if (tempConfig is IDisposable diposableConfig) + { + diposableConfig.Dispose(); + } + }) .ConfigureServices((hostContext, services) => { services.AddSingleton(); diff --git a/src/IotTelemetrySimulator/RunnerConfiguration.cs b/src/IotTelemetrySimulator/RunnerConfiguration.cs index bcfaccd..dae7ff3 100644 --- a/src/IotTelemetrySimulator/RunnerConfiguration.cs +++ b/src/IotTelemetrySimulator/RunnerConfiguration.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Linq; + using System.Text; using System.Text.RegularExpressions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -12,7 +13,7 @@ public class RunnerConfiguration { public const string DefaultTemplate = "{\"deviceId\": \"$.DeviceId\", \"time\": \"$.Time\", \"counter\": $.Counter}"; private const string RegexExpression = "(?fixsize|template|fix)(\\()(?[[0-9a-z,=,\\s]+)"; - static Regex templateParser = new Regex(RegexExpression, RegexOptions.IgnoreCase | RegexOptions.Singleline); + static readonly Regex TemplateParser = new Regex(RegexExpression, RegexOptions.IgnoreCase | RegexOptions.Singleline); public string IotHubConnectionString { get; set; } @@ -46,6 +47,8 @@ public class RunnerConfiguration public byte[] FixPayload { get; set; } + public Dictionary Intervals { get; set; } + public void EnsureIsValid() { var numberOfConnectionSettings = 0; @@ -90,6 +93,16 @@ public void EnsureIsValid() throw new Exception($"{nameof(this.DuplicateEvery)} must be greater than or equal to zero"); } + public int GetMessageIntervalForDevice(string deviceId) + { + if (this.Intervals != null && this.Intervals.TryGetValue(deviceId, out var customInterval)) + { + return customInterval; + } + + return this.Interval; + } + public static RunnerConfiguration Load(IConfiguration configuration, ILogger logger) { var config = new RunnerConfiguration(); @@ -118,31 +131,44 @@ public static RunnerConfiguration Load(IConfiguration configuration, ILogger log config.KafkaTopic = configuration.GetValue(nameof(KafkaTopic)); - var rawValues = configuration.GetValue(nameof(Variables)); - if (!string.IsNullOrWhiteSpace(rawValues)) + // Variables can come in 2 forms: + // - Configuration section, when reading configuration from a JSON file + // - JSON string, when reading configuration from CLI parameters. + var variablesSection = configuration.GetSection(nameof(Variables)); + if (variablesSection.Exists() && variablesSection.Value is not string) { - try - { - var values = JsonConvert.DeserializeObject(rawValues); - config.Variables = new TelemetryValues(values); - } - catch (JsonReaderException ex) - { - throw new Exception($"Failed to parse variables from: {rawValues}", ex); - } + var values = new List(); + variablesSection.Bind(values); + config.Variables = new TelemetryValues(values); } else { - logger.LogWarning("No custom telemetry variables found"); - config.Variables = new TelemetryValues(new[] + var rawValues = configuration.GetValue(nameof(Variables)); + if (!string.IsNullOrWhiteSpace(rawValues)) { - new TelemetryVariable + try { - Min = 1, - Name = "Counter", - Step = 1, + var values = JsonConvert.DeserializeObject(rawValues); + config.Variables = new TelemetryValues(values); } - }); + catch (JsonReaderException ex) + { + throw new Exception($"Failed to parse variables from: {rawValues}", ex); + } + } + else + { + logger.LogWarning("No custom telemetry variables found"); + config.Variables = new TelemetryValues(new[] + { + new TelemetryVariable + { + Min = 1, + Name = "Counter", + Step = 1, + } + }); + } } var futureVariableNames = config.Variables.VariableNames().ToList(); @@ -162,12 +188,28 @@ public static RunnerConfiguration Load(IConfiguration configuration, ILogger log } } + config.Intervals = LoadIntervals(configuration); + config.Header = GetTelemetryTemplate(configuration, nameof(Header), futureVariableNames); config.PartitionKey = GetTelemetryTemplate(configuration, nameof(PartitionKey), futureVariableNames); return config; } + private static Dictionary LoadIntervals(IConfiguration configuration) + { + var section = configuration.GetSection(nameof(RunnerConfiguration.Intervals)); + if (!section.Exists()) + { + return null; + } + + var result = new Dictionary(); + section.Bind(result); + + return result; + } + private static TelemetryTemplate GetTelemetryTemplate(IConfiguration configuration, string headerName, IEnumerable futureVariableNames) { var rawHeaderTemplate = configuration.GetValue(headerName); @@ -180,6 +222,34 @@ private static TelemetryTemplate GetTelemetryTemplate(IConfiguration configurati } private static List LoadPayloads(IConfiguration configuration, RunnerConfiguration config, ILogger logger, ICollection futureVariableNames) + { + List payloads = null; + var payloadSection = configuration.GetSection("Payloads"); + if (payloadSection.Exists() && payloadSection.Value is not string) + { + payloads = LoadPayloadsFromSection(payloadSection, configuration, config, logger, futureVariableNames); + } + else + { + payloads = LoadPayloadsSimple(configuration, config, logger, futureVariableNames); + } + + foreach (var group in payloads.GroupBy(x => x.DeviceId)) + { + var totalDistribution = group.Select(x => x.Distribution).Sum(); + if (totalDistribution != 100) + { + logger.LogWarning( + "Payload percentage distribution is {Total} != 100 for device {DeviceId}", + totalDistribution, + group.Key); + } + } + + return payloads; + } + + private static List LoadPayloadsSimple(IConfiguration configuration, RunnerConfiguration config, ILogger logger, ICollection futureVariableNames) { var payloads = new List(); @@ -199,7 +269,7 @@ private static List LoadPayloads(IConfiguration configuration, Runn var rawDynamicPayload = configuration.GetValue(Constants.PayloadDistributionConfigName); if (!string.IsNullOrEmpty(rawDynamicPayload)) { - var matches = templateParser.Matches(rawDynamicPayload); + var matches = TemplateParser.Matches(rawDynamicPayload); foreach (Match m in matches) { if (m.Groups.TryGetValue("type", out var templateType) && m.Groups.TryGetValue("pv", out var paramValuesRaw)) @@ -289,12 +359,62 @@ private static List LoadPayloads(IConfiguration configuration, Runn logger.LogWarning("Using default telemetry template"); } - if (payloads.Select(x => x.Distribution).Sum() != 100) + return payloads; + } + + private static List LoadPayloadsFromSection( + IConfigurationSection payloadSection, + IConfiguration configuration, + RunnerConfiguration config, + ILogger logger, + ICollection futureVariableNames) + { + var payloads = new List(); + foreach (var subSection in payloadSection.GetChildren()) { - logger.LogWarning("Payload percentage distribution is not equal 100"); + var deviceId = subSection["deviceId"]; + var distribution = subSection.GetValue("distribution", 100); + PayloadBase payload = subSection["type"] switch + { + "template" => new TemplatedPayload(distribution, deviceId, new TelemetryTemplate(GetTemplateFromSection(subSection, "template"), futureVariableNames), config.Variables), + "fix" => new FixPayload(distribution, deviceId, Encoding.UTF8.GetBytes(GetTemplateFromSection(subSection, "value"))), + _ => throw new InvalidOperationException("Unknown payload type. Accepted types are [template, fix]"), + }; + + payloads.Add(payload); } return payloads; } + + private static string GetTemplateFromSection(IConfigurationSection subSection, string key) + { + var templateConfigSection = subSection.GetSection(key); + if (templateConfigSection.Value is string valueString) + { + return valueString; + } + + var dictionaryVals = new Dictionary(); + ConvertToDictionary(templateConfigSection, dictionaryVals, templateConfigSection); + + return JsonConvert.SerializeObject(dictionaryVals); + } + + static void ConvertToDictionary(IConfigurationSection configuration, Dictionary data, IConfigurationSection top = null) + { + var children = configuration.GetChildren(); + foreach (var child in children) + { + if (child.Value == null) + { + ConvertToDictionary(configuration.GetSection(child.Key), data, configuration); + continue; + } + + var key = top != null ? child.Path.Substring(top.Path.Length + 1) : child.Path; + data[key] = child.Value; + } + } } } diff --git a/src/IotTelemetrySimulator/SenderBase.cs b/src/IotTelemetrySimulator/SenderBase.cs index f3afe57..987d1c9 100644 --- a/src/IotTelemetrySimulator/SenderBase.cs +++ b/src/IotTelemetrySimulator/SenderBase.cs @@ -12,7 +12,7 @@ public abstract class SenderBase : ISender protected const int MaxSendAttempts = 3; private readonly IRandomizer random = new DefaultRandomizer(); - private string deviceId; + private readonly string deviceId; private Dictionary variableValues; protected RunnerConfiguration Config { get; } @@ -65,7 +65,7 @@ protected TMessage CreateMessage() }; } - var (messageBytes, nextVariableValues) = this.Config.PayloadGenerator.Generate(this.variableValues); + var (messageBytes, nextVariableValues) = this.Config.PayloadGenerator.Generate(this.deviceId, this.variableValues); this.variableValues = nextVariableValues; TMessage msg = this.BuildMessage(messageBytes); diff --git a/src/IotTelemetrySimulator/SimulatedDevice.cs b/src/IotTelemetrySimulator/SimulatedDevice.cs index b71c48d..be85c0d 100644 --- a/src/IotTelemetrySimulator/SimulatedDevice.cs +++ b/src/IotTelemetrySimulator/SimulatedDevice.cs @@ -8,8 +8,9 @@ public class SimulatedDevice { private readonly ISender sender; - private RunnerConfiguration config; - private IRandomizer random = new DefaultRandomizer(); + private readonly int interval; + private readonly RunnerConfiguration config; + private readonly IRandomizer random = new DefaultRandomizer(); public string DeviceID { get; private set; } @@ -18,6 +19,7 @@ public SimulatedDevice(string deviceId, RunnerConfiguration config, ISender send this.DeviceID = deviceId; this.config = config; this.sender = sender; + this.interval = config.GetMessageIntervalForDevice(deviceId); } public Task Start(RunnerStats stats, CancellationToken cancellationToken) @@ -33,7 +35,7 @@ async Task RunnerAsync(RunnerStats stats, CancellationToken cancellationToken) stats.IncrementDeviceConnected(); // Delay first event by a random amount to avoid bursts - await Task.Delay(this.random.Next(this.config.Interval), cancellationToken); + await Task.Delay(this.random.Next(this.interval), cancellationToken); var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -41,7 +43,7 @@ async Task RunnerAsync(RunnerStats stats, CancellationToken cancellationToken) { await this.sender.SendMessageAsync(stats, cancellationToken); - var millisecondsDelay = Math.Max(0, this.config.Interval * i - stopwatch.ElapsedMilliseconds); + var millisecondsDelay = Math.Max(0, this.interval * i - stopwatch.ElapsedMilliseconds); await Task.Delay((int)millisecondsDelay, cancellationToken); } diff --git a/src/IotTelemetrySimulator/SimulationWorker.cs b/src/IotTelemetrySimulator/SimulationWorker.cs index 1392ab8..4ee61b2 100644 --- a/src/IotTelemetrySimulator/SimulationWorker.cs +++ b/src/IotTelemetrySimulator/SimulationWorker.cs @@ -15,8 +15,8 @@ class SimulationWorker : IHostedService { private readonly IDeviceSimulatorFactory deviceSimulatorFactory; private readonly IHostApplicationLifetime applicationLifetime; - private CancellationTokenSource stopping; - private RunnerConfiguration config; + private readonly CancellationTokenSource stopping; + private readonly RunnerConfiguration config; private RunnerStats stats; private List devices; private Task runner; diff --git a/src/IotTelemetrySimulator/TelemetryValues.cs b/src/IotTelemetrySimulator/TelemetryValues.cs index d858125..12a86b1 100644 --- a/src/IotTelemetrySimulator/TelemetryValues.cs +++ b/src/IotTelemetrySimulator/TelemetryValues.cs @@ -6,8 +6,8 @@ public class TelemetryValues { - private IRandomizer random = new DefaultRandomizer(); - string machineName; + private readonly IRandomizer random = new DefaultRandomizer(); + readonly string machineName; public IList Variables { get; } @@ -22,21 +22,34 @@ public Dictionary NextValues(Dictionary previous var next = new Dictionary(); var now = DateTime.Now; + ulong iterationNumber = 0; + if (previous != null && previous.TryGetValue(Constants.IterationNumberValueName, out var previousIterationNumber)) + { + iterationNumber = (ulong)previousIterationNumber + 1; + } + next[Constants.TimeValueName] = now.ToUniversalTime().ToString("o"); next[Constants.LocalTimeValueName] = now.ToString("o"); next[Constants.TicksValueName] = now.Ticks; next[Constants.EpochValueName] = new DateTimeOffset(now).ToUnixTimeSeconds(); next[Constants.GuidValueName] = Guid.NewGuid().ToString(); next[Constants.MachineNameValueName] = this.machineName; + next[Constants.IterationNumberValueName] = iterationNumber; if (previous != null) { next[Constants.DeviceIdValueName] = previous[Constants.DeviceIdValueName]; } + var hasSequenceVars = false; + foreach (var val in this.Variables) { - if (val.Random) + if (val.Sequence) + { + hasSequenceVars = true; + } + else if (val.Random) { if (val.Min.HasValue && val.Max.HasValue && val.Max > val.Min) { @@ -74,9 +87,67 @@ public Dictionary NextValues(Dictionary previous } } + // We generate values of sequence vars after the non-sequence vars, because + // sequence vars might reference non-sequence vars. + if (hasSequenceVars) + { + var notUsedSequenceVariables = this.Variables + .Where(x => x.Sequence) + .SelectMany(x => x.GetReferenceVariableNames()) + .ToHashSet(); + + foreach (var seqVar in this.Variables.Where(x => x.Sequence)) + { + var value = seqVar.Values[iterationNumber % (ulong)seqVar.Values.Length]; + string usedVariable = null; + if (value is string valueString && valueString.StartsWith("$.")) + { + usedVariable = valueString[2..]; + if (next.TryGetValue(usedVariable, out var valueFromVariable)) + { + next[seqVar.Name] = valueFromVariable; + notUsedSequenceVariables.Remove(usedVariable); + } + else + { + next[seqVar.Name] = value; + } + } + else + { + next[seqVar.Name] = value; + } + } + + ResetNotUsedReferencedVariables(previous, next, notUsedSequenceVariables); + } + return next; } + /// + /// Removes non-used variables in a sequence. + /// This way we can keep the a counter variable incrementally correctly if the sequence did not use it in current iteration. + /// + private static void ResetNotUsedReferencedVariables( + Dictionary previous, + Dictionary next, + IEnumerable notUsedVariables) + { + foreach (var notUsedVariable in notUsedVariables) + { + // Restore it from the previous value. + if (previous != null && previous.TryGetValue(notUsedVariable, out var previousValue)) + { + next[notUsedVariable] = previousValue; + } + else + { + next.Remove(notUsedVariable); + } + } + } + /// /// All possible variable names this object can produce. /// diff --git a/src/IotTelemetrySimulator/TelemetryVariable.cs b/src/IotTelemetrySimulator/TelemetryVariable.cs index 9409c99..53604ad 100644 --- a/src/IotTelemetrySimulator/TelemetryVariable.cs +++ b/src/IotTelemetrySimulator/TelemetryVariable.cs @@ -1,5 +1,7 @@ namespace IotTelemetrySimulator { + using System; + using System.Collections.Generic; using Newtonsoft.Json; public class TelemetryVariable @@ -10,6 +12,9 @@ public class TelemetryVariable [JsonProperty("random")] public bool Random { get; set; } + [JsonProperty("sequence")] + public bool Sequence { get; set; } + [JsonProperty("max")] public int? Max { get; set; } @@ -24,5 +29,33 @@ public class TelemetryVariable [JsonProperty("values")] public object[] Values { get; set; } + + IReadOnlyList referencedVariableNames; + + public IReadOnlyList GetReferenceVariableNames() + { + if (this.referencedVariableNames == null) + { + if (this.Values == null || this.Values.Length == 0) + { + this.referencedVariableNames = Array.Empty(); + } + else + { + var variableNames = new List(); + foreach (var val in this.Values) + { + if (val is string valString && valString.StartsWith("$.")) + { + variableNames.Add(valString[2..]); + } + } + + this.referencedVariableNames = variableNames.ToArray(); + } + } + + return this.referencedVariableNames; + } } } diff --git a/src/IotTelemetrySimulator/TemplatedPayload.cs b/src/IotTelemetrySimulator/TemplatedPayload.cs index 35ce629..a772662 100644 --- a/src/IotTelemetrySimulator/TemplatedPayload.cs +++ b/src/IotTelemetrySimulator/TemplatedPayload.cs @@ -11,7 +11,12 @@ public class TemplatedPayload : PayloadBase public TelemetryValues Variables { get; } public TemplatedPayload(int distribution, TelemetryTemplate template, TelemetryValues variables) - : base(distribution) + : this(distribution, null, template, variables) + { + } + + public TemplatedPayload(int distribution, string deviceId, TelemetryTemplate template, TelemetryValues variables) + : base(distribution, deviceId) { this.Template = template; this.Variables = variables; diff --git a/test/IotTelemetrySimulator.Test/IotTelemetrySimulator.Test.csproj b/test/IotTelemetrySimulator.Test/IotTelemetrySimulator.Test.csproj index 4673913..9859f95 100644 --- a/test/IotTelemetrySimulator.Test/IotTelemetrySimulator.Test.csproj +++ b/test/IotTelemetrySimulator.Test/IotTelemetrySimulator.Test.csproj @@ -31,6 +31,23 @@ + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + ../../stylecop.ruleset diff --git a/test/IotTelemetrySimulator.Test/PayloadGeneratorTest.cs b/test/IotTelemetrySimulator.Test/PayloadGeneratorTest.cs index 450dd27..49bfeef 100644 --- a/test/IotTelemetrySimulator.Test/PayloadGeneratorTest.cs +++ b/test/IotTelemetrySimulator.Test/PayloadGeneratorTest.cs @@ -1,6 +1,7 @@ namespace IotTelemetrySimulator.Test { using System; + using System.Collections.Generic; using System.Text; using Moq; using Xunit; @@ -36,9 +37,254 @@ public void When_Getting_Payload_Should_Distribute_Correctly() foreach (var tt in t) { randomizer.Setup(x => x.Next(It.IsAny(), It.IsAny())).Returns(tt.distribution); - var (p, v) = target.Generate(null); + var (p, v) = target.Generate(null, null); Assert.Equal(tt.expectedPayload, Encoding.UTF8.GetString(p)); } } + + [Fact] + public void Sequence_Generator() + { + var telemetryTemplate = new TelemetryTemplate("{\"val\":\"$.Value\"}", new[] { "Value", "Counter" }); + var telemetryVariables = new[] + { + new TelemetryVariable + { + Name = "Value", + Sequence = true, + Values = new object[] { "$.Counter", "true", "false", "$.Counter" }, + }, + + new TelemetryVariable + { + Name = "Counter", + Step = 1, + Min = 1 + } + }; + var telemetryValues = new TelemetryValues(telemetryVariables); + + var payload = new TemplatedPayload(100, telemetryTemplate, telemetryValues); + + var target = new PayloadGenerator(new[] { payload }, new DefaultRandomizer()); + + var expectedValues = new[] + { + "{\"val\":\"1\"}", + "{\"val\":\"true\"}", + "{\"val\":\"false\"}", + "{\"val\":\"2\"}", + "{\"val\":\"3\"}", + "{\"val\":\"true\"}", + "{\"val\":\"false\"}", + "{\"val\":\"4\"}", + }; + + var variables = new Dictionary + { + { Constants.DeviceIdValueName, "mydevice" }, + }; + + byte[] result; + foreach (var expectedValue in expectedValues) + { + (result, variables) = target.Generate(null, variables); + Assert.NotEmpty(variables); + Assert.Equal(expectedValue, Encoding.UTF8.GetString(result)); + } + } + + [Fact] + public void Sequence_With_Exchanging_Counters_Generator() + { + var telemetryTemplate = new TelemetryTemplate("{\"val1\":\"$.Value1\",\"val2\":\"$.Value2\"}", new[] { "Value1", "Value2", "Counter1", "Counter2" }); + var telemetryVariables = new[] + { + new TelemetryVariable + { + Name = "Value1", + Sequence = true, + Values = new object[] { "$.Counter1", "$.Counter2" }, + }, + + new TelemetryVariable + { + Name = "Value2", + Sequence = true, + Values = new object[] { "$.Counter2", "$.Counter1" }, + }, + + new TelemetryVariable + { + Name = "Counter1", + Step = 1, + Min = 1 + }, + + new TelemetryVariable + { + Name = "Counter2", + Step = 1, + Min = 1_001 + } + }; + var telemetryValues = new TelemetryValues(telemetryVariables); + + var payload = new TemplatedPayload(100, telemetryTemplate, telemetryValues); + + var target = new PayloadGenerator(new[] { payload }, new DefaultRandomizer()); + + var expectedValues = new[] + { + "{\"val1\":\"1\",\"val2\":\"1001\"}", + "{\"val1\":\"1002\",\"val2\":\"2\"}", + "{\"val1\":\"3\",\"val2\":\"1003\"}", + "{\"val1\":\"1004\",\"val2\":\"4\"}", + }; + + var variables = new Dictionary + { + { Constants.DeviceIdValueName, "mydevice" }, + }; + + byte[] result; + foreach (var expectedValue in expectedValues) + { + (result, variables) = target.Generate(null, variables); + Assert.NotEmpty(variables); + Assert.Equal(expectedValue, Encoding.UTF8.GetString(result)); + } + } + + [Fact] + public void Sequence_With_Mixed_Counters_Generator() + { + var telemetryTemplate = new TelemetryTemplate("{\"val1\":\"$.Value1\",\"val2\":\"$.Value2\",\"counter_3\":\"$.Counter3\"}", new[] { "Value1", "Value2", "Counter1", "Counter2", "Counter3" }); + var telemetryVariables = new[] + { + new TelemetryVariable + { + Name = "Value1", + Sequence = true, + Values = new object[] { "$.Counter1", "$.Counter2" }, + }, + + new TelemetryVariable + { + Name = "Value2", + Sequence = true, + Values = new object[] { "$.Counter2", "$.Counter1" }, + }, + + new TelemetryVariable + { + Name = "Counter1", + Step = 1, + Min = 1 + }, + + new TelemetryVariable + { + Name = "Counter2", + Step = 1, + Min = 1_001 + }, + + new TelemetryVariable + { + Name = "Counter3", + Step = 1, + Min = 1_000_001 + } + }; + var telemetryValues = new TelemetryValues(telemetryVariables); + + var payload = new TemplatedPayload(100, telemetryTemplate, telemetryValues); + + var target = new PayloadGenerator(new[] { payload }, new DefaultRandomizer()); + + var expectedValues = new[] + { + "{\"val1\":\"1\",\"val2\":\"1001\",\"counter_3\":\"1000001\"}", + "{\"val1\":\"1002\",\"val2\":\"2\",\"counter_3\":\"1000002\"}", + "{\"val1\":\"3\",\"val2\":\"1003\",\"counter_3\":\"1000003\"}", + "{\"val1\":\"1004\",\"val2\":\"4\",\"counter_3\":\"1000004\"}", + }; + + var variables = new Dictionary + { + { Constants.DeviceIdValueName, "mydevice" }, + }; + + byte[] result; + foreach (var expectedValue in expectedValues) + { + (result, variables) = target.Generate(null, variables); + Assert.NotEmpty(variables); + Assert.Equal(expectedValue, Encoding.UTF8.GetString(result)); + } + } + + [Fact] + public void Sequence_As_Array_Attribute() + { + var telemetryTemplate = new TelemetryTemplate("{\"val1\":\"$.Value1\",\"array_var\":$.ArrayValue,\"array_fix\":[\"FixCategory\"]}", new[] { "Value1", "Counter1", "Counter2", "ArrayValue" }); + var telemetryVariables = new[] + { + new TelemetryVariable + { + Name = "Value1", + Sequence = true, + Values = new object[] { "$.Counter1", "$.Counter2" }, + }, + + new TelemetryVariable + { + Name = "Counter1", + Step = 1, + Min = 1 + }, + + new TelemetryVariable + { + Name = "Counter2", + Step = 1, + Min = 1_001 + }, + + new TelemetryVariable + { + Name = "ArrayValue", + Sequence = true, + Values = new object[] { "[\"MyCategory\"]", "[]" } + } + }; + var telemetryValues = new TelemetryValues(telemetryVariables); + + var payload = new TemplatedPayload(100, telemetryTemplate, telemetryValues); + + var target = new PayloadGenerator(new[] { payload }, new DefaultRandomizer()); + + var expectedValues = new[] + { + "{\"val1\":\"1\",\"array_var\":[\"MyCategory\"],\"array_fix\":[\"FixCategory\"]}", + "{\"val1\":\"1001\",\"array_var\":[],\"array_fix\":[\"FixCategory\"]}", + "{\"val1\":\"2\",\"array_var\":[\"MyCategory\"],\"array_fix\":[\"FixCategory\"]}", + "{\"val1\":\"1002\",\"array_var\":[],\"array_fix\":[\"FixCategory\"]}", + }; + + var variables = new Dictionary + { + { Constants.DeviceIdValueName, "mydevice" }, + }; + + byte[] result; + foreach (var expectedValue in expectedValues) + { + (result, variables) = target.Generate(null, variables); + Assert.NotEmpty(variables); + Assert.Equal(expectedValue, Encoding.UTF8.GetString(result)); + } + } } } diff --git a/test/IotTelemetrySimulator.Test/RunnerConfigurationTest.cs b/test/IotTelemetrySimulator.Test/RunnerConfigurationTest.cs index a263f0f..cacd24e 100644 --- a/test/IotTelemetrySimulator.Test/RunnerConfigurationTest.cs +++ b/test/IotTelemetrySimulator.Test/RunnerConfigurationTest.cs @@ -5,6 +5,7 @@ namespace IotTelemetrySimulator.Test using System.Text; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; + using Newtonsoft.Json; using Xunit; public class RunnerConfigurationTest @@ -111,5 +112,112 @@ public void When_Using_Base64_Fix_Payload_Loads_Correctly() Assert.Equal("10", Encoding.UTF8.GetString(fixPayload10.Payload)); Assert.Equal(2, fixPayload10.Payload.Length); } + + [Fact] + public void When_Using_Sequence_Payload_Loads_Correctly() + { + const string rawTemplate = "{\"value\": \"$.Value\" }"; + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary() + { + { "Variables", "[{\"name\":\"Value\", \"sequence\":true, \"values\":[\"$.Counter\", \"true\"]}, {\"name\":\"Counter\"}]" }, + { "Template", rawTemplate }, + }) + .Build(); + + var target = RunnerConfiguration.Load(configuration, NullLogger.Instance); + Assert.NotNull(target.PayloadGenerator); + var payload = Assert.Single(target.PayloadGenerator.Payloads); + var templatedPayload = Assert.IsType(payload); + Assert.Equal(2, templatedPayload.Variables.Variables.Count); + Assert.True(templatedPayload.Variables.Variables[0].Sequence); + Assert.Equal(new[] { "Counter" }, templatedPayload.Variables.Variables[0].GetReferenceVariableNames()); + } + + [Fact] + public void When_Loading_From_File_Loads_Correctly() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile("./test_files/test1-config.json", false, false) + .Build(); + + var target = RunnerConfiguration.Load(configuration, NullLogger.Instance); + Assert.NotNull(target.PayloadGenerator); + var payload = Assert.Single(target.PayloadGenerator.Payloads); + var templatedPayload = Assert.IsType(payload); + Assert.Equal(2, templatedPayload.Variables.Variables.Count); + Assert.True(templatedPayload.Variables.Variables[0].Sequence); + Assert.Equal(new[] { "Counter" }, templatedPayload.Variables.Variables[0].GetReferenceVariableNames()); + } + + [Fact] + public void When_Loading_From_File_With_Multiple_Payloads_Loads_Correctly() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile("./test_files/test2-config-multiple-payloads.json", false, false) + .Build(); + + var target = RunnerConfiguration.Load(configuration, NullLogger.Instance); + Assert.NotNull(target.PayloadGenerator); + Assert.Equal(2, target.PayloadGenerator.Payloads.Length); + + var templatedPayload = Assert.IsType(target.PayloadGenerator.Payloads[0]); + Assert.Equal(2, templatedPayload.Variables.Variables.Count); + Assert.True(templatedPayload.Variables.Variables[0].Sequence); + Assert.Equal("device0001", templatedPayload.DeviceId); + Assert.Equal(new[] { "Counter" }, templatedPayload.Variables.Variables[0].GetReferenceVariableNames()); + + var fixPayload = Assert.IsType(target.PayloadGenerator.Payloads[1]); + Assert.Equal("{\"value\":\"myfixvalue\"}", Encoding.UTF8.GetString(fixPayload.Payload)); + Assert.Equal("device0002", fixPayload.DeviceId); + } + + [Fact] + public void When_Loading_From_File_With_Non_Encoded_Json_Payloads_Loads_Correctly() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile("./test_files/test5-config-payloads-as-json.json", false, false) + .Build(); + + var target = RunnerConfiguration.Load(configuration, NullLogger.Instance); + Assert.NotNull(target.PayloadGenerator); + Assert.Equal(2, target.PayloadGenerator.Payloads.Length); + + var templatedPayload = Assert.IsType(target.PayloadGenerator.Payloads[0]); + + var device1Vars = new Dictionary + { + { Constants.DeviceIdValueName, "device0001" }, + }; + var (device1Message, _) = templatedPayload.Generate(device1Vars); + var device1MessageMap = JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(device1Message)); + Assert.Equal(2, device1MessageMap.Count); + Assert.Equal("1", device1MessageMap["value"]); + Assert.Equal("20", device1MessageMap["a_second_value"]); + + var fixPayload = Assert.IsType(target.PayloadGenerator.Payloads[1]); + var device2Vars = new Dictionary + { + { Constants.DeviceIdValueName, "device0002" }, + }; + var (device2Message, _) = fixPayload.Generate(device2Vars); + var device2MessageMap = JsonConvert.DeserializeObject>(Encoding.UTF8.GetString(device2Message)); + Assert.Single(device2MessageMap); + Assert.Equal("myfixvalue", device2MessageMap["value"]); + } + + [Fact] + public void When_Loading_From_File_With_Custom_Intervals_Loads_Correctly() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile("./test_files/test4-config-multiple-internals-per-device.json", false, false) + .Build(); + + var target = RunnerConfiguration.Load(configuration, NullLogger.Instance); + + Assert.Equal(10_000, target.GetMessageIntervalForDevice("sim000001")); + Assert.Equal(100, target.GetMessageIntervalForDevice("sim000002")); + Assert.Equal(1_000, target.GetMessageIntervalForDevice("sim000003")); + } } } diff --git a/test/IotTelemetrySimulator.Test/SenderBaseTest.cs b/test/IotTelemetrySimulator.Test/SenderBaseTest.cs index 149fcfa..24755f3 100644 --- a/test/IotTelemetrySimulator.Test/SenderBaseTest.cs +++ b/test/IotTelemetrySimulator.Test/SenderBaseTest.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; + using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; @@ -103,6 +104,52 @@ public async Task When_SendMessageAsync_With_Header_Property_Is_Added() Assert.Single(sender.TestMessages); } + [Fact] + public async Task When_SendingMessage_With_Different_Payload_Per_Device_Should_Work() + { + var configuration = new ConfigurationBuilder() + .AddJsonFile("./test_files/test3-config-multiple-devices.json") + .Build(); + + var stats = new RunnerStats(); + var config = RunnerConfiguration.Load(configuration, NullLogger.Instance); + var senderDevice1 = new TestSender(false, false, "device0001", config); + var senderDevice2 = new TestSender(false, false, "device0002", config); + var senderDevice3 = new TestSender(false, false, "device0003", config); + + const int messageCount = 3; + for (int i = 0; i < messageCount; i++) + { + await Task.WhenAll( + senderDevice1.SendMessageAsync(stats, default), + senderDevice2.SendMessageAsync(stats, default), + senderDevice3.SendMessageAsync(stats, default)); + } + + foreach (var expectedMessageForDevice1 in new[] + { + "{\"value\":\"1\"}", + "{\"value\":\"true\"}", + "{\"value\":\"2\"}", + }) + { + Assert.Equal(1, senderDevice1.TestMessages.Count(x => expectedMessageForDevice1 == Encoding.UTF8.GetString(x.MessageBytes))); + } + + foreach (var expectedMessageForDevice3 in new[] + { + "{\"a\":\"b\",\"value\":\"1\"}", + "{\"a\":\"b\",\"value\":\"true\"}", + "{\"a\":\"b\",\"value\":\"2\"}", + }) + { + Assert.Equal(1, senderDevice3.TestMessages.Count(x => expectedMessageForDevice3 == Encoding.UTF8.GetString(x.MessageBytes))); + } + + const string expectedMessageForDevice2 = "{\"value\":\"myfixvalue\"}"; + Assert.Equal(messageCount, senderDevice2.TestMessages.Count(x => Encoding.UTF8.GetString(x.MessageBytes) == expectedMessageForDevice2)); + } + class TestMessage { public IDictionary Properties { get; private set; } = new Dictionary(); diff --git a/test/IotTelemetrySimulator.Test/test_files/test1-config.json b/test/IotTelemetrySimulator.Test/test_files/test1-config.json new file mode 100644 index 0000000..73f1138 --- /dev/null +++ b/test/IotTelemetrySimulator.Test/test_files/test1-config.json @@ -0,0 +1,13 @@ +{ + "Variables": [ + { + "name": "Value", + "sequence": true, + "values": [ "$.Counter", "true" ] + }, + { + "name": "Counter" + } + ], + "Template": "{\"value\": \"$.Value\" }" +} diff --git a/test/IotTelemetrySimulator.Test/test_files/test2-config-multiple-payloads.json b/test/IotTelemetrySimulator.Test/test_files/test2-config-multiple-payloads.json new file mode 100644 index 0000000..9b3de01 --- /dev/null +++ b/test/IotTelemetrySimulator.Test/test_files/test2-config-multiple-payloads.json @@ -0,0 +1,24 @@ +{ + "Variables": [ + { + "name": "Value", + "sequence": true, + "values": [ "$.Counter", "true" ] + }, + { + "name": "Counter" + } + ], + "Payloads": [ + { + "type": "template", + "deviceId": "device0001", + "template": "{\"value\":\"$.Value\"}" + }, + { + "type": "fix", + "deviceId": "device0002", + "value": "{\"value\":\"myfixvalue\"}" + } + ] +} diff --git a/test/IotTelemetrySimulator.Test/test_files/test3-config-multiple-devices.json b/test/IotTelemetrySimulator.Test/test_files/test3-config-multiple-devices.json new file mode 100644 index 0000000..070c41d --- /dev/null +++ b/test/IotTelemetrySimulator.Test/test_files/test3-config-multiple-devices.json @@ -0,0 +1,29 @@ +{ + "Variables": [ + { + "name": "Value", + "sequence": true, + "values": [ "$.Counter", "true" ] + }, + { + "name": "Counter" + } + ], + "Payloads": [ + { + "type": "template", + "deviceId": "device0001", + "template": "{\"value\":\"$.Value\"}" + }, + { + "type": "fix", + "deviceId": "device0002", + "value": "{\"value\":\"myfixvalue\"}" + }, + { + "type": "template", + "deviceId": "device0003", + "template": "{\"a\":\"b\",\"value\":\"$.Value\"}" + } + ] +} diff --git a/test/IotTelemetrySimulator.Test/test_files/test4-config-multiple-internals-per-device.json b/test/IotTelemetrySimulator.Test/test_files/test4-config-multiple-internals-per-device.json new file mode 100644 index 0000000..a740741 --- /dev/null +++ b/test/IotTelemetrySimulator.Test/test_files/test4-config-multiple-internals-per-device.json @@ -0,0 +1,42 @@ +{ + "Variables": [ + { + "name": "DeviceSequenceValue1", + "sequence": true, + "values": [ "$.Counter", "$.Counter", "$.Counter", "$.Counter", "$.Counter", "true", "false", "$.Counter" ] + }, + { + "name": "Device1Tags", + "sequence": true, + "values": [ "['ProducedPartCount']", "['ProducedPartCount']", "['ProducedPartCount']", "['ProducedPartCount']", "['ProducedPartCount']", "['Downtime']", "['Downtime']", "['ProducedPartCount']" ] + }, + { + "name": "Device1Downtime", + "values": [ "true", "true", "true", "true", "false" ] + }, + { + "name": "Counter" + } + ], + "Intervals": { + "sim000001": 10000, + "sim000002": 100 + }, + "Payloads": [ + { + "type": "template", + "deviceId": "sim000001", + "template": "{\"device\":\"$.DeviceId\",\"value\":\"$.DeviceSequenceValue1\",\"tags\": $.Device1Tags}" + }, + { + "type": "fix", + "deviceId": "sim000002", + "value": "{\"value\":\"myfixvalue\"}" + }, + { + "type": "template", + "deviceId": "sim000003", + "template": "{\"device\":\"$.DeviceId\",\"a\":\"b\",\"value\":\"$.DeviceSequenceValue1\"}" + } + ] +} diff --git a/test/IotTelemetrySimulator.Test/test_files/test5-config-payloads-as-json.json b/test/IotTelemetrySimulator.Test/test_files/test5-config-payloads-as-json.json new file mode 100644 index 0000000..3441576 --- /dev/null +++ b/test/IotTelemetrySimulator.Test/test_files/test5-config-payloads-as-json.json @@ -0,0 +1,27 @@ +{ + "Variables": [ + { + "name": "Value", + "sequence": true, + "values": [ "$.Counter", "true" ] + }, + { + "name": "Counter" + } + ], + "Payloads": [ + { + "type": "template", + "deviceId": "device0001", + "template": { + "value": "$.Value", + "a_second_value": "20" + } + }, + { + "type": "fix", + "deviceId": "device0002", + "value": { "value": "myfixvalue" } + } + ] +}