* 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:
Devis Lucato 2017-08-16 12:37:56 -07:00 коммит произвёл GitHub
Родитель f2c8d6e92d
Коммит 570645bf2e
79 изменённых файлов: 2042 добавлений и 973 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -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;
}

88
Services/data/devicemodels/scripts/prototype-01-state.js поставляемый Normal file
Просмотреть файл

@ -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