IOTSharp的启动与其脚本引擎部分研究
项目地址:github.com/IoTSharp/Io...
为什么要研究这个
IoTSharp是一个开源的物联网平台,用于数据收集、处理、可视化和设备管理。项目中,需要做基础设施模块,脚本引擎部分,参考他的脚本引擎设计
IOTSharp 容器的启动
根据官方文档
这个目录下的 docker-compose.yml、appsettings.Production.json 复制出来
启动
然后进入http://localhost:8086 来配置 influxDb,创建一个 token
然后修改appsettings.Production.json
json
"TelemetryStorage":"http://influx:8086/?org=iotsharp&bucket=iotsharp-bucket&token=iotsharp-token&&latest=-72h",
重启 IotSharp 容器
docker restart iotsharp
Chrome浏览器访问 http://localhost:2927/ 来注册账号
使用邮箱+密码登录
IOTSharp 脚本引擎部分源码分析
位于源代码,IoTSharp.Interpreter 文件夹下
说明:这里的脚本应该使用了 对象.属性名 的形式作为变量,所以传来一个对象即可。项目中脚本变量是单独的 变量名 (脚本需要前端配置的,这样方便配置脚本),就需要对每个变量单独 SetValue,而不是只 Set 一个对象。
ScriptEngineBase 基类,Do 方法是虚方法,参数 input 是一个 json,需要后续反序列化为ExpandoObject 对象
csharp
public class ScriptEngineBase
{
internal CancellationToken _cancellationToken;
internal readonly ILogger _logger;
internal readonly EngineSetting _setting;
public ScriptEngineBase(ILogger logger, EngineSetting setting, CancellationToken cancellationToken)
{
_cancellationToken = cancellationToken;
_logger = logger;
_setting = setting;
}
public virtual void UseCancellationToken(CancellationToken cancellation)
{
_cancellationToken = cancellation;
}
public virtual string Do(string _source, string input)
{
return input;
}
}
BASICScriptEngine 默认引擎
JSON字符串转换成一个可以在运行时动态操作其成员的对象,并将这个对象再转换回JSON字符串
_source 是脚本,input 是脚本中的参数
csharp
public class BASICScriptEngine : ScriptEngineBase
{
public BASICScriptEngine(ILogger<PythonScriptEngine> logger , IOptions<EngineSetting> _opt) : base(logger, _opt.Value, System.Threading.Tasks.Task.Factory.CancellationToken)
{
}
public override string Do(string _source, string input)
{
var expConverter = new ExpandoObjectConverter();
dynamic obj = JsonConvert.DeserializeObject<ExpandoObject>(input, expConverter);
//https://github.com/Timu5/BasicSharp
var outputjson= JsonConvert.SerializeObject(obj);
return outputjson;
}
}
实现类, JavaScriptEngine
csharp
public class JavaScriptEngine:ScriptEngineBase, IDisposable
{
private Engine _engine;
private JsonParser _parser;
private bool disposedValue;
public JavaScriptEngine(ILogger<JavaScriptEngine> logger, IOptions<EngineSetting> _opt):base(logger,_opt.Value, Task.Factory.CancellationToken)
{
var engine = new Engine(options =>
{
// Limit memory allocations to MB
options.LimitMemory(4_000_000);
// Set a timeout to 4 seconds.
options.TimeoutInterval(TimeSpan.FromSeconds(_opt.Value.Timeout));
// Set limit of 1000 executed statements.
// options.MaxStatements(1000);
// Use a cancellation token.
options.CancellationToken(_cancellationToken);
});
_engine = engine;
_parser = new JsonParser(_engine);
}
public override string Do(string _source,string input)
{
var js = _engine.SetValue("input",_parser.Parse(input)).Evaluate(_source).ToObject();
var json= System.Text.Json.JsonSerializer.Serialize(js);
_logger.LogDebug($"source:{Environment.NewLine}{ _source}{Environment.NewLine}{Environment.NewLine}input:{Environment.NewLine}{ input}{Environment.NewLine}{Environment.NewLine} ouput:{Environment.NewLine}{ json}{Environment.NewLine}{Environment.NewLine}");
return json;
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
_engine= null;
_parser = null;
// TODO: 释放托管状态(托管对象)
}
// TODO: 释放未托管的资源(未托管的对象)并重写终结器
// TODO: 将大型字段设置为 null
disposedValue = true;
}
}
public void Dispose()
{
// 不要更改此代码。请将清理代码放入"Dispose(bool disposing)"方法中
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
EngineSetting 配置类
arduino
public class EngineSetting
{
public double Timeout { get; set; } = 4;
}
ScriptEnginesExtensions 类
定义了添加脚本引擎的扩展方法
swift
public static class ScriptEnginesExtensions
{
public static IServiceCollection AddScriptEngines(this IServiceCollection services, IConfiguration configuration)
{
services.AddTransient<JavaScriptEngine>();
services.AddTransient<PythonScriptEngine>();
services.AddTransient<SQLEngine>();
services.AddTransient<LuaScriptEngine>();
services.AddTransient<CSharpScriptEngine>();
services.Configure<EngineSetting>(configuration);
return services;
}
}
示例 1: 调用 jsEngine
ini
[TestClass]
public class ScriptEngineTest
{
private JavaScriptEngine _js_engine;
private PythonScriptEngine _python_engine;
private LuaScriptEngine _lua_engine;
private CScriptEngine _c_engine;
private SQLEngine _sql_engine;
private CSharpScriptEngine _csharp_engine;
[TestInitialize]
public void InitTestScriptEngine()
{
var lgf = LoggerFactory.Create(f =>
{
f.AddConsole();
});
_js_engine = new JavaScriptEngine(lgf.CreateLogger<JavaScriptEngine>(), Options.Create( new Interpreter.EngineSetting() { Timeout = 4 }));
_python_engine = new PythonScriptEngine(lgf.CreateLogger<PythonScriptEngine>(), Options.Create(new Interpreter.EngineSetting() { Timeout = 4 }));
_lua_engine = new LuaScriptEngine (lgf.CreateLogger<LuaScriptEngine>(), Options.Create(new Interpreter.EngineSetting() { Timeout = 4 }));
_c_engine = new CScriptEngine(lgf.CreateLogger<CScriptEngine>(), Options.Create(new Interpreter.EngineSetting() { Timeout = 4 }));
_sql_engine=new SQLEngine(lgf.CreateLogger<SQLEngine>(), Options.Create(new Interpreter.EngineSetting() { Timeout = 4 }));
_csharp_engine = new CSharpScriptEngine (lgf.CreateLogger<CSharpScriptEngine>(), Options.Create(new Interpreter.EngineSetting() { Timeout = 4 }), new MemoryCache(new MemoryCacheOptions()));
}
[TestMethod]
public void TestJavaScript()
{
var intput = System.Text.Json.JsonSerializer.Serialize(new { temperature = 39, height = 192, weight = 121 });
string output = _js_engine.Do(@"
var _m = (input.height / 100);
var output = {
fever: input.temperature > 38 ? true : false,
fat: input.weight / (_m * _m)>28?true:false
};
return output;
", intput);
var t = new { fever = true, fat = true };
var outpuobj = System.Text.Json.JsonSerializer.Deserialize(output, t.GetType());
Assert.AreEqual(outpuobj, t);
}
示例 2: 调用 jsEngine
ini
public class FlowOperation
{
[Key]
public Guid OperationId { get; set; }
public DateTime? AddDate { get; set; }
/// <summary>
/// 节点处理状态,0 创建完
/// </summary>
public int NodeStatus { get; set; }
public string OperationDesc { get; set; }
public string Data { get; set; }
public string BizId { get; set; }
public string bpmnid { get; set; }
public Flow Flow { get; set; }
public FlowRule FlowRule { get; set; }
public BaseEvent BaseEvent { get; set; }
public int Step { get; set; }
public string Tag { get; set; }
}
var taskoperation = new FlowOperation()
{
OperationId = Guid.NewGuid(),
bpmnid = flow.bpmnid,
AddDate = DateTime.UtcNow,
FlowRule = peroperation.BaseEvent.FlowRule,
Flow = flow,
Data = JsonConvert.SerializeObject(data),
NodeStatus = 1,
OperationDesc = "Run" + flow.NodeProcessScriptType + "Task:" + flow.Flowname,
Step = step,
BaseEvent = peroperation.BaseEvent
};
case "javascript":
{
using (var js = _sp.GetRequiredService<JavaScriptEngine>())
{
try
{
string result = js.Do(scriptsrc, taskoperation.Data);
obj = JsonConvert.DeserializeObject<object>(result);
}
catch (Exception ex)
{
_logger.Log(LogLevel.Warning, "javascript脚本执行异常");
taskoperation.OperationDesc += ex.Message;
taskoperation.NodeStatus = 2;
}
}
}
break;
脚本引擎选型与基本使用
目标
将脚本存在数据库中,取出脚本,传递参数进行计算,来达到不用写死脚本在代码中的效果
可以指定计算引擎类型,比如 javascript 的 jint、C# Python 等
方案选择
- NCalc:一个灵活的数学表达式计算器,支持.NET环境,允许运行时解析和执行字符串形式的表达式。
- Jint:一个JavaScript解释器,可以在.NET环境中执行JavaScript代码,适合需要执行复杂逻辑的场景。
- Roslyn:.NET的编译器平台,支持运行时代码生成和编译,可以用于动态编译并执行C#代码。
Roslyn 使用示例
ini
using Microsoft.CodeAnalysis.CSharp.Scripting;
var code = "1 + 2";
var result = await CSharpScript.EvaluateAsync<int>(code);
Console.WriteLine(result); // 输出: 3
如果需要传递参数
csharp
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
// 假设这是从数据库中读取到的计算公式
string formula = "a + b";
// 创建一个包含计算所需参数的全局对象
var globals = new Globals { a = 1, b = 2 };
// 执行计算
var result = await CSharpScript.EvaluateAsync<int>(formula, globals: globals);
Console.WriteLine(result); // 输出: 3
// 定义一个包含参数的类
public class Globals
{
public int a;
public int b;
}
Ncalc 使用示例
csharp
using NCalc;
// 从数据库中获取的公式字符串
string formula = "3 + 2 * [Parameter1]";
// 创建参数字典
Dictionary<string, object> parameters = new Dictionary<string, object>
{
{ "Parameter1", 10 }
};
Expression expression = new Expression(formula);
// 设置参数
foreach(var param in parameters)
{
expression.Parameters[param.Key] = param.Value;
}
// 执行计算
object result = expression.Evaluate();
Console.WriteLine(result); // 输出:23
Jint 使用示例
ini
using Jint;
var engine = new Engine()
.SetValue("参数1", 10); // 设置JavaScript变量
var result = engine.Execute("var 结果 = 3 + 2 * 参数1; 结果").GetCompletionValue().AsNumber();
Console.WriteLine(result); // 输出: 23
Jint 用法最简单,采用 Jint 实现。