Simulation seed data (#33)
* Complete seed data, complete all the device models as per specs * Rename “device type” to “device model” * Clean JS syntax, check with JS Lint * Generate device IDs from device model ID, instead of device model Name * Complete health check endpoint
This commit is contained in:
Родитель
f2c8d6e92d
Коммит
570645bf2e
|
@ -11,6 +11,7 @@ _dev/
|
|||
.cache/**
|
||||
target/**
|
||||
.travis
|
||||
.vscode
|
||||
|
||||
|
||||
### MacOS
|
||||
|
|
|
@ -11,7 +11,7 @@ cloud-to-device (C2D) commands, methods, etc.
|
|||
|
||||
The microservice provides a RESTful endpoint to set the simulation details,
|
||||
to start and stop the simulation, to add and remove virtual devices. The
|
||||
simulation is composed by a set of virtual devices, of different types,
|
||||
simulation is composed by a set of virtual devices, of different models,
|
||||
each sending telemetry and replying to method calls.
|
||||
|
||||
* [Device Simulation Wiki](https://github.com/Azure/device-simulation-dotnet/wiki)
|
||||
|
@ -37,12 +37,12 @@ After cloning the repository, follow these steps:
|
|||
|
||||
By default, Docker Compose will start the service using the sample device
|
||||
types defined in [sample-volume](scripts/docker/sample-volume):
|
||||
* to load device types definitions from a different folder, edit the
|
||||
* to load device models definitions from a different folder, edit the
|
||||
[docker-compose.yml](scripts/docker/docker-compose.yml)
|
||||
* to add your custom simulations, add the JSON and Javascript files into the
|
||||
folder and restart the service. See the
|
||||
[wiki](https://github.com/Azure/device-simulation-dotnet/wiki)
|
||||
for more information about device types and the API.
|
||||
for more information about device models and the API.
|
||||
|
||||
## Working with Visual Studio
|
||||
|
||||
|
|
|
@ -28,8 +28,8 @@ namespace Services.Test.Simulation
|
|||
this.log = log;
|
||||
|
||||
this.config = new Mock<IServicesConfig>();
|
||||
this.config.SetupGet(x => x.DeviceTypesFolder).Returns("./data/DeviceTypes/");
|
||||
this.config.SetupGet(x => x.DeviceTypesScriptsFolder).Returns("./data/DeviceTypes/Scripts/");
|
||||
this.config.SetupGet(x => x.DeviceModelsFolder).Returns("./data/devicemodels/");
|
||||
this.config.SetupGet(x => x.DeviceModelsScriptsFolder).Returns("./data/devicemodels/scripts/");
|
||||
|
||||
this.logger = new Mock<ILogger>();
|
||||
this.CaptureApplicationLogs(this.logger);
|
||||
|
@ -41,12 +41,12 @@ namespace Services.Test.Simulation
|
|||
public void ReturnedStateIsIntact()
|
||||
{
|
||||
// Arrange
|
||||
var filename = "room-state.js";
|
||||
var filename = "chiller-01-state.js";
|
||||
var context = new Dictionary<string, object>
|
||||
{
|
||||
["currentTime"] = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:sszzz"),
|
||||
["deviceId"] = "device-123",
|
||||
["deviceType"] = "room"
|
||||
["deviceModel"] = "room"
|
||||
};
|
||||
var state = new Dictionary<string, object>
|
||||
{
|
||||
|
@ -75,16 +75,22 @@ namespace Services.Test.Simulation
|
|||
// Arrange
|
||||
var files = new List<string>
|
||||
{
|
||||
"chiller-state.js",
|
||||
"elevator-state.js",
|
||||
"room-state.js",
|
||||
"truck-state.js"
|
||||
"chiller-01-state.js",
|
||||
"chiller-02-state.js",
|
||||
"elevator-01-state.js",
|
||||
"elevator-02-state.js",
|
||||
"engine-01-state.js",
|
||||
"engine-02-state.js",
|
||||
"prototype-01-state.js",
|
||||
"prototype-02-state.js",
|
||||
"truck-01-state.js",
|
||||
"truck-02-state.js"
|
||||
};
|
||||
var context = new Dictionary<string, object>
|
||||
{
|
||||
["currentTime"] = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:sszzz"),
|
||||
["deviceId"] = "device-123",
|
||||
["deviceType"] = "room"
|
||||
["deviceModel"] = "room"
|
||||
};
|
||||
|
||||
// Act - Assert (no exception should occur)
|
||||
|
|
|
@ -24,29 +24,29 @@ namespace Services.Test
|
|||
private const string StorageCollection = "simulations";
|
||||
private const string SimulationId = "1";
|
||||
|
||||
private readonly Mock<IDeviceTypes> deviceTypes;
|
||||
private readonly Mock<IDeviceModels> deviceModels;
|
||||
private readonly Mock<IStorageAdapterClient> storage;
|
||||
private readonly Mock<ILogger> logger;
|
||||
private readonly Simulations target;
|
||||
private readonly List<DeviceType> types;
|
||||
private readonly List<DeviceModel> models;
|
||||
|
||||
public SimulationsTest(ITestOutputHelper log)
|
||||
{
|
||||
this.log = log;
|
||||
|
||||
this.deviceTypes = new Mock<IDeviceTypes>();
|
||||
this.deviceModels = new Mock<IDeviceModels>();
|
||||
this.storage = new Mock<IStorageAdapterClient>();
|
||||
this.logger = new Mock<ILogger>();
|
||||
|
||||
this.types = new List<DeviceType>
|
||||
this.models = new List<DeviceModel>
|
||||
{
|
||||
new DeviceType { Id = "01" },
|
||||
new DeviceType { Id = "05" },
|
||||
new DeviceType { Id = "02" },
|
||||
new DeviceType { Id = "AA" }
|
||||
new DeviceModel { Id = "01" },
|
||||
new DeviceModel { Id = "05" },
|
||||
new DeviceModel { Id = "02" },
|
||||
new DeviceModel { Id = "AA" }
|
||||
};
|
||||
|
||||
this.target = new Simulations(this.deviceTypes.Object, this.storage.Object, this.logger.Object);
|
||||
this.target = new Simulations(this.deviceModels.Object, this.storage.Object, this.logger.Object);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.Type, Constants.UnitTest)]
|
||||
|
@ -67,7 +67,7 @@ namespace Services.Test
|
|||
{
|
||||
// Arrange
|
||||
this.ThereAreNoSimulationsInTheStorage();
|
||||
this.ThereAreSomeDeviceTypes();
|
||||
this.ThereAreSomeDeviceModels();
|
||||
|
||||
// Act
|
||||
SimulationModel result = this.target.InsertAsync(new SimulationModel(), "default").Result;
|
||||
|
@ -83,17 +83,17 @@ namespace Services.Test
|
|||
// Arrange
|
||||
const int defaultDeviceCount = 2;
|
||||
this.ThereAreNoSimulationsInTheStorage();
|
||||
this.ThereAreSomeDeviceTypes();
|
||||
this.ThereAreSomeDeviceModels();
|
||||
|
||||
// Act
|
||||
SimulationModel result = this.target.InsertAsync(new SimulationModel(), "default").Result;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(this.types.Count, result.DeviceTypes.Count);
|
||||
for (var i = 0; i < this.types.Count; i++)
|
||||
Assert.Equal(this.models.Count, result.DeviceModels.Count);
|
||||
for (var i = 0; i < this.models.Count; i++)
|
||||
{
|
||||
Assert.Equal(this.types[i].Id, result.DeviceTypes[i].Id);
|
||||
Assert.Equal(defaultDeviceCount, result.DeviceTypes[i].Count);
|
||||
Assert.Equal(this.models[i].Id, result.DeviceModels[i].Id);
|
||||
Assert.Equal(defaultDeviceCount, result.DeviceModels[i].Count);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,7 @@ namespace Services.Test
|
|||
{
|
||||
// Arrange
|
||||
this.ThereAreNoSimulationsInTheStorage();
|
||||
this.ThereAreSomeDeviceTypes();
|
||||
this.ThereAreSomeDeviceModels();
|
||||
|
||||
// Act
|
||||
SimulationModel result = this.target.InsertAsync(new SimulationModel(), "default").Result;
|
||||
|
@ -115,7 +115,7 @@ namespace Services.Test
|
|||
public void CreateSimulationWithId()
|
||||
{
|
||||
// Arrange
|
||||
this.ThereAreSomeDeviceTypes();
|
||||
this.ThereAreSomeDeviceModels();
|
||||
this.ThereAreNoSimulationsInTheStorage();
|
||||
|
||||
// Act
|
||||
|
@ -138,7 +138,7 @@ namespace Services.Test
|
|||
public void CreatingMultipleSimulationsIsNotAllowed()
|
||||
{
|
||||
// Arrange
|
||||
this.ThereAreSomeDeviceTypes();
|
||||
this.ThereAreSomeDeviceModels();
|
||||
this.ThereIsAnEnabledSimulationInTheStorage();
|
||||
|
||||
// Act + Assert
|
||||
|
@ -151,7 +151,7 @@ namespace Services.Test
|
|||
public void CreatedSimulationsAreStored()
|
||||
{
|
||||
// Arrange
|
||||
this.ThereAreSomeDeviceTypes();
|
||||
this.ThereAreSomeDeviceModels();
|
||||
this.ThereAreNoSimulationsInTheStorage();
|
||||
|
||||
// Act
|
||||
|
@ -167,7 +167,7 @@ namespace Services.Test
|
|||
public void SimulationsCanBeUpserted()
|
||||
{
|
||||
// Arrange
|
||||
this.ThereAreSomeDeviceTypes();
|
||||
this.ThereAreSomeDeviceModels();
|
||||
this.ThereAreNoSimulationsInTheStorage();
|
||||
|
||||
// Act
|
||||
|
@ -200,7 +200,7 @@ namespace Services.Test
|
|||
public void UpsertUsesOptimisticConcurrency()
|
||||
{
|
||||
// Arrange
|
||||
this.deviceTypes.Setup(x => x.GetList()).Returns(this.types);
|
||||
this.deviceModels.Setup(x => x.GetList()).Returns(this.models);
|
||||
|
||||
var id = Guid.NewGuid().ToString();
|
||||
var s1 = new SimulationModel { Id = id, Enabled = false };
|
||||
|
@ -211,14 +211,17 @@ namespace Services.Test
|
|||
Assert.ThrowsAsync<ResourceOutOfDateException>(() => this.target.UpsertAsync(s1updated));
|
||||
}
|
||||
|
||||
private void ThereAreSomeDeviceTypes()
|
||||
private void ThereAreSomeDeviceModels()
|
||||
{
|
||||
this.deviceTypes.Setup(x => x.GetList()).Returns(this.types);
|
||||
this.deviceModels.Setup(x => x.GetList()).Returns(this.models);
|
||||
}
|
||||
|
||||
private void ThereAreNoSimulationsInTheStorage()
|
||||
{
|
||||
this.storage.Setup(x => x.GetAllAsync(StorageCollection)).ReturnsAsync(new ValueListApiModel());
|
||||
// In case the test inserts a record, return a valid storage object
|
||||
this.storage.Setup(x => x.UpdateAsync(StorageCollection, SimulationId, It.IsAny<string>(), "*"))
|
||||
.ReturnsAsync(new ValueApiModel { Key = SimulationId, Data = "{}", ETag = "someEtag" });
|
||||
}
|
||||
|
||||
private void ThereIsAnEnabledSimulationInTheStorage()
|
||||
|
|
|
@ -16,13 +16,13 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
{
|
||||
IoTHubProtocol Protocol { get; }
|
||||
|
||||
Task SendMessageAsync(string message, DeviceType.DeviceTypeMessageSchema schema);
|
||||
Task SendMessageAsync(string message, DeviceModel.DeviceModelMessageSchema schema);
|
||||
|
||||
Task SendRawMessageAsync(Message message);
|
||||
|
||||
Task DisconnectAsync();
|
||||
|
||||
Task UpdateTwinAsync(DeviceServiceModel device);
|
||||
Task UpdateTwinAsync(Device device);
|
||||
}
|
||||
|
||||
public class DeviceClient : IDeviceClient
|
||||
|
@ -51,7 +51,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
|
||||
public IoTHubProtocol Protocol { get { return this.protocol; } }
|
||||
|
||||
public async Task SendMessageAsync(string message, DeviceType.DeviceTypeMessageSchema schema)
|
||||
public async Task SendMessageAsync(string message, DeviceModel.DeviceModelMessageSchema schema)
|
||||
{
|
||||
var eventMessage = new Message(Encoding.UTF8.GetBytes(message));
|
||||
eventMessage.Properties.Add(CreationTimeProperty, DateTimeOffset.UtcNow.ToString(DateFormat));
|
||||
|
@ -80,7 +80,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
}
|
||||
}
|
||||
|
||||
public async Task UpdateTwinAsync(DeviceServiceModel device)
|
||||
public async Task UpdateTwinAsync(Device device)
|
||||
{
|
||||
var azureTwin = await this.GetTwinAsync();
|
||||
|
||||
|
|
|
@ -14,59 +14,59 @@ using Newtonsoft.Json;
|
|||
// TODO: handle errors
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
||||
{
|
||||
public interface IDeviceTypes
|
||||
public interface IDeviceModels
|
||||
{
|
||||
IEnumerable<DeviceType> GetList();
|
||||
DeviceType Get(string id);
|
||||
IEnumerable<DeviceModel> GetList();
|
||||
DeviceModel Get(string id);
|
||||
}
|
||||
|
||||
public class DeviceTypes : IDeviceTypes
|
||||
public class DeviceModels : IDeviceModels
|
||||
{
|
||||
private const string Ext = ".json";
|
||||
|
||||
private readonly IServicesConfig config;
|
||||
private readonly ILogger log;
|
||||
|
||||
private List<string> deviceTypeFiles;
|
||||
private List<DeviceType> deviceTypes;
|
||||
private List<string> deviceModelFiles;
|
||||
private List<DeviceModel> deviceModels;
|
||||
|
||||
public DeviceTypes(
|
||||
public DeviceModels(
|
||||
IServicesConfig config,
|
||||
ILogger logger)
|
||||
{
|
||||
this.config = config;
|
||||
this.log = logger;
|
||||
this.deviceTypeFiles = null;
|
||||
this.deviceTypes = null;
|
||||
this.deviceModelFiles = null;
|
||||
this.deviceModels = null;
|
||||
}
|
||||
|
||||
public IEnumerable<DeviceType> GetList()
|
||||
public IEnumerable<DeviceModel> GetList()
|
||||
{
|
||||
if (this.deviceTypes != null) return this.deviceTypes;
|
||||
if (this.deviceModels != null) return this.deviceModels;
|
||||
|
||||
this.deviceTypes = new List<DeviceType>();
|
||||
this.deviceModels = new List<DeviceModel>();
|
||||
|
||||
try
|
||||
{
|
||||
var files = this.GetDeviceTypeFiles();
|
||||
var files = this.GetDeviceModelFiles();
|
||||
foreach (var f in files)
|
||||
{
|
||||
var c = JsonConvert.DeserializeObject<DeviceType>(File.ReadAllText(f));
|
||||
this.deviceTypes.Add(c);
|
||||
var c = JsonConvert.DeserializeObject<DeviceModel>(File.ReadAllText(f));
|
||||
this.deviceModels.Add(c);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.log.Error("Unable to load Device Type configuration",
|
||||
this.log.Error("Unable to load Device Model configuration",
|
||||
() => new { e.Message, Exception = e });
|
||||
|
||||
throw new InvalidConfigurationException("Unable to load Device Type configuration: " + e.Message, e);
|
||||
throw new InvalidConfigurationException("Unable to load Device Model configuration: " + e.Message, e);
|
||||
}
|
||||
|
||||
return this.deviceTypes;
|
||||
return this.deviceModels;
|
||||
}
|
||||
|
||||
public DeviceType Get(string id)
|
||||
public DeviceModel Get(string id)
|
||||
{
|
||||
var list = this.GetList();
|
||||
foreach (var x in list)
|
||||
|
@ -74,24 +74,24 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
if (x.Id == id) return x;
|
||||
}
|
||||
|
||||
this.log.Warn("Device type not found", () => new { id });
|
||||
this.log.Warn("Device model not found", () => new { id });
|
||||
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
private List<string> GetDeviceTypeFiles()
|
||||
private List<string> GetDeviceModelFiles()
|
||||
{
|
||||
if (this.deviceTypeFiles != null) return this.deviceTypeFiles;
|
||||
if (this.deviceModelFiles != null) return this.deviceModelFiles;
|
||||
|
||||
this.log.Debug("Device types folder", () => new { this.config.DeviceTypesFolder });
|
||||
this.log.Debug("Device models folder", () => new { this.config.DeviceModelsFolder });
|
||||
|
||||
var fileEntries = Directory.GetFiles(this.config.DeviceTypesFolder);
|
||||
var fileEntries = Directory.GetFiles(this.config.DeviceModelsFolder);
|
||||
|
||||
this.deviceTypeFiles = fileEntries.Where(fileName => fileName.EndsWith(Ext)).ToList();
|
||||
this.deviceModelFiles = fileEntries.Where(fileName => fileName.EndsWith(Ext)).ToList();
|
||||
|
||||
this.log.Debug("Device type files", () => new { this.deviceTypeFiles });
|
||||
this.log.Debug("Device model files", () => new { this.deviceModelFiles });
|
||||
|
||||
return this.deviceTypeFiles;
|
||||
return this.deviceModelFiles;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
|
|||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
|
||||
using Device = Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models.Device;
|
||||
using TransportType = Microsoft.Azure.Devices.Client.TransportType;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
||||
|
@ -14,10 +15,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
public interface IDevices
|
||||
{
|
||||
Task<Tuple<bool, string>> PingRegistryAsync();
|
||||
IDeviceClient GetClient(DeviceServiceModel device, IoTHubProtocol protocol);
|
||||
Task<DeviceServiceModel> GetOrCreateAsync(string deviceId);
|
||||
Task<DeviceServiceModel> GetAsync(string deviceId);
|
||||
Task<DeviceServiceModel> CreateAsync(string deviceId);
|
||||
IDeviceClient GetClient(Device device, IoTHubProtocol protocol);
|
||||
Task<Device> GetOrCreateAsync(string deviceId);
|
||||
Task<Device> GetAsync(string deviceId);
|
||||
Task<Device> CreateAsync(string deviceId);
|
||||
}
|
||||
|
||||
public class Devices : IDevices
|
||||
|
@ -50,7 +51,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
}
|
||||
}
|
||||
|
||||
public IDeviceClient GetClient(DeviceServiceModel device, IoTHubProtocol protocol)
|
||||
public IDeviceClient GetClient(Device device, IoTHubProtocol protocol)
|
||||
{
|
||||
var connectionString = $"HostName={device.IoTHubHostName};DeviceId={device.Id};SharedAccessKey={device.AuthPrimaryKey}";
|
||||
|
||||
|
@ -88,7 +89,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
return new DeviceClient(sdkClient, protocol, this.log);
|
||||
}
|
||||
|
||||
public async Task<DeviceServiceModel> GetOrCreateAsync(string deviceId)
|
||||
public async Task<Device> GetOrCreateAsync(string deviceId)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -106,9 +107,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
}
|
||||
}
|
||||
|
||||
public async Task<DeviceServiceModel> GetAsync(string deviceId)
|
||||
public async Task<Device> GetAsync(string deviceId)
|
||||
{
|
||||
DeviceServiceModel result = null;
|
||||
Device result = null;
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -118,7 +119,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
|
||||
if (device.Result != null)
|
||||
{
|
||||
result = new DeviceServiceModel(device.Result, twin.Result, this.ioTHubHostName);
|
||||
result = new Device(device.Result, twin.Result, this.ioTHubHostName);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
|
@ -135,20 +136,20 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
return result;
|
||||
}
|
||||
|
||||
public async Task<DeviceServiceModel> CreateAsync(string deviceId)
|
||||
public async Task<Device> CreateAsync(string deviceId)
|
||||
{
|
||||
this.log.Debug("Creating device", () => new { deviceId });
|
||||
var device = new Device(deviceId);
|
||||
var device = new Azure.Devices.Device(deviceId);
|
||||
var azureDevice = await this.registry.AddDeviceAsync(device);
|
||||
|
||||
this.log.Debug("Fetching device twin", () => new { azureDevice.Id });
|
||||
var azureTwin = await this.registry.GetTwinAsync(azureDevice.Id);
|
||||
|
||||
this.log.Debug("Writing device twin", () => new { azureDevice.Id });
|
||||
azureTwin.Tags[DeviceTwinServiceModel.SimulatedTagKey] = DeviceTwinServiceModel.SimulatedTagValue;
|
||||
azureTwin.Tags[DeviceTwin.SimulatedTagKey] = DeviceTwin.SimulatedTagValue;
|
||||
azureTwin = await this.registry.UpdateTwinAsync(azureDevice.Id, azureTwin, "*");
|
||||
|
||||
return new DeviceServiceModel(azureDevice, azureTwin, this.ioTHubHostName);
|
||||
return new Device(azureDevice, azureTwin, this.ioTHubHostName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ using Newtonsoft.Json.Linq;
|
|||
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
||||
{
|
||||
public class DeviceServiceModel
|
||||
public class Device
|
||||
{
|
||||
public string Etag { get; set; }
|
||||
public string Id { get; set; }
|
||||
|
@ -18,11 +18,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
|||
public bool Connected { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public DateTimeOffset LastStatusUpdated { get; set; }
|
||||
public DeviceTwinServiceModel Twin { get; set; }
|
||||
public DeviceTwin Twin { get; set; }
|
||||
public string IoTHubHostName { get; set; }
|
||||
public string AuthPrimaryKey { get; set; }
|
||||
|
||||
public DeviceServiceModel(
|
||||
public Device(
|
||||
string etag,
|
||||
string id,
|
||||
int c2DMessageCount,
|
||||
|
@ -30,7 +30,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
|||
bool connected,
|
||||
bool enabled,
|
||||
DateTimeOffset lastStatusUpdated,
|
||||
DeviceTwinServiceModel twin,
|
||||
DeviceTwin twin,
|
||||
string primaryKey,
|
||||
string ioTHubHostName)
|
||||
{
|
||||
|
@ -46,7 +46,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
|||
this.AuthPrimaryKey = primaryKey;
|
||||
}
|
||||
|
||||
public DeviceServiceModel(Device azureDevice, DeviceTwinServiceModel twin, string ioTHubHostName) :
|
||||
public Device(Azure.Devices.Device azureDevice, DeviceTwin twin, string ioTHubHostName) :
|
||||
this(
|
||||
etag: azureDevice.ETag,
|
||||
id: azureDevice.Id,
|
||||
|
@ -61,12 +61,12 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
|||
{
|
||||
}
|
||||
|
||||
public DeviceServiceModel(Device azureDevice, Twin azureTwin, string ioTHubHostName) :
|
||||
this(azureDevice, new DeviceTwinServiceModel(azureTwin), ioTHubHostName)
|
||||
public Device(Azure.Devices.Device azureDevice, Twin azureTwin, string ioTHubHostName) :
|
||||
this(azureDevice, new DeviceTwin(azureTwin), ioTHubHostName)
|
||||
{
|
||||
}
|
||||
|
||||
public DeviceServiceModel SetReportedProperty(string key, JToken value)
|
||||
public Device SetReportedProperty(string key, JToken value)
|
||||
{
|
||||
this.Twin.ReportedProperties[key] = value;
|
||||
return this;
|
|
@ -12,93 +12,41 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
|||
{
|
||||
public string Type { get; set; }
|
||||
public string Path { get; set; }
|
||||
public TimeSpan Interval { get; set; }
|
||||
|
||||
public Script()
|
||||
{
|
||||
this.Type = "javascript";
|
||||
this.Path = "scripts" + System.IO.Path.DirectorySeparatorChar;
|
||||
this.Interval = TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public class DeviceType
|
||||
public class DeviceModel
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Version { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public IoTHubProtocol Protocol { get; set; }
|
||||
public InternalState DeviceState { get; set; }
|
||||
public IList<DeviceTypeMessage> Telemetry { get; set; }
|
||||
public StateSimulation Simulation { get; set; }
|
||||
public Dictionary<string, object> Properties { get; set; }
|
||||
public IList<DeviceModelMessage> Telemetry { get; set; }
|
||||
public IDictionary<string, Script> CloudToDeviceMethods { get; set; }
|
||||
|
||||
public DeviceType()
|
||||
public DeviceModel()
|
||||
{
|
||||
this.Id = string.Empty;
|
||||
this.Version = "0.0.0";
|
||||
this.Name = string.Empty;
|
||||
this.Description = string.Empty;
|
||||
this.Protocol = IoTHubProtocol.AMQP;
|
||||
this.DeviceState = new InternalState();
|
||||
this.Telemetry = new List<DeviceTypeMessage>();
|
||||
this.Simulation = new StateSimulation();
|
||||
this.Properties = new Dictionary<string, object>();
|
||||
this.Telemetry = new List<DeviceModelMessage>();
|
||||
this.CloudToDeviceMethods = new Dictionary<string, Script>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If the simulated device has some geolocation properties, we publish
|
||||
/// them also in the twin, so that the device can be shown in the map
|
||||
/// even when it hasn't sent (or is not sending) telemetry.
|
||||
/// </summary>
|
||||
public JObject GetLocationReportedProperty()
|
||||
{
|
||||
// 3D
|
||||
if (this.DeviceState.Initial.ContainsKey("latitude")
|
||||
&& this.DeviceState.Initial.ContainsKey("longitude")
|
||||
&& this.DeviceState.Initial.ContainsKey("altitude"))
|
||||
{
|
||||
return new JObject
|
||||
{
|
||||
["Latitude"] = this.DeviceState.Initial["latitude"].ToString(),
|
||||
["Longitude"] = this.DeviceState.Initial["longitude"].ToString(),
|
||||
["Altitude"] = this.DeviceState.Initial["altitude"].ToString()
|
||||
};
|
||||
}
|
||||
|
||||
// 2D
|
||||
if (this.DeviceState.Initial.ContainsKey("latitude")
|
||||
&& this.DeviceState.Initial.ContainsKey("longitude"))
|
||||
{
|
||||
return new JObject
|
||||
{
|
||||
["Latitude"] = this.DeviceState.Initial["latitude"].ToString(),
|
||||
["Longitude"] = this.DeviceState.Initial["longitude"].ToString()
|
||||
};
|
||||
}
|
||||
|
||||
// Geostationary
|
||||
if (this.DeviceState.Initial.ContainsKey("longitude"))
|
||||
{
|
||||
return new JObject
|
||||
{
|
||||
["Longitude"] = this.DeviceState.Initial["longitude"].ToString()
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This data is published in the device twin, so that the UI can
|
||||
/// show information about a device.
|
||||
/// </summary>
|
||||
public JObject GetDeviceTypeReportedProperty()
|
||||
{
|
||||
return new JObject
|
||||
{
|
||||
["Name"] = this.Name,
|
||||
["Version"] = this.Version
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This data is published in the device twin, so that clients
|
||||
/// parsing the telemetry have information about the schema used,
|
||||
|
@ -113,9 +61,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
|||
{
|
||||
if (t == null)
|
||||
{
|
||||
log.Error("The device type contains an invalid message definition",
|
||||
log.Error("The device model contains an invalid message definition",
|
||||
() => new { this.Id, this.Name });
|
||||
throw new InvalidConfigurationException("The device type contains an invalid message definition");
|
||||
throw new InvalidConfigurationException("The device model contains an invalid message definition");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(t.MessageSchema.Name))
|
||||
|
@ -151,56 +99,54 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
|||
return result;
|
||||
}
|
||||
|
||||
public class InternalState
|
||||
public class StateSimulation
|
||||
{
|
||||
public Dictionary<string, object> Initial { get; set; }
|
||||
public TimeSpan SimulationInterval { get; set; }
|
||||
public Script SimulationScript { get; set; }
|
||||
public Dictionary<string, object> InitialState { get; set; }
|
||||
public Script Script { get; set; }
|
||||
|
||||
public InternalState()
|
||||
public StateSimulation()
|
||||
{
|
||||
this.Initial = new Dictionary<string, object>();
|
||||
this.SimulationInterval = TimeSpan.Zero;
|
||||
this.SimulationScript = new Script();
|
||||
this.InitialState = new Dictionary<string, object>();
|
||||
this.Script = new Script();
|
||||
}
|
||||
}
|
||||
|
||||
public class DeviceTypeMessage
|
||||
public class DeviceModelMessage
|
||||
{
|
||||
public TimeSpan Interval { get; set; }
|
||||
public string MessageTemplate { get; set; }
|
||||
public DeviceTypeMessageSchema MessageSchema { get; set; }
|
||||
public DeviceModelMessageSchema MessageSchema { get; set; }
|
||||
|
||||
public DeviceTypeMessage()
|
||||
public DeviceModelMessage()
|
||||
{
|
||||
this.Interval = TimeSpan.Zero;
|
||||
this.MessageTemplate = string.Empty;
|
||||
this.MessageSchema = new DeviceTypeMessageSchema();
|
||||
this.MessageSchema = new DeviceModelMessageSchema();
|
||||
}
|
||||
}
|
||||
|
||||
public class DeviceTypeMessageSchema
|
||||
public class DeviceModelMessageSchema
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public DeviceTypeMessageSchemaFormat Format { get; set; }
|
||||
public IDictionary<string, DeviceTypeMessageSchemaType> Fields { get; set; }
|
||||
public DeviceModelMessageSchemaFormat Format { get; set; }
|
||||
public IDictionary<string, DeviceModelMessageSchemaType> Fields { get; set; }
|
||||
|
||||
public DeviceTypeMessageSchema()
|
||||
public DeviceModelMessageSchema()
|
||||
{
|
||||
this.Name = string.Empty;
|
||||
this.Format = DeviceTypeMessageSchemaFormat.JSON;
|
||||
this.Fields = new Dictionary<string, DeviceTypeMessageSchemaType>();
|
||||
this.Format = DeviceModelMessageSchemaFormat.JSON;
|
||||
this.Fields = new Dictionary<string, DeviceModelMessageSchemaType>();
|
||||
}
|
||||
}
|
||||
|
||||
public enum DeviceTypeMessageSchemaFormat
|
||||
public enum DeviceModelMessageSchemaFormat
|
||||
{
|
||||
Binary = 0,
|
||||
Text = 10,
|
||||
JSON = 20
|
||||
}
|
||||
|
||||
public enum DeviceTypeMessageSchemaType
|
||||
public enum DeviceModelMessageSchemaType
|
||||
{
|
||||
Object = 0,
|
||||
Binary = 10,
|
|
@ -7,7 +7,7 @@ using Newtonsoft.Json.Linq;
|
|||
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
||||
{
|
||||
public class DeviceTwinServiceModel
|
||||
public class DeviceTwin
|
||||
{
|
||||
// Simulated devices are marked with a tag "IsSimulated = Y"
|
||||
public const string SimulatedTagKey = "IsSimulated";
|
||||
|
@ -20,7 +20,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
|||
public Dictionary<string, JToken> ReportedProperties { get; set; }
|
||||
public Dictionary<string, JToken> Tags { get; set; }
|
||||
|
||||
public DeviceTwinServiceModel(Twin twin)
|
||||
public DeviceTwin(Twin twin)
|
||||
{
|
||||
if (twin != null)
|
||||
{
|
|
@ -10,17 +10,17 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
|||
public string Etag { get; set; }
|
||||
public string Id { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public IList<DeviceTypeRef> DeviceTypes { get; set; }
|
||||
public IList<DeviceModelRef> DeviceModels { get; set; }
|
||||
public DateTimeOffset Created { get; set; }
|
||||
public DateTimeOffset Modified { get; set; }
|
||||
public long Version { get; set; }
|
||||
|
||||
public Simulation()
|
||||
{
|
||||
this.DeviceTypes = new List<DeviceTypeRef>();
|
||||
this.DeviceModels = new List<DeviceModelRef>();
|
||||
}
|
||||
|
||||
public class DeviceTypeRef
|
||||
public class DeviceModelRef
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public int Count { get; set; }
|
||||
|
|
|
@ -6,8 +6,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime
|
|||
{
|
||||
public interface IServicesConfig
|
||||
{
|
||||
string DeviceTypesFolder { get; set; }
|
||||
string DeviceTypesScriptsFolder { get; set; }
|
||||
string DeviceModelsFolder { get; set; }
|
||||
string DeviceModelsScriptsFolder { get; set; }
|
||||
string IoTHubConnString { get; set; }
|
||||
string StorageAdapterApiUrl { get; set; }
|
||||
int StorageAdapterApiTimeout { get; set; }
|
||||
|
@ -19,13 +19,13 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime
|
|||
private string dtf = string.Empty;
|
||||
private string dtbf = string.Empty;
|
||||
|
||||
public string DeviceTypesFolder
|
||||
public string DeviceModelsFolder
|
||||
{
|
||||
get { return this.dtf; }
|
||||
set { this.dtf = this.NormalizePath(value); }
|
||||
}
|
||||
|
||||
public string DeviceTypesScriptsFolder
|
||||
public string DeviceModelsScriptsFolder
|
||||
{
|
||||
get { return this.dtbf; }
|
||||
set { this.dtbf = this.NormalizePath(value); }
|
||||
|
|
|
@ -5,38 +5,6 @@
|
|||
<AssemblyName>Microsoft.Azure.IoTSolutions.DeviceSimulation.Services</AssemblyName>
|
||||
<RootNamespace>Microsoft.Azure.IoTSolutions.DeviceSimulation.Services</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Update="data\DeviceTypes\chiller.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\DeviceTypes\drone.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\DeviceTypes\elevator.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\DeviceTypes\room.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\DeviceTypes\truck.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\DeviceTypes\Scripts\chiller-state.js">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\DeviceTypes\Scripts\drone-state.js">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\DeviceTypes\Scripts\elevator-state.js">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\DeviceTypes\Scripts\room-state.js">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\DeviceTypes\Scripts\truck-state.js">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="jint" Version="2.10.4" />
|
||||
<PackageReference Include="Microsoft.Azure.Devices" Version="1.3.0" />
|
||||
|
@ -44,8 +12,65 @@
|
|||
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Threading.Thread, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<HintPath>..\..\..\..\.nuget\packages\system.threading.thread\4.3.0\ref\netstandard1.3\System.Threading.Thread.dll</HintPath>
|
||||
</Reference>
|
||||
<None Update="data\devicemodels\chiller-01.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\chiller-02.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\elevator-01.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\elevator-02.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\engine-01.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\engine-02.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\prototype-01.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\prototype-02.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\truck-01.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\truck-02.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\chiller-01-state.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\chiller-02-state.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\elevator-01-state.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\elevator-02-state.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\engine-01-state.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\engine-02-state.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\prototype-01-state.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\prototype-02-state.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\truck-01-state.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\truck-02-state.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -29,7 +29,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
IServicesConfig config,
|
||||
ILogger logger)
|
||||
{
|
||||
this.folder = config.DeviceTypesScriptsFolder;
|
||||
this.folder = config.DeviceModelsScriptsFolder;
|
||||
this.log = logger;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,18 +24,18 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
{
|
||||
private const string StorageCollection = "simulations";
|
||||
private const string SimulationId = "1";
|
||||
private const int DevicesPerTypeInDefaultTemplate = 2;
|
||||
private const int DevicesPerModelInDefaultTemplate = 2;
|
||||
|
||||
private readonly IDeviceTypes deviceTypes;
|
||||
private readonly IDeviceModels deviceModels;
|
||||
private readonly IStorageAdapterClient storage;
|
||||
private readonly ILogger log;
|
||||
|
||||
public Simulations(
|
||||
IDeviceTypes deviceTypes,
|
||||
IDeviceModels deviceModels,
|
||||
IStorageAdapterClient storage,
|
||||
ILogger logger)
|
||||
{
|
||||
this.deviceTypes = deviceTypes;
|
||||
this.deviceModels = deviceModels;
|
||||
this.storage = storage;
|
||||
this.log = logger;
|
||||
}
|
||||
|
@ -88,25 +88,27 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
// Create default simulation
|
||||
if (!string.IsNullOrEmpty(template) && template.ToLowerInvariant() == "default")
|
||||
{
|
||||
var types = this.deviceTypes.GetList();
|
||||
simulation.DeviceTypes = new List<Models.Simulation.DeviceTypeRef>();
|
||||
var types = this.deviceModels.GetList();
|
||||
simulation.DeviceModels = new List<Models.Simulation.DeviceModelRef>();
|
||||
foreach (var type in types)
|
||||
{
|
||||
simulation.DeviceTypes.Add(new Models.Simulation.DeviceTypeRef
|
||||
simulation.DeviceModels.Add(new Models.Simulation.DeviceModelRef
|
||||
{
|
||||
Id = type.Id,
|
||||
Count = DevicesPerTypeInDefaultTemplate
|
||||
Count = DevicesPerModelInDefaultTemplate
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Note: using UpdateAsync because the service generates the ID
|
||||
await this.storage.UpdateAsync(
|
||||
var result = await this.storage.UpdateAsync(
|
||||
StorageCollection,
|
||||
SimulationId,
|
||||
JsonConvert.SerializeObject(simulation),
|
||||
"*");
|
||||
|
||||
simulation.Etag = result.ETag;
|
||||
|
||||
return simulation;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -15,6 +17,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
{
|
||||
public interface IStorageAdapterClient
|
||||
{
|
||||
Task<Tuple<bool, string>> PingAsync();
|
||||
Task<ValueListApiModel> GetAllAsync(string collectionId);
|
||||
Task<ValueApiModel> GetAsync(string collectionId, string key);
|
||||
Task<ValueApiModel> CreateAsync(string collectionId, string value);
|
||||
|
@ -29,7 +32,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
private const bool AllowInsecureSSLServer = true;
|
||||
|
||||
private readonly IHttpClient httpClient;
|
||||
private readonly ILogger logger;
|
||||
private readonly ILogger log;
|
||||
private readonly string serviceUri;
|
||||
private readonly int timeout;
|
||||
|
||||
|
@ -39,11 +42,39 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
ILogger logger)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
this.logger = logger;
|
||||
this.log = logger;
|
||||
this.serviceUri = config.StorageAdapterApiUrl;
|
||||
this.timeout = config.StorageAdapterApiTimeout;
|
||||
}
|
||||
|
||||
public async Task<Tuple<bool, string>> PingAsync()
|
||||
{
|
||||
var status = false;
|
||||
var message = "";
|
||||
|
||||
try
|
||||
{
|
||||
var response = await httpClient.GetAsync(this.PrepareRequest($"status"));
|
||||
if (response.IsError)
|
||||
{
|
||||
message = "Status code: " + response.StatusCode;
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(response.Content);
|
||||
message = data["Status"].ToString();
|
||||
status = data["Status"].ToString().StartsWith("OK:");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.log.Error("Storage adapter check failed", () => new { e });
|
||||
message = e.Message;
|
||||
}
|
||||
|
||||
return new Tuple<bool, string>(status, message);
|
||||
}
|
||||
|
||||
public async Task<ValueListApiModel> GetAllAsync(string collectionId)
|
||||
{
|
||||
var response = await httpClient.GetAsync(
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
For more information about simulated device types
|
||||
[see the wiki](https://github.com/Azure/device-simulation-dotnet/wiki/Device-Types).
|
|
@ -1,2 +0,0 @@
|
|||
For more information about simulated device types
|
||||
[see the wiki](https://github.com/Azure/device-simulation-dotnet/wiki/Device-Types).
|
|
@ -1,28 +0,0 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/**
|
||||
* Simulate a Drone
|
||||
*/
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device type and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Fly in a circle, once a minute
|
||||
var now = new Date(context.currentTime);
|
||||
var twopi = (Math.PI * 2);
|
||||
var rad = (now.getSeconds() / 60) * twopi;
|
||||
|
||||
return {
|
||||
vertical_speed: 0.0,
|
||||
horizontal_speed: 10.0,
|
||||
compass: 360 * rad / twopi,
|
||||
latitude: 44.898556 + (0.01 * Math.sin(rad)),
|
||||
longitude: 10.043592 + (0.01 * Math.sin(rad)),
|
||||
altitude: 10.0
|
||||
};
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/**
|
||||
* Simulate an Elevator that moves up and down, starting from
|
||||
* floor 1, up to floor 10, and back.
|
||||
*
|
||||
* Example: elevator 'Simulated.Elevator.1' travels to the 20th floor.
|
||||
*/
|
||||
|
||||
// Default state, just in case main() is called with an empty previousState.
|
||||
var state = {
|
||||
floor: 1,
|
||||
direction: "up"
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device type and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
|
||||
// Make sure there is a valid direction set
|
||||
if (typeof(state.direction) === "undefined" || state.direction === null
|
||||
|| (state.direction !== "up" && state.direction !== "down")) {
|
||||
state.direction = "up";
|
||||
}
|
||||
|
||||
// Floors in the building
|
||||
var floors = 10;
|
||||
|
||||
// Example: elevator 'Simulated.Elevator.1' travels to the 20th floor.
|
||||
if (context.deviceId === "Simulated.Elevator.1") floors = 20;
|
||||
|
||||
if (state.direction === "up") state.floor++;
|
||||
if (state.direction === "down") state.floor--;
|
||||
|
||||
if (state.floor < 1) {
|
||||
state.floor = 1;
|
||||
state.direction = "up";
|
||||
}
|
||||
|
||||
if (state.floor > floors) {
|
||||
state.floor = floors;
|
||||
state.direction = "down";
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the global state using data from the previous iteration.
|
||||
*
|
||||
* @param previousState The output of main() from the previous iteration
|
||||
*/
|
||||
function restoreState(previousState) {
|
||||
// If the previous state is null, force a default state
|
||||
if (typeof(previousState) !== "undefined" && previousState !== null) {
|
||||
state = previousState;
|
||||
} else {
|
||||
log("Using default state");
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/**
|
||||
* Simulate a Room with temperature increasing during the day, and decreasing
|
||||
* during the night. The humidity also varies depending on the temperature.
|
||||
*/
|
||||
|
||||
// Default state, just in case main() is called with an empty previousState.
|
||||
var state = {
|
||||
temperature: 50,
|
||||
temperature_unit: "F",
|
||||
humidity: 50,
|
||||
humidity_unit: "%",
|
||||
lights_on: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device type and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
|
||||
// Parse the date
|
||||
var now = new Date(context.currentTime);
|
||||
var hourOfTheDay = now.getHours();
|
||||
|
||||
/**
|
||||
* The temperature increases for 12 hours, to a maximum of 90F,
|
||||
* and decreases for 12 hours, to a minimum of 50F.
|
||||
*
|
||||
* The humidity is indirectly correlated.
|
||||
*/
|
||||
if (hourOfTheDay >= 6 && hourOfTheDay < 18) {
|
||||
state.temperature = Math.min(state.temperature + 0.1, 90);
|
||||
} else {
|
||||
state.temperature = Math.max(state.temperature - 0.1, 50);
|
||||
}
|
||||
|
||||
/**
|
||||
* When it's hot the humidity decreases to a minimum of 0%
|
||||
* When it's cold the humidity increases to a maximum of 90%
|
||||
*/
|
||||
if (state.temperature > 90) {
|
||||
state.humidity = Math.max(0, state.humidity - 1);
|
||||
} else if (state.temperature < 70) {
|
||||
state.humidity = Math.min(100, state.humidity + 1);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the global state using data from the previous iteration.
|
||||
*
|
||||
* @param previousState The output of main() from the previous iteration
|
||||
*/
|
||||
function restoreState(previousState) {
|
||||
// If the previous state is null, force a default state
|
||||
if (typeof(previousState) !== "undefined" && previousState !== null) {
|
||||
state = previousState;
|
||||
} else {
|
||||
log("Using default state");
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
"Id": "44ff1e0c-58e2-4a11-9fa0-1a29b3ebe564",
|
||||
"Version": "0.0.1",
|
||||
"Name": "Chiller",
|
||||
"Description": "Simulated Chiller with temperature and humidity sensors",
|
||||
"Protocol": "AMQP",
|
||||
"DeviceState": {
|
||||
"Initial": {
|
||||
"temperature": 50.5,
|
||||
"temperature_unit": "F",
|
||||
"humidity": 50,
|
||||
"voltage": 110.0,
|
||||
"power": 3.0,
|
||||
"power_unit": "kW",
|
||||
"latitude": 47.642117,
|
||||
"longitude": -122.136611
|
||||
},
|
||||
"SimulationInterval": "00:00:15",
|
||||
"SimulationScript": {
|
||||
"Type": "javascript",
|
||||
"Path": "chiller-state.js"
|
||||
}
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:15",
|
||||
"MessageTemplate":
|
||||
"{\"temperature\": ${temperature},\"temperature_unit\":\"{temperature_unit}\",\"humidity\": ${humidity},\"humidity_unit\":\"%\",\"voltage\": ${voltage},\"voltage_unit\":\"V\",\"power\": ${power},\"power_unit\":\"${power_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "chiller;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"temperature": "double",
|
||||
"humidity": "integer",
|
||||
"voltage": "double",
|
||||
"power": "double",
|
||||
"temperature_unit": "text",
|
||||
"humidity_unit": "text",
|
||||
"voltage_unit": "text",
|
||||
"power_unit": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"CloudToDeviceMethods": {
|
||||
"Reboot": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"Upgrade": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
"Id": "314d736a-022d-4ca1-aa9c-5da9c872d5da",
|
||||
"Version": "0.0.1",
|
||||
"Name": "Drone",
|
||||
"Description": "Simulated Drone",
|
||||
"Protocol": "AMQP",
|
||||
"DeviceState": {
|
||||
"Initial": {
|
||||
"vertical_speed": 0.0,
|
||||
"horizontal_speed": 0.0,
|
||||
"compass": 0,
|
||||
"latitude": 0.0,
|
||||
"longitude": 0.0,
|
||||
"altitude": 0.0
|
||||
},
|
||||
"SimulationInterval": "00:00:01",
|
||||
"SimulationScript": {
|
||||
"Type": "javascript",
|
||||
"Path": "drone-state.js"
|
||||
}
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:02",
|
||||
"MessageTemplate":
|
||||
"{\"v_speed\":${vertical_speed},\"h_speed\":${horizontal_speed},\"compass\":${compass},\"latitude\":${latitude},\"longitude\":${longitude}}",
|
||||
"MessageSchema": {
|
||||
"Name": "Drone;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"v_speed": "double",
|
||||
"h_speed": "double",
|
||||
"compass": "double",
|
||||
"latitude": "double",
|
||||
"longitude": "double"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
"Id": "8dd272c0-514e-4a87-a1ad-be12a0b2809c",
|
||||
"Version": "0.0.1",
|
||||
"Name": "Elevator",
|
||||
"Description": "Simulated Elevator with floor number sensor",
|
||||
"Protocol": "HTTP",
|
||||
"DeviceState": {
|
||||
"Initial": {
|
||||
"floor": 1,
|
||||
"latitude": 47.642117,
|
||||
"longitude": -122.136611
|
||||
},
|
||||
"SimulationInterval": "00:00:15",
|
||||
"SimulationScript": {
|
||||
"Type": "javascript",
|
||||
"Path": "elevator-state.js"
|
||||
}
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:01",
|
||||
"MessageTemplate": "{\"current_floor\": ${floor}}",
|
||||
"MessageSchema": {
|
||||
"Name": "elevator;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"current_floor": "integer"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"CloudToDeviceMethods": {
|
||||
"Start": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
"Id": "333c0182-16e0-41d7-9864-0e602d0588e6",
|
||||
"Version": "0.0.1",
|
||||
"Name": "Room",
|
||||
"Description": "Simulated Room with multiple sensors",
|
||||
"Protocol": "MQTT",
|
||||
"DeviceState": {
|
||||
"Initial": {
|
||||
"temperature": 50.5,
|
||||
"temperature_unit": "F",
|
||||
"humidity": 50,
|
||||
"humidity_unit": "%",
|
||||
"lights_on": false,
|
||||
"latitude": 47.642117,
|
||||
"longitude": -122.136611
|
||||
},
|
||||
"SimulationInterval": "00:00:05",
|
||||
"SimulationScript": {
|
||||
"Type": "javascript",
|
||||
"Path": "room-state.js"
|
||||
}
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate":
|
||||
"{\"t\":${temperature},\"t_unit\":\"${temperature_unit}\",\"h\":\"${humidity}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "RoomComfort;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"t": "double",
|
||||
"t_unit": "text",
|
||||
"h": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:01:00",
|
||||
"MessageTemplate": "{\"lights\":${lights_on}}",
|
||||
"MessageSchema": {
|
||||
"Name": "RoomLights;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"lights": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"CloudToDeviceMethods": {
|
||||
"TurnLightsOn": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"TurnLightsOff": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
"Id": "c84aaaac-0ba0-4e52-a103-ebb8a9b7f8f9",
|
||||
"Version": "0.0.1",
|
||||
"Name": "Truck",
|
||||
"Description": "Simulated Truck with speed and geolocation sensors",
|
||||
"Protocol": "MQTT",
|
||||
"DeviceState": {
|
||||
"Initial": {
|
||||
"latitude": 44.898556,
|
||||
"longitude": 10.043592,
|
||||
"speed": 10
|
||||
},
|
||||
"SimulationInterval": "00:00:05",
|
||||
"SimulationScript": {
|
||||
"Type": "javascript",
|
||||
"Path": "truck-state.js"
|
||||
}
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate":
|
||||
"{\"latitude\":${latitude},\"longitude\":${longitude},\"speed\":${speed},\"speed_unit\":\"mph\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "truck;v2",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"latitude": "double",
|
||||
"longitude": "double",
|
||||
"speed": "integer",
|
||||
"speed_unit": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"CloudToDeviceMethods": {
|
||||
"GoDownOneFloor": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"GoUpOneFloor": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
For more information about simulated device models
|
||||
[see the wiki](https://github.com/Azure/device-simulation-dotnet/wiki/Device-Models).
|
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
"Id": "chiller-01",
|
||||
"Version": "0.0.1",
|
||||
"Name": "Chiller",
|
||||
"Description": "Chiller with external temperature, humidity and pressure sensors.",
|
||||
"Protocol": "MQTT",
|
||||
"Simulation": {
|
||||
"InitialState": {
|
||||
"online": true,
|
||||
"temperature": 75.0,
|
||||
"temperature_unit": "F",
|
||||
"humidity": 70.0,
|
||||
"humidity_unit": "%",
|
||||
"pressure": 150.0,
|
||||
"pressure_unit": "psig"
|
||||
},
|
||||
"Script": {
|
||||
"Type": "javascript",
|
||||
"Path": "chiller-01-state.js",
|
||||
"Interval": "00:00:05"
|
||||
}
|
||||
},
|
||||
"Properties": {
|
||||
"Type": "Chiller",
|
||||
"Firmware": "1.0.0",
|
||||
"FirmwareUpdateStatus": "",
|
||||
"Location": "Building 43",
|
||||
"Latitude": 47.638928,
|
||||
"Longitude": -122.13476
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "chiller-temperature;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"temperature": "double",
|
||||
"temperature_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"humidity\":${humidity},\"humidity_unit\":\"${humidity_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "chiller-humidity;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"humidity": "double",
|
||||
"humidity_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"pressure\":${pressure},\"pressure_unit\":\"${pressure_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "chiller-pressure;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"pressure": "double",
|
||||
"pressure_unit": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"CloudToDeviceMethods": {
|
||||
"Reboot": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"FirmwareUpdate": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"EmergencyValveRelease": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"IncreasePressure": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
"Id": "chiller-02",
|
||||
"Version": "0.0.1",
|
||||
"Name": "Faulty Chiller",
|
||||
"Description": "Faulty chiller with wrong pressure sensor. Pressure too high.",
|
||||
"Protocol": "AMQP",
|
||||
"Simulation": {
|
||||
"InitialState": {
|
||||
"online": true,
|
||||
"temperature": 75.0,
|
||||
"temperature_unit": "F",
|
||||
"humidity": 70.0,
|
||||
"humidity_unit": "%",
|
||||
"pressure": 250.0,
|
||||
"pressure_unit": "psig"
|
||||
},
|
||||
"Script": {
|
||||
"Type": "javascript",
|
||||
"Path": "chiller-02-state.js",
|
||||
"Interval": "00:00:05"
|
||||
}
|
||||
},
|
||||
"Properties": {
|
||||
"Type": "Chiller",
|
||||
"Firmware": "1.0.0",
|
||||
"FirmwareUpdateStatus": "",
|
||||
"Location": "Building 92",
|
||||
"Latitude": 47.642358,
|
||||
"Longitude": -122.137156
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "chiller-temperature;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"temperature": "double",
|
||||
"temperature_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"humidity\":${humidity},\"humidity_unit\":\"${humidity_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "chiller-humidity;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"humidity": "double",
|
||||
"humidity_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"pressure\":${pressure},\"pressure_unit\":\"${pressure_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "chiller-pressure;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"pressure": "double",
|
||||
"pressure_unit": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"CloudToDeviceMethods": {
|
||||
"Reboot": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"FirmwareUpdate": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"EmergencyValveRelease": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"IncreasePressure": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
"Id": "elevator-01",
|
||||
"Version": "0.0.1",
|
||||
"Name": "Elevator",
|
||||
"Description": "Elevator with floor, vibration and temperature sensors.",
|
||||
"Protocol": "AMQP",
|
||||
"Simulation": {
|
||||
"InitialState": {
|
||||
"online": true,
|
||||
"floor": 1,
|
||||
"vibration": 10.0,
|
||||
"vibration_unit": "mm",
|
||||
"temperature": 75.0,
|
||||
"temperature_unit": "F"
|
||||
},
|
||||
"Script": {
|
||||
"Type": "javascript",
|
||||
"Path": "elevator-01-state.js",
|
||||
"Interval": "00:00:05"
|
||||
}
|
||||
},
|
||||
"Properties": {
|
||||
"Type": "Elevator",
|
||||
"Location": "Building 40",
|
||||
"Latitude": 47.636369,
|
||||
"Longitude": -122.133132
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"floor\":${floor}}",
|
||||
"MessageSchema": {
|
||||
"Name": "elevator-floor;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"floor": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"vibration\":${vibration},\"vibration_unit\":\"${vibration_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "elevator-vibration;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"vibration": "double",
|
||||
"vibration_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "elevator-temperature;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"temperature": "double",
|
||||
"temperature_unit": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"CloudToDeviceMethods": {
|
||||
"StopElevator": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"StartElevator": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
"Id": "elevator-02",
|
||||
"Version": "0.0.1",
|
||||
"Name": "Elevator",
|
||||
"Description": "Elevator with floor, vibration and temperature sensors. Elevator blocked on 4th floor.",
|
||||
"Protocol": "AMQP",
|
||||
"Simulation": {
|
||||
"InitialState": {
|
||||
"online": true,
|
||||
"floor": 4,
|
||||
"vibration": 10.0,
|
||||
"vibration_unit": "mm",
|
||||
"temperature": 75.0,
|
||||
"temperature_unit": "F"
|
||||
},
|
||||
"Script": {
|
||||
"Type": "javascript",
|
||||
"Path": "elevator-02-state.js",
|
||||
"Interval": "00:00:05"
|
||||
}
|
||||
},
|
||||
"Properties": {
|
||||
"Type": "Elevator",
|
||||
"Location": "Building 17",
|
||||
"Latitude": 47.643786,
|
||||
"Longitude": -122.128047
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"floor\":${floor}}",
|
||||
"MessageSchema": {
|
||||
"Name": "elevator-floor;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"floor": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"vibration\":${vibration},\"vibration_unit\":\"${vibration_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "elevator-vibration;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"vibration": "double",
|
||||
"vibration_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "elevator-temperature;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"temperature": "double",
|
||||
"temperature_unit": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"CloudToDeviceMethods": {
|
||||
"StopElevator": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"StartElevator": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
"Id": "engine-01",
|
||||
"Version": "0.0.1",
|
||||
"Name": "Engine",
|
||||
"Description": "Engine with fuel level, coolant and vibration sensors",
|
||||
"Protocol": "AMQP",
|
||||
"Simulation": {
|
||||
"InitialState": {
|
||||
"online": true,
|
||||
"fuellevel": 70.0,
|
||||
"fuellevel_unit": "Gal",
|
||||
"coolant": 7500.0,
|
||||
"coolant_unit": "ohm",
|
||||
"vibration": 10.0,
|
||||
"vibration_unit": "mm"
|
||||
},
|
||||
"Script": {
|
||||
"Type": "javascript",
|
||||
"Path": "engine-01-state.js",
|
||||
"Interval": "00:00:05"
|
||||
}
|
||||
},
|
||||
"Properties": {
|
||||
"Type": "Engine",
|
||||
"Location": "Factory 1",
|
||||
"Latitude": 47.65358,
|
||||
"Longitude": -122.133545
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"fuellevel\":${fuellevel},\"fuellevel_unit\":\"${fuellevel_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "engine-fuellevel;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"fuellevel": "double",
|
||||
"fuellevel_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"coolant\":${coolant},\"coolant_unit\":\"${coolant_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "engine-coolant;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"coolant": "double",
|
||||
"coolant_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:03",
|
||||
"MessageTemplate": "{\"vibration\":${vibration},\"vibration_unit\":\"${vibration_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "engine-vibration;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"vibration": "double",
|
||||
"vibration_unit": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"CloudToDeviceMethods": {
|
||||
"Restart": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"EmptyTank": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"FillTank": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
"Id": "engine-02",
|
||||
"Version": "0.0.1",
|
||||
"Name": "Faulty Engine",
|
||||
"Description": "Engine with fuel level, coolant and vibration sensors",
|
||||
"Protocol": "AMQP",
|
||||
"Simulation": {
|
||||
"InitialState": {
|
||||
"online": true,
|
||||
"fuellevel": 0.0,
|
||||
"fuellevel_unit": "Gal",
|
||||
"coolant": 7500.0,
|
||||
"coolant_unit": "ohm",
|
||||
"vibration": 0.0,
|
||||
"vibration_unit": "mm"
|
||||
},
|
||||
"Script": {
|
||||
"Type": "javascript",
|
||||
"Path": "engine-02-state.js",
|
||||
"Interval": "00:00:05"
|
||||
}
|
||||
},
|
||||
"Properties": {
|
||||
"Type": "Engine",
|
||||
"Location": "Factory 1",
|
||||
"Latitude": 47.583589,
|
||||
"Longitude": -122.13067
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"fuellevel\":${fuellevel},\"fuellevel_unit\":\"${fuellevel_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "engine-fuellevel;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"fuellevel": "double",
|
||||
"fuellevel_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"coolant\":${coolant},\"coolant_unit\":\"${coolant_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "engine-coolant;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"coolant": "double",
|
||||
"coolant_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:03",
|
||||
"MessageTemplate": "{\"vibration\":${vibration},\"vibration_unit\":\"${vibration_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "engine-vibration;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"vibration": "double",
|
||||
"vibration_unit": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"CloudToDeviceMethods": {
|
||||
"Restart": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"EmptyTank": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"FillTank": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
"Id": "prototype-01",
|
||||
"Version": "0.0.1",
|
||||
"Name": "Prototyping Device",
|
||||
"Description": "Prototyping device with temperature, pressure and GPS.",
|
||||
"Protocol": "AMQP",
|
||||
"Simulation": {
|
||||
"InitialState": {
|
||||
"online": true,
|
||||
"temperature": 65.0,
|
||||
"temperature_unit": "F",
|
||||
"pressure": 150.0,
|
||||
"pressure_unit": "psig",
|
||||
"latitude": 47.612514,
|
||||
"longitude": -122.204184,
|
||||
"moving": false
|
||||
},
|
||||
"Script": {
|
||||
"Type": "javascript",
|
||||
"Path": "prototype-01-state.js",
|
||||
"Interval": "00:00:05"
|
||||
}
|
||||
},
|
||||
"Properties": {
|
||||
"Type": "Prototyping",
|
||||
"Firmware": "1.0.0",
|
||||
"FirmwareUpdateStatus": "",
|
||||
"Location": "Field",
|
||||
"Latitude": 47.612514,
|
||||
"Longitude": -122.204184
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "prototype-temperature;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"temperature": "double",
|
||||
"temperature_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"pressure\":${pressure},\"pressure_unit\":\"${pressure_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "prototype-pressure;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"pressure": "double",
|
||||
"pressure_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:03",
|
||||
"MessageTemplate": "{\"latitude\":${latitude},\"longitude\":${longitude}}",
|
||||
"MessageSchema": {
|
||||
"Name": "prototype-geolocation;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"latitude": "double",
|
||||
"longitude": "double"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"CloudToDeviceMethods": {
|
||||
"Reboot": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"UpdateFirmware": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"StartMoving": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"StopMoving": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"DecreaseTemperature": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"IncreaseTemperature": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
"Id": "prototype-02",
|
||||
"Version": "0.0.1",
|
||||
"Name": "Faulty Prototyping Device",
|
||||
"Description": "Prototyping device with temperature, pressure and GPS. Device is moving.",
|
||||
"Protocol": "AMQP",
|
||||
"Simulation": {
|
||||
"InitialState": {
|
||||
"online": true,
|
||||
"temperature": 85.0,
|
||||
"temperature_unit": "F",
|
||||
"pressure": 150.0,
|
||||
"pressure_unit": "psig",
|
||||
"latitude": 47.620433,
|
||||
"longitude": -122.350987
|
||||
},
|
||||
"Script": {
|
||||
"Type": "javascript",
|
||||
"Path": "prototype-02-state.js",
|
||||
"Interval": "00:00:05"
|
||||
}
|
||||
},
|
||||
"Properties": {
|
||||
"Type": "Prototyping",
|
||||
"Firmware": "1.0.0",
|
||||
"FirmwareUpdateStatus": "",
|
||||
"Location": "Field",
|
||||
"Latitude": 47.620433,
|
||||
"Longitude": -122.350987
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "prototype-temperature;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"temperature": "double",
|
||||
"temperature_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"pressure\":${pressure},\"pressure_unit\":\"${pressure_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "prototype-pressure;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"pressure": "double",
|
||||
"pressure_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:03",
|
||||
"MessageTemplate": "{\"latitude\":${latitude},\"longitude\":${longitude}}",
|
||||
"MessageSchema": {
|
||||
"Name": "prototype-geolocation;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"latitude": "double",
|
||||
"longitude": "double"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"CloudToDeviceMethods": {
|
||||
"Reboot": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"UpdateFirmware": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"DecreaseTemperature": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"IncreaseTemperature": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
For more information about simulated device models
|
||||
[see the wiki](https://github.com/Azure/device-simulation-dotnet/wiki/Device-Models).
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// Default state
|
||||
var state = {
|
||||
online: true,
|
||||
temperature: 75.0,
|
||||
temperature_unit: "F",
|
||||
humidity: 70.0,
|
||||
humidity_unit: "%",
|
||||
pressure: 250.0,
|
||||
pressure_unit: "psig"
|
||||
};
|
||||
|
||||
/**
|
||||
* Restore the global state using data from the previous iteration.
|
||||
*
|
||||
* @param previousState The output of main() from the previous iteration
|
||||
*/
|
||||
function restoreState(previousState) {
|
||||
// If the previous state is null, force a default state
|
||||
if (previousState !== undefined && previousState !== null) {
|
||||
state = previousState;
|
||||
} else {
|
||||
log("Using default state");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple formula generating a random value around the average
|
||||
* in between min and max
|
||||
*/
|
||||
function vary(avg, percentage, min, max) {
|
||||
var value = avg * (1 + ((percentage / 100) * (2 * Math.random() - 1)));
|
||||
value = Math.max(value, min);
|
||||
value = Math.min(value, max);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
|
||||
// 75 +/- 5%, Min 25, Max 100
|
||||
state.temperature = vary(75, 5, 25, 100);
|
||||
|
||||
// 70 +/- 5%, Min 2, Max 99
|
||||
state.humidity = vary(70, 5, 2, 99);
|
||||
|
||||
// 150 +/- 10%, Min 50, Max 300
|
||||
state.pressure = vary(150, 10, 50, 300);
|
||||
|
||||
return state;
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// Default state
|
||||
var state = {
|
||||
online: true,
|
||||
temperature: 75.0,
|
||||
temperature_unit: "F",
|
||||
humidity: 70.0,
|
||||
humidity_unit: "%",
|
||||
pressure: 250.0,
|
||||
pressure_unit: "psig"
|
||||
};
|
||||
|
||||
/**
|
||||
* Restore the global state using data from the previous iteration.
|
||||
*
|
||||
* @param previousState The output of main() from the previous iteration
|
||||
*/
|
||||
function restoreState(previousState) {
|
||||
// If the previous state is null, force a default state
|
||||
if (previousState !== undefined && previousState !== null) {
|
||||
state = previousState;
|
||||
} else {
|
||||
log("Using default state");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple formula generating a random value around the average
|
||||
* in between min and max
|
||||
*/
|
||||
function vary(avg, percentage, min, max) {
|
||||
var value = avg * (1 + ((percentage / 100) * (2 * Math.random() - 1)));
|
||||
value = Math.max(value, min);
|
||||
value = Math.min(value, max);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
|
||||
// 75F +/- 5%, Min 25F, Max 100F
|
||||
state.temperature = vary(75, 5, 25, 100);
|
||||
|
||||
// 70% +/- 5%, Min 2%, Max 99%
|
||||
state.humidity = vary(70, 5, 2, 99);
|
||||
|
||||
// 250 psig +/- 25%, Min 50 psig, Max 300 psig
|
||||
state.pressure = vary(250, 25, 50, 300);
|
||||
|
||||
return state;
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var floors = 15;
|
||||
|
||||
// Default state
|
||||
var state = {
|
||||
online: true,
|
||||
floor: 1,
|
||||
vibration: 10.0,
|
||||
vibration_unit: "mm",
|
||||
temperature: 75.0,
|
||||
temperature_unit: "F",
|
||||
moving: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Restore the global state using data from the previous iteration.
|
||||
*
|
||||
* @param previousState The output of main() from the previous iteration
|
||||
*/
|
||||
function restoreState(previousState) {
|
||||
// If the previous state is null, force a default state
|
||||
if (previousState !== undefined && previousState !== null) {
|
||||
state = previousState;
|
||||
} else {
|
||||
log("Using default state");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple formula generating a random value around the average
|
||||
* in between min and max
|
||||
*/
|
||||
function vary(avg, percentage, min, max) {
|
||||
var value = avg * (1 + ((percentage / 100) * (2 * Math.random() - 1)));
|
||||
value = Math.max(value, min);
|
||||
value = Math.min(value, max);
|
||||
return value;
|
||||
}
|
||||
|
||||
function varyfloor(current, min, max) {
|
||||
if (current === min) {
|
||||
return current + 1;
|
||||
}
|
||||
if (current === max) {
|
||||
return current - 1;
|
||||
}
|
||||
if (Math.random() < 0.5) {
|
||||
return current - 1;
|
||||
}
|
||||
return current + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
|
||||
if (state.moving) {
|
||||
state.floor = varyfloor(state.floor, 1, floors);
|
||||
// 10 +/- 5%, Min 0, Max 20
|
||||
state.vibration = vary(10, 5, 0, 20);
|
||||
} else {
|
||||
state.vibration = 0;
|
||||
}
|
||||
|
||||
// 75 +/- 1%, Min 25, Max 100
|
||||
state.temperature = vary(75, 1, 25, 100);
|
||||
|
||||
return state;
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var floors = 15;
|
||||
|
||||
// Default state
|
||||
var state = {
|
||||
online: true,
|
||||
floor: 4,
|
||||
vibration: 10.0,
|
||||
vibration_unit: "mm",
|
||||
temperature: 75.0,
|
||||
temperature_unit: "F",
|
||||
moving: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Restore the global state using data from the previous iteration.
|
||||
*
|
||||
* @param previousState The output of main() from the previous iteration
|
||||
*/
|
||||
function restoreState(previousState) {
|
||||
// If the previous state is null, force a default state
|
||||
if (previousState !== undefined && previousState !== null) {
|
||||
state = previousState;
|
||||
} else {
|
||||
log("Using default state");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple formula generating a random value around the average
|
||||
* in between min and max
|
||||
*/
|
||||
function vary(avg, percentage, min, max) {
|
||||
var value = avg * (1 + ((percentage / 100) * (2 * Math.random() - 1)));
|
||||
value = Math.max(value, min);
|
||||
value = Math.min(value, max);
|
||||
return value;
|
||||
}
|
||||
|
||||
function varyfloor(current, min, max) {
|
||||
if (current === min) {
|
||||
return current + 1;
|
||||
}
|
||||
if (current === max) {
|
||||
return current - 1;
|
||||
}
|
||||
if (Math.random() < 0.5) {
|
||||
return current - 1;
|
||||
}
|
||||
return current + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
|
||||
if (state.moving) {
|
||||
state.floor = varyfloor(state.floor, 1, floors);
|
||||
// 10 +/- 5%, Min 0, Max 20
|
||||
state.vibration = vary(10, 5, 0, 20);
|
||||
} else {
|
||||
state.vibration = 0;
|
||||
}
|
||||
|
||||
// 75 +/- 1%, Min 25, Max 100
|
||||
state.temperature = vary(75, 1, 25, 100);
|
||||
|
||||
return state;
|
||||
}
|
|
@ -1,37 +1,21 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/**
|
||||
* Simulate a moving Truck.
|
||||
*/
|
||||
/*global log*/
|
||||
/*jslint node: true*/
|
||||
|
||||
// Default state, just in case main() is called with an empty previousState.
|
||||
"use strict";
|
||||
|
||||
// Default state
|
||||
var state = {
|
||||
latitude: 44.898556,
|
||||
longitude: 10.043592,
|
||||
speed: 10
|
||||
online: true,
|
||||
fuellevel: 70.0,
|
||||
fuellevel_unit: "Gal",
|
||||
coolant: 7500.0,
|
||||
coolant_unit: "ohm",
|
||||
vibration: 10.0,
|
||||
vibration_unit: "mm"
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device type and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
|
||||
state.latitude -= 0.01;
|
||||
state.longitude -= 0.01;
|
||||
|
||||
if (state.latitude < 42) state.latitude = 44.898556;
|
||||
if (state.longitude < 9) state.latitude = 10.043592;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the global state using data from the previous iteration.
|
||||
*
|
||||
|
@ -39,9 +23,42 @@ function main(context, previousState) {
|
|||
*/
|
||||
function restoreState(previousState) {
|
||||
// If the previous state is null, force a default state
|
||||
if (typeof(previousState) !== "undefined" && previousState !== null) {
|
||||
if (previousState !== undefined && previousState !== null) {
|
||||
state = previousState;
|
||||
} else {
|
||||
log("Using default state");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple formula generating a random value around the average
|
||||
* in between min and max
|
||||
*/
|
||||
function vary(avg, percentage, min, max) {
|
||||
var value = avg * (1 + ((percentage / 100) * (2 * Math.random() - 1)));
|
||||
value = Math.max(value, min);
|
||||
value = Math.min(value, max);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
|
||||
// 7500 +/- 5%, Min 200, Max 10000
|
||||
state.coolant = vary(7500, 5, 200, 10000);
|
||||
|
||||
// 10 +/- 5%, Min 0, Max 20
|
||||
state.vibration = vary(10, 5, 0, 20);
|
||||
|
||||
return state;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// Default state
|
||||
var state = {
|
||||
online: true,
|
||||
fuellevel: 0.0,
|
||||
fuellevel_unit: "Gal",
|
||||
coolant: 7500.0,
|
||||
coolant_unit: "ohm",
|
||||
vibration: 10.0,
|
||||
vibration_unit: "mm"
|
||||
};
|
||||
|
||||
/**
|
||||
* Restore the global state using data from the previous iteration.
|
||||
*
|
||||
* @param previousState The output of main() from the previous iteration
|
||||
*/
|
||||
function restoreState(previousState) {
|
||||
// If the previous state is null, force a default state
|
||||
if (previousState !== undefined && previousState !== null) {
|
||||
state = previousState;
|
||||
} else {
|
||||
log("Using default state");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple formula generating a random value around the average
|
||||
* in between min and max
|
||||
*/
|
||||
function vary(avg, percentage, min, max) {
|
||||
var value = avg * (1 + ((percentage / 100) * (2 * Math.random() - 1)));
|
||||
value = Math.max(value, min);
|
||||
value = Math.min(value, max);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
|
||||
// 7500 +/- 5%, Min 200, Max 10000
|
||||
state.coolant = vary(7500, 5, 200, 10000);
|
||||
|
||||
// 10 +/- 5%, Min 0, Max 20
|
||||
if (state.fuellevel > 0) {
|
||||
state.vibration = vary(10, 5, 0, 20);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var center_latitude = 47.612514;
|
||||
var center_longitude = -122.204184;
|
||||
|
||||
// Default state
|
||||
var state = {
|
||||
online: true,
|
||||
temperature: 65.0,
|
||||
temperature_unit: "F",
|
||||
pressure: 150.0,
|
||||
pressure_unit: "psig",
|
||||
latitude: center_latitude,
|
||||
longitude: center_longitude,
|
||||
moving: false
|
||||
};
|
||||
|
||||
/**
|
||||
* Restore the global state using data from the previous iteration.
|
||||
*
|
||||
* @param previousState The output of main() from the previous iteration
|
||||
*/
|
||||
function restoreState(previousState) {
|
||||
// If the previous state is null, force a default state
|
||||
if (previousState !== undefined && previousState !== null) {
|
||||
state = previousState;
|
||||
} else {
|
||||
log("Using default state");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple formula generating a random value around the average
|
||||
* in between min and max
|
||||
*/
|
||||
function vary(avg, percentage, min, max) {
|
||||
var value = avg * (1 + ((percentage / 100) * (2 * Math.random() - 1)));
|
||||
value = Math.max(value, min);
|
||||
value = Math.min(value, max);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random geolocation at some distance (in miles)
|
||||
* from a given location
|
||||
*/
|
||||
function varylocation(latitude, longitude, distance) {
|
||||
// Convert to meters, use Earth radius, convert to radians
|
||||
var radians = (distance * 1609.344 / 6378137) * (180 / Math.PI);
|
||||
return {
|
||||
latitude: latitude + radians,
|
||||
longitude: longitude + radians / Math.cos(latitude * Math.PI / 180)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
|
||||
// 65 +/- 1%, Min 35, Max 100
|
||||
state.temperature = vary(65, 1, 35, 100);
|
||||
|
||||
// 150 +/- 5%, Min 50, Max 300
|
||||
state.pressure = vary(150, 5, 50, 300);
|
||||
|
||||
// 0.1 miles around some location
|
||||
if (state.moving) {
|
||||
var coords = varylocation(center_latitude, center_longitude, 0.1);
|
||||
state.latitude = coords.latitude;
|
||||
state.longitude = coords.longitude;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
|
@ -1,45 +1,24 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/**
|
||||
* Simulate a Chiller at with fluctuating temperature.
|
||||
*
|
||||
* Example: chiller 'Simulated.Chiller.0' has a different average temperature.
|
||||
*/
|
||||
/*global log*/
|
||||
/*jslint node: true*/
|
||||
|
||||
// Default state, just in case main() is called with an empty previousState.
|
||||
"use strict";
|
||||
|
||||
var center_latitude = 47.620433;
|
||||
var center_longitude = -122.350987;
|
||||
|
||||
// Default state
|
||||
var state = {
|
||||
temperature: 50.5,
|
||||
online: true,
|
||||
temperature: 85.0,
|
||||
temperature_unit: "F",
|
||||
humidity: 50,
|
||||
voltage: 110.0,
|
||||
power: 3.0,
|
||||
power_unit: "kW"
|
||||
pressure: 150.0,
|
||||
pressure_unit: "psig",
|
||||
latitude: center_latitude,
|
||||
longitude: center_longitude
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device type and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
|
||||
// 49...51
|
||||
state.temperature = 49 + (Math.random() * 2);
|
||||
|
||||
// Example: chiller 'Simulated.Chiller.0' has a different average temperature.
|
||||
if (context.deviceId === "Simulated.Chiller.0") {
|
||||
// 19...21
|
||||
state.temperature = 19 + (Math.random() * 2);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the global state using data from the previous iteration.
|
||||
*
|
||||
|
@ -47,9 +26,39 @@ function main(context, previousState) {
|
|||
*/
|
||||
function restoreState(previousState) {
|
||||
// If the previous state is null, force a default state
|
||||
if (typeof(previousState) !== "undefined" && previousState !== null) {
|
||||
if (previousState !== undefined && previousState !== null) {
|
||||
state = previousState;
|
||||
} else {
|
||||
log("Using default state");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple formula generating a random value around the average
|
||||
* in between min and max
|
||||
*/
|
||||
function vary(avg, percentage, min, max) {
|
||||
var value = avg * (1 + ((percentage / 100) * (2 * Math.random() - 1)));
|
||||
value = Math.max(value, min);
|
||||
value = Math.min(value, max);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
|
||||
// 85 +/- 25%, Min 35, Max 100
|
||||
state.temperature = vary(85, 25, 35, 100);
|
||||
|
||||
return state;
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var center_latitude = 47.445301;
|
||||
var center_longitude = -122.296307;
|
||||
|
||||
// Default state
|
||||
var state = {
|
||||
online: true,
|
||||
latitude: center_latitude,
|
||||
longitude: center_longitude,
|
||||
speed: 80.0,
|
||||
speed_unit: "mph",
|
||||
cargotemperature: 38.0,
|
||||
cargotemperature_unit: "F"
|
||||
};
|
||||
|
||||
/**
|
||||
* Restore the global state using data from the previous iteration.
|
||||
*
|
||||
* @param previousState The output of main() from the previous iteration
|
||||
*/
|
||||
function restoreState(previousState) {
|
||||
// If the previous state is null, force a default state
|
||||
if (previousState !== undefined && previousState !== null) {
|
||||
state = previousState;
|
||||
} else {
|
||||
log("Using default state");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple formula generating a random value around the average
|
||||
* in between min and max
|
||||
*/
|
||||
function vary(avg, percentage, min, max) {
|
||||
var value = avg * (1 + ((percentage / 100) * (2 * Math.random() - 1)));
|
||||
value = Math.max(value, min);
|
||||
value = Math.min(value, max);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random geolocation at some distance (in miles)
|
||||
* from a given location
|
||||
*/
|
||||
function varylocation(latitude, longitude, distance) {
|
||||
// Convert to meters, use Earth radius, convert to radians
|
||||
var radians = (distance * 1609.344 / 6378137) * (180 / Math.PI);
|
||||
return {
|
||||
latitude: latitude + radians,
|
||||
longitude: longitude + radians / Math.cos(latitude * Math.PI / 180)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
|
||||
// 0.1 miles around some location
|
||||
var coords = varylocation(center_latitude, center_longitude, 0.1);
|
||||
state.latitude = coords.latitude;
|
||||
state.longitude = coords.longitude;
|
||||
|
||||
// 30 +/- 5%, Min 0, Max 80
|
||||
state.speed = vary(30, 5, 0, 80);
|
||||
|
||||
// 38 +/- 1%, Min 35, Max 50
|
||||
state.cargotemperature = vary(38, 1, 35, 50);
|
||||
|
||||
return state;
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var center_latitude = 47.25433;
|
||||
var center_longitude = -121.177075;
|
||||
|
||||
// Default state
|
||||
var state = {
|
||||
online: true,
|
||||
latitude: center_latitude,
|
||||
longitude: center_longitude,
|
||||
speed: 80.0,
|
||||
speed_unit: "mph",
|
||||
cargotemperature: 49.0,
|
||||
cargotemperature_unit: "F"
|
||||
};
|
||||
|
||||
/**
|
||||
* Restore the global state using data from the previous iteration.
|
||||
*
|
||||
* @param previousState The output of main() from the previous iteration
|
||||
*/
|
||||
function restoreState(previousState) {
|
||||
// If the previous state is null, force a default state
|
||||
if (previousState !== undefined && previousState !== null) {
|
||||
state = previousState;
|
||||
} else {
|
||||
log("Using default state");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple formula generating a random value around the average
|
||||
* in between min and max
|
||||
*/
|
||||
function vary(avg, percentage, min, max) {
|
||||
var value = avg * (1 + ((percentage / 100) * (2 * Math.random() - 1)));
|
||||
value = Math.max(value, min);
|
||||
value = Math.min(value, max);
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random geolocation at some distance (in miles)
|
||||
* from a given location
|
||||
*/
|
||||
function varylocation(latitude, longitude, distance) {
|
||||
// Convert to meters, use Earth radius, convert to radians
|
||||
var radians = (distance * 1609.344 / 6378137) * (180 / Math.PI);
|
||||
return {
|
||||
latitude: latitude + radians,
|
||||
longitude: longitude + radians / Math.cos(latitude * Math.PI / 180)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
|
||||
// 0.1 miles around some location
|
||||
var coords = varylocation(center_latitude, center_longitude, 0.1);
|
||||
state.latitude = coords.latitude;
|
||||
state.longitude = coords.longitude;
|
||||
|
||||
// 30 +/- 5%, Min 0, Max 80
|
||||
state.speed = vary(30, 5, 0, 80);
|
||||
|
||||
// 49 +/- 1%, Min 35, Max 50
|
||||
state.cargotemperature = vary(49, 1, 35, 50);
|
||||
|
||||
return state;
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
"Id": "truck-01",
|
||||
"Version": "0.0.1",
|
||||
"Name": "Truck",
|
||||
"Description": "Truck with GPS, speed and cargo temperature sensors",
|
||||
"Protocol": "AMQP",
|
||||
"Simulation": {
|
||||
"InitialState": {
|
||||
"online": true,
|
||||
"latitude": 47.445301,
|
||||
"longitude": -122.296307,
|
||||
"speed": 30.0,
|
||||
"speed_unit": "mph",
|
||||
"cargotemperature": 38.0,
|
||||
"cargotemperature_unit": "F"
|
||||
},
|
||||
"Script": {
|
||||
"Type": "javascript",
|
||||
"Path": "truck-01-state.js",
|
||||
"Interval": "00:00:05"
|
||||
}
|
||||
},
|
||||
"Properties": {
|
||||
"Type": "Truck",
|
||||
"Location": "Field",
|
||||
"Latitude": 47.445301,
|
||||
"Longitude": -122.296307
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:03",
|
||||
"MessageTemplate": "{\"latitude\":${latitude},\"longitude\":${longitude}}",
|
||||
"MessageSchema": {
|
||||
"Name": "truck-geolocation;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"latitude": "double",
|
||||
"longitude": "double"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"speed\":${speed},\"speed_unit\":\"${speed_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "truck-speed;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"speed": "double",
|
||||
"speed_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"cargotemperature\":${cargotemperature},\"cargotemperature_unit\":\"${cargotemperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "truck-cargotemperature;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"cargotemperature": "double",
|
||||
"cargotemperature_unit": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"CloudToDeviceMethods": {
|
||||
"DecreaseCargoTemperature": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"IncreaseCargoTemperature": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
"Id": "truck-02",
|
||||
"Version": "0.0.1",
|
||||
"Name": "Faulty Truck",
|
||||
"Description": "Truck with GPS, speed and cargo temperature sensors. Temperature is higher than normal.",
|
||||
"Protocol": "AMQP",
|
||||
"Simulation": {
|
||||
"InitialState": {
|
||||
"online": true,
|
||||
"latitude": 47.25433,
|
||||
"longitude": -121.177075,
|
||||
"speed": 30.0,
|
||||
"speed_unit": "mph",
|
||||
"cargotemperature": 49.0,
|
||||
"cargotemperature_unit": "F"
|
||||
},
|
||||
"Script": {
|
||||
"Type": "javascript",
|
||||
"Path": "truck-02-state.js",
|
||||
"Interval": "00:00:05"
|
||||
}
|
||||
},
|
||||
"Properties": {
|
||||
"Type": "Truck",
|
||||
"Location": "Field",
|
||||
"Latitude": 47.25433,
|
||||
"Longitude": -121.177075
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:03",
|
||||
"MessageTemplate": "{\"latitude\":${latitude},\"longitude\":${longitude}}",
|
||||
"MessageSchema": {
|
||||
"Name": "truck-geolocation;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"latitude": "double",
|
||||
"longitude": "double"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"speed\": ${speed},\"speed_unit\":\"${speed_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "truck-speed;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"speed": "double",
|
||||
"speed_unit": "text"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"MessageTemplate": "{\"cargotemperature\":${cargotemperature},\"cargotemperature_unit\":\"${cargotemperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "truck-cargotemperature;v1",
|
||||
"Format": "JSON",
|
||||
"Fields": {
|
||||
"cargotemperature": "double",
|
||||
"cargotemperature_unit": "text"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"CloudToDeviceMethods": {
|
||||
"DecreaseCargoTemperature": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
},
|
||||
"IncreaseCargoTemperature": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -69,7 +69,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
|
|||
// for example to reuse IoT Hub connections, as opposed to creating
|
||||
// a new connection every time.
|
||||
builder.RegisterType<Simulations>().As<ISimulations>().SingleInstance();
|
||||
builder.RegisterType<DeviceTypes>().As<IDeviceTypes>().SingleInstance();
|
||||
builder.RegisterType<DeviceModels>().As<IDeviceModels>().SingleInstance();
|
||||
builder.RegisterType<Services.Devices>().As<IDevices>().SingleInstance();
|
||||
|
||||
// Registrations required by Autofac, these classes all implement the same interface
|
||||
|
|
|
@ -25,8 +25,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent
|
|||
var logger = container.Resolve<ILogger>();
|
||||
var config = container.Resolve<IConfig>();
|
||||
logger.Info("Simulation agent started", () => new { Uptime.ProcessId });
|
||||
logger.Info("Device Types folder: " + config.ServicesConfig.DeviceTypesFolder, () => { });
|
||||
logger.Info("Scripts folder: " + config.ServicesConfig.DeviceTypesScriptsFolder, () => { });
|
||||
logger.Info("Device Models folder: " + config.ServicesConfig.DeviceModelsFolder, () => { });
|
||||
logger.Info("Scripts folder: " + config.ServicesConfig.DeviceModelsScriptsFolder, () => { });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,8 +19,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Runtime
|
|||
public class Config : IConfig
|
||||
{
|
||||
private const string ApplicationKey = "devicesimulation:";
|
||||
private const string DeviceTypesFolderKey = ApplicationKey + "device_types_folder";
|
||||
private const string DeviceTypesScriptsFolderKey = ApplicationKey + "device_types_scripts_folder";
|
||||
private const string DeviceModelsFolderKey = ApplicationKey + "device_models_folder";
|
||||
private const string DeviceModelsScriptsFolderKey = ApplicationKey + "device_models_scripts_folder";
|
||||
private const string IoTHubConnStringKey = ApplicationKey + "iothub_connstring";
|
||||
|
||||
private const string StorageAdapterKey = "storageadapter:";
|
||||
|
@ -52,8 +52,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Runtime
|
|||
|
||||
this.ServicesConfig = new ServicesConfig
|
||||
{
|
||||
DeviceTypesFolder = MapRelativePath(configData.GetString(DeviceTypesFolderKey)),
|
||||
DeviceTypesScriptsFolder = MapRelativePath(configData.GetString(DeviceTypesScriptsFolderKey)),
|
||||
DeviceModelsFolder = MapRelativePath(configData.GetString(DeviceModelsFolderKey)),
|
||||
DeviceModelsScriptsFolder = MapRelativePath(configData.GetString(DeviceModelsScriptsFolderKey)),
|
||||
IoTHubConnString = connstring,
|
||||
StorageAdapterApiUrl = configData.GetString(StorageAdapterApiUrlKey),
|
||||
StorageAdapterApiTimeout = configData.GetInt(StorageAdapterApiTimeoutKey)
|
||||
|
|
|
@ -50,12 +50,12 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
|
||||
/// <summary>
|
||||
/// Invoke this method before calling Start(), to initialize the actor
|
||||
/// with details like the device type and message type to simulate.
|
||||
/// with details like the device model and message type to simulate.
|
||||
/// If this method is not called before Start(), the application will
|
||||
/// thrown an exception.
|
||||
/// Setup() should be called only once, typically after the constructor.
|
||||
/// </summary>
|
||||
IDeviceActor Setup(DeviceType deviceType, int position);
|
||||
IDeviceActor Setup(DeviceModel deviceModel, int position);
|
||||
|
||||
/// <summary>
|
||||
/// Call this method to start the simulated device, e.g. sending
|
||||
|
@ -81,6 +81,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
|
||||
public class DeviceActor : IDeviceActor
|
||||
{
|
||||
private const string DeviceIdPrefix = "Simulated.";
|
||||
|
||||
// When the actor fails to connect to IoT Hub, it retries every 10 seconds
|
||||
private static readonly TimeSpan retryConnectingFrequency = TimeSpan.FromSeconds(10);
|
||||
|
||||
|
@ -105,11 +107,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
|
||||
// How often the simulated device state needs to be updated, i.e.
|
||||
// when to execute the external script. The value is configured in
|
||||
// the device type.
|
||||
// the device model.
|
||||
private TimeSpan deviceStateInterval;
|
||||
|
||||
// Info about the messages to generate and send
|
||||
private IList<DeviceType.DeviceTypeMessage> messages;
|
||||
private IList<DeviceModel.DeviceModelMessage> messages;
|
||||
|
||||
// ID of the simulated device, used with Azure IoT Hub
|
||||
private string deviceId;
|
||||
|
@ -179,12 +181,12 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
|
||||
/// <summary>
|
||||
/// Invoke this method before calling Start(), to initialize the actor
|
||||
/// with details like the device type and message type to simulate.
|
||||
/// with details like the device model and message type to simulate.
|
||||
/// If this method is not called before Start(), the application will
|
||||
/// thrown an exception.
|
||||
/// Setup() should be called only once, typically after the constructor.
|
||||
/// </summary>
|
||||
public IDeviceActor Setup(DeviceType deviceType, int position)
|
||||
public IDeviceActor Setup(DeviceModel deviceModel, int position)
|
||||
{
|
||||
if (this.ActorStatus != Status.None || this.setupDone)
|
||||
{
|
||||
|
@ -192,7 +194,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
() => new
|
||||
{
|
||||
CurrentDeviceId = this.deviceId,
|
||||
NewDeviceType = deviceType.Name,
|
||||
NewDeviceModelName = deviceModel.Name,
|
||||
NewDeviceModelId = deviceModel.Id,
|
||||
NewPosition = position
|
||||
});
|
||||
throw new DeviceActorAlreadyInitializedException();
|
||||
|
@ -200,17 +203,17 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
|
||||
this.setupDone = true;
|
||||
|
||||
this.deviceId = "Simulated." + deviceType.Name + "." + position;
|
||||
this.messages = deviceType.Telemetry;
|
||||
this.deviceId = DeviceIdPrefix + deviceModel.Id + "." + position;
|
||||
this.messages = deviceModel.Telemetry;
|
||||
|
||||
this.deviceStateInterval = deviceType.DeviceState.SimulationInterval;
|
||||
this.DeviceState = CloneObject(deviceType.DeviceState.Initial);
|
||||
this.deviceStateInterval = deviceModel.Simulation.Script.Interval;
|
||||
this.DeviceState = CloneObject(deviceModel.Simulation.InitialState);
|
||||
this.log.Debug("Initial device state", () => new { this.deviceId, this.DeviceState });
|
||||
|
||||
this.connectLogic.Setup(this.deviceId, deviceType);
|
||||
this.updateDeviceStateLogic.Setup(this.deviceId, deviceType);
|
||||
this.deviceBootstrapLogic.Setup(this.deviceId, deviceType);
|
||||
this.sendTelemetryLogic.Setup(this.deviceId, deviceType);
|
||||
this.connectLogic.Setup(this.deviceId, deviceModel);
|
||||
this.updateDeviceStateLogic.Setup(this.deviceId, deviceModel);
|
||||
this.deviceBootstrapLogic.Setup(this.deviceId, deviceModel);
|
||||
this.sendTelemetryLogic.Setup(this.deviceId, deviceModel);
|
||||
|
||||
this.log.Debug("Setup complete", () => new { this.deviceId });
|
||||
this.MoveNext();
|
||||
|
@ -377,7 +380,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
private class TelemetryContext
|
||||
{
|
||||
public DeviceActor Self { get; set; }
|
||||
public DeviceType.DeviceTypeMessage Message { get; set; }
|
||||
public DeviceModel.DeviceModelMessage Message { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
this.devices = devices;
|
||||
}
|
||||
|
||||
public void Setup(string deviceId, DeviceType deviceType)
|
||||
public void Setup(string deviceId, DeviceModel deviceModel)
|
||||
{
|
||||
if (this.setupDone)
|
||||
{
|
||||
|
@ -46,7 +46,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
|
||||
this.setupDone = true;
|
||||
this.deviceId = deviceId;
|
||||
this.protocol = deviceType.Protocol;
|
||||
this.protocol = deviceModel.Protocol;
|
||||
}
|
||||
|
||||
public void Run(object context)
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Exceptions;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulation.DeviceStatusLogic
|
||||
{
|
||||
|
@ -17,7 +19,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
private readonly ILogger log;
|
||||
private readonly IDevices devices;
|
||||
private string deviceId;
|
||||
private DeviceType deviceType;
|
||||
private DeviceModel deviceModel;
|
||||
|
||||
// Ensure that setup is called once and only once (which helps also detecting thread safety issues)
|
||||
private bool setupDone = false;
|
||||
|
@ -30,7 +32,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
this.log = logger;
|
||||
}
|
||||
|
||||
public void Setup(string deviceId, DeviceType deviceType)
|
||||
public void Setup(string deviceId, DeviceModel deviceModel)
|
||||
{
|
||||
if (this.setupDone)
|
||||
{
|
||||
|
@ -41,7 +43,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
|
||||
this.setupDone = true;
|
||||
this.deviceId = deviceId;
|
||||
this.deviceType = deviceType;
|
||||
this.deviceModel = deviceModel;
|
||||
}
|
||||
|
||||
public void Run(object context)
|
||||
|
@ -72,28 +74,32 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
}
|
||||
}
|
||||
|
||||
private void UpdateTwin(DeviceServiceModel device, IDeviceClient client, CancellationToken token)
|
||||
private void UpdateTwin(Device device, IDeviceClient client, CancellationToken token)
|
||||
{
|
||||
device.SetReportedProperty("Protocol", this.deviceType.Protocol.ToString());
|
||||
device.SetReportedProperty("SupportedMethods", string.Join(",", this.deviceType.CloudToDeviceMethods.Keys));
|
||||
device.SetReportedProperty("DeviceType", this.deviceType.GetDeviceTypeReportedProperty());
|
||||
device.SetReportedProperty("Telemetry", this.deviceType.GetTelemetryReportedProperty(this.log));
|
||||
device.SetReportedProperty("Location", this.deviceType.GetLocationReportedProperty());
|
||||
// Generate some properties using the device model specs
|
||||
device.SetReportedProperty("Protocol", this.deviceModel.Protocol.ToString());
|
||||
device.SetReportedProperty("SupportedMethods", string.Join(",", this.deviceModel.CloudToDeviceMethods.Keys));
|
||||
device.SetReportedProperty("Telemetry", this.deviceModel.GetTelemetryReportedProperty(this.log));
|
||||
|
||||
// Copy all the properties defined in the device model specs
|
||||
foreach (KeyValuePair<string, object> p in this.deviceModel.Properties)
|
||||
{
|
||||
device.SetReportedProperty(p.Key, new JValue(p.Value));
|
||||
}
|
||||
|
||||
client.UpdateTwinAsync(device).Wait((int) connectionTimeout.TotalMilliseconds, token);
|
||||
|
||||
this.log.Debug("Simulated device properties updated", () => { });
|
||||
}
|
||||
|
||||
private static bool IsTwinNotUpdated(DeviceServiceModel device)
|
||||
private static bool IsTwinNotUpdated(Device device)
|
||||
{
|
||||
return !device.Twin.ReportedProperties.ContainsKey("Protocol")
|
||||
|| !device.Twin.ReportedProperties.ContainsKey("SupportedMethods")
|
||||
|| !device.Twin.ReportedProperties.ContainsKey("DeviceType")
|
||||
|| !device.Twin.ReportedProperties.ContainsKey("Telemetry");
|
||||
}
|
||||
|
||||
private DeviceServiceModel GetDevice(CancellationToken token)
|
||||
private Device GetDevice(CancellationToken token)
|
||||
{
|
||||
var task = this.devices.GetAsync(this.deviceId);
|
||||
task.Wait((int) connectionTimeout.TotalMilliseconds, token);
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
{
|
||||
public interface IDeviceStatusLogic
|
||||
{
|
||||
void Setup(string deviceId, DeviceType deviceType);
|
||||
void Setup(string deviceId, DeviceModel deviceModel);
|
||||
void Run(object context);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
this.log = logger;
|
||||
}
|
||||
|
||||
public void Setup(string deviceId, DeviceType deviceType)
|
||||
public void Setup(string deviceId, DeviceModel deviceModel)
|
||||
{
|
||||
if (this.setupDone)
|
||||
{
|
||||
|
|
|
@ -7,6 +7,6 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
internal class SendTelemetryContext
|
||||
{
|
||||
public IDeviceActor Self { get; set; }
|
||||
public DeviceType.DeviceTypeMessage Message { get; set; }
|
||||
public DeviceModel.DeviceModelMessage Message { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,14 +11,14 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
{
|
||||
/// <summary>
|
||||
/// Periodically update the device state (i.e. sensors data), executing
|
||||
/// the script provided in the device type configuration.
|
||||
/// the script provided in the device model configuration.
|
||||
/// </summary>
|
||||
public class UpdateDeviceState : IDeviceStatusLogic
|
||||
{
|
||||
private readonly IScriptInterpreter scriptInterpreter;
|
||||
private readonly ILogger log;
|
||||
private string deviceId;
|
||||
private DeviceType deviceType;
|
||||
private DeviceModel deviceModel;
|
||||
|
||||
// Ensure that setup is called once and only once (which helps also detecting thread safety issues)
|
||||
private bool setupDone = false;
|
||||
|
@ -31,7 +31,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
this.log = logger;
|
||||
}
|
||||
|
||||
public void Setup(string deviceId, DeviceType deviceType)
|
||||
public void Setup(string deviceId, DeviceModel deviceModel)
|
||||
{
|
||||
if (this.setupDone)
|
||||
{
|
||||
|
@ -42,7 +42,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
|
||||
this.setupDone = true;
|
||||
this.deviceId = deviceId;
|
||||
this.deviceType = deviceType;
|
||||
this.deviceModel = deviceModel;
|
||||
}
|
||||
|
||||
public void Run(object context)
|
||||
|
@ -60,7 +60,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
{
|
||||
["currentTime"] = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:sszzz"),
|
||||
["deviceId"] = this.deviceId,
|
||||
["deviceType"] = this.deviceType.Name
|
||||
["deviceModel"] = this.deviceModel.Name
|
||||
};
|
||||
|
||||
this.log.Debug("Updating device status", () => new { this.deviceId, deviceState = actor.DeviceState });
|
||||
|
@ -68,7 +68,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
lock (actor.DeviceState)
|
||||
{
|
||||
actor.DeviceState = this.scriptInterpreter.Invoke(
|
||||
this.deviceType.DeviceState.SimulationScript,
|
||||
this.deviceModel.Simulation.Script,
|
||||
scriptContext,
|
||||
actor.DeviceState);
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
if (!this.setupDone)
|
||||
{
|
||||
this.log.Error("Application error: Setup() must be invoked before Run().",
|
||||
() => new { this.deviceId, this.deviceType });
|
||||
() => new { this.deviceId, this.deviceModel });
|
||||
throw new DeviceActorAlreadyInitializedException();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,25 +18,25 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
public class SimulationRunner : ISimulationRunner
|
||||
{
|
||||
private readonly ILogger log;
|
||||
private readonly IDeviceTypes deviceTypes;
|
||||
private readonly IDeviceModels deviceModels;
|
||||
private readonly DependencyResolution.IFactory factory;
|
||||
private readonly List<bool> running;
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
public SimulationRunner(
|
||||
ILogger logger,
|
||||
IDeviceTypes deviceTypes,
|
||||
IDeviceModels deviceModels,
|
||||
DependencyResolution.IFactory factory)
|
||||
{
|
||||
this.log = logger;
|
||||
this.deviceTypes = deviceTypes;
|
||||
this.deviceModels = deviceModels;
|
||||
this.factory = factory;
|
||||
|
||||
this.running = new List<bool> { false };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For each device type in the simulation, create a 'Count'
|
||||
/// For each device model in the simulation, create a 'Count'
|
||||
/// number of actors, which individually connects and starts
|
||||
/// sending messages.
|
||||
/// </summary>
|
||||
|
@ -50,14 +50,15 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
this.log.Info("Starting simulation...", () => new { simulation.Id });
|
||||
this.cancellationToken = new CancellationTokenSource();
|
||||
|
||||
foreach (var dt in simulation.DeviceTypes)
|
||||
foreach (var dt in simulation.DeviceModels)
|
||||
{
|
||||
var deviceType = this.deviceTypes.Get(dt.Id);
|
||||
var deviceModel = this.deviceModels.Get(dt.Id);
|
||||
Parallel.For(0, dt.Count, i =>
|
||||
{
|
||||
this.log.Debug("Starting device...", () => new { deviceType.Name, i });
|
||||
this.log.Debug("Starting device...",
|
||||
() => new { ModelName = deviceModel.Name, ModelId = dt.Id, i });
|
||||
this.factory.Resolve<IDeviceActor>()
|
||||
.Setup(deviceType, i)
|
||||
.Setup(deviceModel, i)
|
||||
.Start(this.cancellationToken.Token);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
webservice_port = 9003
|
||||
iothub_connstring = "${PCS_IOTHUB_CONNSTRING}"
|
||||
# Note: in linux containers, the `data` folder is a symlink to /app/data
|
||||
# which can be mounted to inject custom device types and scripts.
|
||||
device_types_folder = ./data/DeviceTypes/
|
||||
device_types_scripts_folder = ./data/DeviceTypes/Scripts/
|
||||
# which can be mounted to inject custom device models and scripts.
|
||||
device_models_folder = ./data/devicemodels/
|
||||
device_models_scripts_folder = ./data/devicemodels/scripts/
|
||||
|
||||
[storageadapter]
|
||||
webservice_url = "${PCS_STORAGEADAPTER_WEBSERVICE_URL}"
|
||||
|
|
|
@ -64,7 +64,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService
|
|||
// for example to reuse IoT Hub connections, as opposed to creating
|
||||
// a new connection every time.
|
||||
builder.RegisterType<Simulations>().As<ISimulations>().SingleInstance();
|
||||
builder.RegisterType<DeviceTypes>().As<IDeviceTypes>().SingleInstance();
|
||||
builder.RegisterType<DeviceModels>().As<IDeviceModels>().SingleInstance();
|
||||
builder.RegisterType<Services.Devices>().As<IDevices>().SingleInstance();
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Runtime
|
|||
{
|
||||
private const string ApplicationKey = "devicesimulation:";
|
||||
private const string PortKey = ApplicationKey + "webservice_port";
|
||||
private const string DeviceTypesFolderKey = ApplicationKey + "device_types_folder";
|
||||
private const string DeviceTypesScriptsFolderKey = ApplicationKey + "device_types_scripts_folder";
|
||||
private const string DeviceModelsFolderKey = ApplicationKey + "device_models_folder";
|
||||
private const string DeviceModelsScriptsFolderKey = ApplicationKey + "device_models_scripts_folder";
|
||||
private const string IoTHubConnStringKey = ApplicationKey + "iothub_connstring";
|
||||
private const string CorsWhitelistKey = ApplicationKey + "cors_whitelist";
|
||||
|
||||
|
@ -69,8 +69,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.Runtime
|
|||
|
||||
this.ServicesConfig = new ServicesConfig
|
||||
{
|
||||
DeviceTypesFolder = MapRelativePath(configData.GetString(DeviceTypesFolderKey)),
|
||||
DeviceTypesScriptsFolder = MapRelativePath(configData.GetString(DeviceTypesScriptsFolderKey)),
|
||||
DeviceModelsFolder = MapRelativePath(configData.GetString(DeviceModelsFolderKey)),
|
||||
DeviceModelsScriptsFolder = MapRelativePath(configData.GetString(DeviceModelsScriptsFolderKey)),
|
||||
IoTHubConnString = connstring,
|
||||
StorageAdapterApiUrl = configData.GetString(StorageAdapterApiUrlKey),
|
||||
StorageAdapterApiTimeout = configData.GetInt(StorageAdapterApiTimeoutKey)
|
||||
|
|
|
@ -57,8 +57,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService
|
|||
var logger = container.Resolve<Services.Diagnostics.ILogger>();
|
||||
var config = container.Resolve<IConfig>();
|
||||
logger.Info("Web service started", () => new { Uptime.ProcessId });
|
||||
logger.Info("Device Types folder: " + config.ServicesConfig.DeviceTypesFolder, () => { });
|
||||
logger.Info("Scripts folder: " + config.ServicesConfig.DeviceTypesScriptsFolder, () => { });
|
||||
logger.Info("Device Models folder: " + config.ServicesConfig.DeviceModelsFolder, () => { });
|
||||
logger.Info("Scripts folder: " + config.ServicesConfig.DeviceModelsScriptsFolder, () => { });
|
||||
}
|
||||
|
||||
// This method is called by the runtime, after the ConfigureServices
|
||||
|
|
|
@ -4,9 +4,9 @@ iothub_connstring = "${PCS_IOTHUB_CONNSTRING}"
|
|||
cors_whitelist = "{ 'origins': ['*'], 'methods': ['*'], 'headers': ['*'] }"
|
||||
|
||||
# Note: in linux containers, the `data` folder is a symlink to /app/data
|
||||
# which can be mounted to inject custom device types and scripts.
|
||||
device_types_folder = ./data/DeviceTypes/
|
||||
device_types_scripts_folder = ./data/DeviceTypes/Scripts/
|
||||
# which can be mounted to inject custom device models and scripts.
|
||||
device_models_folder = ./data/devicemodels/
|
||||
device_models_scripts_folder = ./data/devicemodels/scripts/
|
||||
|
||||
[storageadapter]
|
||||
webservice_url = "${PCS_STORAGEADAPTER_WEBSERVICE_URL}"
|
||||
|
|
|
@ -8,25 +8,25 @@ using Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models;
|
|||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controllers
|
||||
{
|
||||
[Route(Version.Path + "/[controller]"), ExceptionsFilter]
|
||||
public class DeviceTypesController : Controller
|
||||
public class DeviceModelsController : Controller
|
||||
{
|
||||
private readonly IDeviceTypes deviceTypesService;
|
||||
private readonly IDeviceModels deviceModelsService;
|
||||
|
||||
public DeviceTypesController(IDeviceTypes deviceTypesService)
|
||||
public DeviceModelsController(IDeviceModels deviceModelsService)
|
||||
{
|
||||
this.deviceTypesService = deviceTypesService;
|
||||
this.deviceModelsService = deviceModelsService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public DeviceTypeListApiModel Get()
|
||||
public DeviceModelListApiModel Get()
|
||||
{
|
||||
return new DeviceTypeListApiModel(this.deviceTypesService.GetList());
|
||||
return new DeviceModelListApiModel(this.deviceModelsService.GetList());
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public DeviceTypeApiModel Get(string id)
|
||||
public DeviceModelApiModel Get(string id)
|
||||
{
|
||||
return new DeviceTypeApiModel(this.deviceTypesService.Get(id));
|
||||
return new DeviceModelApiModel(this.deviceModelsService.Get(id));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -14,15 +16,18 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
|
|||
public sealed class StatusController : Controller
|
||||
{
|
||||
private readonly IDevices devices;
|
||||
private IStorageAdapterClient storage;
|
||||
private readonly ISimulations simulations;
|
||||
private readonly ILogger log;
|
||||
|
||||
public StatusController(
|
||||
IDevices devices,
|
||||
IStorageAdapterClient storage,
|
||||
ISimulations simulations,
|
||||
ILogger logger)
|
||||
{
|
||||
this.devices = devices;
|
||||
this.storage = storage;
|
||||
this.simulations = simulations;
|
||||
this.log = logger;
|
||||
}
|
||||
|
@ -32,22 +37,50 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Controller
|
|||
{
|
||||
var statusIsOk = true;
|
||||
var statusMsg = "Alive and well";
|
||||
var errors = new List<string>();
|
||||
|
||||
// Check access to Azure IoT Hub
|
||||
var iotHubStatus = await this.devices.PingRegistryAsync();
|
||||
if (!iotHubStatus.Item1)
|
||||
{
|
||||
statusIsOk = false;
|
||||
statusMsg = "Unable to use Azure IoT Hub service";
|
||||
errors.Add("Unable to use Azure IoT Hub service");
|
||||
}
|
||||
|
||||
var simulation = (await this.simulations.GetListAsync()).FirstOrDefault();
|
||||
var running = (simulation != null && simulation.Enabled);
|
||||
// Check access to storage
|
||||
var storageStatus = await this.storage.PingAsync();
|
||||
if (!storageStatus.Item1)
|
||||
{
|
||||
statusIsOk = false;
|
||||
errors.Add("Unable to use Storage");
|
||||
}
|
||||
|
||||
// Check simulation status
|
||||
bool? simulationRunning = null;
|
||||
try
|
||||
{
|
||||
var simulation = (await this.simulations.GetListAsync()).FirstOrDefault();
|
||||
simulationRunning = (simulation != null && simulation.Enabled);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
errors.Add("Unable to fetch simulation status");
|
||||
this.log.Error("Unable to fetch simulation status", () => new { e });
|
||||
}
|
||||
|
||||
// Prepare status message
|
||||
if (!statusIsOk)
|
||||
{
|
||||
statusMsg = string.Join(";", errors);
|
||||
}
|
||||
|
||||
// Prepare response
|
||||
var result = new StatusApiModel(statusIsOk, statusMsg);
|
||||
result.Properties.Add("Simulation", running ? "on" : "off");
|
||||
result.Properties.Add("Simulation", simulationRunning.HasValue ? (simulationRunning.Value ? "on" : "off") : "unknown");
|
||||
result.Dependencies.Add("IoTHub", iotHubStatus.Item2);
|
||||
result.Dependencies.Add("Storage", storageStatus.Item2);
|
||||
|
||||
this.log.Info("Service status request", () => new { Healthy = statusIsOk, statusMsg, running });
|
||||
this.log.Info("Service status request", () => new { Healthy = statusIsOk, statusMsg, running = simulationRunning });
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ using Newtonsoft.Json;
|
|||
// TODO: handle errors
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models
|
||||
{
|
||||
public class DeviceTypeApiModel
|
||||
public class DeviceModelApiModel
|
||||
{
|
||||
[JsonProperty(PropertyName = "Id")]
|
||||
public string Id { get; set; }
|
||||
|
@ -25,11 +25,14 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models
|
|||
[JsonProperty(PropertyName = "Protocol")]
|
||||
public string Protocol { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "DeviceState")]
|
||||
public InternalStateApiModel DeviceState { get; set; }
|
||||
[JsonProperty(PropertyName = "Simulation")]
|
||||
public StateSimulationApiModel Simulation { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "Properties")]
|
||||
public IDictionary<string, object> Properties { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "Telemetry")]
|
||||
public IList<DeviceTypeMessageApiModel> Telemetry { get; set; }
|
||||
public IList<DeviceModelMessageApiModel> Telemetry { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "CloudToDeviceMethods")]
|
||||
public IDictionary<string, ScriptApiModel> CloudToDeviceMethods { get; set; }
|
||||
|
@ -37,66 +40,67 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models
|
|||
[JsonProperty(PropertyName = "$metadata", Order = 1000)]
|
||||
public IDictionary<string, string> Metadata => new Dictionary<string, string>
|
||||
{
|
||||
{ "$type", "DeviceType;" + v1.Version.Number },
|
||||
{ "$uri", "/" + v1.Version.Path + "/devicetypes/" + this.Id }
|
||||
{ "$type", "DeviceModel;" + v1.Version.Number },
|
||||
{ "$uri", "/" + v1.Version.Path + "/devicemodels/" + this.Id }
|
||||
};
|
||||
|
||||
public DeviceTypeApiModel()
|
||||
public DeviceModelApiModel()
|
||||
{
|
||||
this.DeviceState = new InternalStateApiModel();
|
||||
this.Telemetry = new List<DeviceTypeMessageApiModel>();
|
||||
this.Simulation = new StateSimulationApiModel();
|
||||
this.Telemetry = new List<DeviceModelMessageApiModel>();
|
||||
this.Properties = new Dictionary<string, object>();
|
||||
this.CloudToDeviceMethods = new Dictionary<string, ScriptApiModel>();
|
||||
}
|
||||
|
||||
/// <summary>Map a service model to the corresponding API model</summary>
|
||||
public DeviceTypeApiModel(DeviceType type) : this()
|
||||
public DeviceModelApiModel(DeviceModel model) : this()
|
||||
{
|
||||
if (type == null) return;
|
||||
if (model == null) return;
|
||||
|
||||
this.Id = type.Id;
|
||||
this.Version = type.Version;
|
||||
this.Name = type.Name;
|
||||
this.Description = type.Description;
|
||||
this.Protocol = type.Protocol.ToString();
|
||||
this.DeviceState = new InternalStateApiModel(type.DeviceState);
|
||||
this.Id = model.Id;
|
||||
this.Version = model.Version;
|
||||
this.Name = model.Name;
|
||||
this.Description = model.Description;
|
||||
this.Protocol = model.Protocol.ToString();
|
||||
this.Simulation = new StateSimulationApiModel(model.Simulation);
|
||||
|
||||
foreach (var message in type.Telemetry)
|
||||
foreach (var property in model.Properties)
|
||||
{
|
||||
this.Telemetry.Add(new DeviceTypeMessageApiModel(message));
|
||||
this.Properties.Add(property.Key, property.Value);
|
||||
}
|
||||
|
||||
foreach (var method in type.CloudToDeviceMethods)
|
||||
foreach (var message in model.Telemetry)
|
||||
{
|
||||
this.Telemetry.Add(new DeviceModelMessageApiModel(message));
|
||||
}
|
||||
|
||||
foreach (var method in model.CloudToDeviceMethods)
|
||||
{
|
||||
this.CloudToDeviceMethods.Add(method.Key, new ScriptApiModel(method.Value));
|
||||
}
|
||||
}
|
||||
|
||||
public class InternalStateApiModel
|
||||
public class StateSimulationApiModel
|
||||
{
|
||||
[JsonProperty(PropertyName = "Initial")]
|
||||
[JsonProperty(PropertyName = "InitialState")]
|
||||
public Dictionary<string, object> Initial { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "SimulationInterval")]
|
||||
public string SimulationInterval { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "SimulationScript")]
|
||||
[JsonProperty(PropertyName = "Script")]
|
||||
public ScriptApiModel SimulationScript { get; set; }
|
||||
|
||||
public InternalStateApiModel()
|
||||
public StateSimulationApiModel()
|
||||
{
|
||||
this.Initial = new Dictionary<string, object>();
|
||||
this.SimulationInterval = "00:00:00";
|
||||
this.SimulationScript = new ScriptApiModel();
|
||||
}
|
||||
|
||||
/// <summary>Map a service model to the corresponding API model</summary>
|
||||
public InternalStateApiModel(DeviceType.InternalState state) : this()
|
||||
public StateSimulationApiModel(DeviceModel.StateSimulation state) : this()
|
||||
{
|
||||
if (state == null) return;
|
||||
|
||||
this.Initial = state.Initial;
|
||||
this.SimulationInterval = state.SimulationInterval.ToString("c");
|
||||
this.SimulationScript = new ScriptApiModel(state.SimulationScript);
|
||||
this.Initial = state.InitialState;
|
||||
this.SimulationScript = new ScriptApiModel(state.Script);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,10 +112,14 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models
|
|||
[JsonProperty(PropertyName = "Path")]
|
||||
public string Path { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "Interval", NullValueHandling = NullValueHandling.Ignore)]
|
||||
public string Interval { get; set; }
|
||||
|
||||
public ScriptApiModel()
|
||||
{
|
||||
this.Type = "javascript";
|
||||
this.Path = "scripts" + System.IO.Path.DirectorySeparatorChar;
|
||||
this.Interval = null;
|
||||
}
|
||||
|
||||
/// <summary>Map a service model to the corresponding API model</summary>
|
||||
|
@ -121,10 +129,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models
|
|||
|
||||
this.Type = script.Type;
|
||||
this.Path = script.Path;
|
||||
this.Interval = script.Interval.ToString("c");
|
||||
}
|
||||
}
|
||||
|
||||
public class DeviceTypeMessageApiModel
|
||||
public class DeviceModelMessageApiModel
|
||||
{
|
||||
[JsonProperty(PropertyName = "Interval")]
|
||||
public string Interval { get; set; }
|
||||
|
@ -133,27 +142,27 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models
|
|||
public string MessageTemplate { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "MessageSchema")]
|
||||
public DeviceTypeMessageSchemaApiModel MessageSchema { get; set; }
|
||||
public DeviceModelMessageSchemaApiModel MessageSchema { get; set; }
|
||||
|
||||
public DeviceTypeMessageApiModel()
|
||||
public DeviceModelMessageApiModel()
|
||||
{
|
||||
this.Interval = "00:00:00";
|
||||
this.MessageTemplate = string.Empty;
|
||||
this.MessageSchema = new DeviceTypeMessageSchemaApiModel();
|
||||
this.MessageSchema = new DeviceModelMessageSchemaApiModel();
|
||||
}
|
||||
|
||||
/// <summary>Map a service model to the corresponding API model</summary>
|
||||
public DeviceTypeMessageApiModel(DeviceType.DeviceTypeMessage message) : this()
|
||||
public DeviceModelMessageApiModel(DeviceModel.DeviceModelMessage message) : this()
|
||||
{
|
||||
if (message == null) return;
|
||||
|
||||
this.Interval = message.Interval.ToString("c");
|
||||
this.MessageTemplate = message.MessageTemplate;
|
||||
this.MessageSchema = new DeviceTypeMessageSchemaApiModel(message.MessageSchema);
|
||||
this.MessageSchema = new DeviceModelMessageSchemaApiModel(message.MessageSchema);
|
||||
}
|
||||
}
|
||||
|
||||
public class DeviceTypeMessageSchemaApiModel
|
||||
public class DeviceModelMessageSchemaApiModel
|
||||
{
|
||||
[JsonProperty(PropertyName = "Name")]
|
||||
public string Name { get; set; }
|
||||
|
@ -164,7 +173,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models
|
|||
[JsonProperty(PropertyName = "Fields")]
|
||||
public IDictionary<string, string> Fields { get; set; }
|
||||
|
||||
public DeviceTypeMessageSchemaApiModel()
|
||||
public DeviceModelMessageSchemaApiModel()
|
||||
{
|
||||
this.Name = string.Empty;
|
||||
this.Format = "JSON";
|
||||
|
@ -172,7 +181,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models
|
|||
}
|
||||
|
||||
/// <summary>Map a service model to the corresponding API model</summary>
|
||||
public DeviceTypeMessageSchemaApiModel(DeviceType.DeviceTypeMessageSchema schema) : this()
|
||||
public DeviceModelMessageSchemaApiModel(DeviceModel.DeviceModelMessageSchema schema) : this()
|
||||
{
|
||||
if (schema == null) return;
|
||||
|
|
@ -7,28 +7,28 @@ using Newtonsoft.Json;
|
|||
// TODO: handle errors
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models
|
||||
{
|
||||
public class DeviceTypeListApiModel
|
||||
public class DeviceModelListApiModel
|
||||
{
|
||||
[JsonProperty(PropertyName = "Items")]
|
||||
public List<DeviceTypeApiModel> Items { get; set; }
|
||||
public List<DeviceModelApiModel> Items { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "$metadata")]
|
||||
public Dictionary<string, string> Metadata => new Dictionary<string, string>
|
||||
{
|
||||
{ "$type", "DeviceTypeList;" + Version.Number },
|
||||
{ "$uri", "/" + Version.Path + "/devicetypes" }
|
||||
{ "$type", "DeviceModelList;" + Version.Number },
|
||||
{ "$uri", "/" + Version.Path + "/devicemodels" }
|
||||
};
|
||||
|
||||
public DeviceTypeListApiModel()
|
||||
public DeviceModelListApiModel()
|
||||
{
|
||||
this.Items = new List<DeviceTypeApiModel>();
|
||||
this.Items = new List<DeviceModelApiModel>();
|
||||
}
|
||||
|
||||
/// <summary>Map a service model to the corresponding API model</summary>
|
||||
public DeviceTypeListApiModel(IEnumerable<Services.Models.DeviceType> deviceTypes)
|
||||
public DeviceModelListApiModel(IEnumerable<Services.Models.DeviceModel> deviceModels)
|
||||
{
|
||||
this.Items = new List<DeviceTypeApiModel>();
|
||||
foreach (var x in deviceTypes) this.Items.Add(new DeviceTypeApiModel(x));
|
||||
this.Items = new List<DeviceModelApiModel>();
|
||||
foreach (var x in deviceModels) this.Items.Add(new DeviceModelApiModel(x));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,8 +23,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models
|
|||
[JsonProperty(PropertyName = "Enabled")]
|
||||
public bool? Enabled { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "DeviceTypes")]
|
||||
public List<DeviceTypeRef> DeviceTypes { get; set; }
|
||||
[JsonProperty(PropertyName = "DeviceModels")]
|
||||
public List<DeviceModelRef> DeviceModels { get; set; }
|
||||
|
||||
[JsonProperty(PropertyName = "$metadata", Order = 1000)]
|
||||
public IDictionary<string, string> Metadata => new Dictionary<string, string>
|
||||
|
@ -38,26 +38,26 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models
|
|||
|
||||
public SimulationApiModel()
|
||||
{
|
||||
this.DeviceTypes = new List<DeviceTypeRef>();
|
||||
this.DeviceModels = new List<DeviceModelRef>();
|
||||
}
|
||||
|
||||
/// <summary>Map a service model to the corresponding API model</summary>
|
||||
public SimulationApiModel(Simulation simulation)
|
||||
{
|
||||
this.DeviceTypes = new List<DeviceTypeRef>();
|
||||
this.DeviceModels = new List<DeviceModelRef>();
|
||||
|
||||
this.Etag = simulation.Etag;
|
||||
this.Id = simulation.Id;
|
||||
this.Enabled = simulation.Enabled;
|
||||
|
||||
foreach (var x in simulation.DeviceTypes)
|
||||
foreach (var x in simulation.DeviceModels)
|
||||
{
|
||||
var dt = new DeviceTypeRef
|
||||
var dt = new DeviceModelRef
|
||||
{
|
||||
Id = x.Id,
|
||||
Count = x.Count
|
||||
};
|
||||
this.DeviceTypes.Add(dt);
|
||||
this.DeviceModels.Add(dt);
|
||||
}
|
||||
|
||||
this.version = simulation.Version;
|
||||
|
@ -65,7 +65,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models
|
|||
this.modified = simulation.Modified;
|
||||
}
|
||||
|
||||
public class DeviceTypeRef
|
||||
public class DeviceModelRef
|
||||
{
|
||||
[JsonProperty(PropertyName = "Id")]
|
||||
public string Id { get; set; }
|
||||
|
@ -89,14 +89,14 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.WebService.v1.Models
|
|||
Enabled = this.Enabled ?? true
|
||||
};
|
||||
|
||||
foreach (var x in this.DeviceTypes)
|
||||
foreach (var x in this.DeviceModels)
|
||||
{
|
||||
var dt = new Simulation.DeviceTypeRef
|
||||
var dt = new Simulation.DeviceModelRef
|
||||
{
|
||||
Id = x.Id,
|
||||
Count = x.Count
|
||||
};
|
||||
result.DeviceTypes.Add(dt);
|
||||
result.DeviceModels.Add(dt);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
@ -74,18 +74,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "content", "content", "{39EA
|
|||
scripts\docker\content\run.sh = scripts\docker\content\run.sh
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sample-volume", "sample-volume", "{1B6A5847-A864-4AC7-ACF5-E7921E387C16}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DeviceTypes", "DeviceTypes", "{67ACBCBE-7F7A-4DAB-A8D8-5F2456B41AF1}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
scripts\docker\sample-volume\DeviceTypes\drone.json = scripts\docker\sample-volume\DeviceTypes\drone.json
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{F9981C3F-DE64-42FE-8A19-A63CED68B942}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
scripts\docker\sample-volume\DeviceTypes\Scripts\drone-state.js = scripts\docker\sample-volume\DeviceTypes\Scripts\drone-state.js
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -137,8 +125,5 @@ Global
|
|||
{DEF8CFF7-B332-4BA3-AB0B-8F7AC3275054} = {2B58D756-04D5-427F-9161-E1B45D35682A}
|
||||
{4C88469C-C551-435D-AE2C-36C58EE686A5} = {2B58D756-04D5-427F-9161-E1B45D35682A}
|
||||
{39EA50D7-B4CD-41C4-85EA-E53E89E9BC98} = {DEF8CFF7-B332-4BA3-AB0B-8F7AC3275054}
|
||||
{1B6A5847-A864-4AC7-ACF5-E7921E387C16} = {DEF8CFF7-B332-4BA3-AB0B-8F7AC3275054}
|
||||
{67ACBCBE-7F7A-4DAB-A8D8-5F2456B41AF1} = {1B6A5847-A864-4AC7-ACF5-E7921E387C16}
|
||||
{F9981C3F-DE64-42FE-8A19-A63CED68B942} = {67ACBCBE-7F7A-4DAB-A8D8-5F2456B41AF1}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
@ -42,6 +42,37 @@ run_tests() {
|
|||
done
|
||||
}
|
||||
|
||||
json_lint() {
|
||||
cd $APP_HOME
|
||||
set +e
|
||||
TEST=$(which jsonlint)
|
||||
set -e
|
||||
if [[ ! -z "$TEST" ]]; then
|
||||
for foo in Services/data/devicemodels/*.json; do
|
||||
set +e
|
||||
TEST=$(cat $foo | jsonlint -s 2> /dev/null)
|
||||
if [[ -z "$TEST" ]]; then
|
||||
error "$foo:"
|
||||
cat $foo | jsonlint -s
|
||||
exit 1
|
||||
fi
|
||||
set -e
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
js_lint() {
|
||||
cd $APP_HOME
|
||||
set +e
|
||||
TEST=$(which jslint4java)
|
||||
set -e
|
||||
if [[ ! -z "$TEST" ]]; then
|
||||
for foo in Services/data/devicemodels/scripts/*.js; do
|
||||
jslint4java $foo
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
setup_sandbox_cache() {
|
||||
mkdir -p $PCS_CACHE/sandbox/.config
|
||||
mkdir -p $PCS_CACHE/sandbox/.dotnet
|
||||
|
@ -85,6 +116,8 @@ else
|
|||
# workaround for https://github.com/dotnet/cli/issues/3995
|
||||
unset home
|
||||
|
||||
json_lint
|
||||
js_lint
|
||||
compile
|
||||
run_tests
|
||||
fi
|
||||
|
|
|
@ -12,7 +12,7 @@ RUN \
|
|||
# Both web service and simulation agent have a copy of the "data" folder,
|
||||
# inherited from the Service project. This remove the two copies and move
|
||||
# the files in /app/data, which is the folder that can be mounted to
|
||||
# provide custom device types and/or customized scripts.
|
||||
# provide custom device models and/or customized scripts.
|
||||
mv /app/webservice/data /app && \
|
||||
cd /app/webservice && rm -fR data && ln -s /app/data && \
|
||||
cd /app/simulationagent && rm -fR data && ln -s /app/data && \
|
||||
|
|
|
@ -9,16 +9,17 @@ services:
|
|||
devicesimulation:
|
||||
image: azureiotpcs/device-simulation-dotnet:latest
|
||||
depends_on:
|
||||
# - storageadapter
|
||||
- storageadapter
|
||||
ports:
|
||||
- "9003:9003"
|
||||
environment:
|
||||
- PCS_IOTHUB_CONNSTRING
|
||||
volumes:
|
||||
- ./sample-volume:/app/data:ro
|
||||
# storageadapter:
|
||||
# image: azureiotpcs/pcs-storage-adapter-dotnet:latest
|
||||
# ports:
|
||||
# - "9022:9022"
|
||||
# environment:
|
||||
# - PCS_STORAGEADAPTER_CONTAINER_NAME
|
||||
- PCS_STORAGEADAPTER_WEBSERVICE_URL=http://storageadapter:9022/v1
|
||||
#volumes:
|
||||
# - ./sample-volume:/app/data:ro
|
||||
storageadapter:
|
||||
image: azureiotpcs/pcs-storage-adapter-dotnet:latest
|
||||
ports:
|
||||
- "9022:9022"
|
||||
environment:
|
||||
- PCS_STORAGEADAPTER_DOCUMENTDB_CONNSTRING
|
|
@ -18,6 +18,7 @@ run_container() {
|
|||
echo "Starting Device Simulation ..."
|
||||
docker run -it -p 9003:9003 \
|
||||
-e "PCS_IOTHUB_CONNSTRING=$PCS_IOTHUB_CONNSTRING" \
|
||||
-e "PCS_STORAGEADAPTER_WEBSERVICE_URL=$PCS_STORAGEADAPTER_WEBSERVICE_URL" \
|
||||
"$DOCKER_IMAGE:$APP_VERSION"
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ IF %ERRORLEVEL% NEQ 0 GOTO FAIL
|
|||
echo Starting Device Simulation ...
|
||||
docker run -it -p 9003:9003 ^
|
||||
-e PCS_IOTHUB_CONNSTRING=%PCS_IOTHUB_CONNSTRING% ^
|
||||
-e PCS_STORAGEADAPTER_WEBSERVICE_URL=%PCS_STORAGEADAPTER_WEBSERVICE_URL% ^
|
||||
%DOCKER_IMAGE%:%APP_VERSION%
|
||||
|
||||
:: - - - - - - - - - - - - - -
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/**
|
||||
* Simulate a Drone
|
||||
*/
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device type and id
|
||||
* @param previousState The device state since the last iteration
|
||||
*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Fly in a circle, once a minute
|
||||
var now = new Date(context.currentTime);
|
||||
var twopi = (Math.PI * 2);
|
||||
var rad = (now.getSeconds() / 60) * twopi;
|
||||
|
||||
return {
|
||||
vertical_speed: 0.0,
|
||||
horizontal_speed: 10.0,
|
||||
compass: 360 * rad / twopi,
|
||||
latitude: 44.898556 + (0.01 * Math.sin(rad)),
|
||||
longitude: 10.043592 + (0.01 * Math.sin(rad)),
|
||||
altitude: 10.0
|
||||
};
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
{
|
||||
"SchemaVersion": "1.0.0",
|
||||
|
||||
"##": "The ID must to be unique - any alphanumeric string can be used",
|
||||
"Id": "mydrone01",
|
||||
|
||||
"Version": "0.0.1",
|
||||
"Name": "Drone",
|
||||
"Description": "Simulated Drone",
|
||||
|
||||
"########": "Available protocols: AMQP, MQTT, HTTP",
|
||||
"Protocol": "AMQP",
|
||||
|
||||
"###########": "The block below configures how the simulated device behaves",
|
||||
"DeviceState": {
|
||||
|
||||
"#######": "This defines the simulated device initial state",
|
||||
"Initial": {
|
||||
"vertical_speed": 0.0,
|
||||
"horizontal_speed": 0.0,
|
||||
"compass": 0,
|
||||
"latitude": 0.0,
|
||||
"longitude": 0.0,
|
||||
"altitude": 0.0
|
||||
},
|
||||
|
||||
"##################": "How often the script below is invoked to update the simulated state",
|
||||
"SimulationInterval": "00:00:01",
|
||||
|
||||
"################": "This is the script executed evry X seconds to simulate the device behavior",
|
||||
"SimulationScript": {
|
||||
"Type": "javascript",
|
||||
"Path": "drone-state.js"
|
||||
}
|
||||
},
|
||||
|
||||
"#########": "The block below defines the messages sent by the simulated device to Azure IoT Hub",
|
||||
"Telemetry": [
|
||||
{
|
||||
"########": "Send this message every 2 seconds",
|
||||
"Interval": "00:00:02",
|
||||
|
||||
"###############": "This is the message sent, with placeholders that are replaced with data copied from the 'device state'",
|
||||
"MessageTemplate": "{\"v_speed\":${vertical_speed},\"h_speed\":${horizontal_speed},\"compass\":${compass},\"latitude\":${latitude},\"longitude\":${longitude}}",
|
||||
|
||||
"#############": "This schema represents the format of the message above. The information is useful for interpreting and visualizing the device telemetry",
|
||||
"MessageSchema": {
|
||||
|
||||
"####": "This information is attached to each message and used to deserialize the message",
|
||||
"Name": "Drone;v1",
|
||||
|
||||
"######": "This information is attached to each message and used to deserialize the message",
|
||||
"Format": "JSON",
|
||||
|
||||
"#######": "This information is written once into the Device Twin and used to interpret the telemetry messages",
|
||||
"Fields": {
|
||||
"v_speed": "double",
|
||||
"h_speed": "double",
|
||||
"compass": "double",
|
||||
"latitude": "double",
|
||||
"longitude": "double"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
# Run the service inside a Docker container: ./scripts/run --in-sandbox
|
||||
# Run only the web service: ./scripts/run --webservice
|
||||
# Run only the simulation: ./scripts/run --simulation
|
||||
# Run only the simulation: ./scripts/run --storageadapter
|
||||
# Run the storage dependency: ./scripts/run --storage
|
||||
# Show how to use this script: ./scripts/run -h
|
||||
# Show how to use this script: ./scripts/run --help
|
||||
|
||||
|
@ -28,7 +28,7 @@ help() {
|
|||
echo " Run the service inside a Docker container: ./scripts/run -s | --in-sandbox"
|
||||
echo " Run only the web service: ./scripts/run --webservice"
|
||||
echo " Run only the simulation: ./scripts/run --simulation"
|
||||
echo " Run the storage adapter dependency: ./scripts/run --storageadapter"
|
||||
echo " Run the storage dependency: ./scripts/run --storage"
|
||||
echo " Show how to use this script: ./scripts/run -h | --help"
|
||||
}
|
||||
|
||||
|
@ -117,7 +117,7 @@ elif [[ "$1" == "--webservice" ]]; then
|
|||
elif [[ "$1" == "--simulation" ]]; then
|
||||
prepare_for_run
|
||||
run_simulation
|
||||
elif [[ "$1" == "--storageadapter" ]]; then
|
||||
elif [[ "$1" == "--storage" ]]; then
|
||||
run_storageadapter
|
||||
fi
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
:: Run the service inside a Docker container: scripts\run --in-sandbox
|
||||
:: Run only the web service: scripts\run --webservice
|
||||
:: Run only the simulation: scripts\run --simulation
|
||||
:: Run only the simulation: scripts\run --storageadapter
|
||||
:: Run the storage dependency: scripts\run --storage
|
||||
:: Show how to use this script: scripts\run -h
|
||||
:: Show how to use this script: scripts\run --help
|
||||
|
||||
|
@ -24,7 +24,7 @@ IF "%1"=="-s" GOTO :RunInSandbox
|
|||
IF "%1"=="--in-sandbox" GOTO :RunInSandbox
|
||||
IF "%1"=="--webservice" GOTO :RunWebService
|
||||
IF "%1"=="--simulation" GOTO :RunSimulation
|
||||
IF "%1"=="--storageadapter" GOTO :RunStorageAdapter
|
||||
IF "%1"=="--storage" GOTO :RunStorageAdapter
|
||||
|
||||
:Help
|
||||
|
||||
|
@ -33,7 +33,7 @@ IF "%1"=="--storageadapter" GOTO :RunStorageAdapter
|
|||
echo " Run the service inside a Docker container: ./scripts/run -s | --in-sandbox"
|
||||
echo " Run only the web service: ./scripts/run --webservice"
|
||||
echo " Run only the simulation: ./scripts/run --simulation"
|
||||
echo " Run the storage adapter dependency: ./scripts/run --storageadapter"
|
||||
echo " Run the storage dependency: ./scripts/run --storage"
|
||||
echo " Show how to use this script: ./scripts/run -h | --help"
|
||||
|
||||
goto :END
|
||||
|
|
Загрузка…
Ссылка в новой задаче