Add Device Properties logic to internal simulation (#173)
* uncomment UpdateTwinAsync * add folder for DeviceTwinActor * Add support classes for DeviceTwinActor to update twin -- uses empty methods * remove Properties from DeviceStateActor class * Add InternalDeviceState Class and ability to update twin via callback from javascript * add placeholders for twin update logic for future pr * remove eslint jsdoc updates and update javascriptinterpreter tests * Add new thread for device properties updates * return null for not implemented methods * Add Unit Tests for Internal Device State * Update existing tests with new InternalDeviceState class * Update scripts with instructions on device property updates * Fix errors with JS device scripts and readonly dictionary * revert changes to DeviceTwin class * Update log messages for readability with timestamp * Rename logging methods for consistency * Separate properties and state and remove unused lines * update comments for twin update branch * Revert UpdateTwinAsync signature in DeviceClient * revert UpdateState and JSValueToDictionary to private * update comment on restoreState in chiller js script * Update InternalDevicePropertiesTest names * Consolidated to SmartDictionary class * remove DevicePropertiesActor, revert DeviceTwin, change terminology from sensors to state * revert actors logger * revert simulation runner * revert simulation runner test * revert deletion of UpdateReportedProperties.cs * fix spacing in SimulationRunner * fix spacing in device twin * consolidate restore state javascript methods * add properties to internal script method call * variable/method naming, whitespace cleanup, add missing method in elevator state script
This commit is contained in:
Родитель
c6ef837442
Коммит
37a720d044
|
@ -3,6 +3,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation;
|
||||
using Moq;
|
||||
|
@ -21,6 +22,7 @@ namespace Services.Test.Simulation
|
|||
private readonly ITestOutputHelper log;
|
||||
private readonly Mock<IServicesConfig> config;
|
||||
private readonly Mock<ILogger> logger;
|
||||
private readonly Mock<ISmartDictionary> properties;
|
||||
private readonly JavascriptInterpreter target;
|
||||
|
||||
public JavascriptInterpreterTest(ITestOutputHelper log)
|
||||
|
@ -30,6 +32,7 @@ namespace Services.Test.Simulation
|
|||
this.config = new Mock<IServicesConfig>();
|
||||
this.config.SetupGet(x => x.DeviceModelsFolder).Returns("./data/devicemodels/");
|
||||
this.config.SetupGet(x => x.DeviceModelsScriptsFolder).Returns("./data/devicemodels/scripts/");
|
||||
this.properties = new Mock<ISmartDictionary>();
|
||||
|
||||
this.logger = new Mock<ILogger>();
|
||||
this.CaptureApplicationLogs(this.logger);
|
||||
|
@ -41,6 +44,8 @@ namespace Services.Test.Simulation
|
|||
public void ReturnedStateIsIntact()
|
||||
{
|
||||
// Arrange
|
||||
SmartDictionary deviceState = new SmartDictionary();
|
||||
|
||||
var filename = "chiller-01-state.js";
|
||||
var context = new Dictionary<string, object>
|
||||
{
|
||||
|
@ -57,22 +62,26 @@ namespace Services.Test.Simulation
|
|||
["lights_on"] = false
|
||||
};
|
||||
|
||||
deviceState.SetAll(state);
|
||||
|
||||
// Act
|
||||
Dictionary<string, object> result = this.target.Invoke(filename, context, state);
|
||||
this.target.Invoke(filename, context, deviceState, properties.Object);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(state.Count, result.Count);
|
||||
Assert.IsType<Double>(result["temperature"]);
|
||||
Assert.IsType<string>(result["temperature_unit"]);
|
||||
Assert.IsType<Double>(result["humidity"]);
|
||||
Assert.IsType<string>(result["humidity_unit"]);
|
||||
Assert.IsType<bool>(result["lights_on"]);
|
||||
Assert.Equal(state.Count, deviceState.GetAll().Count);
|
||||
Assert.IsType<Double>(deviceState.Get("temperature"));
|
||||
Assert.IsType<string>(deviceState.Get("temperature_unit"));
|
||||
Assert.IsType<Double>(deviceState.Get("humidity"));
|
||||
Assert.IsType<string>(deviceState.Get("humidity_unit"));
|
||||
Assert.IsType<bool>(deviceState.Get("lights_on"));
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void TestJavascriptFiles()
|
||||
{
|
||||
// Arrange
|
||||
SmartDictionary deviceState = new SmartDictionary();
|
||||
|
||||
var files = new List<string>
|
||||
{
|
||||
"chiller-01-state.js",
|
||||
|
@ -89,8 +98,8 @@ namespace Services.Test.Simulation
|
|||
// Act - Assert (no exception should occur)
|
||||
foreach (var file in files)
|
||||
{
|
||||
var result = this.target.Invoke(file, context, null);
|
||||
Assert.NotNull(result);
|
||||
this.target.Invoke(file, context, deviceState, this.properties.Object);
|
||||
Assert.NotNull(deviceState.GetAll());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
|
||||
using Services.Test.helpers;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using static Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models.DeviceModel;
|
||||
|
||||
namespace Services.Test
|
||||
{
|
||||
public class SmartDictionaryTest
|
||||
{
|
||||
private ISmartDictionary target;
|
||||
|
||||
public SmartDictionaryTest(ITestOutputHelper log)
|
||||
{
|
||||
// Initialize device properties
|
||||
this.target = this.GetTestProperties();
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void Should_Be_Empty_On_Get_All_When_No_Properties_Added()
|
||||
{
|
||||
// Arrange
|
||||
this.target = this.GetEmptyProperties();
|
||||
|
||||
// Act
|
||||
var props = this.target.GetAll();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(props);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void Should_Return_All_Test_Properties_On_Get_All_When_Initialized_With_Device_Model()
|
||||
{
|
||||
// Arrange
|
||||
this.target = this.GetTestProperties();
|
||||
var expectedCount = this.GetTestChillerModel().Properties.Count;
|
||||
|
||||
// Act
|
||||
var props = this.target.GetAll();
|
||||
|
||||
// Assert
|
||||
Assert.NotEmpty(props);
|
||||
Assert.Equal(props.Count, expectedCount);
|
||||
Assert.Equal("TestChiller", props["Type"]);
|
||||
Assert.Equal("1.0", props["Firmware"]);
|
||||
Assert.Equal("TestCH101", props["Model"]);
|
||||
Assert.Equal("TestBuilding 2", props["Location"]);
|
||||
Assert.Equal(47.640792, props["Latitude"]);
|
||||
Assert.Equal(-122.126258, props["Longitude"]);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void Should_Return_Value_When_Calling_Get_And_Property_Exists()
|
||||
{
|
||||
// Arrange
|
||||
this.target = this.GetTestProperties();
|
||||
var expectedCount = this.GetTestChillerModel().Properties.Count;
|
||||
|
||||
// Act
|
||||
var props = this.target.GetAll();
|
||||
|
||||
// Assert
|
||||
Assert.NotEmpty(props);
|
||||
Assert.Equal(props.Count, expectedCount);
|
||||
Assert.Equal("TestChiller", props["Type"]);
|
||||
Assert.Equal("1.0", props["Firmware"]);
|
||||
Assert.Equal("TestCH101", props["Model"]);
|
||||
Assert.Equal("TestBuilding 2", props["Location"]);
|
||||
Assert.Equal(47.640792, props["Latitude"]);
|
||||
Assert.Equal(-122.126258, props["Longitude"]);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void Should_Throw_On_Get_When_Key_Does_Not_Exist()
|
||||
{
|
||||
// Arrange
|
||||
this.target = this.GetTestProperties();
|
||||
const string KEY = "KeyThatDoesNotExist";
|
||||
|
||||
// Act and Assert
|
||||
Assert.Throws<KeyNotFoundException>(() => this.target.Get(KEY));
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void Should_Return_Copy_From_Get_All()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// test values
|
||||
const string KEY1 = "key1";
|
||||
const string VALUE1 = "value1";
|
||||
const string NEW_VALUE = "newvalue";
|
||||
|
||||
var dictionary = new Dictionary<string,object>
|
||||
{
|
||||
{ KEY1, VALUE1 }
|
||||
};
|
||||
|
||||
this.target = new SmartDictionary(dictionary);
|
||||
|
||||
// Act
|
||||
|
||||
// modify copy
|
||||
var dictionaryCopy = this.target.GetAll();
|
||||
dictionaryCopy[KEY1] = NEW_VALUE;
|
||||
|
||||
// check original
|
||||
var result = this.target.Get(KEY1);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(result, VALUE1);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void Should_Add_Value_When_Set()
|
||||
{
|
||||
// Arrange
|
||||
this.target = this.GetEmptyProperties();
|
||||
const string KEY = "testSetKey";
|
||||
const string VALUE = "testSetValue";
|
||||
|
||||
// Act
|
||||
this.target.Set(KEY, VALUE);
|
||||
var result = this.target.Get(KEY);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(result, VALUE);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void Should_Set_Value_With_Key()
|
||||
{
|
||||
// Arrange
|
||||
this.target = this.GetEmptyProperties();
|
||||
const string KEY = "testSetKey";
|
||||
const string VALUE = "testSetValue";
|
||||
this.target.Set(KEY, VALUE);
|
||||
|
||||
// Act
|
||||
var result = this.target.Get(KEY);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(result, VALUE);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void Has_Should_Return_True_When_Property_Exists()
|
||||
{
|
||||
// Arrange
|
||||
this.target = this.GetEmptyProperties();
|
||||
const string KEY = "testHasKey";
|
||||
const string VALUE = "testHasValue";
|
||||
this.target.Set(KEY, VALUE);
|
||||
|
||||
// Act
|
||||
var result = this.target.Has(KEY);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void Has_Should_Return_False_When_Property_Does_Not_Exist()
|
||||
{
|
||||
// Arrange
|
||||
this.target = this.GetEmptyProperties();
|
||||
const string KEY = "KeyThatDoesNotExist";
|
||||
|
||||
// Act
|
||||
var result = this.target.Has(KEY);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void Changed_Should_Return_True_When_New_Property_Added()
|
||||
{
|
||||
// Arrange
|
||||
this.target = this.GetEmptyProperties();
|
||||
Assert.False(this.target.Changed);
|
||||
|
||||
const string KEY = "testKey";
|
||||
const string VALUE = "testValue";
|
||||
|
||||
// Act
|
||||
this.target.Set(KEY, VALUE);
|
||||
|
||||
// Assert
|
||||
Assert.True(this.target.Changed);
|
||||
}
|
||||
|
||||
[Fact, Trait(Constants.TYPE, Constants.UNIT_TEST)]
|
||||
public void Changed_Should_Be_False_When_Reset()
|
||||
{
|
||||
// Arrange
|
||||
this.target = this.GetTestProperties();
|
||||
|
||||
const string KEY = "testKey";
|
||||
const string VALUE = "testValue";
|
||||
this.target.Set(KEY, VALUE);
|
||||
|
||||
// Act
|
||||
this.target.ResetChanged();
|
||||
|
||||
// Assert
|
||||
Assert.False(this.target.Changed);
|
||||
}
|
||||
|
||||
private SmartDictionary GetEmptyProperties()
|
||||
{
|
||||
return new SmartDictionary();
|
||||
}
|
||||
|
||||
private SmartDictionary GetTestProperties()
|
||||
{
|
||||
return new SmartDictionary(this.GetTestChillerModel().Properties);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the a test chiller model
|
||||
/// </summary>
|
||||
private DeviceModel GetTestChillerModel()
|
||||
{
|
||||
return new DeviceModel()
|
||||
{
|
||||
Id = "TestChiller01",
|
||||
Properties = new Dictionary<string, object>()
|
||||
{
|
||||
{ "TestPropKey", "TestPropValue" },
|
||||
{ "Type", "TestChiller" },
|
||||
{ "Firmware", "1.0" },
|
||||
{ "Model", "TestCH101" },
|
||||
{ "Location", "TestBuilding 2" },
|
||||
{ "Latitude", 47.640792 },
|
||||
{ "Longitude", -122.126258 }
|
||||
},
|
||||
Simulation = new StateSimulation()
|
||||
{
|
||||
InitialState = new Dictionary<string, object>()
|
||||
{
|
||||
{ "testKey", "testValue" },
|
||||
{ "online", true },
|
||||
{ "temperature", 75.0 },
|
||||
{ "temperature_unit", "F" },
|
||||
{ "humidity", 70.0 },
|
||||
{ "humidity_unit", "%" },
|
||||
{ "pressure", 150.0 },
|
||||
{ "pressure_unit", "psig" },
|
||||
{ "simulation_state", "normal_pressure" }
|
||||
},
|
||||
Interval = TimeSpan.Parse("00:00:10")
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -77,7 +77,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics
|
|||
this.telemetryLogFile = this.path + Path.DirectorySeparatorChar + "telemetry.log";
|
||||
|
||||
this.enabled = this.enabledInConfig && !string.IsNullOrEmpty(this.path);
|
||||
|
||||
|
||||
if (!this.enabled) return;
|
||||
|
||||
try
|
||||
|
@ -363,4 +363,4 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics
|
|||
this.log.LogToFile(filename, text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
|
||||
{
|
||||
public interface ISmartDictionary
|
||||
{
|
||||
IDictionary<string, object> GetAll();
|
||||
void SetAll(Dictionary<string, object> newState);
|
||||
bool Has(string key);
|
||||
object Get(string key);
|
||||
void Set(string key, object value);
|
||||
bool Changed { get; }
|
||||
void ResetChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for a dictionary that supports concurrent reads and writes
|
||||
/// and tracks if any entries have been changed.
|
||||
/// </summary>
|
||||
public class SmartDictionary : ISmartDictionary
|
||||
{
|
||||
/// <summary>
|
||||
/// A collection of items that can support concurrent reads and writes.
|
||||
/// </summary>
|
||||
private IDictionary<string, object> dictionary;
|
||||
|
||||
public bool Changed { get; private set; }
|
||||
|
||||
public SmartDictionary()
|
||||
{
|
||||
// needs to support concurrent reads and writes
|
||||
this.dictionary = new ConcurrentDictionary<string, object>();
|
||||
this.Changed = false;
|
||||
}
|
||||
|
||||
public SmartDictionary(IDictionary<string, object> dictionary)
|
||||
{
|
||||
this.dictionary = new ConcurrentDictionary<string, object>(dictionary);
|
||||
this.Changed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when values have been synchronized. Resets 'changed' flag to false.
|
||||
/// </summary>
|
||||
public void ResetChanged()
|
||||
{
|
||||
this.Changed = false;
|
||||
}
|
||||
|
||||
/// <param name="key"></param>
|
||||
/// <exception cref="KeyNotFoundException">
|
||||
/// thrown when the key specified does not match any key in the collection.
|
||||
/// </exception>
|
||||
public object Get(string key)
|
||||
{
|
||||
if (!this.dictionary.ContainsKey(key))
|
||||
{
|
||||
throw new KeyNotFoundException();
|
||||
}
|
||||
|
||||
return this.dictionary[key];
|
||||
}
|
||||
|
||||
public IDictionary<string, object> GetAll()
|
||||
{
|
||||
return new Dictionary<string, object>(this.dictionary);
|
||||
}
|
||||
|
||||
public bool Has(string key)
|
||||
{
|
||||
return this.dictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a property with the given key, adds new value if key does not exist.
|
||||
/// Sets the changed flag to true.
|
||||
/// </summary>
|
||||
public void Set(string key, object value)
|
||||
{
|
||||
if (this.dictionary.ContainsKey(key))
|
||||
{
|
||||
this.dictionary[key] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.dictionary.Add(key, value);
|
||||
}
|
||||
|
||||
this.Changed = true;
|
||||
}
|
||||
|
||||
public void SetAll(Dictionary<string, object> newState)
|
||||
{
|
||||
this.dictionary = newState;
|
||||
this.Changed = true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
||||
|
@ -17,11 +18,13 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
/// <param name="scriptParams">Script parameters, e.g. min, max, step</param>
|
||||
/// <param name="context">Context, e.g. current time, device Id, device Model</param>
|
||||
/// <param name="state">Current device sensors state</param>
|
||||
/// <returns>New device sensors state</returns>
|
||||
Dictionary<string, object> Invoke(
|
||||
/// <param name="properties">Current device properties state</param>
|
||||
/// <remarks>Updates the internal device sensors state</remarks>
|
||||
void Invoke(
|
||||
string scriptPath, object scriptParams,
|
||||
Dictionary<string, object> context,
|
||||
Dictionary<string, object> state);
|
||||
ISmartDictionary state,
|
||||
ISmartDictionary properties);
|
||||
}
|
||||
|
||||
public class InternalInterpreter : IInternalInterpreter
|
||||
|
@ -47,43 +50,43 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
this.random = new Random();
|
||||
}
|
||||
|
||||
public Dictionary<string, object> Invoke(
|
||||
public void Invoke(
|
||||
string scriptPath,
|
||||
object scriptParams,
|
||||
Dictionary<string, object> context,
|
||||
Dictionary<string, object> state)
|
||||
ISmartDictionary state,
|
||||
ISmartDictionary properties)
|
||||
{
|
||||
switch (scriptPath.ToLowerInvariant())
|
||||
{
|
||||
case SCRIPT_RANDOM:
|
||||
return this.RunRandomNumberScript(scriptParams, state);
|
||||
|
||||
this.RunRandomNumberScript(scriptParams, state);
|
||||
break;
|
||||
case SCRIPT_INCREASING:
|
||||
return this.RunIncreasingScript(scriptParams, state);
|
||||
|
||||
this.RunIncreasingScript(scriptParams, state);
|
||||
break;
|
||||
case SCRIPT_DECREASING:
|
||||
return this.RunDecreasingScript(scriptParams, state);
|
||||
|
||||
this.RunDecreasingScript(scriptParams, state);
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException($"Unknown script `{scriptPath}`.");
|
||||
}
|
||||
}
|
||||
|
||||
// For each sensors specified, generate a random number in the range requested
|
||||
private Dictionary<string, object> RunRandomNumberScript(object scriptParams, Dictionary<string, object> state)
|
||||
private void RunRandomNumberScript(object scriptParams, ISmartDictionary state)
|
||||
{
|
||||
var sensors = this.JsonParamAsDictionary(scriptParams);
|
||||
foreach (var sensor in sensors)
|
||||
{
|
||||
(double min, double max) = this.GetMinMaxParameters(sensor.Value);
|
||||
state[sensor.Key] = this.random.NextDouble() * (max - min) + min;
|
||||
var value = this.random.NextDouble() * (max - min) + min;
|
||||
state.Set(sensor.Key, value);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
// For each sensors specified, increase the current state, up to a maximum, then restart from a minimum
|
||||
private Dictionary<string, object> RunIncreasingScript(object scriptParams, Dictionary<string, object> state)
|
||||
private void RunIncreasingScript(object scriptParams, ISmartDictionary state)
|
||||
{
|
||||
var sensors = this.JsonParamAsDictionary(scriptParams);
|
||||
foreach (var sensor in sensors)
|
||||
|
@ -92,22 +95,20 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
(double min, double max, double step) = this.GetMinMaxStepParameters(sensor.Value);
|
||||
|
||||
// Add the sensor to the state if missing
|
||||
if (!state.ContainsKey(sensor.Key))
|
||||
if (!state.Has(sensor.Key))
|
||||
{
|
||||
state[sensor.Key] = min;
|
||||
state.Set(sensor.Key, min);
|
||||
}
|
||||
|
||||
double current = Convert.ToDouble(state[sensor.Key]);
|
||||
double current = Convert.ToDouble(state.Get(sensor.Key));
|
||||
double next = AreEqual(current, max) ? min : Math.Min(current + step, max);
|
||||
|
||||
state[sensor.Key] = next;
|
||||
state.Set(sensor.Key, next);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
// For each sensors specified, decrease the current state, down to a minimum, then restart from a maximum
|
||||
private Dictionary<string, object> RunDecreasingScript(object scriptParams, Dictionary<string, object> state)
|
||||
private void RunDecreasingScript(object scriptParams, ISmartDictionary state)
|
||||
{
|
||||
var sensors = this.JsonParamAsDictionary(scriptParams);
|
||||
foreach (var sensor in sensors)
|
||||
|
@ -116,18 +117,16 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
(double min, double max, double step) = this.GetMinMaxStepParameters(sensor.Value);
|
||||
|
||||
// Add the sensor to the state if missing
|
||||
if (!state.ContainsKey(sensor.Key))
|
||||
if (!state.Has(sensor.Key))
|
||||
{
|
||||
state[sensor.Key] = max;
|
||||
state.Set(sensor.Key, max);
|
||||
}
|
||||
|
||||
double current = Convert.ToDouble(state[sensor.Key]);
|
||||
double current = Convert.ToDouble(state.Get(sensor.Key));
|
||||
double next = AreEqual(current, min) ? max : Math.Max(current - step, min);
|
||||
|
||||
state[sensor.Key] = next;
|
||||
state.Set(sensor.Key, next);
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
private (double, double) GetMinMaxParameters(object parameters)
|
||||
|
|
|
@ -11,23 +11,27 @@ using Jint.Parser;
|
|||
using Jint.Parser.Ast;
|
||||
using Jint.Runtime.Descriptors;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
||||
{
|
||||
public interface IJavascriptInterpreter
|
||||
{
|
||||
Dictionary<string, object> Invoke(
|
||||
void Invoke(
|
||||
string filename,
|
||||
Dictionary<string, object> context,
|
||||
Dictionary<string, object> state);
|
||||
ISmartDictionary state,
|
||||
ISmartDictionary properties);
|
||||
}
|
||||
|
||||
public class JavascriptInterpreter : IJavascriptInterpreter
|
||||
{
|
||||
private readonly ILogger log;
|
||||
private readonly string folder;
|
||||
private Dictionary<string, object> deviceState;
|
||||
private ISmartDictionary deviceState;
|
||||
private ISmartDictionary deviceProperties;
|
||||
|
||||
// The following are static to improve overall performance
|
||||
// TODO make the class a singleton - https://github.com/Azure/device-simulation-dotnet/issues/45
|
||||
|
@ -46,14 +50,16 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
/// <summary>
|
||||
/// Load a JS file and execute the main() function, passing in
|
||||
/// context information and the output from the previous execution.
|
||||
/// Returns a map of values.
|
||||
/// Modifies the internal device state with the latest values.
|
||||
/// </summary>
|
||||
public Dictionary<string, object> Invoke(
|
||||
public void Invoke(
|
||||
string filename,
|
||||
Dictionary<string, object> context,
|
||||
Dictionary<string, object> state)
|
||||
ISmartDictionary state,
|
||||
ISmartDictionary properties)
|
||||
{
|
||||
this.deviceState = state;
|
||||
this.deviceProperties = properties;
|
||||
|
||||
var engine = new Engine();
|
||||
|
||||
|
@ -61,10 +67,13 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
// logging into the service logs
|
||||
engine.SetValue("log", new Action<object>(this.JsLog));
|
||||
|
||||
//register callback for state updates
|
||||
// register callback for state updates
|
||||
engine.SetValue("updateState", new Action<JsValue>(this.UpdateState));
|
||||
|
||||
//register sleep function for javascript use
|
||||
// register callback for property updates
|
||||
engine.SetValue("updateProperty", new Action<string, object>(this.UpdateProperty));
|
||||
|
||||
// register sleep function for javascript use
|
||||
engine.SetValue("sleep", new Action<int>(this.Sleep));
|
||||
|
||||
try
|
||||
|
@ -84,16 +93,21 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
}
|
||||
|
||||
this.log.Debug("Executing JS function", () => new { filename });
|
||||
JsValue output = engine.Execute(program).Invoke("main", context, this.deviceState);
|
||||
|
||||
var result = this.JsValueToDictionary(output);
|
||||
this.log.Debug("JS function success", () => new { filename, result });
|
||||
return result;
|
||||
JsValue output = engine.Execute(program).Invoke(
|
||||
"main",
|
||||
context,
|
||||
this.deviceState.GetAll(),
|
||||
this.deviceProperties.GetAll());
|
||||
|
||||
// update the internal device state with the new state
|
||||
this.UpdateState(output);
|
||||
|
||||
this.log.Debug("JS function success", () => new { filename, output });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.log.Error("JS function failure", () => new { e.Message, e.GetType().FullName });
|
||||
return new Dictionary<string, object>();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,26 +192,34 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
{
|
||||
string key;
|
||||
object value;
|
||||
Dictionary<string, object> stateChanges;
|
||||
Dictionary<string, object> stateChanges = this.JsValueToDictionary(data);
|
||||
|
||||
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
|
||||
// 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;
|
||||
}
|
||||
this.log.Debug("state change", () => new { key, value });
|
||||
this.deviceState.Set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Move this out of the scriptinterpreter class into DeviceStateActor to keep this class stateless
|
||||
// https://github.com/Azure/device-simulation-dotnet/issues/45
|
||||
private void UpdateProperty(string key, object value)
|
||||
{
|
||||
this.log.Debug("Updating device property from the script", () => new { key, value });
|
||||
|
||||
// Update device property at key with the script value passed
|
||||
lock (this.deviceProperties)
|
||||
{
|
||||
this.deviceProperties.Set(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,17 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
{
|
||||
public interface IScriptInterpreter
|
||||
{
|
||||
Dictionary<string, object> Invoke(
|
||||
/// <summary>Invoke one of the device script files</summary>
|
||||
/// <param name="script">Name of the script</param>
|
||||
/// <param name="context">Context, e.g. current time, device Id, device Model</param>
|
||||
/// <param name="state">Current device state</param>
|
||||
/// <param name="properties">Current device properties</param>
|
||||
/// <remarks> Updates the internal device state and internal device properties</remarks>
|
||||
void Invoke(
|
||||
Script script,
|
||||
Dictionary<string, object> context,
|
||||
Dictionary<string, object> state);
|
||||
ISmartDictionary state,
|
||||
ISmartDictionary properties);
|
||||
}
|
||||
|
||||
public class ScriptInterpreter : IScriptInterpreter
|
||||
|
@ -31,10 +38,11 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
this.log = logger;
|
||||
}
|
||||
|
||||
public Dictionary<string, object> Invoke(
|
||||
public void Invoke(
|
||||
Script script,
|
||||
Dictionary<string, object> context,
|
||||
Dictionary<string, object> state)
|
||||
ISmartDictionary state,
|
||||
ISmartDictionary properties)
|
||||
{
|
||||
switch (script.Type.ToLowerInvariant())
|
||||
{
|
||||
|
@ -44,15 +52,15 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation
|
|||
|
||||
case "javascript":
|
||||
this.log.Debug("Invoking JS", () => new { script.Path, context, state });
|
||||
var jsResult = this.jsInterpreter.Invoke(script.Path, context, state);
|
||||
this.log.Debug("JS result", () => new { result = jsResult });
|
||||
return jsResult;
|
||||
this.jsInterpreter.Invoke(script.Path, context, state, properties);
|
||||
this.log.Debug("JS invocation complete", () => {});
|
||||
break;
|
||||
|
||||
case "internal":
|
||||
this.log.Debug("Invoking internal script", () => new { script.Path, context, state });
|
||||
var intResult = this.intInterpreter.Invoke(script.Path, script.Params, context, state);
|
||||
this.log.Debug("Internal script result", () => new { intresult = intResult });
|
||||
return intResult;
|
||||
this.intInterpreter.Invoke(script.Path, script.Params, context, state, properties);
|
||||
this.log.Debug("Internal script complete", () => {});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global updateProperty*/
|
||||
/*global sleep*/
|
||||
/*jslint node: true*/
|
||||
|
||||
|
@ -19,23 +20,35 @@ var state = {
|
|||
simulation_state: "normal_pressure"
|
||||
};
|
||||
|
||||
// Default device properties
|
||||
var properties = {};
|
||||
|
||||
/**
|
||||
* Restore the global state using data from the previous iteration.
|
||||
*
|
||||
* @param previousState The output of main() from the previous iteration
|
||||
* @param previousState device state from the previous iteration
|
||||
* @param previousProperties device properties from the previous iteration
|
||||
*/
|
||||
function restoreState(previousState) {
|
||||
function restoreSimulation(previousState, previousProperties) {
|
||||
// If the previous state is null, force a default state
|
||||
if (previousState !== undefined && previousState !== null) {
|
||||
if (previousState) {
|
||||
state = previousState;
|
||||
} else {
|
||||
log("Using default state");
|
||||
}
|
||||
|
||||
if (previousProperties) {
|
||||
properties = previousProperties;
|
||||
} else {
|
||||
log("Using default properties");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple formula generating a random value around the average
|
||||
* in between min and max
|
||||
*
|
||||
* @returns random value with given parameters
|
||||
*/
|
||||
function vary(avg, percentage, min, max) {
|
||||
var value = avg * (1 + ((percentage / 100) * (2 * Math.random() - 1)));
|
||||
|
@ -46,16 +59,20 @@ function vary(avg, percentage, min, max) {
|
|||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
* Returns updated simulation state.
|
||||
* Device property updates must call updateProperties() to persist.
|
||||
*
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
* @param previousProperties The device properties since the last iteration
|
||||
*/
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
function main(context, previousState, previousProperties) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
// Restore the global device properties and the global state before
|
||||
// generating the new telemetry, so that the telemetry can apply changes
|
||||
// using the previous function state.
|
||||
restoreSimulation(previousState, previousProperties);
|
||||
|
||||
// 75F +/- 5%, Min 25F, Max 100F
|
||||
state.temperature = vary(75, 5, 25, 100);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global updateProperty*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
@ -18,18 +20,28 @@ var state = {
|
|||
moving: true
|
||||
};
|
||||
|
||||
// Default properties
|
||||
var properties = {};
|
||||
|
||||
/**
|
||||
* Restore the global state using data from the previous iteration.
|
||||
*
|
||||
* @param previousState The output of main() from the previous iteration
|
||||
* @param previousState device state from the previous iteration
|
||||
* @param previousProperties device properties from the previous iteration
|
||||
*/
|
||||
function restoreState(previousState) {
|
||||
function restoreSimulation(previousState, previousProperties) {
|
||||
// If the previous state is null, force a default state
|
||||
if (previousState !== undefined && previousState !== null) {
|
||||
if (previousState) {
|
||||
state = previousState;
|
||||
} else {
|
||||
log("Using default state");
|
||||
}
|
||||
|
||||
if (previousProperties) {
|
||||
properties = previousProperties;
|
||||
} else {
|
||||
log("Using default properties");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,16 +70,20 @@ function varyfloor(current, min, max) {
|
|||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
* Returns updated simulation state.
|
||||
* Device property updates must call updateProperties() to persist.
|
||||
*
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
* @param previousProperties The device properties since the last iteration
|
||||
*/
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
function main(context, previousState, previousProperties) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
// Restore the global device properties and the global state before
|
||||
// generating the new telemetry, so that the telemetry can apply changes
|
||||
// using the previous function state.
|
||||
restoreSimulation(previousState, previousProperties);
|
||||
|
||||
if (state.moving) {
|
||||
state.floor = varyfloor(state.floor, 1, floors);
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
/*global log*/
|
||||
/*global updateState*/
|
||||
/*global updateProperty*/
|
||||
/*jslint node: true*/
|
||||
|
||||
"use strict";
|
||||
|
@ -19,18 +21,28 @@ var state = {
|
|||
cargotemperature_unit: "F"
|
||||
};
|
||||
|
||||
// Default properties
|
||||
var properties = {};
|
||||
|
||||
/**
|
||||
* Restore the global state using data from the previous iteration.
|
||||
*
|
||||
* @param previousState The output of main() from the previous iteration
|
||||
* @param previousState device state from the previous iteration
|
||||
* @param previousProperties device properties from the previous iteration
|
||||
*/
|
||||
function restoreState(previousState) {
|
||||
function restoreSimulation(previousState, previousProperties) {
|
||||
// If the previous state is null, force a default state
|
||||
if (previousState !== undefined && previousState !== null) {
|
||||
if (previousState) {
|
||||
state = previousState;
|
||||
} else {
|
||||
log("Using default state");
|
||||
}
|
||||
|
||||
if (previousProperties) {
|
||||
properties = previousProperties;
|
||||
} else {
|
||||
log("Using default properties");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,16 +71,20 @@ function varylocation(latitude, longitude, distance) {
|
|||
|
||||
/**
|
||||
* Entry point function called by the simulation engine.
|
||||
* Returns updated simulation state.
|
||||
* Device property updates must call updateProperties() to persist.
|
||||
*
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
* @param context The context contains current time, device model and id
|
||||
* @param previousState The device state since the last iteration
|
||||
* @param previousProperties The device properties since the last iteration
|
||||
*/
|
||||
/*jslint unparam: true*/
|
||||
function main(context, previousState) {
|
||||
function main(context, previousState, previousProperties) {
|
||||
|
||||
// Restore the global state before generating the new telemetry, so that
|
||||
// the telemetry can apply changes using the previous function state.
|
||||
restoreState(previousState);
|
||||
// Restore the global device properties and the global state before
|
||||
// generating the new telemetry, so that the telemetry can apply changes
|
||||
// using the previous function state.
|
||||
restoreSimulation(previousState, previousProperties);
|
||||
|
||||
// 0.1 miles around some location
|
||||
var coords = varylocation(center_latitude, center_longitude, 0.1);
|
||||
|
|
|
@ -6,8 +6,6 @@ using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Simulation;
|
|||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceState;
|
||||
using Moq;
|
||||
using SimulationAgent.Test.helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
|
@ -66,18 +64,7 @@ namespace SimulationAgent.Test.DeviceState
|
|||
int postion = 1;
|
||||
int total = 10;
|
||||
var deviceModel = new DeviceModel { Id = DEVICE_ID };
|
||||
var deviceState = new Dictionary<string, object>
|
||||
{
|
||||
{ DEVICE_ID, new Object { } }
|
||||
};
|
||||
|
||||
this.scriptInterpreter
|
||||
.Setup(x => x.Invoke(
|
||||
It.IsAny<Script>(),
|
||||
It.IsAny<Dictionary<string, object>>(),
|
||||
It.IsAny<Dictionary<string, object>>()))
|
||||
.Returns(deviceState);
|
||||
|
||||
|
||||
this.target.Setup(DEVICE_ID, deviceModel, postion, total);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -240,4 +240,4 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Simulati
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
*/
|
||||
|
|
|
@ -6,12 +6,14 @@ using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
|
|||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Exceptions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceState
|
||||
{
|
||||
public interface IDeviceStateActor
|
||||
{
|
||||
Dictionary<string, object> DeviceState { get; }
|
||||
ISmartDictionary DeviceState { get; }
|
||||
ISmartDictionary DeviceProperties { get; }
|
||||
bool IsDeviceActive { get; }
|
||||
void Setup(string deviceId, DeviceModel deviceModel, int position, int totalDevices);
|
||||
void Run();
|
||||
|
@ -25,13 +27,10 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceSt
|
|||
Updating
|
||||
}
|
||||
|
||||
public const string CALC_TELEMETRY = "CalculateRandomizedTelemetry";
|
||||
public ISmartDictionary DeviceState { get; set; }
|
||||
public ISmartDictionary DeviceProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The virtual state of the simulated device. The state is
|
||||
/// periodically updated using an external script.
|
||||
/// </summary>
|
||||
public Dictionary<string, object> DeviceState { get; set; }
|
||||
public const string CALC_TELEMETRY = "CalculateRandomizedTelemetry";
|
||||
|
||||
/// <summary>
|
||||
/// The device is considered active when the state is being updated.
|
||||
|
@ -70,7 +69,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceSt
|
|||
/// Invoke this method before calling Start(), to initialize the actor
|
||||
/// with details like the device model and message type to simulate.
|
||||
/// If this method is not called before Start(), the application will
|
||||
/// thrown an exception.
|
||||
/// throw an exception.
|
||||
/// Setup() should be called only once, typically after the constructor.
|
||||
/// </summary>
|
||||
public void Setup(string deviceId, DeviceModel deviceModel, int position, int totalDevices)
|
||||
|
@ -99,8 +98,9 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceSt
|
|||
// Prepare the dependencies
|
||||
case ActorStatus.None:
|
||||
this.updateDeviceStateLogic.Setup(this, this.deviceId, this.deviceModel);
|
||||
this.DeviceState = this.SetupTelemetryAndProperties(this.deviceModel);
|
||||
this.log.Debug("Initial device state", () => new { this.deviceId, this.DeviceState });
|
||||
this.DeviceState = this.GetInitialState(this.deviceModel);
|
||||
this.DeviceProperties = this.GetInitialProperties(this.deviceModel);
|
||||
this.log.Debug("Initial device state", () => new { this.deviceId, this.DeviceState, this.DeviceProperties });
|
||||
this.MoveForward();
|
||||
return;
|
||||
|
||||
|
@ -145,27 +145,41 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceSt
|
|||
throw new Exception("Application error, MoveForward() should not be invoked when status = " + this.status);
|
||||
}
|
||||
|
||||
private Dictionary<string, object> SetupTelemetryAndProperties(DeviceModel deviceModel)
|
||||
/// <summary>
|
||||
/// Initializes device properties from the device model.
|
||||
/// </summary>
|
||||
private ISmartDictionary GetInitialProperties(DeviceModel model)
|
||||
{
|
||||
// put telemetry properties in state
|
||||
Dictionary<string, object> state = CloneObject(deviceModel.Simulation.InitialState);
|
||||
var properties = new SmartDictionary();
|
||||
|
||||
// Ensure the state contains the "online" key
|
||||
if (!state.ContainsKey("online"))
|
||||
foreach (var property in model.Properties)
|
||||
{
|
||||
state["online"] = true;
|
||||
properties.Set(property.Key, JToken.FromObject(property.Value));
|
||||
}
|
||||
|
||||
// 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);
|
||||
return properties;
|
||||
}
|
||||
|
||||
// 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(CALC_TELEMETRY, true);
|
||||
/// <summary>
|
||||
/// Initializes device state from the device model.
|
||||
/// </summary>
|
||||
private ISmartDictionary GetInitialState(DeviceModel model)
|
||||
{
|
||||
var initialState = CloneObject(model.Simulation.InitialState);
|
||||
|
||||
var state = new SmartDictionary(initialState);
|
||||
|
||||
// Ensure the state contains the "online" key
|
||||
if (!state.Has("online"))
|
||||
{
|
||||
state.Set("online", true);
|
||||
}
|
||||
|
||||
// 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
|
||||
// https://github.com/Azure/device-simulation-dotnet/issues/174
|
||||
state.Set(CALC_TELEMETRY, true);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceSt
|
|||
|
||||
public void Run()
|
||||
{
|
||||
if ((bool) this.context.DeviceState[DeviceStateActor.CALC_TELEMETRY])
|
||||
if ((bool) this.context.DeviceState.Get(DeviceStateActor.CALC_TELEMETRY))
|
||||
{
|
||||
this.log.Debug("Updating device telemetry data", () => new { this.deviceId });
|
||||
|
||||
|
@ -63,10 +63,12 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceSt
|
|||
{
|
||||
foreach (var script in this.deviceModel.Simulation.Scripts)
|
||||
{
|
||||
this.context.DeviceState = this.scriptInterpreter.Invoke(
|
||||
// call Invoke() which to update the internal device state
|
||||
this.scriptInterpreter.Invoke(
|
||||
script,
|
||||
scriptContext,
|
||||
this.context.DeviceState);
|
||||
this.context.DeviceState,
|
||||
this.context.DeviceProperties);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Concurrency;
|
||||
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.SimulationAgent.DeviceConnection;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceState;
|
||||
using Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.Exceptions;
|
||||
|
||||
namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceTelemetry
|
||||
{
|
||||
public interface IDeviceTelemetryActor
|
||||
{
|
||||
Dictionary<string, object> DeviceState { get; }
|
||||
ISmartDictionary DeviceState { get; }
|
||||
IDeviceClient Client { get; }
|
||||
DeviceModel.DeviceModelMessage Message { get; }
|
||||
|
||||
|
@ -80,7 +79,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceTe
|
|||
/// <summary>
|
||||
/// State maintained by the state actor
|
||||
/// </summary>
|
||||
public Dictionary<string, object> DeviceState => this.deviceStateActor.DeviceState;
|
||||
public ISmartDictionary DeviceState => this.deviceStateActor.DeviceState;
|
||||
|
||||
/// <summary>
|
||||
/// Azure IoT Hub client created by the connection actor
|
||||
|
|
|
@ -37,39 +37,39 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.SimulationAgent.DeviceTe
|
|||
|
||||
try
|
||||
{
|
||||
var state = this.context.DeviceState;
|
||||
this.log.Debug("Checking to see if device is online", () => new { this.deviceId });
|
||||
if ((bool) state["online"])
|
||||
{
|
||||
// device could be rebooting, updating firmware, etc.
|
||||
this.log.Debug("The device state says the device is online", () => new { this.deviceId });
|
||||
|
||||
// Inject the device state into the message template
|
||||
this.log.Debug("Preparing the message content using the device state", () => new { this.deviceId });
|
||||
var msg = this.message.MessageTemplate;
|
||||
foreach (var value in state)
|
||||
var state = this.context.DeviceState.GetAll();
|
||||
this.log.Debug("Checking to see if device is online", () => new { this.deviceId });
|
||||
if ((bool) state["online"])
|
||||
{
|
||||
msg = msg.Replace("${" + value.Key + "}", value.Value.ToString());
|
||||
}
|
||||
// device could be rebooting, updating firmware, etc.
|
||||
this.log.Debug("The device state says the device is online", () => new { this.deviceId });
|
||||
|
||||
this.log.Debug("Calling SendMessageAsync...",
|
||||
() => new { this.deviceId, MessageSchema = this.message.MessageSchema.Name, msg });
|
||||
|
||||
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
this.context.Client.SendMessageAsync(msg, this.message.MessageSchema)
|
||||
.ContinueWith(t =>
|
||||
// Inject the device state into the message template
|
||||
this.log.Debug("Preparing the message content using the device state", () => new { this.deviceId });
|
||||
var msg = this.message.MessageTemplate;
|
||||
foreach (var value in state)
|
||||
{
|
||||
var timeSpent = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - now;
|
||||
this.log.Debug("Telemetry delivered", () => new { this.deviceId, timeSpent, MessageSchema = this.message.MessageSchema.Name });
|
||||
this.context.HandleEvent(DeviceTelemetryActor.ActorEvents.TelemetryDelivered);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// device could be rebooting, updating firmware, etc.
|
||||
this.log.Debug("No telemetry will be sent as the device is offline...", () => new { this.deviceId });
|
||||
this.context.HandleEvent(DeviceTelemetryActor.ActorEvents.TelemetryDelivered);
|
||||
}
|
||||
msg = msg.Replace("${" + value.Key + "}", value.Value.ToString());
|
||||
}
|
||||
|
||||
this.log.Debug("Calling SendMessageAsync...",
|
||||
() => new { this.deviceId, MessageSchema = this.message.MessageSchema.Name, msg });
|
||||
|
||||
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
this.context.Client.SendMessageAsync(msg, this.message.MessageSchema)
|
||||
.ContinueWith(t =>
|
||||
{
|
||||
var timeSpent = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() - now;
|
||||
this.log.Debug("Telemetry delivered", () => new { this.deviceId, timeSpent, MessageSchema = this.message.MessageSchema.Name });
|
||||
this.context.HandleEvent(DeviceTelemetryActor.ActorEvents.TelemetryDelivered);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// device could be rebooting, updating firmware, etc.
|
||||
this.log.Debug("No telemetry will be sent as the device is offline...", () => new { this.deviceId });
|
||||
this.context.HandleEvent(DeviceTelemetryActor.ActorEvents.TelemetryDelivered);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче