2017-05-31 05:50:03 +03:00
|
|
|
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
|
|
|
|
using System;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
2018-05-23 03:24:57 +03:00
|
|
|
using System.Threading.Tasks;
|
2017-07-07 23:49:03 +03:00
|
|
|
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
|
2017-05-31 05:50:03 +03:00
|
|
|
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions;
|
|
|
|
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
|
2018-06-21 01:56:58 +03:00
|
|
|
using Newtonsoft.Json.Linq;
|
2017-05-31 05:50:03 +03:00
|
|
|
|
|
|
|
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
|
|
|
|
{
|
2017-08-16 22:37:56 +03:00
|
|
|
public interface IDeviceModels
|
2017-05-31 05:50:03 +03:00
|
|
|
{
|
2018-05-23 03:24:57 +03:00
|
|
|
/// <summary>
|
|
|
|
/// Get list of device models.
|
|
|
|
/// </summary>
|
|
|
|
Task<IEnumerable<DeviceModel>> GetListAsync();
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Get a device model.
|
|
|
|
/// </summary>
|
|
|
|
Task<DeviceModel> GetAsync(string id);
|
|
|
|
|
2018-10-06 00:39:33 +03:00
|
|
|
/// <summary>
|
|
|
|
/// Get a device model and apply the overrides from the given simulation.
|
|
|
|
/// </summary>
|
|
|
|
Task<DeviceModel> GetWithOverrideAsync(string id, Models.Simulation simulation);
|
|
|
|
|
2018-05-23 03:24:57 +03:00
|
|
|
/// <summary>
|
|
|
|
/// Create a device model.
|
|
|
|
/// </summary>
|
|
|
|
Task<DeviceModel> InsertAsync(DeviceModel deviceModel);
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Create or replace a device model.
|
|
|
|
/// </summary>
|
|
|
|
Task<DeviceModel> UpsertAsync(DeviceModel deviceModel);
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Delete a custom device model.
|
|
|
|
/// </summary>
|
|
|
|
Task DeleteAsync(string id);
|
2018-06-21 01:56:58 +03:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Get property names from all device models.
|
|
|
|
/// </summary>
|
|
|
|
Task<List<string>> GetPropertyNamesAsync();
|
2017-05-31 05:50:03 +03:00
|
|
|
}
|
|
|
|
|
2018-05-23 03:24:57 +03:00
|
|
|
/// <summary>
|
|
|
|
/// Proxy to stock and custom device models services.
|
|
|
|
/// Note: the exceptions are generated in the stock and custom device
|
|
|
|
/// models services and surface as-is without any more catch/wrapping here.
|
|
|
|
/// </summary>
|
2017-08-16 22:37:56 +03:00
|
|
|
public class DeviceModels : IDeviceModels
|
2017-05-31 05:50:03 +03:00
|
|
|
{
|
Merge azure-iot-pcs-simulation into master (#197)
* Rewrite to use fewer threads and increase throughput
* Expose simulation metrics in the status endpoint
* When available, return a URL to the Azure Portal metrics of the pre-provisioned IoT Hub
* Allow to run a simulation against a custom IoT Hub, passing the connection string
* Allow to schedule a simulation, defining start and end time
* Improve security by running the service as a non-root user
* Support multiple behavior scripts in a device model
* Support custom device with custom sensors, behavior and frequency
* Allow to override the initial device state when creating a simulation
* When a simulation starts, create all the devices in batches
* When a simulation is deleted, delete also the devices
* Refactor timers to start sending telemetry as soon as possible
* Refactor and improve how simulation scripts access state and properties
* Change stock models to use AMQP (#189)
* Overall improvements to exceptions handling
* Disable SDK retry, and change timeout from default (4 minutes) to 10 seconds
* Do not retry sending telemetry on failure, skip message
* Use IoT Hub S2 SKU limits by default
* Upgrade to .NET Core 2.0.3
* Upgrade Azure IoT SDK: Devices 1.4.1 to 1.6.0, Devices Client 1.5.2 to 1.7.0
* Run simulation engine in the same process used for the web service
* Move docs, move wiki, new API specs, fix scripts for Windows Bash
* Fix the spelling of “ETag” and allow ETag=*
* Add internal scripts for increasing/decreasing/random telemetry
* Add env vars documentation ENVIRONMENT_VARIABLES.md
* Add more optional logging by device and by actor
* Use logging level and other logging settings from configuration
* Adjust unit test precision to mitigate flaky test
* Add system properties to telemetry messages
* Removing the squash flag from Docker scripts
* Use env vars in launchSettings.json
2018-04-13 03:42:46 +03:00
|
|
|
// ID used for custom device models, where the list of sensors is provided by the user
|
|
|
|
public const string CUSTOM_DEVICE_MODEL_ID = "custom";
|
|
|
|
|
2018-10-06 00:39:33 +03:00
|
|
|
private const string REPORTED_PREFIX = "Properties.Reported.";
|
|
|
|
|
2017-07-07 23:49:03 +03:00
|
|
|
private readonly ILogger log;
|
2018-05-23 03:24:57 +03:00
|
|
|
private readonly ICustomDeviceModels customDeviceModels;
|
|
|
|
private readonly IStockDeviceModels stockDeviceModels;
|
2018-10-06 00:39:33 +03:00
|
|
|
private readonly IDeviceModelsGeneration deviceModelsOverriding;
|
2017-05-31 05:50:03 +03:00
|
|
|
|
2017-08-16 22:37:56 +03:00
|
|
|
public DeviceModels(
|
2018-05-23 03:24:57 +03:00
|
|
|
ICustomDeviceModels customDeviceModels,
|
|
|
|
IStockDeviceModels stockDeviceModels,
|
2018-10-06 00:39:33 +03:00
|
|
|
IDeviceModelsGeneration deviceModelsOverriding,
|
2017-07-07 23:49:03 +03:00
|
|
|
ILogger logger)
|
2017-05-31 05:50:03 +03:00
|
|
|
{
|
2018-05-23 03:24:57 +03:00
|
|
|
this.stockDeviceModels = stockDeviceModels;
|
|
|
|
this.customDeviceModels = customDeviceModels;
|
2018-10-06 00:39:33 +03:00
|
|
|
this.deviceModelsOverriding = deviceModelsOverriding;
|
|
|
|
this.log = logger;
|
2017-05-31 05:50:03 +03:00
|
|
|
}
|
|
|
|
|
2018-05-23 03:24:57 +03:00
|
|
|
/// <summary>
|
|
|
|
/// Get list of device models.
|
|
|
|
/// </summary>
|
|
|
|
public async Task<IEnumerable<DeviceModel>> GetListAsync()
|
2017-05-31 05:50:03 +03:00
|
|
|
{
|
2018-05-23 03:24:57 +03:00
|
|
|
var stockDeviceModelsList = this.stockDeviceModels.GetList();
|
|
|
|
var customDeviceModelsList = await this.customDeviceModels.GetListAsync();
|
|
|
|
var deviceModels = stockDeviceModelsList
|
|
|
|
.Concat(customDeviceModelsList)
|
|
|
|
.ToList();
|
2017-07-07 23:49:03 +03:00
|
|
|
|
2018-05-23 03:24:57 +03:00
|
|
|
return deviceModels;
|
2017-05-31 05:50:03 +03:00
|
|
|
}
|
|
|
|
|
2018-05-23 03:24:57 +03:00
|
|
|
/// <summary>
|
|
|
|
/// Get a device model.
|
|
|
|
/// </summary>
|
|
|
|
public async Task<DeviceModel> GetAsync(string id)
|
2017-05-31 05:50:03 +03:00
|
|
|
{
|
2018-05-23 03:24:57 +03:00
|
|
|
var list = await this.GetListAsync();
|
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
2017-09-08 01:08:26 +03:00
|
|
|
var item = list.FirstOrDefault(i => i.Id == id);
|
|
|
|
if (item != null)
|
|
|
|
return item;
|
2017-05-31 06:05:48 +03:00
|
|
|
|
2017-08-16 22:37:56 +03:00
|
|
|
this.log.Warn("Device model not found", () => new { id });
|
2017-07-07 23:49:03 +03:00
|
|
|
|
2017-05-31 06:05:48 +03:00
|
|
|
throw new ResourceNotFoundException();
|
2017-05-31 05:50:03 +03:00
|
|
|
}
|
|
|
|
|
2018-10-06 00:39:33 +03:00
|
|
|
/// <summary>
|
|
|
|
/// Get a device model and apply the overrides from the given simulation.
|
|
|
|
/// </summary>
|
|
|
|
public async Task<DeviceModel> GetWithOverrideAsync(string id, Models.Simulation simulation)
|
|
|
|
{
|
|
|
|
var modelDef = new DeviceModel();
|
|
|
|
|
|
|
|
var equals = new Func<string, string, bool>(
|
|
|
|
(x, y) => string.Equals(x, y, StringComparison.InvariantCultureIgnoreCase));
|
|
|
|
|
|
|
|
if (equals(id, CUSTOM_DEVICE_MODEL_ID))
|
|
|
|
{
|
|
|
|
modelDef.Id = CUSTOM_DEVICE_MODEL_ID;
|
|
|
|
modelDef.Name = CUSTOM_DEVICE_MODEL_ID;
|
|
|
|
modelDef.Description = "Simulated device with custom list of sensors";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
modelDef = await this.GetAsync(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
Models.Simulation.DeviceModelOverride overrides = simulation.DeviceModels
|
|
|
|
.Where(x => equals(x.Id, id))
|
|
|
|
.Select(x => x.Override).FirstOrDefault();
|
|
|
|
|
|
|
|
return this.deviceModelsOverriding.Generate(modelDef, overrides);
|
|
|
|
}
|
|
|
|
|
2018-05-23 03:24:57 +03:00
|
|
|
/// <summary>
|
|
|
|
/// Create a device model.
|
|
|
|
/// </summary>
|
|
|
|
public async Task<DeviceModel> InsertAsync(DeviceModel deviceModel)
|
2017-05-31 05:50:03 +03:00
|
|
|
{
|
2018-05-23 03:24:57 +03:00
|
|
|
if (this.CheckStockDeviceModelExistence(deviceModel.Id))
|
|
|
|
{
|
|
|
|
throw new ConflictingResourceException(
|
|
|
|
"Device model with id '" + deviceModel.Id + "' already exists!");
|
|
|
|
}
|
2017-05-31 05:50:03 +03:00
|
|
|
|
2018-05-23 03:24:57 +03:00
|
|
|
var result = await this.customDeviceModels.InsertAsync(deviceModel);
|
|
|
|
deviceModel.ETag = result.ETag;
|
2017-07-07 23:49:03 +03:00
|
|
|
|
2018-05-23 03:24:57 +03:00
|
|
|
return deviceModel;
|
|
|
|
}
|
2017-05-31 06:05:48 +03:00
|
|
|
|
2018-05-23 03:24:57 +03:00
|
|
|
/// <summary>
|
|
|
|
/// Create or replace a device model.
|
|
|
|
/// </summary>
|
|
|
|
public async Task<DeviceModel> UpsertAsync(DeviceModel deviceModel)
|
|
|
|
{
|
|
|
|
if (this.CheckStockDeviceModelExistence(deviceModel.Id))
|
|
|
|
{
|
|
|
|
this.log.Error("Stock device models cannot be updated",
|
|
|
|
() => new { deviceModel });
|
|
|
|
throw new ConflictingResourceException(
|
|
|
|
"Stock device models cannot be updated");
|
|
|
|
}
|
|
|
|
|
|
|
|
var result = await this.customDeviceModels.UpsertAsync(deviceModel);
|
|
|
|
deviceModel.ETag = result.ETag;
|
|
|
|
|
|
|
|
return deviceModel;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Delete a custom device model.
|
|
|
|
/// </summary>
|
|
|
|
public async Task DeleteAsync(string id)
|
|
|
|
{
|
|
|
|
if (this.CheckStockDeviceModelExistence(id))
|
|
|
|
{
|
2018-07-18 02:37:58 +03:00
|
|
|
this.log.Warn("Stock device models cannot be deleted",
|
2018-05-23 03:24:57 +03:00
|
|
|
() => new { Id = id });
|
|
|
|
throw new UnauthorizedAccessException(
|
|
|
|
"Stock device models cannot be deleted");
|
|
|
|
}
|
2017-05-31 05:50:03 +03:00
|
|
|
|
2018-05-23 03:24:57 +03:00
|
|
|
await this.customDeviceModels.DeleteAsync(id);
|
|
|
|
}
|
|
|
|
|
2018-06-21 01:56:58 +03:00
|
|
|
/// <summary>
|
|
|
|
/// Get property names from all device models.
|
|
|
|
/// </summary>
|
|
|
|
public async Task<List<string>> GetPropertyNamesAsync()
|
|
|
|
{
|
|
|
|
var list = await this.GetListAsync();
|
|
|
|
var properties = new HashSet<string>();
|
|
|
|
|
|
|
|
foreach (var model in list)
|
|
|
|
{
|
|
|
|
if (model.Properties != null)
|
|
|
|
{
|
|
|
|
foreach (var property in model.Properties)
|
|
|
|
{
|
|
|
|
this.PreparePropertyNames(properties, property.Value, property.Key);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-09-12 07:47:03 +03:00
|
|
|
|
2018-06-21 01:56:58 +03:00
|
|
|
List<string> result = new List<string>();
|
|
|
|
|
|
|
|
foreach (string property in properties)
|
|
|
|
{
|
|
|
|
result.Add(REPORTED_PREFIX + property);
|
|
|
|
}
|
2018-09-12 07:47:03 +03:00
|
|
|
|
2018-06-21 01:56:58 +03:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-05-23 03:24:57 +03:00
|
|
|
/// <summary>
|
|
|
|
/// Returns True if there is a stock model with the given Id
|
|
|
|
/// </summary>
|
|
|
|
private bool CheckStockDeviceModelExistence(string id)
|
|
|
|
{
|
|
|
|
if (string.IsNullOrEmpty(id)) return false;
|
2017-07-07 23:49:03 +03:00
|
|
|
|
2018-05-23 03:24:57 +03:00
|
|
|
return this.stockDeviceModels.GetList()
|
|
|
|
.Any(model => id.Equals(model.Id, StringComparison.InvariantCultureIgnoreCase));
|
2017-05-31 05:50:03 +03:00
|
|
|
}
|
2018-06-21 01:56:58 +03:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Transforms property names from Json Object to "xxx.yyy.zzz" format
|
|
|
|
/// </summary>
|
|
|
|
private void PreparePropertyNames(HashSet<string> set, object obj, string prefix)
|
|
|
|
{
|
|
|
|
/* Sample conversion:
|
|
|
|
* from -> Foo : {
|
|
|
|
* Bar : Properties
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* to -> Foo.Bar.Properties
|
|
|
|
*/
|
|
|
|
if (obj is JValue)
|
|
|
|
{
|
|
|
|
set.Add(prefix);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (obj is bool || obj is string || double.TryParse(obj.ToString(), out _))
|
|
|
|
{
|
|
|
|
set.Add(prefix);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (var item in (obj as JToken).Values())
|
|
|
|
{
|
|
|
|
var path = item.Path;
|
|
|
|
this.PreparePropertyNames(set, item, $"{prefix}.{(path.Contains(".") ? path.Substring(path.LastIndexOf('.') + 1) : path)}");
|
|
|
|
}
|
|
|
|
}
|
2017-05-31 05:50:03 +03:00
|
|
|
}
|
|
|
|
}
|