device-simulation-dotnet/Services/DeviceModels.cs

262 строки
8.7 KiB
C#
Исходник Постоянная ссылка Обычный вид История

// Copyright (c) Microsoft. All rights reserved.
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;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using Newtonsoft.Json.Linq;
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
{
public interface IDeviceModels
{
/// <summary>
/// Get list of device models.
/// </summary>
Task<IEnumerable<DeviceModel>> GetListAsync();
/// <summary>
/// Get a device model.
/// </summary>
Task<DeviceModel> GetAsync(string id);
/// <summary>
/// Get a device model and apply the overrides from the given simulation.
/// </summary>
Task<DeviceModel> GetWithOverrideAsync(string id, Models.Simulation simulation);
/// <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);
/// <summary>
/// Get property names from all device models.
/// </summary>
Task<List<string>> GetPropertyNamesAsync();
}
/// <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>
public class DeviceModels : IDeviceModels
{
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";
private const string REPORTED_PREFIX = "Properties.Reported.";
private readonly ILogger log;
private readonly ICustomDeviceModels customDeviceModels;
private readonly IStockDeviceModels stockDeviceModels;
private readonly IDeviceModelsGeneration deviceModelsOverriding;
public DeviceModels(
ICustomDeviceModels customDeviceModels,
IStockDeviceModels stockDeviceModels,
IDeviceModelsGeneration deviceModelsOverriding,
ILogger logger)
{
this.stockDeviceModels = stockDeviceModels;
this.customDeviceModels = customDeviceModels;
this.deviceModelsOverriding = deviceModelsOverriding;
this.log = logger;
}
/// <summary>
/// Get list of device models.
/// </summary>
public async Task<IEnumerable<DeviceModel>> GetListAsync()
{
var stockDeviceModelsList = this.stockDeviceModels.GetList();
var customDeviceModelsList = await this.customDeviceModels.GetListAsync();
var deviceModels = stockDeviceModelsList
.Concat(customDeviceModelsList)
.ToList();
return deviceModels;
}
/// <summary>
/// Get a device model.
/// </summary>
public async Task<DeviceModel> GetAsync(string id)
{
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;
this.log.Warn("Device model not found", () => new { id });
throw new ResourceNotFoundException();
}
/// <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);
}
/// <summary>
/// Create a device model.
/// </summary>
public async Task<DeviceModel> InsertAsync(DeviceModel deviceModel)
{
if (this.CheckStockDeviceModelExistence(deviceModel.Id))
{
throw new ConflictingResourceException(
"Device model with id '" + deviceModel.Id + "' already exists!");
}
var result = await this.customDeviceModels.InsertAsync(deviceModel);
deviceModel.ETag = result.ETag;
return deviceModel;
}
/// <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))
{
this.log.Warn("Stock device models cannot be deleted",
() => new { Id = id });
throw new UnauthorizedAccessException(
"Stock device models cannot be deleted");
}
await this.customDeviceModels.DeleteAsync(id);
}
/// <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);
}
}
}
List<string> result = new List<string>();
foreach (string property in properties)
{
result.Add(REPORTED_PREFIX + property);
}
return result;
}
/// <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;
return this.stockDeviceModels.GetList()
.Any(model => id.Equals(model.Id, StringComparison.InvariantCultureIgnoreCase));
}
/// <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)}");
}
}
}
}