v2.0 引用新一代Remoting,简化IoTZero架构智能大石头 authored at 2024-06-20 23:03:45
diff --git "a/IoT.Data/Entity/\350\256\276\345\244\207.Biz.cs" "b/IoT.Data/Entity/\350\256\276\345\244\207.Biz.cs"
index add4f96..985e8c6 100644
--- "a/IoT.Data/Entity/\350\256\276\345\244\207.Biz.cs"
+++ "b/IoT.Data/Entity/\350\256\276\345\244\207.Biz.cs"
@@ -1,6 +1,4 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
+using System.ComponentModel;
using System.Runtime.Serialization;
using System.Web.Script.Serialization;
using System.Xml.Serialization;
@@ -10,13 +8,13 @@ using NewLife.Data;
using NewLife.IoT.Models;
using NewLife.Log;
using NewLife.Remoting;
+using NewLife.Remoting.Models;
using XCode;
using XCode.Cache;
-using XCode.Membership;
namespace IoT.Data;
-public partial class Device : Entity<Device>
+public partial class Device : Entity<Device>, IDeviceModel
{
#region 对象操作
static Device()
diff --git "a/IoT.Data/Entity/\350\256\276\345\244\207\345\234\250\347\272\277.Biz.cs" "b/IoT.Data/Entity/\350\256\276\345\244\207\345\234\250\347\272\277.Biz.cs"
index fdd13af..bec8005 100644
--- "a/IoT.Data/Entity/\350\256\276\345\244\207\345\234\250\347\272\277.Biz.cs"
+++ "b/IoT.Data/Entity/\350\256\276\345\244\207\345\234\250\347\272\277.Biz.cs"
@@ -1,34 +1,16 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.Serialization;
-using System.Text;
-using System.Threading.Tasks;
-using System.Web;
+using System.Runtime.Serialization;
using System.Web.Script.Serialization;
using System.Xml.Serialization;
using NewLife;
using NewLife.Data;
using NewLife.IoT.Models;
-using NewLife.Log;
-using NewLife.Model;
-using NewLife.Reflection;
+using NewLife.Remoting.Models;
using NewLife.Serialization;
-using NewLife.Threading;
-using NewLife.Web;
using XCode;
-using XCode.Cache;
-using XCode.Configuration;
-using XCode.DataAccessLayer;
-using XCode.Membership;
-using XCode.Shards;
namespace IoT.Data;
-public partial class DeviceOnline : Entity<DeviceOnline>
+public partial class DeviceOnline : Entity<DeviceOnline>, IOnlineModel
{
#region 对象操作
static DeviceOnline()
diff --git a/IoT.Data/IoT.Data.csproj b/IoT.Data/IoT.Data.csproj
index 4971ce0..d8e4ba6 100644
--- a/IoT.Data/IoT.Data.csproj
+++ b/IoT.Data/IoT.Data.csproj
@@ -1,17 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <TargetFramework>netstandard2.1</TargetFramework>
+ <TargetFramework>netstandard2.0</TargetFramework>
<AssemblyTitle>物联网数据层</AssemblyTitle>
<Description>IoT数据层</Description>
<Company>新生命开发团队</Company>
- <Copyright>©2002-2023 NewLife</Copyright>
- <VersionPrefix>1.0</VersionPrefix>
+ <Copyright>©2002-2024 NewLife</Copyright>
+ <VersionPrefix>2.0</VersionPrefix>
<VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
<Version>$(VersionPrefix).$(VersionSuffix)</Version>
<FileVersion>$(Version)</FileVersion>
<AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
<Deterministic>false</Deterministic>
+ <ImplicitUsings>enable</ImplicitUsings>
<LangVersion>latest</LangVersion>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
@@ -25,17 +26,23 @@
</PropertyGroup>
<ItemGroup>
+ <Compile Remove="Config\**" />
<Compile Remove="Entity\Config\**" />
<Compile Remove="Entity\Log\**" />
+ <Compile Remove="Log\**" />
+ <EmbeddedResource Remove="Config\**" />
<EmbeddedResource Remove="Entity\Config\**" />
<EmbeddedResource Remove="Entity\Log\**" />
+ <EmbeddedResource Remove="Log\**" />
+ <None Remove="Config\**" />
<None Remove="Entity\Config\**" />
<None Remove="Entity\Log\**" />
+ <None Remove="Log\**" />
</ItemGroup>
<ItemGroup>
- <PackageReference Include="NewLife.IoT" Version="1.8.2023.611-beta1629" />
- <PackageReference Include="NewLife.XCode" Version="11.8.2023.628-beta0652" />
+ <PackageReference Include="NewLife.IoT" Version="2.2.2024.501" />
+ <PackageReference Include="NewLife.XCode" Version="11.13.2024.606" />
</ItemGroup>
<ItemGroup>
diff --git a/IoTCore/IoTCore.csproj b/IoTCore/IoTCore.csproj
index a12439d..40e9297 100644
--- a/IoTCore/IoTCore.csproj
+++ b/IoTCore/IoTCore.csproj
@@ -5,8 +5,8 @@
<AssemblyTitle>新生命IoT核心库</AssemblyTitle>
<Description>IoT框架基础类库</Description>
<Company>新生命开发团队</Company>
- <Copyright>©2002-2023 NewLife</Copyright>
- <VersionPrefix>1.6</VersionPrefix>
+ <Copyright>©2002-2024 NewLife</Copyright>
+ <VersionPrefix>2.0</VersionPrefix>
<VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
<Version>$(VersionPrefix).$(VersionSuffix)</Version>
<FileVersion>$(Version)</FileVersion>
@@ -19,7 +19,12 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="NewLife.IoT" Version="1.8.2023.611-beta1629" />
+ <Compile Remove="Models\UpgradeInfo.cs" />
+ </ItemGroup>
+
+ <ItemGroup>
+ <PackageReference Include="NewLife.IoT" Version="2.2.2024.501" />
+ <PackageReference Include="NewLife.Remoting" Version="3.0.2024.620-beta1407" />
</ItemGroup>
</Project>
diff --git a/IoTCore/Models/LoginInfo.cs b/IoTCore/Models/LoginInfo.cs
index 9b98f11..e97818a 100644
--- a/IoTCore/Models/LoginInfo.cs
+++ b/IoTCore/Models/LoginInfo.cs
@@ -1,37 +1,39 @@
-using System;
+using NewLife.Remoting.Models;
-namespace NewLife.IoT.Models
+namespace NewLife.IoT.Models;
+
+/// <summary>节点登录信息</summary>
+public class LoginInfo : ILoginRequest
{
- /// <summary>节点登录信息</summary>
- public class LoginInfo
- {
- #region 属性
- /// <summary>设备编码</summary>
- public String Code { get; set; }
+ #region 属性
+ /// <summary>设备编码</summary>
+ public String Code { get; set; }
+
+ /// <summary>设备密钥</summary>
+ public String Secret { get; set; }
- /// <summary>设备密钥</summary>
- public String Secret { get; set; }
+ /// <summary>产品证书</summary>
+ public String ProductKey { get; set; }
- /// <summary>产品证书</summary>
- public String ProductKey { get; set; }
+ /// <summary>产品密钥</summary>
+ public String ProductSecret { get; set; }
- /// <summary>产品密钥</summary>
- public String ProductSecret { get; set; }
+ /// <summary>实例。应用可能多实例部署,ip@proccessid</summary>
+ public String ClientId { get; set; }
- /// <summary>名称。可用于标识设备的名称</summary>
- public String Name { get; set; }
+ /// <summary>名称。可用于标识设备的名称</summary>
+ public String Name { get; set; }
- /// <summary>版本</summary>
- public String Version { get; set; }
+ /// <summary>版本</summary>
+ public String Version { get; set; }
- /// <summary>本地IP地址</summary>
- public String IP { get; set; }
+ /// <summary>本地IP地址</summary>
+ public String IP { get; set; }
- /// <summary>唯一标识</summary>
- public String UUID { get; set; }
+ /// <summary>唯一标识</summary>
+ public String UUID { get; set; }
- /// <summary>本地UTC时间</summary>
- public Int64 Time { get; set; }
- #endregion
- }
+ /// <summary>本地UTC时间</summary>
+ public Int64 Time { get; set; }
+ #endregion
}
\ No newline at end of file
diff --git a/IoTCore/Models/LoginResponse.cs b/IoTCore/Models/LoginResponse.cs
index 4778d71..3dd4329 100644
--- a/IoTCore/Models/LoginResponse.cs
+++ b/IoTCore/Models/LoginResponse.cs
@@ -1,34 +1,34 @@
using System;
+using NewLife.Remoting.Models;
-namespace NewLife.IoT.Models
+namespace NewLife.IoT.Models;
+
+/// <summary>设备登录响应</summary>
+public class LoginResponse : ILoginResponse
{
- /// <summary>设备登录响应</summary>
- public class LoginResponse
- {
- #region 属性
- /// <summary>产品</summary>
- public String ProductKey { get; set; }
+ #region 属性
+ /// <summary>产品</summary>
+ public String ProductKey { get; set; }
- /// <summary>节点编码</summary>
- public String Code { get; set; }
+ /// <summary>节点编码</summary>
+ public String Code { get; set; }
- /// <summary>节点密钥</summary>
- public String Secret { get; set; }
+ /// <summary>节点密钥</summary>
+ public String Secret { get; set; }
- /// <summary>名称</summary>
- public String Name { get; set; }
+ /// <summary>名称</summary>
+ public String Name { get; set; }
- /// <summary>令牌</summary>
- public String Token { get; set; }
+ /// <summary>令牌</summary>
+ public String Token { get; set; }
- /// <summary>服务器时间</summary>
- public Int64 Time { get; set; }
+ /// <summary>服务器时间</summary>
+ public Int64 Time { get; set; }
- ///// <summary>设备通道</summary>
- //public String Channels { get; set; }
+ ///// <summary>设备通道</summary>
+ //public String Channels { get; set; }
- /// <summary>客户端唯一标识</summary>
- public String ClientId { get; set; }
- #endregion
- }
+ /// <summary>客户端唯一标识</summary>
+ public String ClientId { get; set; }
+ #endregion
}
\ No newline at end of file
diff --git a/IoTCore/Models/LogoutResponse.cs b/IoTCore/Models/LogoutResponse.cs
index b5aaca3..352fba1 100644
--- a/IoTCore/Models/LogoutResponse.cs
+++ b/IoTCore/Models/LogoutResponse.cs
@@ -1,22 +1,22 @@
using System;
+using NewLife.Remoting.Models;
-namespace NewLife.IoT.Models
+namespace NewLife.IoT.Models;
+
+/// <summary>设备注销响应</summary>
+public class LogoutResponse : ILogoutResponse
{
- /// <summary>设备注销响应</summary>
- public class LogoutResponse
- {
- #region 属性
- /// <summary>节点编码</summary>
- public String Code { get; set; }
+ #region 属性
+ /// <summary>节点编码</summary>
+ public String Code { get; set; }
- /// <summary>节点密钥</summary>
- public String Secret { get; set; }
+ /// <summary>节点密钥</summary>
+ public String Secret { get; set; }
- /// <summary>名称</summary>
- public String Name { get; set; }
+ /// <summary>名称</summary>
+ public String Name { get; set; }
- /// <summary>令牌</summary>
- public String Token { get; set; }
- #endregion
- }
+ /// <summary>令牌</summary>
+ public String Token { get; set; }
+ #endregion
}
\ No newline at end of file
diff --git a/IoTCore/Models/PingInfo.cs b/IoTCore/Models/PingInfo.cs
index d1b6c6b..ca2fbaa 100644
--- a/IoTCore/Models/PingInfo.cs
+++ b/IoTCore/Models/PingInfo.cs
@@ -1,43 +1,42 @@
-using System;
+using NewLife.Remoting.Models;
-namespace NewLife.IoT.Models
+namespace NewLife.IoT.Models;
+
+/// <summary>心跳信息</summary>
+public class PingInfo : IPingRequest
{
- /// <summary>心跳信息</summary>
- public class PingInfo
- {
- #region 属性
- /// <summary>内存大小</summary>
- public UInt64 Memory { get; set; }
+ #region 属性
+ /// <summary>内存大小</summary>
+ public UInt64 Memory { get; set; }
- /// <summary>可用内存大小</summary>
- public UInt64 AvailableMemory { get; set; }
+ /// <summary>可用内存大小</summary>
+ public UInt64 AvailableMemory { get; set; }
- /// <summary>磁盘大小。应用所在盘</summary>
- public UInt64 TotalSize { get; set; }
+ /// <summary>磁盘大小。应用所在盘</summary>
+ public UInt64 TotalSize { get; set; }
- /// <summary>磁盘可用空间。应用所在盘</summary>
- public UInt64 AvailableFreeSpace { get; set; }
+ /// <summary>磁盘可用空间。应用所在盘</summary>
+ public UInt64 AvailableFreeSpace { get; set; }
- /// <summary>CPU使用率</summary>
- public Single CpuRate { get; set; }
+ /// <summary>CPU使用率</summary>
+ public Double CpuRate { get; set; }
- /// <summary>温度</summary>
- public Double Temperature { get; set; }
+ /// <summary>温度</summary>
+ public Double Temperature { get; set; }
- /// <summary>电量</summary>
- public Double Battery { get; set; }
+ /// <summary>电量</summary>
+ public Double Battery { get; set; }
- /// <summary>本地IP</summary>
- public String IP { get; set; }
+ /// <summary>本地IP</summary>
+ public String IP { get; set; }
- /// <summary>开机时间,单位s</summary>
- public Int32 Uptime { get; set; }
+ /// <summary>开机时间,单位s</summary>
+ public Int32 Uptime { get; set; }
- /// <summary>本地UTC时间。ms毫秒</summary>
- public Int64 Time { get; set; }
+ /// <summary>本地UTC时间。ms毫秒</summary>
+ public Int64 Time { get; set; }
- /// <summary>延迟。ms毫秒</summary>
- public Int32 Delay { get; set; }
- #endregion
- }
+ /// <summary>延迟。ms毫秒</summary>
+ public Int32 Delay { get; set; }
+ #endregion
}
\ No newline at end of file
diff --git a/IoTCore/Models/PingResponse.cs b/IoTCore/Models/PingResponse.cs
index ec717a5..3296b17 100644
--- a/IoTCore/Models/PingResponse.cs
+++ b/IoTCore/Models/PingResponse.cs
@@ -1,21 +1,22 @@
-using System;
-using NewLife.IoT.ThingModels;
+using NewLife.Remoting.Models;
-namespace NewLife.IoT.Models
+namespace NewLife.IoT.Models;
+
+/// <summary>心跳响应</summary>
+public class PingResponse : IPingResponse
{
- /// <summary>心跳响应</summary>
- public class PingResponse
- {
- /// <summary>本地时间。ms毫秒</summary>
- public Int64 Time { get; set; }
+ /// <summary>本地时间。ms毫秒</summary>
+ public Int64 Time { get; set; }
+
+ /// <summary>服务器时间</summary>
+ public Int64 ServerTime { get; set; }
- /// <summary>服务器时间</summary>
- public Int64 ServerTime { get; set; }
+ /// <summary>心跳周期。单位秒</summary>
+ public Int32 Period { get; set; }
- /// <summary>心跳周期。单位秒</summary>
- public Int32 Period { get; set; }
+ /// <summary>令牌。现有令牌即将过期时,颁发新的令牌</summary>
+ public String Token { get; set; }
- /// <summary>令牌。现有令牌即将过期时,颁发新的令牌</summary>
- public String Token { get; set; }
- }
+ /// <summary>下发命令</summary>
+ public CommandModel[]? Commands { get; set; }
}
\ No newline at end of file
diff --git a/IoTEdge/ClientSetting.cs b/IoTEdge/ClientSetting.cs
index 749f03d..3a4e38c 100644
--- a/IoTEdge/ClientSetting.cs
+++ b/IoTEdge/ClientSetting.cs
@@ -1,11 +1,12 @@
using System.ComponentModel;
using NewLife.Configuration;
+using NewLife.Remoting.Clients;
namespace IoTEdge;
/// <summary>配置</summary>
[Config("IoTClient")]
-public class ClientSetting : Config<ClientSetting>
+public class ClientSetting : Config<ClientSetting>, IClientSetting
{
#region 属性
/// <summary>服务端地址。IoT服务平台地址</summary>
@@ -24,4 +25,9 @@ public class ClientSetting : Config<ClientSetting>
[Description("产品证书。用于一型一密验证,对一机一密无效")]
public String ProductKey { get; set; } = "EdgeGateway";
#endregion
+
+ #region IClientSetting
+ String IClientSetting.Code { get => DeviceCode; set => DeviceCode = value; }
+ String IClientSetting.Secret { get => DeviceSecret; set => DeviceSecret = value; }
+ #endregion
}
\ No newline at end of file
diff --git a/IoTEdge/EdgeService.cs b/IoTEdge/EdgeService.cs
index 36cdc8d..983a17e 100644
--- a/IoTEdge/EdgeService.cs
+++ b/IoTEdge/EdgeService.cs
@@ -25,14 +25,14 @@ internal class EdgeService : IHostedService
Log = XTrace.Log,
};
- await device.LoginAsync();
+ await device.Login();
_device = device;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
- if (_device != null) await _device.LogoutAsync(nameof(StopAsync));
+ if (_device != null) await _device.Logout(nameof(StopAsync));
_device.TryDispose();
}
diff --git a/IoTEdge/HttpDevice.cs b/IoTEdge/HttpDevice.cs
index 05a2ef0..ab556d5 100644
--- a/IoTEdge/HttpDevice.cs
+++ b/IoTEdge/HttpDevice.cs
@@ -1,281 +1,91 @@
-using System.Diagnostics;
-using System.Net;
-using System.Net.NetworkInformation;
-using System.Net.WebSockets;
-using NewLife;
-using NewLife.Caching;
-using NewLife.IoT.Models;
+using NewLife.IoT.Models;
using NewLife.IoT.ThingModels;
using NewLife.Log;
-using NewLife.Remoting;
+using NewLife.Model;
+using NewLife.Remoting.Clients;
+using NewLife.Remoting.Models;
using NewLife.Security;
-using NewLife.Serialization;
-using NewLife.Threading;
namespace IoTEdge;
/// <summary>Http协议设备</summary>
-public class HttpDevice : DisposeBase
+public class HttpDevice : ClientBase
{
#region 属性
- /// <summary>服务器地址</summary>
- public String Server { get; set; }
-
- /// <summary>设备编码。从IoT管理平台获取(需提前分配),或者本地提交后动态注册</summary>
- public String DeviceCode { get; set; }
-
- /// <summary>密钥。设备密钥或产品密钥,分别用于一机一密和一型一密,从IoT管理平台获取</summary>
- public String DeviceSecret { get; set; }
-
/// <summary>产品编码。从IoT管理平台获取</summary>
public String ProductKey { get; set; }
- /// <summary>密码散列提供者。避免密码明文提交</summary>
- public IPasswordProvider PasswordProvider { get; set; } = new SaltPasswordProvider { Algorithm = "md5", SaltTime = 60 };
-
- /// <summary>收到命令时触发</summary>
- public event EventHandler<ServiceEventArgs> Received;
-
private readonly ClientSetting _setting;
- private ApiHttpClient _client;
- private Int32 _delay;
#endregion
#region 构造
- public HttpDevice() { }
+ public HttpDevice() => Prefix = "Device/";
- public HttpDevice(ClientSetting setting)
+ public HttpDevice(ClientSetting setting) : base(setting)
{
_setting = setting;
- Server = setting.Server;
- DeviceCode = setting.DeviceCode;
- DeviceSecret = setting.DeviceSecret;
ProductKey = setting.ProductKey;
}
-
- protected override void Dispose(Boolean disposing)
- {
- base.Dispose(disposing);
-
- StopTimer();
- }
#endregion
- #region 登录注销
- /// <summary>
- /// 登录
- /// </summary>
- /// <param name="inf"></param>
- /// <returns></returns>
- public async Task LoginAsync()
+ #region 方法
+ protected override void OnInit()
{
- var client = new ApiHttpClient(Server)
- {
- Tracer = Tracer,
- Log = XTrace.Log,
- };
-
- var info = new LoginInfo
- {
- Code = DeviceCode,
- Secret = DeviceSecret.IsNullOrEmpty() ? null : PasswordProvider.Hash(DeviceSecret),
- ProductKey = ProductKey,
- };
- var rs = await client.PostAsync<LoginResponse>("Device/Login", info);
- client.Token = rs.Token;
+ var provider = ServiceProvider ??= ObjectContainer.Provider;
- if (!rs.Code.IsNullOrEmpty() && !rs.Secret.IsNullOrEmpty())
+ // 找到容器,注册默认的模型实现,供后续InvokeAsync时自动创建正确的模型对象
+ var container = ModelExtension.GetService<IObjectContainer>(provider) ?? ObjectContainer.Current;
+ if (container != null)
{
- WriteLog("下发证书:{0}/{1}", rs.Code, rs.Secret);
- DeviceCode = rs.Code;
- DeviceSecret = rs.Secret;
-
- _setting.DeviceCode = rs.Code;
- _setting.DeviceSecret = rs.Secret;
- _setting.Save();
+ container.TryAddTransient<ILoginRequest, LoginInfo>();
+ //container.TryAddTransient<ILoginResponse, LoginResponse>();
+ //container.TryAddTransient<ILogoutResponse, LogoutResponse>();
+ container.TryAddTransient<IPingRequest, PingInfo>();
+ //container.TryAddTransient<IPingResponse, PingResponse>();
+ //container.TryAddTransient<IUpgradeInfo, UpgradeInfo>();
}
- _client = client;
-
- StartTimer();
+ base.OnInit();
}
-
- /// <summary>注销</summary>
- /// <param name="reason"></param>
- /// <returns></returns>
- public async Task LogoutAsync(String reason) => await _client.PostAsync<LogoutResponse>("Device/Logout", new { reason });
#endregion
- #region 心跳&长连接
- /// <summary>心跳</summary>
- /// <returns></returns>
- public virtual async Task PingAsync()
+ #region 登录注销
+ public override ILoginRequest BuildLoginRequest()
{
- if (Tracer != null) DefaultSpan.Current = null;
-
- using var span = Tracer?.NewSpan("Ping");
- try
+ var request = base.BuildLoginRequest();
+ if (request is LoginInfo info)
{
- var info = GetHeartInfo();
-
- var rs = await _client.PostAsync<PingResponse>("Device/Ping", info);
-
- var dt = rs.Time.ToDateTime();
- if (dt.Year > 2000)
- {
- // 计算延迟
- var ts = DateTime.UtcNow - dt;
- var ms = (Int32)ts.TotalMilliseconds;
- _delay = (_delay + ms) / 2;
- }
-
- var svc = _client.Services.FirstOrDefault();
- if (svc != null && _client.Token != null && (_websocket == null || _websocket.State != WebSocketState.Open))
- {
- var url = svc.Address.ToString().Replace("http://", "ws://").Replace("https://", "wss://");
- var uri = new Uri(new Uri(url), "/Device/Notify");
- var client = new ClientWebSocket();
- client.Options.SetRequestHeader("Authorization", "Bearer " + _client.Token);
- await client.ConnectAsync(uri, default);
-
- _websocket = client;
-
- _source = new CancellationTokenSource();
- _ = Task.Run(() => DoPull(client, _source.Token));
- }
-
- // 令牌
- if (rs is PingResponse pr && !pr.Token.IsNullOrEmpty())
- _client.Token = pr.Token;
+ info.ProductKey = ProductKey;
}
- catch (Exception ex)
- {
- span?.SetError(ex, null);
- throw;
- }
+ return request;
}
+ #endregion
- /// <summary>获取心跳信息</summary>
- public PingInfo GetHeartInfo()
+ #region 心跳
+ public override IPingRequest BuildPingRequest()
{
- var mi = MachineInfo.GetCurrent();
- mi.Refresh();
-
- var properties = IPGlobalProperties.GetIPGlobalProperties();
- var connections = properties.GetActiveTcpConnections();
-
- var mcs = NetHelper.GetMacs().Select(e => e.ToHex("-")).OrderBy(e => e).Join(",");
- var driveInfo = new DriveInfo(Path.GetPathRoot(".".GetFullPath()));
- var ip = NetHelper.GetIPs().Where(ip => ip.IsIPv4() && !IPAddress.IsLoopback(ip) && ip.GetAddressBytes()[0] != 169).Join();
- var ext = new PingInfo
+ var request = base.BuildPingRequest();
+ if (request is PingInfo info)
{
- Memory = mi.Memory,
- AvailableMemory = mi.AvailableMemory,
- TotalSize = (UInt64)driveInfo.TotalSize,
- AvailableFreeSpace = (UInt64)driveInfo.AvailableFreeSpace,
- CpuRate = (Single)Math.Round(mi.CpuRate, 4),
- Temperature = mi.Temperature,
- Battery = mi.Battery,
- Uptime = Environment.TickCount / 1000,
-
- IP = ip,
-
- Time = DateTime.UtcNow.ToLong(),
- Delay = _delay,
- };
- // 开始时间 Environment.TickCount 很容易溢出,导致开机24天后变成负数。
- // 后来在 netcore3.0 增加了Environment.TickCount64
- // 现在借助 Stopwatch 来解决
- if (Stopwatch.IsHighResolution) ext.Uptime = (Int32)(Stopwatch.GetTimestamp() / Stopwatch.Frequency);
-
- return ext;
- }
-
- private TimerX _timer;
- /// <summary>开始心跳定时器</summary>
- protected virtual void StartTimer()
- {
- if (_timer == null)
- lock (this)
- _timer ??= new TimerX(async s => await PingAsync(), null, 3_000, 60_000, "Device") { Async = true };
- }
-
- /// <summary>停止心跳定时器</summary>
- protected void StopTimer()
- {
- if (_websocket != null && _websocket.State == WebSocketState.Open)
- _websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "finish", default).Wait();
- _source?.Cancel();
-
- _websocket = null;
-
- _timer.TryDispose();
- _timer = null;
- }
-
- private WebSocket _websocket;
- private CancellationTokenSource _source;
- private ICache _cache = new MemoryCache();
- private async Task DoPull(WebSocket socket, CancellationToken cancellationToken)
- {
- DefaultSpan.Current = null;
- try
- {
- var buf = new Byte[4 * 1024];
- while (!cancellationToken.IsCancellationRequested && socket.State == WebSocketState.Open)
- {
- var data = await socket.ReceiveAsync(new ArraySegment<Byte>(buf), cancellationToken);
- var model = buf.ToStr(null, 0, data.Count).ToJsonEntity<ServiceModel>();
- if (model != null && _cache.Add($"cmd:{model.Id}", model, 3600))
- {
- // 建立追踪链路
- using var span = Tracer?.NewSpan("service:" + model.Name, model);
- span?.Detach(model.TraceId);
- try
- {
- if (model.Expire.Year < 2000 || model.Expire > DateTime.Now)
- await OnReceiveCommand(model);
- else
- await ServiceReply(new ServiceReplyModel { Id = model.Id, Status = ServiceStatus.取消 });
- }
- catch (Exception ex)
- {
- span?.SetError(ex, null);
- }
- }
- }
- }
- catch (Exception ex)
- {
- XTrace.WriteException(ex);
}
- if (socket.State == WebSocketState.Open) await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "finish", default);
+ return request;
}
- #endregion
- #region 服务
- /// <summary>
- /// 触发收到命令的动作
- /// </summary>
- /// <param name="model"></param>
- protected virtual async Task OnReceiveCommand(ServiceModel model)
+ public override Task<Object> CommandReply(CommandReplyModel model) => InvokeAsync<Object>("Thing/ServiceReply", new ServiceReplyModel
{
- var e = new ServiceEventArgs { Model = model };
- Received?.Invoke(this, e);
-
- if (e.Reply != null) await ServiceReply(e.Reply);
- }
-
- /// <summary>上报服务调用结果</summary>
- /// <param name="model"></param>
- /// <returns></returns>
- public virtual async Task<Object> ServiceReply(ServiceReplyModel model) => await _client.PostAsync<Object>("Thing/ServiceReply", model);
+ Id = model.Id,
+ Status = (ServiceStatus)model.Status,
+ Data = model.Data,
+ });
#endregion
+ #region 数据
+ /// <summary>上传数据</summary>
+ /// <returns></returns>
public async Task PostDataAsync()
{
if (Tracer != null) DefaultSpan.Current = null;
@@ -285,17 +95,16 @@ public class HttpDevice : DisposeBase
{
var items = new List<DataModel>
{
- new DataModel
- {
+ new() {
Time = DateTime.UtcNow.ToLong(),
Name = "TestValue",
Value = Rand.Next(0, 100) + ""
}
};
- var data = new DataModels { DeviceCode = DeviceCode, Items = items.ToArray() };
+ var data = new DataModels { DeviceCode = Code, Items = items.ToArray() };
- await _client.PostAsync<Int32>("Thing/PostData", data);
+ await InvokeAsync<Int32>("Thing/PostData", data);
}
catch (Exception ex)
{
@@ -304,17 +113,5 @@ public class HttpDevice : DisposeBase
throw;
}
}
-
- #region 日志
- /// <summary>链路追踪</summary>
- public ITracer Tracer { get; set; }
-
- /// <summary>日志</summary>
- public ILog Log { get; set; }
-
- /// <summary>写日志</summary>
- /// <param name="format"></param>
- /// <param name="args"></param>
- public void WriteLog(String format, params Object[] args) => Log?.Info(format, args);
#endregion
}
\ No newline at end of file
diff --git a/IoTEdge/IoTEdge.csproj b/IoTEdge/IoTEdge.csproj
index 7ade8c9..c55b801 100644
--- a/IoTEdge/IoTEdge.csproj
+++ b/IoTEdge/IoTEdge.csproj
@@ -6,8 +6,8 @@
<AssemblyTitle>物联网网关</AssemblyTitle>
<Description>IoT边缘网关</Description>
<Company>新生命开发团队</Company>
- <Copyright>©2002-2023 NewLife</Copyright>
- <VersionPrefix>1.0</VersionPrefix>
+ <Copyright>©2002-2024 NewLife</Copyright>
+ <VersionPrefix>2.0</VersionPrefix>
<VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
<Version>$(VersionPrefix).$(VersionSuffix)</Version>
<FileVersion>$(Version)</FileVersion>
@@ -21,17 +21,17 @@
<ItemGroup>
<PackageReference Include="NewLife.BACnet" Version="1.0.2023.520-beta0022" />
- <PackageReference Include="NewLife.Core" Version="10.3.2023.627-beta1611" />
- <PackageReference Include="NewLife.IoT" Version="1.8.2023.611-beta1629" />
- <PackageReference Include="NewLife.Modbus" Version="1.6.2023.511" />
- <PackageReference Include="NewLife.ModbusRTU" Version="1.6.2023.511" />
- <PackageReference Include="NewLife.MQTT" Version="1.4.2023.620-beta1039" />
- <PackageReference Include="NewLife.NetPing" Version="1.1.2023.511" />
- <PackageReference Include="NewLife.PC" Version="1.0.2023.511" />
- <PackageReference Include="NewLife.Schneider" Version="1.0.2023.511" />
- <PackageReference Include="NewLife.Siemens" Version="1.0.2023.511" />
- <PackageReference Include="NewLife.Stardust" Version="2.9.2023.627-beta0441" />
- <PackageReference Include="SmartA2" Version="1.0.2023.606-beta1304" />
+ <PackageReference Include="NewLife.Core" Version="10.10.2024.601" />
+ <PackageReference Include="NewLife.IoT" Version="2.2.2024.501" />
+ <PackageReference Include="NewLife.Modbus" Version="1.8.2024.217" />
+ <PackageReference Include="NewLife.ModbusRTU" Version="1.8.2024.217" />
+ <PackageReference Include="NewLife.MQTT" Version="2.0.2024.516" />
+ <PackageReference Include="NewLife.NetPing" Version="1.1.2024.217" />
+ <PackageReference Include="NewLife.PC" Version="1.0.2024.217" />
+ <PackageReference Include="NewLife.Schneider" Version="1.0.2024.218" />
+ <PackageReference Include="NewLife.Siemens" Version="1.1.2024.218" />
+ <PackageReference Include="NewLife.Stardust" Version="2.9.2024.402" />
+ <PackageReference Include="SmartA2" Version="1.1.2024.218" />
<PackageReference Include="SmartA4" Version="1.0.2023.606-beta1305" />
</ItemGroup>
diff --git a/IoTEdge/Program.cs b/IoTEdge/Program.cs
index d80c712..86f3b61 100644
--- a/IoTEdge/Program.cs
+++ b/IoTEdge/Program.cs
@@ -10,6 +10,10 @@ using Stardust;
// 启用控制台日志,拦截所有异常
XTrace.UseConsole();
+#if DEBUG
+XTrace.Log.Level = NewLife.Log.LogLevel.Debug;
+#endif
+
// 初始化对象容器,提供注入能力
var services = ObjectContainer.Current;
services.AddSingleton(XTrace.Log);
@@ -18,6 +22,9 @@ services.AddSingleton(XTrace.Log);
var star = new StarFactory();
if (star.Server.IsNullOrEmpty()) star = null;
+var set = ClientSetting.Current;
+services.AddSingleton(set);
+
// 初始化Redis、MQTT、RocketMQ,注册服务到容器
InitMqtt(services, star?.Tracer);
diff --git a/IoTZero/Controllers/AppController.cs b/IoTZero/Controllers/AppController.cs
index f1cae7a..7796929 100644
--- a/IoTZero/Controllers/AppController.cs
+++ b/IoTZero/Controllers/AppController.cs
@@ -1,36 +1,23 @@
using IoT.Data;
-using IoTZero.Common;
using IoTZero.Services;
using Microsoft.AspNetCore.Mvc;
-using Microsoft.AspNetCore.Mvc.Controllers;
-using Microsoft.AspNetCore.Mvc.Filters;
using NewLife;
-using NewLife.Cube;
using NewLife.IoT.Models;
using NewLife.IoT.ThingModels;
using NewLife.Log;
using NewLife.Remoting;
-using NewLife.Serialization;
-using NewLife.Web;
-using IActionFilter = Microsoft.AspNetCore.Mvc.Filters.IActionFilter;
+using NewLife.Remoting.Extensions;
namespace IoTZero.Controllers;
/// <summary>物模型Api控制器。用于应用系统调用</summary>
+[ApiFilter]
[ApiController]
[Route("[controller]")]
-public class AppController : ControllerBase, IActionFilter
+public class AppController : BaseController
{
- /// <summary>用户主机</summary>
- public String UserHost => HttpContext.GetUserHost();
-
- /// <summary>令牌</summary>
- public String Token { get; set; }
-
- private readonly MyDeviceService _deviceService;
private readonly ThingService _thingService;
private readonly ITracer _tracer;
- private IDictionary<String, Object> _args;
#region 构造
/// <summary>
@@ -40,91 +27,11 @@ public class AppController : ControllerBase, IActionFilter
/// <param name="deviceService"></param>
/// <param name="thingService"></param>
/// <param name="tracer"></param>
- public AppController(MyDeviceService deviceService, ThingService thingService, ITracer tracer)
+ public AppController(IServiceProvider serviceProvider, ThingService thingService, ITracer tracer) : base(serviceProvider)
{
- _deviceService = deviceService;
_thingService = thingService;
_tracer = tracer;
}
-
- void IActionFilter.OnActionExecuting(ActionExecutingContext context)
- {
- _args = context.ActionArguments;
-
- // 访问令牌
- var request = context.HttpContext.Request;
- var token = request.Query["Token"] + "";
- if (token.IsNullOrEmpty()) token = (request.Headers["Authorization"] + "").TrimStart("Bearer ");
- if (token.IsNullOrEmpty()) token = request.Headers["X-Token"] + "";
- if (token.IsNullOrEmpty()) token = request.Cookies["Token"] + "";
- Token = token;
-
- try
- {
- if (!token.IsNullOrEmpty())
- {
- //todo 验证令牌有效性
- }
-
- //if (context.ActionDescriptor is ControllerActionDescriptor act && !act.MethodInfo.IsDefined(typeof(AllowAnonymousAttribute)))
- // throw new ApiException(403, "设备认证失败");
- }
- catch (Exception ex)
- {
- var traceId = DefaultSpan.Current?.TraceId;
- context.Result = ex is ApiException aex
- ? new JsonResult(new { code = aex.Code, data = aex.Message, traceId })
- : new JsonResult(new { code = 500, data = ex.Message, traceId });
-
- WriteError(ex, context);
- }
- }
-
- /// <summary>请求处理后</summary>
- /// <param name="context"></param>
- void IActionFilter.OnActionExecuted(ActionExecutedContext context)
- {
- if (context.Exception != null) WriteError(context.Exception, context);
-
- var traceId = DefaultSpan.Current?.TraceId;
-
- if (context.Result != null)
- if (context.Result is ObjectResult obj)
- {
- //context.Result = new JsonResult(new { code = obj.StatusCode ?? 0, data = obj.Value });
- var rs = new { code = obj.StatusCode ?? 0, data = obj.Value, traceId };
- context.Result = new ContentResult
- {
- Content = rs.ToJson(false, true, true),
- ContentType = "application/json",
- StatusCode = 200
- };
- }
- else if (context.Result is EmptyResult)
- context.Result = new JsonResult(new { code = 0, data = new { }, traceId });
- else if (context.Exception != null && !context.ExceptionHandled)
- {
- var ex = context.Exception.GetTrue();
- if (ex is ApiException aex)
- context.Result = new JsonResult(new { code = aex.Code, data = aex.Message, traceId });
- else
- context.Result = new JsonResult(new { code = 500, data = ex.Message, traceId });
-
- context.ExceptionHandled = true;
-
- // 输出异常日志
- if (XTrace.Debug) XTrace.WriteException(ex);
- }
- }
-
- private void WriteError(Exception ex, ActionContext context)
- {
- // 拦截全局异常,写日志
- var action = context.HttpContext.Request.Path + "";
- if (context.ActionDescriptor is ControllerActionDescriptor act) action = $"{act.ControllerName}/{act.ActionName}";
-
- XTrace.WriteLine("{0} {1}", action, ex.Message);
- }
#endregion
#region 物模型
@@ -132,7 +39,6 @@ public class AppController : ControllerBase, IActionFilter
/// <param name="deviceId">设备编号</param>
/// <param name="deviceCode">设备编码</param>
/// <returns></returns>
- [ApiFilter]
[HttpGet(nameof(GetProperty))]
public PropertyModel[] GetProperty(Int32 deviceId, String deviceCode)
{
@@ -145,9 +51,8 @@ public class AppController : ControllerBase, IActionFilter
/// <summary>设置设备属性</summary>
/// <param name="model">数据</param>
/// <returns></returns>
- [ApiFilter]
[HttpPost(nameof(SetProperty))]
- public async Task<ServiceReplyModel> SetProperty(DevicePropertyModel model)
+ public Task<ServiceReplyModel> SetProperty(DevicePropertyModel model)
{
var dv = Device.FindByCode(model.DeviceCode);
if (dv == null) return null;
@@ -158,7 +63,6 @@ public class AppController : ControllerBase, IActionFilter
/// <summary>调用设备服务</summary>
/// <param name="service">服务</param>
/// <returns></returns>
- [ApiFilter]
[HttpPost(nameof(InvokeService))]
public async Task<ServiceReplyModel> InvokeService(ServiceRequest service)
{
diff --git a/IoTZero/Controllers/DeviceController.cs b/IoTZero/Controllers/DeviceController.cs
index 69b5cf5..fdfe300 100644
--- a/IoTZero/Controllers/DeviceController.cs
+++ b/IoTZero/Controllers/DeviceController.cs
@@ -1,126 +1,86 @@
-using IoTZero.Common;
+using IoT.Data;
using IoTZero.Services;
-using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
-using NewLife.Http;
using NewLife.IoT.Drivers;
using NewLife.IoT.Models;
using NewLife.IoT.ThingModels;
using NewLife.Log;
using NewLife.Remoting;
-using WebSocket = System.Net.WebSockets.WebSocket;
+using NewLife.Remoting.Extensions;
+using NewLife.Remoting.Models;
namespace IoTZero.Controllers;
/// <summary>设备控制器</summary>
+[ApiFilter]
[ApiController]
[Route("[controller]")]
-public class DeviceController : BaseController
+public class DeviceController : BaseDeviceController
{
- private readonly QueueService _queue;
- private readonly MyDeviceService _deviceService;
+ /// <summary>当前设备</summary>
+ public Device Device { get; set; }
+
private readonly ThingService _thingService;
private readonly ITracer _tracer;
+ #region 构造
/// <summary>实例化设备控制器</summary>
+ /// <param name="serviceProvider"></param>
/// <param name="queue"></param>
/// <param name="deviceService"></param>
/// <param name="thingService"></param>
- /// <param name="hookService"></param>
- /// <param name="setting"></param>
/// <param name="tracer"></param>
- public DeviceController(QueueService queue, MyDeviceService deviceService, ThingService thingService, IoTSetting setting, ITracer tracer) : base(deviceService, setting)
+ public DeviceController(IServiceProvider serviceProvider, ThingService thingService, ITracer tracer) : base(serviceProvider)
{
- _queue = queue;
- _deviceService = deviceService;
_thingService = thingService;
_tracer = tracer;
}
- #region 登录
- /// <summary>设备登录</summary>
- /// <param name="model"></param>
- /// <returns></returns>
- [AllowAnonymous]
- [ApiFilter]
- [HttpPost(nameof(Login))]
- public LoginResponse Login(LoginInfo model) => _deviceService.Login(model, "Http", UserHost);
-
- /// <summary>设备注销</summary>
- /// <param name="reason">注销原因</param>
- /// <returns></returns>
- [ApiFilter]
- [HttpGet(nameof(Logout))]
- public LogoutResponse Logout(String reason)
+ protected override Boolean OnAuthorize(String token)
{
- var device = Device;
- if (device != null) _deviceService.Logout(device, reason, "Http", UserHost);
+ if (!base.OnAuthorize(token)) return false;
- return new LogoutResponse
- {
- Name = device?.Name,
- Token = null,
- };
+ Device = _device as Device;
+
+ return true;
}
#endregion
#region 心跳
/// <summary>设备心跳</summary>
- /// <param name="model"></param>
+ /// <param name="request"></param>
/// <returns></returns>
- [ApiFilter]
[HttpPost(nameof(Ping))]
- public PingResponse Ping(PingInfo model)
+ public override IPingResponse Ping([FromBody] IPingRequest request)
{
- var rs = new PingResponse
- {
- Time = model.Time,
- ServerTime = DateTime.UtcNow.ToLong(),
- };
+ var rs = base.Ping(request);
var device = Device;
- if (device != null)
+ if (device != null && rs != null)
{
rs.Period = device.Period;
-
- var olt = _deviceService.Ping(device, model, Token, UserHost);
-
- // 令牌有效期检查,10分钟内到期的令牌,颁发新令牌。
- // 这里将来由客户端提交刷新令牌,才能颁发新的访问令牌。
- var tm = _deviceService.ValidAndIssueToken(device.Code, Token);
- if (tm != null)
- {
- rs.Token = tm.AccessToken;
-
- //_deviceService.WriteHistory(device, "刷新令牌", true, tm.ToJson(), UserHost);
- }
}
return rs;
}
-
- [ApiFilter]
- [HttpGet(nameof(Ping))]
- public PingResponse Ping() => new() { Time = 0, ServerTime = DateTime.UtcNow.ToLong(), };
#endregion
#region 升级
/// <summary>升级检查</summary>
/// <returns></returns>
- [ApiFilter]
[HttpGet(nameof(Upgrade))]
- public UpgradeInfo Upgrade()
+ public override IUpgradeInfo Upgrade()
{
- var device = Device ?? throw new ApiException(402, "节点未登录");
+ var device = Device ?? throw new ApiException(ApiCode.Unauthorized, "节点未登录");
- throw new NotImplementedException();
+ //throw new NotImplementedException();
+ return new UpgradeInfo { };
}
#endregion
#region 设备通道
/// <summary>获取设备信息,包括主设备和子设备</summary>
/// <returns></returns>
- [ApiFilter]
[HttpGet(nameof(GetDevices))]
public DeviceModel[] GetDevices() => throw new NotImplementedException();
@@ -132,7 +92,6 @@ public class DeviceController : BaseController
/// </remarks>
/// <param name="devices">设备信息集合。可传递参数模版</param>
/// <returns>返回上报信息对应的反馈,如果新增子设备,则返回子设备信息</returns>
- [ApiFilter]
[HttpPost(nameof(SetOnline))]
public IDeviceInfo[] SetOnline(DeviceModel[] devices) => throw new NotImplementedException();
@@ -142,53 +101,19 @@ public class DeviceController : BaseController
/// </remarks>
/// <param name="devices">设备编码集合。用于子设备离线</param>
/// <returns>返回上报信息对应的反馈,如果新增子设备,则返回子设备信息</returns>
- [ApiFilter]
[HttpPost(nameof(SetOffline))]
public IDeviceInfo[] SetOffline(String[] devices) => throw new NotImplementedException();
/// <summary>获取设备点位表</summary>
/// <param name="deviceCode">设备编码</param>
/// <returns></returns>
- [ApiFilter]
[HttpGet(nameof(GetPoints))]
public PointModel[] GetPoints(String deviceCode) => throw new NotImplementedException();
/// <summary>提交驱动信息。客户端把自己的驱动信息提交到平台</summary>
/// <param name="drivers"></param>
/// <returns></returns>
- [ApiFilter]
[HttpPost(nameof(PostDriver))]
public Int32 PostDriver(DriverInfo[] drivers) => throw new NotImplementedException();
#endregion
-
- #region 下行通知
- /// <summary>下行通知</summary>
- /// <returns></returns>
- [HttpGet("/Device/Notify")]
- public async Task Notify()
- {
- if (HttpContext.WebSockets.IsWebSocketRequest)
- {
- using var socket = await HttpContext.WebSockets.AcceptWebSocketAsync();
-
- await Handle(socket, Token);
- }
- else
- {
- HttpContext.Response.StatusCode = 400;
- }
- }
-
- private async Task Handle(WebSocket socket, String token)
- {
- var device = Device ?? throw new InvalidOperationException("未登录!");
-
- _deviceService.WriteHistory(device, "WebSocket连接", true, socket.State + "", UserHost);
-
- var source = new CancellationTokenSource();
- var queue = _queue.GetQueue(device.Code);
- _ = Task.Run(() => socket.ConsumeAndPushAsync(queue, onProcess: null, source));
- await socket.WaitForClose(null, source);
- }
- #endregion
}
\ No newline at end of file
diff --git a/IoTZero/Controllers/ThingController.cs b/IoTZero/Controllers/ThingController.cs
index c7e9830..77011a1 100644
--- a/IoTZero/Controllers/ThingController.cs
+++ b/IoTZero/Controllers/ThingController.cs
@@ -1,57 +1,73 @@
-using IoTZero.Common;
+using IoT.Data;
using IoTZero.Services;
using Microsoft.AspNetCore.Mvc;
+using NewLife;
using NewLife.IoT.Models;
using NewLife.IoT.ThingModels;
using NewLife.IoT.ThingSpecification;
+using NewLife.Remoting;
+using NewLife.Remoting.Extensions;
namespace IoTZero.Controllers;
/// <summary>物模型控制器</summary>
+[ApiFilter]
[ApiController]
[Route("[controller]")]
public class ThingController : BaseController
{
+ /// <summary>当前设备</summary>
+ public Device Device { get; set; }
+
private readonly QueueService _queue;
private readonly ThingService _thingService;
+ #region 构造
/// <summary>实例化物模型控制器</summary>
+ /// <param name="serviceProvider"></param>
/// <param name="queue"></param>
- /// <param name="deviceService"></param>
/// <param name="thingService"></param>
- /// <param name="setting"></param>
- public ThingController(QueueService queue, MyDeviceService deviceService, ThingService thingService, IoTSetting setting) : base(deviceService, setting)
+ public ThingController(IServiceProvider serviceProvider, QueueService queue, ThingService thingService) : base(serviceProvider)
{
_queue = queue;
_thingService = thingService;
}
+ protected override Boolean OnAuthorize(String token)
+ {
+ if (!base.OnAuthorize(token) || Jwt == null) return false;
+
+ var dv = Device.FindByCode(Jwt.Subject);
+ if (dv == null || !dv.Enable) throw new ApiException(ApiCode.Forbidden, "无效设备!");
+
+ Device = dv;
+
+ return true;
+ }
+ #endregion
+
#region 设备属性
/// <summary>上报设备属性</summary>
/// <param name="model">属性集合</param>
/// <returns></returns>
- [ApiFilter]
[HttpPost(nameof(PostProperty))]
public Int32 PostProperty(PropertyModels model) => throw new NotImplementedException();
/// <summary>批量上报设备属性,融合多个子设备数据批量上传</summary>
/// <param name="models">属性集合</param>
/// <returns></returns>
- [ApiFilter]
[HttpPost(nameof(PostProperties))]
public Int32 PostProperties(PropertyModels[] models) => throw new NotImplementedException();
/// <summary>获取设备属性</summary>
/// <param name="deviceCode">设备编码</param>
/// <returns></returns>
- [ApiFilter]
[HttpGet(nameof(GetProperty))]
public PropertyModel[] GetProperty(String deviceCode) => throw new NotImplementedException();
/// <summary>设备数据上报</summary>
/// <param name="model">模型</param>
/// <returns></returns>
- [ApiFilter]
[HttpPost(nameof(PostData))]
public Int32 PostData(DataModels model)
{
@@ -63,7 +79,6 @@ public class ThingController : BaseController
/// <summary>批量设备数据上报,融合多个子设备数据批量上传</summary>
/// <param name="models">模型</param>
/// <returns></returns>
- [ApiFilter]
[HttpPost(nameof(PostDatas))]
public Int32 PostDatas(DataModels[] models) => throw new NotImplementedException();
#endregion
@@ -72,14 +87,12 @@ public class ThingController : BaseController
/// <summary>设备事件上报</summary>
/// <param name="model">模型</param>
/// <returns></returns>
- [ApiFilter]
[HttpPost(nameof(PostEvent))]
public Int32 PostEvent(EventModels model) => throw new NotImplementedException();
/// <summary>批量设备事件上报,融合多个子设备数据批量上传</summary>
/// <param name="models">模型</param>
/// <returns></returns>
- [ApiFilter]
[HttpPost(nameof(PostEvents))]
public Int32 PostEvents(EventModels[] models) => throw new NotImplementedException();
#endregion
@@ -88,7 +101,6 @@ public class ThingController : BaseController
/// <summary>设备端响应服务调用</summary>
/// <param name="model">服务</param>
/// <returns></returns>
- [ApiFilter]
[HttpPost(nameof(ServiceReply))]
public Int32 ServiceReply(ServiceReplyModel model) => throw new NotImplementedException();
#endregion
@@ -97,14 +109,12 @@ public class ThingController : BaseController
/// <summary>获取设备所属产品的物模型</summary>
/// <param name="deviceCode">设备编码</param>
/// <returns></returns>
- [ApiFilter]
[HttpGet(nameof(GetSpecification))]
public ThingSpec GetSpecification(String deviceCode) => throw new NotImplementedException();
/// <summary>上报物模型</summary>
/// <param name="model"></param>
/// <returns></returns>
- [ApiFilter]
[HttpPost(nameof(PostSpecification))]
public IPoint[] PostSpecification(ThingSpecModel model) => throw new NotImplementedException();
#endregion
@@ -118,14 +128,12 @@ public class ThingController : BaseController
/// </remarks>
/// <param name="model">数据</param>
/// <returns></returns>
- [ApiFilter]
[HttpPost(nameof(PostShadow))]
public Int32 PostShadow(ShadowModel model) => throw new NotImplementedException();
/// <summary>获取设备影子</summary>
/// <param name="deviceCode">设备编码</param>
/// <returns></returns>
- [ApiFilter]
[HttpGet(nameof(GetShadow))]
public String GetShadow(String deviceCode) => throw new NotImplementedException();
#endregion
@@ -134,8 +142,30 @@ public class ThingController : BaseController
/// <summary>设备端查询配置信息</summary>
/// <param name="deviceCode">设备编码</param>
/// <returns></returns>
- [ApiFilter]
[HttpGet(nameof(GetConfig))]
public IDictionary<String, Object> GetConfig(String deviceCode) => throw new NotImplementedException();
#endregion
+
+ #region 辅助
+
+ /// <summary>
+ /// 查找子设备
+ /// </summary>
+ /// <param name="deviceCode"></param>
+ /// <returns></returns>
+ protected Device GetDevice(String deviceCode)
+ {
+ var dv = Device;
+ if (dv == null) return null;
+
+ if (deviceCode.IsNullOrEmpty() || dv.Code == deviceCode) return dv;
+
+ var child = Device.FindByCode(deviceCode);
+
+ //dv = dv.Childs.FirstOrDefault(e => e.Code == deviceCode);
+ if (child == null || child.Id != dv.Id) throw new Exception($"非法设备编码,[{deviceCode}]并非当前登录设备[{Device}]的子设备");
+
+ return child;
+ }
+ #endregion
}
\ No newline at end of file
diff --git a/IoTZero/IoTSetting.cs b/IoTZero/IoTSetting.cs
index 988b3d7..4d34d69 100644
--- a/IoTZero/IoTSetting.cs
+++ b/IoTZero/IoTSetting.cs
@@ -1,6 +1,7 @@
using System.ComponentModel;
using NewLife;
using NewLife.Configuration;
+using NewLife.Remoting.Extensions.Models;
using NewLife.Security;
using XCode.Configuration;
@@ -8,7 +9,7 @@ namespace IoTZero;
/// <summary>配置</summary>
[Config("IoTZero")]
-public class IoTSetting : Config<IoTSetting>
+public class IoTSetting : Config<IoTSetting>, ITokenSetting
{
#region 静态
static IoTSetting() => Provider = new DbConfigProvider { UserId = 0, Category = "IoTServer" };
diff --git a/IoTZero/IoTZero.csproj b/IoTZero/IoTZero.csproj
index 57dcfaa..79e9783 100644
--- a/IoTZero/IoTZero.csproj
+++ b/IoTZero/IoTZero.csproj
@@ -5,8 +5,8 @@
<AssemblyTitle>物联网服务平台</AssemblyTitle>
<Description>IoT服务平台</Description>
<Company>新生命开发团队</Company>
- <Copyright>©2002-2023 NewLife</Copyright>
- <VersionPrefix>1.0</VersionPrefix>
+ <Copyright>©2002-2024 NewLife</Copyright>
+ <VersionPrefix>2.0</VersionPrefix>
<VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
<Version>$(VersionPrefix).$(VersionSuffix)</Version>
<FileVersion>$(Version)</FileVersion>
@@ -29,11 +29,12 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="NewLife.Cube.Core" Version="5.5.2023.625-beta1355" />
- <PackageReference Include="NewLife.IoT" Version="1.8.2023.611-beta1629" />
- <PackageReference Include="NewLife.MQTT" Version="1.4.2023.620-beta1039" />
- <PackageReference Include="NewLife.Redis" Version="5.4.2023.624-beta0342" />
- <PackageReference Include="NewLife.Stardust.Extensions" Version="2.9.2023.627-beta0441" />
+ <PackageReference Include="NewLife.Cube.Core" Version="6.1.2024.403" />
+ <PackageReference Include="NewLife.IoT" Version="2.2.2024.501" />
+ <PackageReference Include="NewLife.MQTT" Version="2.0.2024.516" />
+ <PackageReference Include="NewLife.Redis" Version="5.7.2024.602" />
+ <PackageReference Include="NewLife.Remoting.Extensions" Version="3.0.2024.620-beta1407" />
+ <PackageReference Include="NewLife.Stardust.Extensions" Version="2.9.2024.402" />
</ItemGroup>
<ItemGroup>
diff --git a/IoTZero/Program.cs b/IoTZero/Program.cs
index 23a1584..f8dede0 100644
--- a/IoTZero/Program.cs
+++ b/IoTZero/Program.cs
@@ -3,7 +3,6 @@ using IoTZero.Services;
using NewLife.Caching;
using NewLife.Cube;
using NewLife.Log;
-using NewLife.Security;
using XCode;
// 日志输出到控制台,并拦截全局异常
@@ -12,46 +11,23 @@ XTrace.UseConsole();
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
+InitConfig();
+
// 配置星尘。借助StarAgent,或者读取配置文件 config/star.config 中的服务器地址、应用标识、密钥
var star = services.AddStardust(null);
-// 把数据目录指向上层,例如部署到 /root/iot/edge/,这些目录放在 /root/iot/
-var set = NewLife.Setting.Current;
-if (set.IsNew)
-{
- set.LogPath = "../Log";
- set.DataPath = "../Data";
- set.BackupPath = "../Backup";
- set.Save();
-}
-var set2 = CubeSetting.Current;
-if (set2.IsNew)
-{
- set2.AvatarPath = "../Avatars";
- set2.UploadPath = "../Uploads";
- set2.Save();
-}
-var set3 = XCodeSetting.Current;
-if (set3.IsNew)
-{
- set3.ShowSQL = false;
- set3.EntityCacheExpire = 60;
- set3.SingleCacheExpire = 60;
- set3.Save();
-}
-
// 系统设置
-services.AddSingleton(IoTSetting.Current);
+var set = IoTSetting.Current;
+services.AddSingleton(set);
// 逐个注册每一个用到的服务,必须做到清晰明了
-services.AddSingleton<IPasswordProvider>(new SaltPasswordProvider { Algorithm = "md5" });
-
services.AddSingleton<ThingService>();
services.AddSingleton<DataService>();
services.AddSingleton<QueueService>();
-services.AddSingleton<MyDeviceService>();
-services.AddHttpClient("hc", e => e.Timeout = TimeSpan.FromSeconds(5));
+// 注册IoT
+services.AddIoT(set);
+//services.AddRemoting(set);
services.AddSingleton<ICache, MemoryCache>();
@@ -100,3 +76,31 @@ app.MapControllerRoute(
app.RegisterService("AlarmServer", null, app.Environment.EnvironmentName);
app.Run();
+
+void InitConfig()
+{
+ // 把数据目录指向上层,例如部署到 /root/iot/edge/,这些目录放在 /root/iot/
+ var set = NewLife.Setting.Current;
+ if (set.IsNew)
+ {
+ set.LogPath = "../Log";
+ set.DataPath = "../Data";
+ set.BackupPath = "../Backup";
+ set.Save();
+ }
+ var set2 = CubeSetting.Current;
+ if (set2.IsNew)
+ {
+ set2.AvatarPath = "../Avatars";
+ set2.UploadPath = "../Uploads";
+ set2.Save();
+ }
+ var set3 = XCodeSetting.Current;
+ if (set3.IsNew)
+ {
+ set3.ShowSQL = false;
+ set3.EntityCacheExpire = 60;
+ set3.SingleCacheExpire = 60;
+ set3.Save();
+ }
+}
\ No newline at end of file
diff --git a/IoTZero/Services/DeviceOnlineService.cs b/IoTZero/Services/DeviceOnlineService.cs
index f69a407..edcd9d5 100644
--- a/IoTZero/Services/DeviceOnlineService.cs
+++ b/IoTZero/Services/DeviceOnlineService.cs
@@ -1,6 +1,7 @@
using IoT.Data;
using NewLife;
using NewLife.Log;
+using NewLife.Remoting.Extensions.Services;
using NewLife.Threading;
namespace IoTZero.Services;
@@ -10,7 +11,7 @@ public class DeviceOnlineService : IHostedService
{
#region 属性
private TimerX _timer;
- private readonly MyDeviceService _deviceService;
+ private readonly IDeviceService _deviceService;
private readonly IoTSetting _setting;
private readonly ITracer _tracer;
#endregion
@@ -22,7 +23,7 @@ public class DeviceOnlineService : IHostedService
/// <param name="deviceService"></param>
/// <param name="setting"></param>
/// <param name="tracer"></param>
- public DeviceOnlineService(MyDeviceService deviceService, IoTSetting setting, ITracer tracer)
+ public DeviceOnlineService(IDeviceService deviceService, IoTSetting setting, ITracer tracer)
{
_deviceService = deviceService;
_setting = setting;
@@ -71,7 +72,8 @@ public class DeviceOnlineService : IHostedService
var msg = $"[{device}]登录于{olt.CreateTime.ToFullString()},最后活跃于{olt.UpdateTime.ToFullString()}";
_deviceService.WriteHistory(device, "超时下线", true, msg, olt.CreateIP);
- _deviceService.RemoveOnline(olt.DeviceId, olt.CreateIP);
+ if (_deviceService is MyDeviceService ds)
+ ds.RemoveOnline(olt.DeviceId, olt.CreateIP);
if (device != null)
{
diff --git a/IoTZero/Services/IoTExtensions.cs b/IoTZero/Services/IoTExtensions.cs
new file mode 100644
index 0000000..e8cfd5e
--- /dev/null
+++ b/IoTZero/Services/IoTExtensions.cs
@@ -0,0 +1,26 @@
+using NewLife.IoT.Models;
+using NewLife.Remoting.Extensions;
+using NewLife.Remoting.Extensions.Models;
+using NewLife.Remoting.Extensions.Services;
+using NewLife.Remoting.Models;
+
+namespace IoTZero.Services;
+
+/// <summary>IoT扩展</summary>
+public static class IoTExtensions
+{
+ public static IServiceCollection AddIoT(this IServiceCollection services, ITokenSetting setting)
+ {
+ ArgumentNullException.ThrowIfNull(setting);
+
+ services.AddSingleton<IDeviceService, MyDeviceService>();
+
+ services.AddTransient<ILoginRequest, LoginInfo>();
+ services.AddTransient<IPingRequest, PingInfo>();
+
+ // 注册Remoting所必须的服务
+ services.AddRemoting(setting);
+
+ return services;
+ }
+}
diff --git a/IoTZero/Services/MyDeviceService.cs b/IoTZero/Services/MyDeviceService.cs
index f6ca961..9abcb0a 100644
--- a/IoTZero/Services/MyDeviceService.cs
+++ b/IoTZero/Services/MyDeviceService.cs
@@ -2,24 +2,25 @@
using IoT.Data;
using NewLife;
using NewLife.Caching;
+using NewLife.Caching.Queues;
using NewLife.IoT.Models;
using NewLife.Log;
using NewLife.Remoting;
+using NewLife.Remoting.Extensions.Services;
+using NewLife.Remoting.Models;
using NewLife.Security;
using NewLife.Serialization;
using NewLife.Web;
+using LoginResponse = NewLife.Remoting.Models.LoginResponse;
namespace IoTZero.Services;
/// <summary>设备服务</summary>
-public class MyDeviceService
+public class MyDeviceService : IDeviceService
{
- /// <summary>节点引用,令牌无效时使用</summary>
- public Device Current { get; set; }
-
+ private readonly ICacheProvider _cacheProvider;
private readonly ICache _cache;
private readonly IPasswordProvider _passwordProvider;
- private readonly DataService _dataService;
private readonly IoTSetting _setting;
private readonly ITracer _tracer;
@@ -31,10 +32,10 @@ public class MyDeviceService
/// <param name="cacheProvider"></param>
/// <param name="setting"></param>
/// <param name="tracer"></param>
- public MyDeviceService(IPasswordProvider passwordProvider, DataService dataService, ICacheProvider cacheProvider, IoTSetting setting, ITracer tracer)
+ public MyDeviceService(IPasswordProvider passwordProvider, ICacheProvider cacheProvider, IoTSetting setting, ITracer tracer)
{
_passwordProvider = passwordProvider;
- _dataService = dataService;
+ _cacheProvider = cacheProvider;
_cache = cacheProvider.InnerCache;
_setting = setting;
_tracer = tracer;
@@ -44,30 +45,31 @@ public class MyDeviceService
/// <summary>
/// 设备登录验证,内部支持动态注册
/// </summary>
- /// <param name="inf">登录信息</param>
+ /// <param name="request">登录信息</param>
/// <param name="source">登录来源</param>
/// <param name="ip">远程IP</param>
/// <returns></returns>
/// <exception cref="ApiException"></exception>
- public LoginResponse Login(LoginInfo inf, String source, String ip)
+ public (IDeviceModel, IOnlineModel, ILoginResponse) Login(ILoginRequest request, String source, String ip)
{
+ if (request is not LoginInfo inf) throw new ArgumentOutOfRangeException(nameof(request));
+
var code = inf.Code;
var secret = inf.Secret;
var dv = Device.FindByCode(code);
- Current = dv;
var autoReg = false;
if (dv == null)
{
- if (inf.ProductKey.IsNullOrEmpty()) throw new ApiException(98, "找不到设备,且产品证书为空,无法登录");
+ if (inf.ProductKey.IsNullOrEmpty()) throw new ApiException(ApiCode.NotFound, "找不到设备,且产品证书为空,无法登录");
dv = AutoRegister(null, inf, ip);
autoReg = true;
}
else
{
- if (!dv.Enable) throw new ApiException(99, "禁止登录");
+ if (!dv.Enable) throw new ApiException(ApiCode.Forbidden, "禁止登录");
// 校验唯一编码,防止客户端拷贝配置
var uuid = inf.UUID;
@@ -78,7 +80,7 @@ public class MyDeviceService
if (dv == null || !dv.Secret.IsNullOrEmpty()
&& (secret.IsNullOrEmpty() || !_passwordProvider.Verify(dv.Secret, secret)))
{
- if (inf.ProductKey.IsNullOrEmpty()) throw new ApiException(98, "设备验证失败,且产品证书为空,无法登录");
+ if (inf.ProductKey.IsNullOrEmpty()) throw new ApiException(ApiCode.Unauthorized, "设备验证失败,且产品证书为空,无法登录");
dv = AutoRegister(dv, inf, ip);
autoReg = true;
@@ -87,16 +89,13 @@ public class MyDeviceService
//if (dv != null && !dv.Enable) throw new ApiException(99, "禁止登录");
- Current = dv ?? throw new ApiException(12, "节点鉴权失败");
+ if (dv == null) throw new ApiException(ApiCode.Unauthorized, "节点鉴权失败");
dv.Login(inf, ip);
- // 设置令牌
- var tm = IssueToken(dv.Code, _setting);
-
// 在线记录
var olt = GetOnline(dv, ip) ?? CreateOnline(dv, ip);
- olt.Save(inf, null, tm.AccessToken);
+ olt.Save(inf, null, null);
//SetChildOnline(dv, ip);
@@ -105,20 +104,13 @@ public class MyDeviceService
var rs = new LoginResponse
{
- Name = dv.Name,
- Token = tm.AccessToken,
- Time = DateTime.UtcNow.ToLong(),
+ Name = dv.Name
};
- // 动态注册的设备不可用时,不要发令牌,只发证书
- if (!dv.Enable) rs.Token = null;
-
// 动态注册,下发节点证书
if (autoReg) rs.Secret = dv.Secret;
- rs.Code = dv.Code;
-
- return rs;
+ return (dv, olt, rs);
}
/// <summary>设置设备在线,同时检查在线表</summary>
@@ -199,55 +191,55 @@ public class MyDeviceService
/// <param name="source">登录来源</param>
/// <param name="ip">远程IP</param>
/// <returns></returns>
- public Device Logout(Device device, String reason, String source, String ip)
+ public IOnlineModel Logout(IDeviceModel device, String reason, String source, String ip)
{
- var olt = GetOnline(device, ip);
+ var dv = device as Device;
+ var olt = GetOnline(dv, ip);
if (olt != null)
{
var msg = $"{reason} [{device}]]登录于{olt.CreateTime.ToFullString()},最后活跃于{olt.UpdateTime.ToFullString()}";
WriteHistory(device, source + "设备下线", true, msg, ip);
olt.Delete();
- var sid = $"{device.Id}@{ip}";
+ var sid = $"{dv.Id}@{ip}";
_cache.Remove($"DeviceOnline:{sid}");
// 计算在线时长
if (olt.CreateTime.Year > 2000)
{
- device.OnlineTime += (Int32)(DateTime.Now - olt.CreateTime).TotalSeconds;
- device.Logout();
+ dv.OnlineTime += (Int32)(DateTime.Now - olt.CreateTime).TotalSeconds;
+ dv.Logout();
}
//DeviceOnlineService.CheckOffline(device, "注销");
}
- return device;
+ return olt;
}
#endregion
#region 心跳
- /// <summary>
- /// 心跳
- /// </summary>
- /// <param name="device"></param>
+ /// <summary>心跳</summary>
/// <param name="inf"></param>
/// <param name="token"></param>
/// <param name="ip"></param>
/// <returns></returns>
- public DeviceOnline Ping(Device device, PingInfo inf, String token, String ip)
+ public IOnlineModel Ping(IDeviceModel device, IPingRequest? request, String token, String ip)
{
- if (inf != null && !inf.IP.IsNullOrEmpty()) device.IP = inf.IP;
+ var dv = device as Device;
+ var inf = request as PingInfo;
+ if (inf != null && !inf.IP.IsNullOrEmpty()) dv.IP = inf.IP;
// 自动上线
- if (device != null && !device.Online) device.SetOnline(ip, "心跳");
+ if (dv != null && !dv.Online) dv.SetOnline(ip, "心跳");
- device.UpdateIP = ip;
- device.SaveAsync();
+ dv.UpdateIP = ip;
+ dv.SaveAsync();
- var olt = GetOnline(device, ip) ?? CreateOnline(device, ip);
+ var olt = GetOnline(dv, ip) ?? CreateOnline(dv, ip);
olt.Name = device.Name;
- olt.GroupPath = device.GroupPath;
- olt.ProductId = device.ProductId;
+ olt.GroupPath = dv.GroupPath;
+ olt.ProductId = dv.ProductId;
olt.Save(null, inf, token);
return olt;
@@ -344,7 +336,7 @@ public class MyDeviceService
public Device DecodeToken(String token, String tokenSecret)
{
//if (token.IsNullOrEmpty()) throw new ArgumentNullException(nameof(token));
- if (token.IsNullOrEmpty()) throw new ApiException(402, "节点未登录");
+ if (token.IsNullOrEmpty()) throw new ApiException(ApiCode.Unauthorized, "节点未登录");
// 解码令牌
var ss = tokenSecret.Split(':');
@@ -356,8 +348,7 @@ public class MyDeviceService
var rs = jwt.TryDecode(token, out var message);
var node = Device.FindByCode(jwt.Subject);
- Current = node;
- if (!rs) throw new ApiException(403, $"非法访问 {message}");
+ if (!rs) throw new ApiException(ApiCode.Forbidden, $"非法访问 {message}");
return node;
}
@@ -368,7 +359,7 @@ public class MyDeviceService
/// <param name="deviceCode"></param>
/// <param name="token"></param>
/// <returns></returns>
- public TokenModel ValidAndIssueToken(String deviceCode, String token)
+ public TokenModel ValidAndIssueToken(String deviceCode, String? token)
{
if (token.IsNullOrEmpty()) return null;
@@ -388,6 +379,24 @@ public class MyDeviceService
}
/// <summary>
+ /// 获取指定设备的命令队列
+ /// </summary>
+ /// <param name="deviceCode"></param>
+ /// <returns></returns>
+ public IProducerConsumer<String> GetQueue(String deviceCode)
+ {
+ var q = _cacheProvider.GetQueue<String>($"cmd:{deviceCode}");
+ if (q is QueueBase qb) qb.TraceName = "ServiceQueue";
+
+ return q;
+ }
+
+ /// <summary>查找设备</summary>
+ /// <param name="code"></param>
+ /// <returns></returns>
+ public IDeviceModel QueryDevice(String code) => Device.FindByCode(code);
+
+ /// <summary>
/// 写设备历史
/// </summary>
/// <param name="device"></param>
@@ -395,10 +404,10 @@ public class MyDeviceService
/// <param name="success"></param>
/// <param name="remark"></param>
/// <param name="ip"></param>
- public void WriteHistory(Device device, String action, Boolean success, String remark, String ip)
+ public void WriteHistory(IDeviceModel device, String action, Boolean success, String remark, String ip)
{
var traceId = DefaultSpan.Current?.TraceId;
- var hi = DeviceHistory.Create(device ?? Current, action, success, remark, Environment.MachineName, ip, traceId);
+ var hi = DeviceHistory.Create(device as Device, action, success, remark, Environment.MachineName, ip, traceId);
}
#endregion
}
\ No newline at end of file
diff --git a/IoTZero/Services/ThingService.cs b/IoTZero/Services/ThingService.cs
index 48ba707..6e333ad 100644
--- a/IoTZero/Services/ThingService.cs
+++ b/IoTZero/Services/ThingService.cs
@@ -4,6 +4,7 @@ using NewLife.Caching;
using NewLife.Data;
using NewLife.IoT.ThingModels;
using NewLife.Log;
+using NewLife.Remoting.Extensions.Services;
using NewLife.Security;
namespace IoTZero.Services;
@@ -13,7 +14,7 @@ public class ThingService
{
private readonly DataService _dataService;
private readonly QueueService _queueService;
- private readonly MyDeviceService _deviceService;
+ private readonly IDeviceService _deviceService;
private readonly ICacheProvider _cacheProvider;
private readonly IoTSetting _setting;
private readonly ITracer _tracer;
@@ -30,7 +31,7 @@ public class ThingService
/// <param name="cacheProvider"></param>
/// <param name="setting"></param>
/// <param name="tracer"></param>
- public ThingService(DataService dataService, QueueService queueService, MyDeviceService deviceService, ICacheProvider cacheProvider, IoTSetting setting, ITracer tracer)
+ public ThingService(DataService dataService, QueueService queueService, IDeviceService deviceService, ICacheProvider cacheProvider, IoTSetting setting, ITracer tracer)
{
_dataService = dataService;
_queueService = queueService;
@@ -64,7 +65,7 @@ public class ThingService
}
// 自动上线
- if (device != null) _deviceService.SetDeviceOnline(device, ip, kind);
+ if (device != null && _deviceService is MyDeviceService ds) ds.SetDeviceOnline(device, ip, kind);
//todo 触发指定设备的联动策略