Methods and docs (#40)
This PR provides method support for chillers in the default simulation: reboot, firmware update, increase pressure, and decrease pressure. It also keeps reported properties in sync with desired properties (by polling for value changes). * DeviceMethod implmentation class * First round of methods, added: 1) infra for registering methods for devices, 2) walking avail. methods for each device. Main functionality missing is allowing specifation and then running the method script. * remove todo from actor * nits to code style * Move methods ownership from devicebootstrap to deviceclient. * error handling and todos. * retry method registration, minor fixes. * nits, logging, removed exception handling for registering methods (it's handled by the parent method) * bumped version * code style nits * Methods use scripts. Desired properties are polled in UpdateDeviceState and delta is copied into Reported property values. Telemetry is not sent if the device is marked other than online == "True" * code style/cleanup changes. change get query for simulations to linq query rather than iterating the list. * update version * Exception handling for updatedevicestate nit for rename method execution endpoint to include Async suffix. * nit fix comment code style * Inject scriptinterpreter. refactoring. * nits * state update bug * no need for scriptengine null check * Bootstrap individual methods, implement decreasepressure aka EMergencyValveRelease * remove connection string :( * fixed threading problem w/ methods, implemented & tested first four methods * Implement switch to turn random telemetry off; e.g. when increasepressure is called, wait until decreasepressure is called to turn it back on. * remove clearing of DeviceMethodStatus * Code style changes + fix some jslint errors * Fix js methods files * DeviceMethods code style changes * Devices.cs code style * JavascriptInterpreter.cs code style * script changes for messages, docs * Cleanup up Javascript functions, create Issue to track implementing functions not yet done. * Halve the amount of telemetry sent * Address PR comments. * issue for script interpreter todo * todos, nits, pr feedback
This commit is contained in:
Родитель
6ca39dd56c
Коммит
c33241a5cd
|
@ -8,6 +8,7 @@ using Microsoft.Azure.Devices.Client;
|
|||
using Microsoft.Azure.Devices.Shared;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
||||
|
@ -24,25 +25,24 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
|
||||
Task UpdateTwinAsync(Device device);
|
||||
|
||||
void RegisterMethodsForDevice(IDictionary<string, Script> methods);
|
||||
|
||||
|
||||
void RegisterMethodsForDevice(IDictionary<string, Script> methods, Dictionary<string, object> deviceState);
|
||||
}
|
||||
|
||||
public class DeviceClient : IDeviceClient
|
||||
{
|
||||
private const string DateFormat = "yyyy-MM-dd'T'HH:mm:sszzz";
|
||||
private const string DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:sszzz";
|
||||
|
||||
// See also https://github.com/Azure/toketi-iothubreact/blob/master/src/main/scala/com/microsoft/azure/iot/iothubreact/MessageFromDevice.scala
|
||||
private const string CreationTimeProperty = "$$CreationTimeUtc";
|
||||
private const string CREATION_TIME_PROPERTY = "$$CreationTimeUtc";
|
||||
|
||||
private const string MessageSchemaProperty = "$$MessageSchema";
|
||||
private const string ContentProperty = "$$ContentType";
|
||||
private const string MESSAGE_SCHEMA_PROPERTY = "$$MessageSchema";
|
||||
private const string CONTENT_PROPERTY = "$$ContentType";
|
||||
|
||||
private readonly Azure.Devices.Client.DeviceClient client;
|
||||
private readonly ILogger log;
|
||||
private readonly IoTHubProtocol protocol;
|
||||
private readonly string deviceId;
|
||||
private readonly IScriptInterpreter scriptInterpreter;
|
||||
|
||||
//used to create method pointers for the device for the IoTHub to callback to
|
||||
private DeviceMethods deviceMethods;
|
||||
|
@ -50,32 +50,41 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
public DeviceClient(
|
||||
Azure.Devices.Client.DeviceClient client,
|
||||
IoTHubProtocol protocol,
|
||||
ILogger logger,
|
||||
string deviceId)
|
||||
ILogger logger,
|
||||
string deviceId,
|
||||
IScriptInterpreter scriptInterpreter)
|
||||
{
|
||||
this.client = client;
|
||||
this.protocol = protocol;
|
||||
this.log = logger;
|
||||
this.deviceId = deviceId;
|
||||
this.scriptInterpreter = scriptInterpreter;
|
||||
}
|
||||
|
||||
public IoTHubProtocol Protocol { get { return this.protocol; } }
|
||||
|
||||
public void RegisterMethodsForDevice(IDictionary<string, Script> methods)
|
||||
public IoTHubProtocol Protocol
|
||||
{
|
||||
log.Debug("Attempting to setup methods for device", () => new
|
||||
get { return this.protocol; }
|
||||
}
|
||||
|
||||
public void RegisterMethodsForDevice(IDictionary<string, Script> methods,
|
||||
Dictionary<string, object> deviceState)
|
||||
{
|
||||
this.log.Debug("Attempting to setup methods for device", () => new
|
||||
{
|
||||
this.deviceId
|
||||
this.deviceId
|
||||
});
|
||||
deviceMethods = new DeviceMethods(this.client, log, methods, this.deviceId);
|
||||
|
||||
// TODO: Inject through the constructor instead
|
||||
this.deviceMethods = new DeviceMethods(this.client, this.log, methods, deviceState, this.deviceId,
|
||||
this.scriptInterpreter);
|
||||
}
|
||||
|
||||
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));
|
||||
eventMessage.Properties.Add(MessageSchemaProperty, schema.Name);
|
||||
eventMessage.Properties.Add(ContentProperty, "JSON");
|
||||
eventMessage.Properties.Add(CREATION_TIME_PROPERTY, DateTimeOffset.UtcNow.ToString(DATE_FORMAT));
|
||||
eventMessage.Properties.Add(MESSAGE_SCHEMA_PROPERTY, schema.Name);
|
||||
eventMessage.Properties.Add(CONTENT_PROPERTY, "JSON");
|
||||
|
||||
await this.SendRawMessageAsync(eventMessage);
|
||||
|
||||
|
@ -117,7 +126,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
var props = azureTwin.Properties.Reported.GetEnumerator();
|
||||
while (props.MoveNext())
|
||||
{
|
||||
var current = (KeyValuePair<string, object>)props.Current;
|
||||
var current = (KeyValuePair<string, object>) props.Current;
|
||||
|
||||
if (!device.Twin.ReportedProperties.ContainsKey(current.Key))
|
||||
{
|
||||
|
|
|
@ -2,100 +2,140 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Devices.Client;
|
||||
using Microsoft.Azure.Devices.Shared;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation;
|
||||
using System.Net;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
||||
{
|
||||
public interface IDeviceMethods
|
||||
{
|
||||
Task<MethodResponse> MethodExecution(MethodRequest methodRequest, object userContext);
|
||||
}
|
||||
|
||||
public class DeviceMethods : IDeviceMethods
|
||||
|
||||
public class DeviceMethods
|
||||
{
|
||||
private static readonly TimeSpan retryMethodCallbackRegistration = TimeSpan.FromSeconds(10);
|
||||
|
||||
private readonly Azure.Devices.Client.DeviceClient client;
|
||||
private readonly ILogger log;
|
||||
private readonly IScriptInterpreter scriptInterpreter;
|
||||
private IDictionary<string, Script> cloudToDeviceMethods;
|
||||
private string deviceId;
|
||||
private readonly IDictionary<string, Script> cloudToDeviceMethods;
|
||||
private readonly Dictionary<string, object> deviceState;
|
||||
private readonly string deviceId;
|
||||
|
||||
public DeviceMethods(
|
||||
Azure.Devices.Client.DeviceClient client,
|
||||
ILogger logger,
|
||||
IDictionary<string, Script> methods,
|
||||
string device)
|
||||
IDictionary<string, Script> methods,
|
||||
Dictionary<string, object> deviceState,
|
||||
string device,
|
||||
IScriptInterpreter scriptInterpreter)
|
||||
{
|
||||
this.client = client;
|
||||
this.log = logger;
|
||||
this.cloudToDeviceMethods = methods;
|
||||
this.deviceId = device;
|
||||
|
||||
this.deviceState = deviceState;
|
||||
this.scriptInterpreter = scriptInterpreter;
|
||||
this.SetupMethodCallbacksForDevice();
|
||||
}
|
||||
|
||||
public async Task<MethodResponse> MethodExecution(MethodRequest methodRequest, object userContext)
|
||||
|
||||
public async Task<MethodResponse> MethodHandlerAsync(MethodRequest methodRequest, object userContext)
|
||||
{
|
||||
this.log.Info("Creating task to execute method with json payload.", () => new
|
||||
{
|
||||
this.deviceId,
|
||||
methodName = methodRequest.Name,
|
||||
methodRequest.DataAsJson
|
||||
});
|
||||
|
||||
// Kick the method off on a separate thread & immediately return
|
||||
// Not immediately returning would block the client connection to the hub
|
||||
var t = Task.Run(() => this.MethodExecution(methodRequest));
|
||||
|
||||
return new MethodResponse(
|
||||
Encoding.UTF8.GetBytes("Executed Method:" + methodRequest.Name),
|
||||
(int) HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
private void MethodExecution(MethodRequest methodRequest)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.log.Info("Executing method with json payload.", () => new {methodRequest.Name,
|
||||
methodRequest.DataAsJson, this.deviceId});
|
||||
|
||||
//TODO: Use JavaScript engine to execute methods.
|
||||
//lock (actor.DeviceState)
|
||||
//{
|
||||
// actor.DeviceState = this.scriptInterpreter.Invoke(
|
||||
// this.deviceModel.Simulation.Script,
|
||||
// scriptContext,
|
||||
// actor.DeviceState);
|
||||
//}
|
||||
string result = "'I am the simulator. Someone called " + methodRequest.Name + ".'";
|
||||
this.log.Info("Executing method with json payload.", () => new
|
||||
{
|
||||
this.deviceId,
|
||||
methodName = methodRequest.Name,
|
||||
methodRequest.DataAsJson
|
||||
});
|
||||
|
||||
this.log.Info("Executed method.", () => new {methodRequest.Name});
|
||||
byte[] resultEncoded = Encoding.UTF8.GetBytes(result);
|
||||
return new MethodResponse(resultEncoded, (int)HttpStatusCode.OK);
|
||||
var scriptContext = new Dictionary<string, object>
|
||||
{
|
||||
["currentTime"] = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:sszzz"),
|
||||
["deviceId"] = this.deviceId,
|
||||
// TODO: add "deviceModel" so that the method scripts can use it like the "state" scripts
|
||||
//["deviceModel"] = this.device.
|
||||
};
|
||||
if (methodRequest.DataAsJson != "null")
|
||||
this.AddPayloadToContext(methodRequest.DataAsJson, scriptContext);
|
||||
|
||||
this.log.Debug("Executing method for device", () => new
|
||||
{
|
||||
this.deviceId,
|
||||
methodName = methodRequest.Name,
|
||||
this.deviceState
|
||||
});
|
||||
|
||||
// ignore the return state - state updates are handled by callbacks from the script
|
||||
this.scriptInterpreter.Invoke(
|
||||
this.cloudToDeviceMethods[methodRequest.Name],
|
||||
scriptContext,
|
||||
this.deviceState);
|
||||
|
||||
this.log.Debug("Executed method for device", () => new { this.deviceId, methodRequest.Name });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.log.Error("Error while executing method for device",
|
||||
() => new {
|
||||
methodRequest.Name,
|
||||
methodRequest.DataAsJson,
|
||||
() => new
|
||||
{
|
||||
this.deviceId,
|
||||
methodName = methodRequest.Name,
|
||||
methodRequest.DataAsJson,
|
||||
e
|
||||
});
|
||||
return new MethodResponse(Encoding.UTF8.GetBytes("Error while executing method for device"),
|
||||
(int)HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddPayloadToContext(string dataAsJson, Dictionary<string, object> scriptContext)
|
||||
{
|
||||
var values = JsonConvert.DeserializeObject<Dictionary<string, string>>(dataAsJson);
|
||||
foreach (var item in values)
|
||||
scriptContext.Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
private void SetupMethodCallbacksForDevice()
|
||||
{
|
||||
this.log.Debug("Setting up methods for device.", () => new {
|
||||
this.cloudToDeviceMethods.Count,
|
||||
this.deviceId
|
||||
this.log.Debug("Setting up methods for device.", () => new
|
||||
{
|
||||
this.deviceId,
|
||||
methodsCount = this.cloudToDeviceMethods.Count
|
||||
});
|
||||
//walk this list and add a method handler for each method specified
|
||||
|
||||
// walk this list and add a method handler for each method specified
|
||||
foreach (var item in this.cloudToDeviceMethods)
|
||||
{
|
||||
this.log.Debug("Setting up method for device.", () => new {item.Key,this.deviceId});
|
||||
this.log.Debug("Setting up method for device.", () => new { item.Key, this.deviceId });
|
||||
|
||||
this.client.SetMethodHandlerAsync(item.Key, MethodExecution, null).
|
||||
Wait(retryMethodCallbackRegistration);
|
||||
this.client.SetMethodHandlerAsync(item.Key, this.MethodHandlerAsync, null)
|
||||
.Wait(retryMethodCallbackRegistration);
|
||||
|
||||
this.log.Debug("Method for device setup successfully", () => new {
|
||||
item.Key,
|
||||
this.deviceId
|
||||
this.log.Debug("Method for device setup successfully", () => new
|
||||
{
|
||||
this.deviceId,
|
||||
methodName = item.Key
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
|
||||
public class DeviceModels : IDeviceModels
|
||||
{
|
||||
private const string Ext = ".json";
|
||||
private const string EXT = ".json";
|
||||
|
||||
private readonly IServicesConfig config;
|
||||
private readonly ILogger log;
|
||||
|
@ -69,10 +69,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
public DeviceModel Get(string id)
|
||||
{
|
||||
var list = this.GetList();
|
||||
foreach (var x in list)
|
||||
{
|
||||
if (x.Id == id) return x;
|
||||
}
|
||||
var item = list.FirstOrDefault(i => i.Id == id);
|
||||
if (item != null)
|
||||
return item;
|
||||
|
||||
this.log.Warn("Device model not found", () => new { id });
|
||||
|
||||
|
@ -87,7 +86,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
|
||||
var fileEntries = Directory.GetFiles(this.config.DeviceModelsFolder);
|
||||
|
||||
this.deviceModelFiles = fileEntries.Where(fileName => fileName.EndsWith(Ext)).ToList();
|
||||
this.deviceModelFiles = fileEntries.Where(fileName => fileName.EndsWith(EXT)).ToList();
|
||||
|
||||
this.log.Debug("Device model files", () => new { 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 Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation;
|
||||
using Device = Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models.Device;
|
||||
using TransportType = Microsoft.Azure.Devices.Client.TransportType;
|
||||
|
||||
|
@ -15,7 +16,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
public interface IDevices
|
||||
{
|
||||
Task<Tuple<bool, string>> PingRegistryAsync();
|
||||
IDeviceClient GetClient(Device device, IoTHubProtocol protocol);
|
||||
IDeviceClient GetClient(Device device, IoTHubProtocol protocol, IScriptInterpreter scriptInterpreter);
|
||||
Task<Device> GetOrCreateAsync(string deviceId);
|
||||
Task<Device> GetAsync(string deviceId);
|
||||
Task<Device> CreateAsync(string deviceId);
|
||||
|
@ -51,42 +52,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
}
|
||||
}
|
||||
|
||||
public IDeviceClient GetClient(Device device, IoTHubProtocol protocol)
|
||||
public IDeviceClient GetClient(Device device, IoTHubProtocol protocol, IScriptInterpreter scriptInterpreter)
|
||||
{
|
||||
var connectionString = $"HostName={device.IoTHubHostName};DeviceId={device.Id};SharedAccessKey={device.AuthPrimaryKey}";
|
||||
|
||||
Azure.Devices.Client.DeviceClient sdkClient;
|
||||
switch (protocol)
|
||||
{
|
||||
case IoTHubProtocol.AMQP:
|
||||
this.log.Debug("Creating AMQP device client",
|
||||
() => new { device.Id, device.IoTHubHostName });
|
||||
|
||||
sdkClient = Azure.Devices.Client.DeviceClient.CreateFromConnectionString(connectionString, TransportType.Amqp_Tcp_Only);
|
||||
break;
|
||||
|
||||
case IoTHubProtocol.MQTT:
|
||||
this.log.Debug("Creating MQTT device client",
|
||||
() => new { device.Id, device.IoTHubHostName });
|
||||
|
||||
sdkClient = Azure.Devices.Client.DeviceClient.CreateFromConnectionString(connectionString, TransportType.Mqtt_Tcp_Only);
|
||||
break;
|
||||
|
||||
case IoTHubProtocol.HTTP:
|
||||
this.log.Debug("Creating HTTP device client",
|
||||
() => new { device.Id, device.IoTHubHostName });
|
||||
|
||||
sdkClient = Azure.Devices.Client.DeviceClient.CreateFromConnectionString(connectionString, TransportType.Http1);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.log.Error("Unable to create a client for the given protocol",
|
||||
() => new { protocol });
|
||||
|
||||
throw new Exception($"Unable to create a client for the given protocol ({protocol})");
|
||||
}
|
||||
|
||||
return new DeviceClient(sdkClient, protocol, this.log, device.Id);
|
||||
Azure.Devices.Client.DeviceClient sdkClient = this.GetDeviceSDKClient(device, protocol);
|
||||
return new DeviceClient(sdkClient, protocol, this.log, device.Id, scriptInterpreter);
|
||||
}
|
||||
|
||||
public async Task<Device> GetOrCreateAsync(string deviceId)
|
||||
|
@ -146,10 +115,48 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
var azureTwin = await this.registry.GetTwinAsync(azureDevice.Id);
|
||||
|
||||
this.log.Debug("Writing device twin", () => new { azureDevice.Id });
|
||||
azureTwin.Tags[DeviceTwin.SimulatedTagKey] = DeviceTwin.SimulatedTagValue;
|
||||
azureTwin.Tags[DeviceTwin.SIMULATED_TAG_KEY] = DeviceTwin.SIMULATED_TAG_VALUE;
|
||||
azureTwin = await this.registry.UpdateTwinAsync(azureDevice.Id, azureTwin, "*");
|
||||
|
||||
return new Device(azureDevice, azureTwin, this.ioTHubHostName);
|
||||
}
|
||||
|
||||
private Azure.Devices.Client.DeviceClient GetDeviceSDKClient(Device device, IoTHubProtocol protocol)
|
||||
{
|
||||
var connectionString = $"HostName={device.IoTHubHostName};DeviceId={device.Id};SharedAccessKey={device.AuthPrimaryKey}";
|
||||
|
||||
Azure.Devices.Client.DeviceClient sdkClient;
|
||||
switch (protocol)
|
||||
{
|
||||
case IoTHubProtocol.AMQP:
|
||||
this.log.Debug("Creating AMQP device client",
|
||||
() => new { device.Id, device.IoTHubHostName });
|
||||
|
||||
sdkClient = Azure.Devices.Client.DeviceClient.CreateFromConnectionString(connectionString, TransportType.Amqp_Tcp_Only);
|
||||
break;
|
||||
|
||||
case IoTHubProtocol.MQTT:
|
||||
this.log.Debug("Creating MQTT device client",
|
||||
() => new { device.Id, device.IoTHubHostName });
|
||||
|
||||
sdkClient = Azure.Devices.Client.DeviceClient.CreateFromConnectionString(connectionString, TransportType.Mqtt_Tcp_Only);
|
||||
break;
|
||||
|
||||
case IoTHubProtocol.HTTP:
|
||||
this.log.Debug("Creating HTTP device client",
|
||||
() => new { device.Id, device.IoTHubHostName });
|
||||
|
||||
sdkClient = Azure.Devices.Client.DeviceClient.CreateFromConnectionString(connectionString, TransportType.Http1);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.log.Error("Unable to create a client for the given protocol",
|
||||
() => new { protocol });
|
||||
|
||||
throw new Exception($"Unable to create a client for the given protocol ({protocol})");
|
||||
}
|
||||
|
||||
return sdkClient;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.Devices;
|
||||
using Microsoft.Azure.Devices.Shared;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
|
|
@ -10,8 +10,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
|||
public class DeviceTwin
|
||||
{
|
||||
// Simulated devices are marked with a tag "IsSimulated = Y"
|
||||
public const string SimulatedTagKey = "IsSimulated";
|
||||
public const string SimulatedTagValue = "Y";
|
||||
public const string SIMULATED_TAG_KEY = "IsSimulated";
|
||||
|
||||
public const string SIMULATED_TAG_VALUE = "Y";
|
||||
|
||||
public string Etag { get; set; }
|
||||
public string DeviceId { get; set; }
|
||||
|
@ -29,29 +30,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
|||
this.Tags = TwinCollectionToDictionary(twin.Tags);
|
||||
this.DesiredProperties = TwinCollectionToDictionary(twin.Properties.Desired);
|
||||
this.ReportedProperties = TwinCollectionToDictionary(twin.Properties.Reported);
|
||||
this.IsSimulated = this.Tags.ContainsKey(SimulatedTagKey) && this.Tags[SimulatedTagKey].ToString() == SimulatedTagValue;
|
||||
this.IsSimulated = this.Tags.ContainsKey(SIMULATED_TAG_KEY) && this.Tags[SIMULATED_TAG_KEY].ToString() == SIMULATED_TAG_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: remove if not required
|
||||
public Twin ToAzureModel()
|
||||
{
|
||||
var properties = new TwinProperties
|
||||
{
|
||||
Desired = DictionaryToTwinCollection(this.DesiredProperties),
|
||||
Reported = DictionaryToTwinCollection(this.ReportedProperties),
|
||||
};
|
||||
|
||||
return new Twin(this.DeviceId)
|
||||
{
|
||||
ETag = this.Etag,
|
||||
Tags = DictionaryToTwinCollection(this.Tags),
|
||||
Properties = properties
|
||||
};
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
JValue: string, integer, float, boolean
|
||||
JArray: list, array
|
||||
|
@ -75,7 +57,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
|||
{
|
||||
if (twin.Value is JToken)
|
||||
{
|
||||
result.Add(twin.Key, (JToken)twin.Value);
|
||||
result.Add(twin.Key, (JToken) twin.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -36,6 +36,48 @@
|
|||
<None Update="data\devicemodels\prototype-02.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\EmptyTank-method.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\FillTank-method.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\RestartEngine-method.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\StartElevator-method.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\StartDevice-method.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\StopElevator-method.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\TempIncrease-method.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\TempDecrease-method.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\DecreasePressure-method.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\IncreasePressure-method.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\MoveDevice-method.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\StopDevice-method.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\FirmwareUpdate-method.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\scripts\reboot-method.js">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="data\devicemodels\truck-01.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
|
|
@ -4,6 +4,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Jint;
|
||||
using Jint.Native;
|
||||
using Jint.Runtime.Descriptors;
|
||||
|
@ -24,6 +25,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
{
|
||||
private readonly ILogger log;
|
||||
private readonly string folder;
|
||||
private Dictionary<string, object> deviceState;
|
||||
|
||||
public JavascriptInterpreter(
|
||||
IServicesConfig config,
|
||||
|
@ -43,18 +45,26 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
Dictionary<string, object> context,
|
||||
Dictionary<string, object> state)
|
||||
{
|
||||
this.deviceState = state;
|
||||
|
||||
var engine = new Engine();
|
||||
|
||||
// Inject the logger in the JS context, to allow the JS function
|
||||
// logging into the service logs
|
||||
engine.SetValue("log", new Action<object>(this.JsLog));
|
||||
|
||||
//register callback for state updates
|
||||
engine.SetValue("updateState", new Action<JsValue>(this.UpdateState));
|
||||
|
||||
//register sleep function for javascript use
|
||||
engine.SetValue("sleep", new Action<int>(this.Sleep));
|
||||
|
||||
var sourceCode = this.LoadScript(filename);
|
||||
this.log.Debug("Executing JS function", () => new { filename });
|
||||
|
||||
try
|
||||
{
|
||||
var output = engine.Execute(sourceCode).Invoke("main", context, state);
|
||||
var output = engine.Execute(sourceCode).Invoke("main", context, this.deviceState);
|
||||
var result = this.JsValueToDictionary(output);
|
||||
this.log.Debug("JS function success", () => new { filename, result });
|
||||
return result;
|
||||
|
@ -134,5 +144,38 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
{
|
||||
this.log.Debug("Log from JS", () => new { data });
|
||||
}
|
||||
|
||||
private void Sleep(int timeInMs)
|
||||
{
|
||||
Task.Delay(timeInMs).Wait();
|
||||
}
|
||||
|
||||
// TODO:Move this out of the scriptinterpreter class into DeviceClient to keep this class stateless
|
||||
// https://github.com/Azure/device-simulation-dotnet/issues/45
|
||||
private void UpdateState(JsValue data)
|
||||
{
|
||||
string key;
|
||||
object value;
|
||||
Dictionary<string, object> stateChanges;
|
||||
|
||||
this.log.Debug("Updating state from the script", () => new { data, this.deviceState });
|
||||
|
||||
stateChanges = this.JsValueToDictionary((JsValue) data);
|
||||
|
||||
//Update device state with the script data passed
|
||||
lock (this.deviceState)
|
||||
{
|
||||
for (int i = 0; i < stateChanges.Count; i++)
|
||||
{
|
||||
key = stateChanges.Keys.ElementAt(i);
|
||||
value = stateChanges.Values.ElementAt(i);
|
||||
if (this.deviceState.ContainsKey(key))
|
||||
{
|
||||
this.log.Debug("state change", () => new { key, value });
|
||||
this.deviceState[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,8 +36,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
switch (script.Type.ToLowerInvariant())
|
||||
{
|
||||
default:
|
||||
this.log.Error("Unknowne script type", () => new { script.Type });
|
||||
throw new NotSupportedException($"Unknowne script type `${script.Type}`.");
|
||||
this.log.Error("Unknown script type", () => new { script.Type });
|
||||
throw new NotSupportedException($"Unknown script type `${script.Type}`.");
|
||||
|
||||
case "javascript":
|
||||
this.log.Debug("Invoking JS", () => new { script.Path, context, state });
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions;
|
||||
|
@ -23,9 +22,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
|
||||
public class Simulations : ISimulations
|
||||
{
|
||||
private const string StorageCollection = "simulations";
|
||||
private const string SimulationId = "1";
|
||||
private const int DevicesPerModelInDefaultTemplate = 1;
|
||||
private const string STORAGE_COLLECTION = "simulations";
|
||||
private const string SIMULATION_ID = "1";
|
||||
private const int DEVICES_PER_MODEL_IN_DEFAULT_TEMPLATE = 1;
|
||||
|
||||
private readonly IDeviceModels deviceModels;
|
||||
private readonly IStorageAdapterClient storage;
|
||||
|
@ -43,7 +42,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
|
||||
public async Task<IList<Models.Simulation>> GetListAsync()
|
||||
{
|
||||
var data = await this.storage.GetAllAsync(StorageCollection);
|
||||
var data = await this.storage.GetAllAsync(STORAGE_COLLECTION);
|
||||
var result = new List<Models.Simulation>();
|
||||
foreach (var item in data.Items)
|
||||
{
|
||||
|
@ -57,7 +56,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
|
||||
public async Task<Models.Simulation> GetAsync(string id)
|
||||
{
|
||||
var item = await this.storage.GetAsync(StorageCollection, id);
|
||||
var item = await this.storage.GetAsync(STORAGE_COLLECTION, id);
|
||||
var simulation = JsonConvert.DeserializeObject<Models.Simulation>(item.Data);
|
||||
simulation.Etag = item.ETag;
|
||||
return simulation;
|
||||
|
@ -81,7 +80,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
}
|
||||
|
||||
// Note: forcing the ID because only one simulation can be created
|
||||
simulation.Id = SimulationId;
|
||||
simulation.Id = SIMULATION_ID;
|
||||
simulation.Created = DateTimeOffset.UtcNow;
|
||||
simulation.Modified = simulation.Created;
|
||||
simulation.Version = 1;
|
||||
|
@ -96,15 +95,15 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
simulation.DeviceModels.Add(new Models.Simulation.DeviceModelRef
|
||||
{
|
||||
Id = type.Id,
|
||||
Count = DevicesPerModelInDefaultTemplate
|
||||
Count = DEVICES_PER_MODEL_IN_DEFAULT_TEMPLATE
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Note: using UpdateAsync because the service generates the ID
|
||||
var result = await this.storage.UpdateAsync(
|
||||
StorageCollection,
|
||||
SimulationId,
|
||||
STORAGE_COLLECTION,
|
||||
SIMULATION_ID,
|
||||
JsonConvert.SerializeObject(simulation),
|
||||
"*");
|
||||
|
||||
|
@ -119,10 +118,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
/// </summary>
|
||||
public async Task<Models.Simulation> UpsertAsync(Models.Simulation simulation)
|
||||
{
|
||||
if (simulation.Id != SimulationId)
|
||||
if (simulation.Id != SIMULATION_ID)
|
||||
{
|
||||
this.log.Warn("Invalid simulation ID. Only one simulation is allowed", () => { });
|
||||
throw new InvalidInputException("Invalid simulation ID. Use ID '" + SimulationId + "'.");
|
||||
throw new InvalidInputException("Invalid simulation ID. Use ID '" + SIMULATION_ID + "'.");
|
||||
}
|
||||
|
||||
var simulations = await this.GetListAsync();
|
||||
|
@ -135,14 +134,14 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
}
|
||||
|
||||
// Note: forcing the ID because only one simulation can be created
|
||||
simulation.Id = SimulationId;
|
||||
simulation.Id = SIMULATION_ID;
|
||||
simulation.Created = DateTimeOffset.UtcNow;
|
||||
simulation.Modified = simulation.Created;
|
||||
simulation.Version = 1;
|
||||
|
||||
await this.storage.UpdateAsync(
|
||||
StorageCollection,
|
||||
SimulationId,
|
||||
STORAGE_COLLECTION,
|
||||
SIMULATION_ID,
|
||||
JsonConvert.SerializeObject(simulation),
|
||||
simulation.Etag);
|
||||
|
||||
|
@ -151,13 +150,13 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
|
||||
public async Task<Models.Simulation> MergeAsync(SimulationPatch patch)
|
||||
{
|
||||
if (patch.Id != SimulationId)
|
||||
if (patch.Id != SIMULATION_ID)
|
||||
{
|
||||
this.log.Warn("Invalid simulation ID.", () => new { patch.Id });
|
||||
throw new InvalidInputException("Invalid simulation ID. Use ID '" + SimulationId + "'.");
|
||||
throw new InvalidInputException("Invalid simulation ID. Use ID '" + SIMULATION_ID + "'.");
|
||||
}
|
||||
|
||||
var item = await this.storage.GetAsync(StorageCollection, patch.Id);
|
||||
var item = await this.storage.GetAsync(STORAGE_COLLECTION, patch.Id);
|
||||
var simulation = JsonConvert.DeserializeObject<Models.Simulation>(item.Data);
|
||||
simulation.Etag = item.ETag;
|
||||
|
||||
|
@ -181,8 +180,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
simulation.Version += 1;
|
||||
|
||||
item = await this.storage.UpdateAsync(
|
||||
StorageCollection,
|
||||
SimulationId,
|
||||
STORAGE_COLLECTION,
|
||||
SIMULATION_ID,
|
||||
JsonConvert.SerializeObject(simulation),
|
||||
patch.Etag);
|
||||
|
||||
|
@ -191,10 +190,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|||
return simulation;
|
||||
}
|
||||
|
||||
|
||||
public async Task DeleteAsync(string id)
|
||||
{
|
||||
await this.storage.DeleteAsync(StorageCollection, id);
|
||||
await this.storage.DeleteAsync(STORAGE_COLLECTION, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,16 +22,16 @@
|
|||
}
|
||||
},
|
||||
"Properties": {
|
||||
"Type": "Chiller",
|
||||
"Firmware": "1.0.0",
|
||||
"FirmwareUpdateStatus": "",
|
||||
"Location": "Building 43",
|
||||
"Latitude": 47.638928,
|
||||
"Longitude": -122.13476
|
||||
"Type": "Chiller",
|
||||
"Firmware": "1.0.0",
|
||||
"DeviceMethodStatus": "",
|
||||
"Location": "Building 43",
|
||||
"Latitude": 47.638928,
|
||||
"Longitude": -122.13476
|
||||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "chiller-temperature;v1",
|
||||
|
@ -43,7 +43,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"humidity\":${humidity},\"humidity_unit\":\"${humidity_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "chiller-humidity;v1",
|
||||
|
@ -55,7 +55,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"pressure\":${pressure},\"pressure_unit\":\"${pressure_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "chiller-pressure;v1",
|
||||
|
@ -70,19 +70,19 @@
|
|||
"CloudToDeviceMethods": {
|
||||
"Reboot": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
"Path": "Reboot-method.js"
|
||||
},
|
||||
"FirmwareUpdate": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
"Type": "javascript",
|
||||
"Path": "FirmwareUpdate-method.js"
|
||||
},
|
||||
"EmergencyValveRelease": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
"Type": "javascript",
|
||||
"Path": "DecreasePressure-method.js"
|
||||
},
|
||||
"IncreasePressure": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
"Type": "javascript",
|
||||
"Path": "IncreasePressure-method.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@
|
|||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "chiller-temperature;v1",
|
||||
|
@ -43,7 +43,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"humidity\":${humidity},\"humidity_unit\":\"${humidity_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "chiller-humidity;v1",
|
||||
|
@ -55,7 +55,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"pressure\":${pressure},\"pressure_unit\":\"${pressure_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "chiller-pressure;v1",
|
||||
|
@ -70,19 +70,19 @@
|
|||
"CloudToDeviceMethods": {
|
||||
"Reboot": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
"Path": "Reboot-method.js"
|
||||
},
|
||||
"FirmwareUpdate": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
"Type": "javascript",
|
||||
"Path": "FirmwareUpdate-method.js"
|
||||
},
|
||||
"EmergencyValveRelease": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
"Type": "javascript",
|
||||
"Path": "DecreasePressure-method.js"
|
||||
},
|
||||
"IncreasePressure": {
|
||||
"Type": "javascript",
|
||||
"Path": "TBD.js"
|
||||
"Type": "javascript",
|
||||
"Path": "IncreasePressure-method.js"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@
|
|||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"floor\":${floor}}",
|
||||
"MessageSchema": {
|
||||
"Name": "elevator-floor;v1",
|
||||
|
@ -39,7 +39,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"vibration\":${vibration},\"vibration_unit\":\"${vibration_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "elevator-vibration;v1",
|
||||
|
@ -51,7 +51,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "elevator-temperature;v1",
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"floor\":${floor}}",
|
||||
"MessageSchema": {
|
||||
"Name": "elevator-floor;v1",
|
||||
|
@ -39,7 +39,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"vibration\":${vibration},\"vibration_unit\":\"${vibration_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "elevator-vibration;v1",
|
||||
|
@ -51,7 +51,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "elevator-temperature;v1",
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"fuellevel\":${fuellevel},\"fuellevel_unit\":\"${fuellevel_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "engine-fuellevel;v1",
|
||||
|
@ -41,7 +41,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"coolant\":${coolant},\"coolant_unit\":\"${coolant_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "engine-coolant;v1",
|
||||
|
@ -53,7 +53,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:03",
|
||||
"Interval": "00:00:06",
|
||||
"MessageTemplate": "{\"vibration\":${vibration},\"vibration_unit\":\"${vibration_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "engine-vibration;v1",
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"fuellevel\":${fuellevel},\"fuellevel_unit\":\"${fuellevel_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "engine-fuellevel;v1",
|
||||
|
@ -41,7 +41,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"coolant\":${coolant},\"coolant_unit\":\"${coolant_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "engine-coolant;v1",
|
||||
|
@ -53,7 +53,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:03",
|
||||
"Interval": "00:00:06",
|
||||
"MessageTemplate": "{\"vibration\":${vibration},\"vibration_unit\":\"${vibration_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "engine-vibration;v1",
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "prototype-temperature;v1",
|
||||
|
@ -44,7 +44,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"pressure\":${pressure},\"pressure_unit\":\"${pressure_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "prototype-pressure;v1",
|
||||
|
@ -56,7 +56,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:03",
|
||||
"Interval": "00:00:06",
|
||||
"MessageTemplate": "{\"latitude\":${latitude},\"longitude\":${longitude}}",
|
||||
"MessageSchema": {
|
||||
"Name": "prototype-geolocation;v1",
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"temperature\":${temperature},\"temperature_unit\":\"${temperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "prototype-temperature;v1",
|
||||
|
@ -43,7 +43,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"pressure\":${pressure},\"pressure_unit\":\"${pressure_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "prototype-pressure;v1",
|
||||
|
@ -55,7 +55,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:03",
|
||||
"Interval": "00:00:06",
|
||||
"MessageTemplate": "{\"latitude\":${latitude},\"longitude\":${longitude}}",
|
||||
"MessageSchema": {
|
||||
"Name": "prototype-geolocation;v1",
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// Default state
|
||||
var state = {
|
||||
pressure: 250.0,
|
||||
CalculateRandomizedTelemetry: false
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
|
||||
// Reboot - devices goes offline and comes online after 20 seconds
|
||||
log("Executing DecreasePressure simulation function.");
|
||||
state.pressure = 150;
|
||||
state.CalculateRandomizedTelemetry = true;
|
||||
// update the state to 150
|
||||
updateState(state);
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
log("Executing JavaScript EmptyTank function.");
|
||||
|
||||
// TODO: This method is not implemented
|
||||
// https://github.com/Azure/device-simulation-dotnet/issues/44
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
log("Executing JavaScript FillTank function.");
|
||||
|
||||
// TODO: This method is not implemented
|
||||
// https://github.com/Azure/device-simulation-dotnet/issues/44
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// Default state
|
||||
var state = {
|
||||
online: true,
|
||||
Firmware: "1.0.0",
|
||||
DeviceMethodStatus: "Updating Firmware"
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device model and id, not used
|
||||
* @param previousState The device state since the last iteration, not used
|
||||
*/
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Reboot - devices goes offline and comes online after 20 seconds
|
||||
log("Executing firmware update simulation function, firmware version passed:" + context.Firmware);
|
||||
|
||||
// update the state to offline & firmware updating
|
||||
state.online = false;
|
||||
state.CalculateRandomizedTelemetry = false;
|
||||
var status = "Command received, updating firmware version to ";
|
||||
status = status.concat(context.Firmware);
|
||||
state.DeviceMethodStatus = status;
|
||||
updateState(state);
|
||||
sleep(5000);
|
||||
|
||||
log("Image Downloading...");
|
||||
state.DeviceMethodStatus = "Image Downloading...";
|
||||
updateState(state);
|
||||
sleep(10000);
|
||||
|
||||
log("Executing firmware update simulation function, firmware version passed:" + context.Firmware);
|
||||
state.DeviceMethodStatus = "Downloaded, applying firmware...";
|
||||
updateState(state);
|
||||
sleep(5000);
|
||||
|
||||
state.DeviceMethodStatus = "Rebooting...";
|
||||
updateState(state);
|
||||
sleep(5000);
|
||||
|
||||
state.DeviceMethodStatus = "Firmware Updated.";
|
||||
state.Firmware = context.Firmware;
|
||||
updateState(state);
|
||||
sleep(5000);
|
||||
|
||||
state.CalculateRandomizedTelemetry = true;
|
||||
state.online = true;
|
||||
updateState(state);
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// Default state
|
||||
var state = {
|
||||
pressure: 250.0,
|
||||
CalculateRandomizedTelemetry: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
*
|
||||
* @param context The context contains current time, device model and id, not used
|
||||
* @param previousState The device state since the last iteration, not used
|
||||
*/
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
// Reboot - devices goes offline and comes online after 20 seconds
|
||||
log("Executing IncreasePressure simulation function.");
|
||||
state.pressure = 250;
|
||||
state.CalculateRandomizedTelemetry = false;
|
||||
// update the state to 250
|
||||
updateState(state);
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
log("Executing JavaScript MoveDevice function.");
|
||||
|
||||
// TODO: This method is not implemented
|
||||
// https://github.com/Azure/device-simulation-dotnet/issues/44
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
log("Executing JavaScript RestartEngine function.");
|
||||
|
||||
// TODO: This method is not implemented
|
||||
// https://github.com/Azure/device-simulation-dotnet/issues/44
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
log("Executing JavaScript StartDevice function.");
|
||||
|
||||
// TODO: This method is not implemented
|
||||
// https://github.com/Azure/device-simulation-dotnet/issues/44
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
log("Executing JavaScript StartElevator function.");
|
||||
|
||||
// TODO: This method is not implemented
|
||||
// https://github.com/Azure/device-simulation-dotnet/issues/44
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
log("Executing JavaScript StopDevice function.");
|
||||
|
||||
// TODO: This method is not implemented
|
||||
// https://github.com/Azure/device-simulation-dotnet/issues/44
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
log("Executing JavaScript StopElevator function.");
|
||||
|
||||
// TODO: This method is not implemented
|
||||
// https://github.com/Azure/device-simulation-dotnet/issues/44
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
log("Executing JavaScript TempDecrease function.");
|
||||
|
||||
// TODO: This method is not implemented
|
||||
// https://github.com/Azure/device-simulation-dotnet/issues/44
|
||||
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
|
||||
log("Executing JavaScript TempIncrease function.");
|
||||
|
||||
// TODO: This method is not implemented
|
||||
// https://github.com/Azure/device-simulation-dotnet/issues/44
|
||||
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// Default state
|
||||
var state = {
|
||||
// reboot just changes whether the device is on or offline
|
||||
online: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Entry point function called by the method.
|
||||
*
|
||||
* @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) {
|
||||
|
||||
// Reboot - devices goes offline and comes online after 20 seconds
|
||||
log("Executing reboot simulation function.");
|
||||
|
||||
state.DeviceMethodStatus = "Rebooting device...";
|
||||
state.CalculateRandomizedTelemetry = false;
|
||||
state.online = false;
|
||||
// update the state to offline
|
||||
updateState(state);
|
||||
|
||||
// Sleep for 20 seconds
|
||||
sleep(20000);
|
||||
|
||||
state.DeviceMethodStatus = "Successfully rebooted device.";
|
||||
state.CalculateRandomizedTelemetry = true;
|
||||
state.online = true;
|
||||
// update the state back to online
|
||||
updateState(state);
|
||||
|
||||
}
|
|
@ -29,7 +29,7 @@
|
|||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:03",
|
||||
"Interval": "00:00:06",
|
||||
"MessageTemplate": "{\"latitude\":${latitude},\"longitude\":${longitude}}",
|
||||
"MessageSchema": {
|
||||
"Name": "truck-geolocation;v1",
|
||||
|
@ -41,7 +41,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"speed\":${speed},\"speed_unit\":\"${speed_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "truck-speed;v1",
|
||||
|
@ -53,7 +53,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"cargotemperature\":${cargotemperature},\"cargotemperature_unit\":\"${cargotemperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "truck-cargotemperature;v1",
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
},
|
||||
"Telemetry": [
|
||||
{
|
||||
"Interval": "00:00:03",
|
||||
"Interval": "00:00:06",
|
||||
"MessageTemplate": "{\"latitude\":${latitude},\"longitude\":${longitude}}",
|
||||
"MessageSchema": {
|
||||
"Name": "truck-geolocation;v1",
|
||||
|
@ -41,7 +41,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"speed\": ${speed},\"speed_unit\":\"${speed_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "truck-speed;v1",
|
||||
|
@ -53,7 +53,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"Interval": "00:00:05",
|
||||
"Interval": "00:00:10",
|
||||
"MessageTemplate": "{\"cargotemperature\":${cargotemperature},\"cargotemperature_unit\":\"${cargotemperature_unit}\"}",
|
||||
"MessageSchema": {
|
||||
"Name": "truck-cargotemperature;v1",
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"profiles": {
|
||||
"SimulationAgent": {
|
||||
"commandName": "Project",
|
||||
"environmentVariables": {
|
||||
"PCS_STORAGEADAPTER_WEBSERVICE_URL": "http://localhost:9022/v1",
|
||||
"PCS_IOTHUB_CONNSTRING": "EnterHubConnString"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -81,7 +81,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
|
||||
public class DeviceActor : IDeviceActor
|
||||
{
|
||||
private const string DeviceIdPrefix = "Simulated.";
|
||||
private const string DEVICE_ID_PREFIX = "Simulated.";
|
||||
|
||||
// When the actor fails to connect to IoT Hub, it retries every 10 seconds
|
||||
private static readonly TimeSpan retryConnectingFrequency = TimeSpan.FromSeconds(10);
|
||||
|
@ -130,6 +130,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
|
||||
// State machine logic, each of the following has a Run() method
|
||||
private readonly Connect connectLogic;
|
||||
|
||||
private readonly UpdateDeviceState updateDeviceStateLogic;
|
||||
private readonly DeviceBootstrap deviceBootstrapLogic;
|
||||
private readonly SendTelemetry sendTelemetryLogic;
|
||||
|
@ -208,11 +209,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
|
||||
this.setupDone = true;
|
||||
|
||||
this.deviceId = DeviceIdPrefix + deviceModel.Id + "." + position;
|
||||
this.deviceId = DEVICE_ID_PREFIX + deviceModel.Id + "." + position;
|
||||
this.messages = deviceModel.Telemetry;
|
||||
|
||||
this.deviceStateInterval = deviceModel.Simulation.Script.Interval;
|
||||
this.DeviceState = CloneObject(deviceModel.Simulation.InitialState);
|
||||
this.DeviceState = this.SetupTelemetryAndProperties(deviceModel);
|
||||
this.log.Debug("Initial device state", () => new { this.deviceId, this.DeviceState });
|
||||
|
||||
this.connectLogic.Setup(this.deviceId, deviceModel);
|
||||
|
@ -226,6 +227,25 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
return this;
|
||||
}
|
||||
|
||||
private Dictionary<string, object> SetupTelemetryAndProperties(DeviceModel deviceModel)
|
||||
{
|
||||
// put telemetry properties in state
|
||||
Dictionary<string, object> state = CloneObject(deviceModel.Simulation.InitialState);
|
||||
|
||||
//TODO: think about whether these should be pulled from the hub instead of disk
|
||||
//(the device model); i.e. what if someone has modified the hub twin directly
|
||||
// put reported properties from device model into state
|
||||
foreach (var property in deviceModel.Properties)
|
||||
state.Add(property.Key, property.Value);
|
||||
|
||||
//TODO:This is used to control whether telemetry is calculated in UpdateDeviceState.
|
||||
//methods can turn telemetry off/on; e.g. setting temp high- turnoff, set low, turn on
|
||||
//it would be better to do this at the telemetry item level - we should add this in the future
|
||||
state.Add("CalculateRandomizedTelemetry", true);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this method to start the simulated device, e.g. sending
|
||||
/// messages and responding to method calls.
|
||||
|
|
|
@ -6,6 +6,7 @@ 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 Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulation.DeviceStatusLogic
|
||||
{
|
||||
|
@ -23,16 +24,19 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
private readonly ILogger log;
|
||||
private string deviceId;
|
||||
private IoTHubProtocol? protocol;
|
||||
private readonly IScriptInterpreter scriptInterpreter;
|
||||
|
||||
// Ensure that setup is called once and only once (which helps also detecting thread safety issues)
|
||||
private bool setupDone = false;
|
||||
|
||||
public Connect(
|
||||
IDevices devices,
|
||||
ILogger logger)
|
||||
ILogger logger,
|
||||
IScriptInterpreter scriptInterpreter)
|
||||
{
|
||||
this.log = logger;
|
||||
this.devices = devices;
|
||||
this.scriptInterpreter = scriptInterpreter;
|
||||
}
|
||||
|
||||
public void Setup(string deviceId, DeviceModel deviceModel)
|
||||
|
@ -76,7 +80,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
|
||||
this.log.Debug("Device credentials retrieved", () => new { this.deviceId });
|
||||
|
||||
actor.Client = this.devices.GetClient(device, this.protocol.Value);
|
||||
actor.Client = this.devices.GetClient(device, this.protocol.Value, this.scriptInterpreter);
|
||||
|
||||
// Device Twin properties can be set only over MQTT, so we need a dedicated client
|
||||
// for the bootstrap
|
||||
|
@ -86,7 +90,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
}
|
||||
else
|
||||
{
|
||||
actor.BootstrapClient = this.devices.GetClient(device, IoTHubProtocol.MQTT);
|
||||
// bootstrap client is used to call methods and must have a script interpreter associated w/ it.
|
||||
actor.BootstrapClient = this.devices.GetClient(device, IoTHubProtocol.MQTT, this.scriptInterpreter);
|
||||
}
|
||||
|
||||
this.log.Debug("Connection successful", () => new { this.deviceId });
|
||||
|
|
|
@ -40,8 +40,8 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
() => new { this.deviceId });
|
||||
throw new DeviceActorAlreadyInitializedException();
|
||||
}
|
||||
|
||||
this.setupDone = true;
|
||||
|
||||
this.deviceId = deviceId;
|
||||
this.deviceModel = deviceModel;
|
||||
}
|
||||
|
@ -66,8 +66,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
}
|
||||
|
||||
// register methods for the device
|
||||
actor.BootstrapClient.RegisterMethodsForDevice(deviceModel.CloudToDeviceMethods);
|
||||
|
||||
actor.BootstrapClient.RegisterMethodsForDevice(this.deviceModel.CloudToDeviceMethods, actor.DeviceState);
|
||||
actor.MoveNext();
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
@ -56,29 +56,39 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
// Send the telemetry message
|
||||
try
|
||||
{
|
||||
// Inject the device state into the message template
|
||||
var msg = message.MessageTemplate;
|
||||
lock (actor.DeviceState)
|
||||
this.log.Debug("Checking to see if device is online", () => new { this.deviceId });
|
||||
if ((bool) actor.DeviceState["online"])
|
||||
{
|
||||
foreach (var value in actor.DeviceState)
|
||||
// Inject the device state into the message template
|
||||
var msg = message.MessageTemplate;
|
||||
lock (actor.DeviceState)
|
||||
{
|
||||
msg = msg.Replace("${" + value.Key + "}", value.Value.ToString());
|
||||
foreach (var value in actor.DeviceState)
|
||||
{
|
||||
msg = msg.Replace("${" + value.Key + "}", value.Value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.log.Debug("SendTelemetry...",
|
||||
() => new { this.deviceId, MessageSchema = message.MessageSchema.Name, msg });
|
||||
actor.Client
|
||||
.SendMessageAsync(msg, message.MessageSchema)
|
||||
.Wait(connectionTimeout);
|
||||
this.log.Debug("SendTelemetry...",
|
||||
() => new { this.deviceId, MessageSchema = message.MessageSchema.Name, msg });
|
||||
actor.Client
|
||||
.SendMessageAsync(msg, message.MessageSchema)
|
||||
.Wait(connectionTimeout);
|
||||
|
||||
this.log.Debug("SendTelemetry complete", () => new { this.deviceId });
|
||||
}
|
||||
else
|
||||
{
|
||||
// device could be rebooting, updating firmware, etc.
|
||||
this.log.Debug("No telemetry will be sent as the device is not online...",
|
||||
() => new { this.deviceId, actor.DeviceState });
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.log.Error("SendTelemetry failed",
|
||||
() => new { this.deviceId, e.Message, Error = e.GetType().FullName });
|
||||
}
|
||||
|
||||
this.log.Debug("SendTelemetry complete", () => new { this.deviceId });
|
||||
}
|
||||
|
||||
private void ValidateSetup()
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
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.Services.Simulation;
|
||||
|
@ -15,20 +17,26 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
/// </summary>
|
||||
public class UpdateDeviceState : IDeviceStatusLogic
|
||||
{
|
||||
// When connecting to IoT Hub, timeout after 10 seconds
|
||||
private static readonly TimeSpan connectionTimeout = TimeSpan.FromSeconds(10);
|
||||
|
||||
private readonly IScriptInterpreter scriptInterpreter;
|
||||
private readonly ILogger log;
|
||||
private string deviceId;
|
||||
private DeviceModel deviceModel;
|
||||
private IDevices devices;
|
||||
|
||||
// Ensure that setup is called once and only once (which helps also detecting thread safety issues)
|
||||
private bool setupDone = false;
|
||||
|
||||
public UpdateDeviceState(
|
||||
IDevices devices,
|
||||
IScriptInterpreter scriptInterpreter,
|
||||
ILogger logger)
|
||||
{
|
||||
this.scriptInterpreter = scriptInterpreter;
|
||||
this.log = logger;
|
||||
this.devices = devices;
|
||||
}
|
||||
|
||||
public void Setup(string deviceId, DeviceModel deviceModel)
|
||||
|
@ -56,30 +64,125 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
return;
|
||||
}
|
||||
|
||||
var scriptContext = new Dictionary<string, object>
|
||||
// Compute new telemetry, find updated desired properties, push new reported property values.
|
||||
try
|
||||
{
|
||||
["currentTime"] = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:sszzz"),
|
||||
["deviceId"] = this.deviceId,
|
||||
["deviceModel"] = this.deviceModel.Name
|
||||
};
|
||||
var scriptContext = new Dictionary<string, object>
|
||||
{
|
||||
["currentTime"] = DateTimeOffset.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:sszzz"),
|
||||
["deviceId"] = this.deviceId,
|
||||
["deviceModel"] = this.deviceModel.Name
|
||||
};
|
||||
|
||||
this.log.Debug("Updating device status", () => new { this.deviceId, deviceState = actor.DeviceState });
|
||||
// until the correlating function has been called; e.g. when increasepressure is called, don't write
|
||||
// telemetry until decreasepressure is called for that property.
|
||||
// TODO: make this property a constant
|
||||
// https://github.com/Azure/device-simulation-dotnet/issues/46
|
||||
if ((bool) actor.DeviceState["CalculateRandomizedTelemetry"])
|
||||
{
|
||||
this.log.Debug("Updating device telemetry data", () => new { this.deviceId, deviceState = actor.DeviceState });
|
||||
lock (actor.DeviceState)
|
||||
{
|
||||
actor.DeviceState = this.scriptInterpreter.Invoke(
|
||||
this.deviceModel.Simulation.Script,
|
||||
scriptContext,
|
||||
actor.DeviceState);
|
||||
}
|
||||
this.log.Debug("New device telemetry data", () => new { this.deviceId, deviceState = actor.DeviceState });
|
||||
}
|
||||
else
|
||||
{
|
||||
this.log.Debug(
|
||||
"Random telemetry generation is turned off for the actor",
|
||||
() => new { this.deviceId, deviceState = actor.DeviceState });
|
||||
}
|
||||
|
||||
lock (actor.DeviceState)
|
||||
this.log.Debug(
|
||||
"Checking for desired property updates & updated reported properties",
|
||||
() => new { this.deviceId, deviceState = actor.DeviceState });
|
||||
|
||||
// Get device
|
||||
var device = this.GetDevice(actor.CancellationToken);
|
||||
lock (actor.DeviceState)
|
||||
{
|
||||
// TODO: the device state update should be an in-memory task without network access, so we should move this
|
||||
// logic out to a separate task/thread - https://github.com/Azure/device-simulation-dotnet/issues/47
|
||||
// check for differences between reported/desired properties,
|
||||
// update reported properties with any state changes (either from desired prop changes, methods, etc.)
|
||||
if (this.ChangeTwinPropertiesToMatchDesired(device, actor.DeviceState)
|
||||
|| this.ChangeTwinPropertiesToMatchActorState(device, actor.DeviceState))
|
||||
actor.BootstrapClient.UpdateTwinAsync(device).Wait((int) connectionTimeout.TotalMilliseconds);
|
||||
}
|
||||
|
||||
// Start sending telemetry messages
|
||||
if (actor.ActorStatus == Status.UpdatingDeviceState)
|
||||
{
|
||||
actor.MoveNext();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.log.Debug(
|
||||
"Already sending telemetry, running local simulation and watching desired property changes",
|
||||
() => new { this.deviceId });
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
actor.DeviceState = this.scriptInterpreter.Invoke(
|
||||
this.deviceModel.Simulation.Script,
|
||||
scriptContext,
|
||||
actor.DeviceState);
|
||||
this.log.Error("UpdateDeviceState failed",
|
||||
() => new { this.deviceId, e.Message, Error = e.GetType().FullName });
|
||||
}
|
||||
}
|
||||
|
||||
private bool ChangeTwinPropertiesToMatchActorState(Device device, Dictionary<string, object> actorState)
|
||||
{
|
||||
bool differences = false;
|
||||
|
||||
foreach (var item in actorState)
|
||||
{
|
||||
if (device.Twin.ReportedProperties.ContainsKey(item.Key))
|
||||
{
|
||||
if (device.Twin.ReportedProperties[item.Key].ToString() != actorState[item.Key].ToString())
|
||||
{
|
||||
// Update the Hub twin to match the actor state
|
||||
device.Twin.ReportedProperties[item.Key] = actorState[item.Key].ToString();
|
||||
differences = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.log.Debug("New device status", () => new { this.deviceId, deviceState = actor.DeviceState });
|
||||
return differences;
|
||||
}
|
||||
|
||||
// Start sending telemetry messages
|
||||
if (actor.ActorStatus == Status.UpdatingDeviceState)
|
||||
private bool ChangeTwinPropertiesToMatchDesired(Device device, Dictionary<string, object> actorState)
|
||||
{
|
||||
bool differences = false;
|
||||
|
||||
foreach (var item in device.Twin.DesiredProperties)
|
||||
{
|
||||
actor.MoveNext();
|
||||
if (device.Twin.ReportedProperties.ContainsKey(item.Key))
|
||||
{
|
||||
if (device.Twin.ReportedProperties[item.Key].ToString() != device.Twin.DesiredProperties[item.Key].ToString())
|
||||
{
|
||||
// update the hub reported property to match match hub desired property
|
||||
device.Twin.ReportedProperties[item.Key] = device.Twin.DesiredProperties[item.Key];
|
||||
|
||||
// update actor state property to match hub desired changes
|
||||
if (actorState.ContainsKey(item.Key))
|
||||
actorState[item.Key] = device.Twin.DesiredProperties[item.Key];
|
||||
|
||||
differences = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return differences;
|
||||
}
|
||||
|
||||
private Device GetDevice(CancellationToken token)
|
||||
{
|
||||
var task = this.devices.GetAsync(this.deviceId);
|
||||
task.Wait((int) connectionTimeout.TotalMilliseconds, token);
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
private void ValidateSetup()
|
||||
|
|
|
@ -16,7 +16,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
|
||||
public class Simulation : ISimulation
|
||||
{
|
||||
private const int CheckInterval = 3000;
|
||||
private const int CHECK_INTERVAL = 3000;
|
||||
|
||||
private readonly ILogger log;
|
||||
private readonly ISimulations simulations;
|
||||
|
@ -59,7 +59,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
this.runner.Stop();
|
||||
}
|
||||
|
||||
Thread.Sleep(CheckInterval);
|
||||
Thread.Sleep(CHECK_INTERVAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +1,30 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:9003/",
|
||||
"sslPort": 0
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "http://localhost:9003/v1/status",
|
||||
"environmentVariables": {
|
||||
"PCS_IOTHUB_CONNSTRING": "HostName={Your Azure IoT Hub hostname};SharedAccessKeyName=iothubowner;SharedAccessKey={SECRET KEY}"
|
||||
}
|
||||
},
|
||||
"WebService": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "http://localhost:9003/v1/status",
|
||||
"environmentVariables": {
|
||||
"PCS_IOTHUB_CONNSTRING": "HostName={Your Azure IoT Hub hostname};SharedAccessKeyName=iothubowner;SharedAccessKey={SECRET KEY}"
|
||||
},
|
||||
"applicationUrl": "http://localhost:9003/v1/status"
|
||||
}
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:9003/",
|
||||
"sslPort": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "http://localhost:9003/v1/status",
|
||||
"environmentVariables": {
|
||||
"PCS_STORAGEADAPTER_WEBSERVICE_URL": "http://localhost:9022/v1",
|
||||
"PCS_IOTHUB_CONNSTRING": "EnterHubConnString"
|
||||
}
|
||||
},
|
||||
"WebService": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "http://localhost:9003/v1/status",
|
||||
"environmentVariables": {
|
||||
"PCS_IOTHUB_CONNSTRING": "HostName={Your Azure IoT Hub hostname};SharedAccessKeyName=iothubowner;SharedAccessKey={SECRET KEY}"
|
||||
},
|
||||
"applicationUrl": "http://localhost:9003/v1/status"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,7 +19,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "solution", "solution", "{71
|
|||
.gitignore = .gitignore
|
||||
.travis.yml = .travis.yml
|
||||
CONTRIBUTING.md = CONTRIBUTING.md
|
||||
DEVELOPMENT.md = DEVELOPMENT.md
|
||||
device-simulation.sln.DotSettings = device-simulation.sln.DotSettings
|
||||
LICENSE = LICENSE
|
||||
README.md = README.md
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MQTT/@EntryIndexedValue">MQTT</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SSL/@EntryIndexedValue">SSL</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=UID/@EntryIndexedValue">UID</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Constants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
|
|
2
version
2
version
|
@ -1 +1 @@
|
|||
0.1.7
|
||||
0.1.8
|
||||
|
|
Загрузка…
Ссылка в новой задаче