diff --git a/IoT.Data/Entity/IoT.htm b/IoT.Data/Entity/IoT.htm
index 9841b83..4903f2e 100644
--- a/IoT.Data/Entity/IoT.htm
+++ b/IoT.Data/Entity/IoT.htm
@@ -783,6 +783,17 @@
</tr>
<tr>
+ <td>WebSocket</td>
+ <td>长连接</td>
+ <td>Boolean</td>
+ <td></td>
+ <td></td>
+ <td></td>
+ <td>N</td>
+ <td>WebSocket长连接</td>
+ </tr>
+
+ <tr>
<td>Delay</td>
<td>延迟</td>
<td>Int32</td>
diff --git a/IoT.Data/Entity/Model.xml b/IoT.Data/Entity/Model.xml
index 5f49e3b..56cd461 100644
--- a/IoT.Data/Entity/Model.xml
+++ b/IoT.Data/Entity/Model.xml
@@ -1,206 +1,207 @@
<?xml version="1.0" encoding="utf-8"?>
-<EntityModel xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="https://newlifex.com https://newlifex.com/Model2023.xsd" xmlns="https://newlifex.com/Model2023.xsd">
+<EntityModel xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="https://newlifex.com https://newlifex.com/Model202309.xsd" Document="https://newlifex.com/xcode/model" xmlns="https://newlifex.com/Model202309.xsd">
<Option>
- <!--输出目录-->
- <Output>.\</Output>
- <!--是否使用中文文件名。默认false-->
- <ChineseFileName>False</ChineseFileName>
- <!--基类。可能包含基类和接口,其中{name}替换为Table.Name-->
- <BaseClass>Entity</BaseClass>
- <!--命名空间-->
- <Namespace>IoT.Data</Namespace>
<!--类名模板。其中{name}替换为Table.Name,如{name}Model/I{name}Dto等-->
<ClassNameTemplate />
<!--显示名模板。其中{displayName}替换为Table.DisplayName-->
<DisplayNameTemplate />
- <!--用于生成拷贝函数的模型类。例如{name}或I{name}-->
+ <!--基类。可能包含基类和接口,其中{name}替换为Table.Name-->
+ <BaseClass>Entity</BaseClass>
+ <!--命名空间-->
+ <Namespace>IoT.Data</Namespace>
+ <!--输出目录-->
+ <Output>.\</Output>
+ <!--是否使用中文文件名。默认false-->
+ <ChineseFileName>False</ChineseFileName>
+ <!--用于生成Copy函数的参数类型。例如{name}或I{name}-->
<ModelNameForCopy />
<!--带有索引器。实现IModel接口-->
<HasIModel>False</HasIModel>
- <!--模型类模版-->
- <ModelClass />
- <!--模型接口模版-->
- <ModelInterface />
+ <!--可为null上下文。生成String?等-->
+ <Nullable>False</Nullable>
<!--数据库连接名-->
<ConnName>IoT</ConnName>
+ <!--模型类模版。设置后生成模型类,用于接口数据传输,例如{name}Model-->
+ <ModelClass />
<!--模型类输出目录。默认当前目录的Models子目录-->
<ModelsOutput>.\Models\</ModelsOutput>
+ <!--模型接口模版。设置后生成模型接口,用于约束模型类和实体类,例如I{name}-->
+ <ModelInterface />
<!--模型接口输出目录。默认当前目录的Interfaces子目录-->
<InterfacesOutput>.\Interfaces\</InterfacesOutput>
<!--用户实体转为模型类的模型类。例如{name}或{name}DTO-->
<ModelNameForToModel />
<!--命名格式。Default/Upper/Lower/Underline-->
<NameFormat>Default</NameFormat>
- <!--生成器版本-->
- <Version>11.8.2023.0524</Version>
- <!--帮助文档-->
- <Document>https://newlifex.com/xcode/model</Document>
<!--魔方区域显示名-->
<DisplayName>设备管理</DisplayName>
<!--魔方控制器输出目录-->
<CubeOutput>../../IoTZero/Areas/IoT/</CubeOutput>
</Option>
- <Table Name="Product" Description="产品。设备的集合,通常指一组具有相同功能的设备。物联网平台为每个产品颁发全局唯一的ProductKey。">
- <Columns>
- <Column Name="Id" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
- <Column Name="Name" DataType="String" Master="True" Description="名称" />
- <Column Name="Code" DataType="String" Description="编码。ProductKey" />
- <Column Name="Enable" DataType="Boolean" Description="启用。开发中/已发布" />
- <Column Name="DeviceCount" DataType="Int32" Description="设备数量" />
- <Column Name="CreateUser" DataType="String" Description="创建人" Model="False" Category="扩展" />
- <Column Name="CreateUserId" DataType="Int32" Description="创建者" Model="False" Category="扩展" />
- <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" Category="扩展" />
- <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" Category="扩展" />
- <Column Name="UpdateUser" DataType="String" Description="更新人" Model="False" Category="扩展" />
- <Column Name="UpdateUserId" DataType="Int32" Description="更新者" Model="False" Category="扩展" />
- <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" Model="False" Category="扩展" />
- <Column Name="UpdateIP" DataType="String" Description="更新地址" Model="False" Category="扩展" />
- <Column Name="Remark" DataType="String" Length="500" Description="描述" Category="扩展" />
- </Columns>
- <Indexes>
- <Index Columns="Code" Unique="True" />
- </Indexes>
- </Table>
- <Table Name="Device" Description="设备。归属于某个产品下的具体设备。物联网平台为设备颁发产品内唯一的证书DeviceName。设备可以直接连接物联网平台,也可以作为子设备通过网关连接物联网平台。">
- <Columns>
- <Column Name="Id" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
- <Column Name="Name" DataType="String" Master="True" Description="名称" />
- <Column Name="Code" DataType="String" Description="编码。设备唯一证书DeviceName,用于设备认证,在注册时由系统生成" />
- <Column Name="Secret" DataType="String" Description="密钥。设备密钥DeviceSecret,用于设备认证,注册时由系统生成" />
- <Column Name="ProductId" DataType="Int32" Description="产品" />
- <Column Name="GroupId" DataType="Int32" Map="DeviceGroup@Id@Name@GroupPath" Description="分组" />
- <Column Name="Enable" DataType="Boolean" Description="启用" />
- <Column Name="Online" DataType="Boolean" Description="在线" />
- <Column Name="Version" DataType="String" Description="版本" />
- <Column Name="IP" DataType="String" Length="200" Description="本地IP" />
- <Column Name="Uuid" DataType="String" Length="200" Description="唯一标识。硬件标识,或其它能够唯一区分设备的标记" />
- <Column Name="Location" DataType="String" Description="位置。场地安装位置,或者经纬度" Category="登录信息" />
- <Column Name="Period" DataType="Int32" Description="心跳周期。默认60秒" Category="参数设置" />
- <Column Name="PollingTime" DataType="Int32" Description="采集间隔。默认1000ms" Category="参数设置" />
- <Column Name="Logins" DataType="Int32" Description="登录次数" Category="登录信息" />
- <Column Name="LastLogin" DataType="DateTime" Description="最后登录" Category="登录信息" />
- <Column Name="LastLoginIP" DataType="String" Description="最后IP。最后的公网IP地址" Category="登录信息" />
- <Column Name="OnlineTime" DataType="Int32" Description="在线时长。总时长,每次下线后累加,单位,秒" Category="登录信息" />
- <Column Name="RegisterTime" DataType="DateTime" Description="激活时间" Category="登录信息" />
- <Column Name="CreateUserId" DataType="Int32" Description="创建者" Model="False" Category="扩展" />
- <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" Category="扩展" />
- <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" Category="扩展" />
- <Column Name="UpdateUserId" DataType="Int32" Description="更新者" Model="False" Category="扩展" />
- <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" Model="False" Category="扩展" />
- <Column Name="UpdateIP" DataType="String" Description="更新地址" Model="False" Category="扩展" />
- <Column Name="Remark" DataType="String" Length="500" Description="描述" Category="扩展" />
- </Columns>
- <Indexes>
- <Index Columns="Code" Unique="True" />
- <Index Columns="ProductId" />
- <Index Columns="Uuid" />
- <Index Columns="UpdateTime" />
- </Indexes>
- </Table>
- <Table Name="DeviceGroup" Description="设备分组。物联网平台支持建立设备分组,分组中可包含不同产品下的设备。通过设备组来进行跨产品管理设备。" BaseType="EntityTree">
- <Columns>
- <Column Name="Id" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
- <Column Name="Name" DataType="String" Master="True" Description="名称" />
- <Column Name="ParentId" DataType="Int32" Description="父级" />
- <Column Name="Sort" DataType="Int32" Description="排序" />
- <Column Name="Devices" DataType="Int32" Description="设备总数" />
- <Column Name="Activations" DataType="Int32" Description="激活设备" />
- <Column Name="Onlines" DataType="Int32" Description="当前在线" />
- <Column Name="CreateUserId" DataType="Int32" Description="创建者" Model="False" Category="扩展" />
- <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" Category="扩展" />
- <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" Category="扩展" />
- <Column Name="UpdateUserId" DataType="Int32" Description="更新者" Model="False" Category="扩展" />
- <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" Model="False" Category="扩展" />
- <Column Name="UpdateIP" DataType="String" Description="更新地址" Model="False" Category="扩展" />
- <Column Name="Remark" DataType="String" Length="500" Description="描述" Category="扩展" />
- </Columns>
- <Indexes>
- <Index Columns="ParentId,Name" Unique="True" />
- <Index Columns="Name" />
- </Indexes>
- </Table>
- <Table Name="DeviceOnline" Description="设备在线">
- <Columns>
- <Column Name="Id" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
- <Column Name="SessionId" DataType="String" Description="会话" />
- <Column Name="ProductId" DataType="Int32" Description="产品" />
- <Column Name="DeviceId" DataType="Int32" Description="设备" />
- <Column Name="Name" DataType="String" Master="True" Description="名称" />
- <Column Name="IP" DataType="String" Length="200" Description="本地IP" />
- <Column Name="GroupPath" DataType="String" Description="分组" />
- <Column Name="Pings" DataType="Int32" Description="心跳" />
- <Column Name="Delay" DataType="Int32" Description="延迟。网络延迟,单位ms" />
- <Column Name="Offset" DataType="Int32" Description="偏移。客户端时间减服务端时间,单位s" />
- <Column Name="LocalTime" DataType="DateTime" Description="本地时间" />
- <Column Name="Token" DataType="String" Length="200" Description="令牌" />
- <Column Name="Creator" DataType="String" Description="创建者。服务端设备" />
- <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" />
- <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" />
- <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" Model="False" />
- <Column Name="Remark" DataType="String" Length="500" Description="备注" />
- </Columns>
- <Indexes>
- <Index Columns="SessionId" Unique="True" />
- <Index Columns="ProductId" />
- <Index Columns="UpdateTime" />
- </Indexes>
- </Table>
- <Table Name="DeviceHistory" Description="设备历史。记录设备上线下线等操作">
- <Columns>
- <Column Name="Id" DataType="Int64" PrimaryKey="True" Description="编号" />
- <Column Name="DeviceId" DataType="Int32" Description="设备" />
- <Column Name="Name" DataType="String" Master="True" Description="名称" />
- <Column Name="Action" DataType="String" Description="操作" />
- <Column Name="Success" DataType="Boolean" Description="成功" />
- <Column Name="TraceId" DataType="String" Description="追踪。用于记录调用链追踪标识,在APM查找调用链" />
- <Column Name="Creator" DataType="String" Description="创建者。服务端设备" />
- <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" />
- <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" />
- <Column Name="Remark" DataType="String" Length="2000" Description="内容" />
- </Columns>
- <Indexes>
- <Index Columns="DeviceId,Id" />
- <Index Columns="DeviceId,Action,Id" />
- </Indexes>
- </Table>
- <Table Name="DeviceProperty" Description="设备属性。设备的功能模型之一,一般用于描述设备运行时的状态,如环境监测设备所读取的当前环境温度等。一个设备有多个属性,名值表">
- <Columns>
- <Column Name="Id" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
- <Column Name="DeviceId" DataType="Int32" Description="设备" />
- <Column Name="Name" DataType="String" Master="True" Description="名称" />
- <Column Name="NickName" DataType="String" Description="昵称" />
- <Column Name="Type" DataType="String" Description="类型" />
- <Column Name="Value" DataType="String" Length="-1" Description="数值。设备上报数值" />
- <Column Name="Unit" DataType="String" Description="单位" />
- <Column Name="Enable" DataType="Boolean" Description="启用" />
- <Column Name="TraceId" DataType="String" Description="追踪。用于记录调用链追踪标识,在APM查找调用链" Model="False" Category="扩展" />
- <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" Category="扩展" />
- <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" Category="扩展" />
- <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" Model="False" Category="扩展" />
- <Column Name="UpdateIP" DataType="String" Description="更新地址" Model="False" Category="扩展" />
- </Columns>
- <Indexes>
- <Index Columns="DeviceId,Name" Unique="True" />
- <Index Columns="UpdateTime" />
- </Indexes>
- </Table>
- <Table Name="DeviceData" Description="设备数据。设备采集原始数据,按天分表存储">
- <Columns>
- <Column Name="Id" DataType="Int64" PrimaryKey="True" Description="编号" />
- <Column Name="DeviceId" DataType="Int32" Description="设备" />
- <Column Name="Name" DataType="String" Master="True" Description="名称。MQTT的Topic,或者属性名" />
- <Column Name="Kind" DataType="String" Description="类型。数据来源,如PostProperty/PostData/MqttPostData" />
- <Column Name="Value" DataType="String" Length="2000" Description="数值" />
- <Column Name="Timestamp" DataType="Int64" Description="时间戳。设备生成数据时的UTC毫秒" />
- <Column Name="TraceId" DataType="String" Description="追踪标识。用于记录调用链追踪标识,在APM查找调用链" Model="False" Category="扩展" />
- <Column Name="Creator" DataType="String" Description="创建者。服务端设备" Model="False" Category="扩展" />
- <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" Category="扩展" />
- <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" Category="扩展" />
- </Columns>
- <Indexes>
- <Index Columns="DeviceId,Id" />
- <Index Columns="DeviceId,Name,Id" />
- <Index Columns="DeviceId,Kind,Id" />
- </Indexes>
- </Table>
+ <Tables>
+ <Table Name="Product" Description="产品。设备的集合,通常指一组具有相同功能的设备。物联网平台为每个产品颁发全局唯一的ProductKey。">
+ <Columns>
+ <Column Name="Id" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
+ <Column Name="Name" DataType="String" Master="True" Description="名称" />
+ <Column Name="Code" DataType="String" Description="编码。ProductKey" />
+ <Column Name="Enable" DataType="Boolean" Description="启用。开发中/已发布" />
+ <Column Name="DeviceCount" DataType="Int32" Description="设备数量" />
+ <Column Name="CreateUser" DataType="String" Description="创建人" Model="False" Category="扩展" />
+ <Column Name="CreateUserId" DataType="Int32" Description="创建者" Model="False" Category="扩展" />
+ <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" Category="扩展" />
+ <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" Category="扩展" />
+ <Column Name="UpdateUser" DataType="String" Description="更新人" Model="False" Category="扩展" />
+ <Column Name="UpdateUserId" DataType="Int32" Description="更新者" Model="False" Category="扩展" />
+ <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" Model="False" Category="扩展" />
+ <Column Name="UpdateIP" DataType="String" Description="更新地址" Model="False" Category="扩展" />
+ <Column Name="Remark" DataType="String" Length="500" Description="描述" Category="扩展" />
+ </Columns>
+ <Indexes>
+ <Index Columns="Code" Unique="True" />
+ </Indexes>
+ </Table>
+ <Table Name="Device" Description="设备。归属于某个产品下的具体设备。物联网平台为设备颁发产品内唯一的证书DeviceName。设备可以直接连接物联网平台,也可以作为子设备通过网关连接物联网平台。">
+ <Columns>
+ <Column Name="Id" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
+ <Column Name="Name" DataType="String" Master="True" Description="名称" />
+ <Column Name="Code" DataType="String" Description="编码。设备唯一证书DeviceName,用于设备认证,在注册时由系统生成" />
+ <Column Name="Secret" DataType="String" Description="密钥。设备密钥DeviceSecret,用于设备认证,注册时由系统生成" />
+ <Column Name="ProductId" DataType="Int32" Description="产品" />
+ <Column Name="GroupId" DataType="Int32" Map="DeviceGroup@Id@Name@GroupPath" Description="分组" />
+ <Column Name="Enable" DataType="Boolean" Description="启用" />
+ <Column Name="Online" DataType="Boolean" Description="在线" />
+ <Column Name="Version" DataType="String" Description="版本" />
+ <Column Name="IP" DataType="String" Length="200" Description="本地IP" />
+ <Column Name="Uuid" DataType="String" Length="200" Description="唯一标识。硬件标识,或其它能够唯一区分设备的标记" />
+ <Column Name="Location" DataType="String" Description="位置。场地安装位置,或者经纬度" Category="登录信息" />
+ <Column Name="Period" DataType="Int32" Description="心跳周期。默认60秒" Category="参数设置" />
+ <Column Name="PollingTime" DataType="Int32" Description="采集间隔。默认1000ms" Category="参数设置" />
+ <Column Name="Logins" DataType="Int32" Description="登录次数" Category="登录信息" />
+ <Column Name="LastLogin" DataType="DateTime" Description="最后登录" Category="登录信息" />
+ <Column Name="LastLoginIP" DataType="String" Description="最后IP。最后的公网IP地址" Category="登录信息" />
+ <Column Name="OnlineTime" DataType="Int32" Description="在线时长。总时长,每次下线后累加,单位,秒" Category="登录信息" />
+ <Column Name="RegisterTime" DataType="DateTime" Description="激活时间" Category="登录信息" />
+ <Column Name="CreateUserId" DataType="Int32" Description="创建者" Model="False" Category="扩展" />
+ <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" Category="扩展" />
+ <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" Category="扩展" />
+ <Column Name="UpdateUserId" DataType="Int32" Description="更新者" Model="False" Category="扩展" />
+ <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" Model="False" Category="扩展" />
+ <Column Name="UpdateIP" DataType="String" Description="更新地址" Model="False" Category="扩展" />
+ <Column Name="Remark" DataType="String" Length="500" Description="描述" Category="扩展" />
+ </Columns>
+ <Indexes>
+ <Index Columns="Code" Unique="True" />
+ <Index Columns="ProductId" />
+ <Index Columns="Uuid" />
+ <Index Columns="UpdateTime" />
+ </Indexes>
+ </Table>
+ <Table Name="DeviceGroup" Description="设备分组。物联网平台支持建立设备分组,分组中可包含不同产品下的设备。通过设备组来进行跨产品管理设备。" BaseType="EntityTree">
+ <Columns>
+ <Column Name="Id" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
+ <Column Name="Name" DataType="String" Master="True" Description="名称" />
+ <Column Name="ParentId" DataType="Int32" Description="父级" />
+ <Column Name="Sort" DataType="Int32" Description="排序" />
+ <Column Name="Devices" DataType="Int32" Description="设备总数" />
+ <Column Name="Activations" DataType="Int32" Description="激活设备" />
+ <Column Name="Onlines" DataType="Int32" Description="当前在线" />
+ <Column Name="CreateUserId" DataType="Int32" Description="创建者" Model="False" Category="扩展" />
+ <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" Category="扩展" />
+ <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" Category="扩展" />
+ <Column Name="UpdateUserId" DataType="Int32" Description="更新者" Model="False" Category="扩展" />
+ <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" Model="False" Category="扩展" />
+ <Column Name="UpdateIP" DataType="String" Description="更新地址" Model="False" Category="扩展" />
+ <Column Name="Remark" DataType="String" Length="500" Description="描述" Category="扩展" />
+ </Columns>
+ <Indexes>
+ <Index Columns="ParentId,Name" Unique="True" />
+ <Index Columns="Name" />
+ </Indexes>
+ </Table>
+ <Table Name="DeviceOnline" Description="设备在线">
+ <Columns>
+ <Column Name="Id" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
+ <Column Name="SessionId" DataType="String" Description="会话" />
+ <Column Name="ProductId" DataType="Int32" Description="产品" />
+ <Column Name="DeviceId" DataType="Int32" Description="设备" />
+ <Column Name="Name" DataType="String" Master="True" Description="名称" />
+ <Column Name="IP" DataType="String" Length="200" Description="本地IP" />
+ <Column Name="GroupPath" DataType="String" Description="分组" />
+ <Column Name="Pings" DataType="Int32" Description="心跳" />
+ <Column Name="WebSocket" DataType="Boolean" Description="长连接。WebSocket长连接" />
+ <Column Name="Delay" DataType="Int32" Description="延迟。网络延迟,单位ms" />
+ <Column Name="Offset" DataType="Int32" Description="偏移。客户端时间减服务端时间,单位s" />
+ <Column Name="LocalTime" DataType="DateTime" Description="本地时间" />
+ <Column Name="Token" DataType="String" Length="200" Description="令牌" />
+ <Column Name="Creator" DataType="String" Description="创建者。服务端设备" />
+ <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" />
+ <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" />
+ <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" Model="False" />
+ <Column Name="Remark" DataType="String" Length="500" Description="备注" />
+ </Columns>
+ <Indexes>
+ <Index Columns="SessionId" Unique="True" />
+ <Index Columns="ProductId" />
+ <Index Columns="UpdateTime" />
+ </Indexes>
+ </Table>
+ <Table Name="DeviceHistory" Description="设备历史。记录设备上线下线等操作">
+ <Columns>
+ <Column Name="Id" DataType="Int64" PrimaryKey="True" Description="编号" />
+ <Column Name="DeviceId" DataType="Int32" Description="设备" />
+ <Column Name="Name" DataType="String" Master="True" Description="名称" />
+ <Column Name="Action" DataType="String" Description="操作" />
+ <Column Name="Success" DataType="Boolean" Description="成功" />
+ <Column Name="TraceId" DataType="String" Description="追踪。用于记录调用链追踪标识,在APM查找调用链" />
+ <Column Name="Creator" DataType="String" Description="创建者。服务端设备" />
+ <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" />
+ <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" />
+ <Column Name="Remark" DataType="String" Length="2000" Description="内容" />
+ </Columns>
+ <Indexes>
+ <Index Columns="DeviceId,Id" />
+ <Index Columns="DeviceId,Action,Id" />
+ </Indexes>
+ </Table>
+ <Table Name="DeviceProperty" Description="设备属性。设备的功能模型之一,一般用于描述设备运行时的状态,如环境监测设备所读取的当前环境温度等。一个设备有多个属性,名值表">
+ <Columns>
+ <Column Name="Id" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
+ <Column Name="DeviceId" DataType="Int32" Description="设备" />
+ <Column Name="Name" DataType="String" Master="True" Description="名称" />
+ <Column Name="NickName" DataType="String" Description="昵称" />
+ <Column Name="Type" DataType="String" Description="类型" />
+ <Column Name="Value" DataType="String" Length="-1" Description="数值。设备上报数值" />
+ <Column Name="Unit" DataType="String" Description="单位" />
+ <Column Name="Enable" DataType="Boolean" Description="启用" />
+ <Column Name="TraceId" DataType="String" Description="追踪。用于记录调用链追踪标识,在APM查找调用链" Model="False" Category="扩展" />
+ <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" Category="扩展" />
+ <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" Category="扩展" />
+ <Column Name="UpdateTime" DataType="DateTime" Description="更新时间" Model="False" Category="扩展" />
+ <Column Name="UpdateIP" DataType="String" Description="更新地址" Model="False" Category="扩展" />
+ </Columns>
+ <Indexes>
+ <Index Columns="DeviceId,Name" Unique="True" />
+ <Index Columns="UpdateTime" />
+ </Indexes>
+ </Table>
+ <Table Name="DeviceData" Description="设备数据。设备采集原始数据,按天分表存储">
+ <Columns>
+ <Column Name="Id" DataType="Int64" PrimaryKey="True" Description="编号" />
+ <Column Name="DeviceId" DataType="Int32" Description="设备" />
+ <Column Name="Name" DataType="String" Master="True" Description="名称。MQTT的Topic,或者属性名" />
+ <Column Name="Kind" DataType="String" Description="类型。数据来源,如PostProperty/PostData/MqttPostData" />
+ <Column Name="Value" DataType="String" Length="2000" Description="数值" />
+ <Column Name="Timestamp" DataType="Int64" Description="时间戳。设备生成数据时的UTC毫秒" />
+ <Column Name="TraceId" DataType="String" Description="追踪标识。用于记录调用链追踪标识,在APM查找调用链" Model="False" Category="扩展" />
+ <Column Name="Creator" DataType="String" Description="创建者。服务端设备" Model="False" Category="扩展" />
+ <Column Name="CreateTime" DataType="DateTime" Description="创建时间" Model="False" Category="扩展" />
+ <Column Name="CreateIP" DataType="String" Description="创建地址" Model="False" Category="扩展" />
+ </Columns>
+ <Indexes>
+ <Index Columns="DeviceId,Id" />
+ <Index Columns="DeviceId,Name,Id" />
+ <Index Columns="DeviceId,Kind,Id" />
+ </Indexes>
+ </Table>
+ </Tables>
</EntityModel>
\ No newline at end of file
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 bec8005..c962a35 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"
@@ -141,7 +141,7 @@ public partial class DeviceOnline : Entity<DeviceOnline>, IOnlineModel
/// <returns>实体列表</returns>
public static IList<DeviceOnline> FindAllByProductId(Int32 productId)
{
- if (productId <= 0) return new List<DeviceOnline>();
+ if (productId <= 0) return [];
// 实体缓存
if (Meta.Session.Count < 1000) return Meta.Cache.FindAll(e => e.ProductId == productId);
diff --git "a/IoT.Data/Entity/\350\256\276\345\244\207\345\234\250\347\272\277.cs" "b/IoT.Data/Entity/\350\256\276\345\244\207\345\234\250\347\272\277.cs"
index 23168ac..9fc0382 100644
--- "a/IoT.Data/Entity/\350\256\276\345\244\207\345\234\250\347\272\277.cs"
+++ "b/IoT.Data/Entity/\350\256\276\345\244\207\345\234\250\347\272\277.cs"
@@ -88,6 +88,14 @@ public partial class DeviceOnline
[BindColumn("Pings", "心跳", "")]
public Int32 Pings { get => _Pings; set { if (OnPropertyChanging("Pings", value)) { _Pings = value; OnPropertyChanged("Pings"); } } }
+ private Boolean _WebSocket;
+ /// <summary>长连接。WebSocket长连接</summary>
+ [DisplayName("长连接")]
+ [Description("长连接。WebSocket长连接")]
+ [DataObjectField(false, false, false, 0)]
+ [BindColumn("WebSocket", "长连接。WebSocket长连接", "")]
+ public Boolean WebSocket { get => _WebSocket; set { if (OnPropertyChanging("WebSocket", value)) { _WebSocket = value; OnPropertyChanged("WebSocket"); } } }
+
private Int32 _Delay;
/// <summary>延迟。网络延迟,单位ms</summary>
[DisplayName("延迟")]
@@ -177,6 +185,7 @@ public partial class DeviceOnline
"IP" => _IP,
"GroupPath" => _GroupPath,
"Pings" => _Pings,
+ "WebSocket" => _WebSocket,
"Delay" => _Delay,
"Offset" => _Offset,
"LocalTime" => _LocalTime,
@@ -200,6 +209,7 @@ public partial class DeviceOnline
case "IP": _IP = Convert.ToString(value); break;
case "GroupPath": _GroupPath = Convert.ToString(value); break;
case "Pings": _Pings = value.ToInt(); break;
+ case "WebSocket": _WebSocket = value.ToBoolean(); break;
case "Delay": _Delay = value.ToInt(); break;
case "Offset": _Offset = value.ToInt(); break;
case "LocalTime": _LocalTime = value.ToDateTime(); break;
@@ -246,6 +256,9 @@ public partial class DeviceOnline
/// <summary>心跳</summary>
public static readonly Field Pings = FindByName("Pings");
+ /// <summary>长连接。WebSocket长连接</summary>
+ public static readonly Field WebSocket = FindByName("WebSocket");
+
/// <summary>延迟。网络延迟,单位ms</summary>
public static readonly Field Delay = FindByName("Delay");
@@ -303,6 +316,9 @@ public partial class DeviceOnline
/// <summary>心跳</summary>
public const String Pings = "Pings";
+ /// <summary>长连接。WebSocket长连接</summary>
+ public const String WebSocket = "WebSocket";
+
/// <summary>延迟。网络延迟,单位ms</summary>
public const String Delay = "Delay";
diff --git a/IoT.Data/IoT.Data.csproj b/IoT.Data/IoT.Data.csproj
index df4c2cf..99649fe 100644
--- a/IoT.Data/IoT.Data.csproj
+++ b/IoT.Data/IoT.Data.csproj
@@ -41,8 +41,8 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="NewLife.IoT" Version="2.2.2024.501" />
- <PackageReference Include="NewLife.XCode" Version="11.13.2024.606" />
+ <PackageReference Include="NewLife.IoT" Version="2.4.2025.501" />
+ <PackageReference Include="NewLife.XCode" Version="11.19.2025.501" />
</ItemGroup>
<ItemGroup>
diff --git a/IoT.Data/xcodetool.exe b/IoT.Data/xcodetool.exe
index 51c2c0b..7eb8d6c 100644
Binary files a/IoT.Data/xcodetool.exe and b/IoT.Data/xcodetool.exe differ
diff --git a/IoTCore/IoTCore.csproj b/IoTCore/IoTCore.csproj
index 9207712..6108dff 100644
--- a/IoTCore/IoTCore.csproj
+++ b/IoTCore/IoTCore.csproj
@@ -19,12 +19,15 @@
</PropertyGroup>
<ItemGroup>
+ <Compile Remove="Models\LoginResponse.cs" />
+ <Compile Remove="Models\LogoutResponse.cs" />
+ <Compile Remove="Models\PingResponse.cs" />
<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" />
+ <PackageReference Include="NewLife.IoT" Version="2.4.2025.501" />
+ <PackageReference Include="NewLife.Remoting" Version="3.3.2025.501" />
</ItemGroup>
</Project>
diff --git a/IoTCore/Models/LoginInfo.cs b/IoTCore/Models/LoginInfo.cs
index e97818a..26b25cb 100644
--- a/IoTCore/Models/LoginInfo.cs
+++ b/IoTCore/Models/LoginInfo.cs
@@ -3,14 +3,14 @@
namespace NewLife.IoT.Models;
/// <summary>节点登录信息</summary>
-public class LoginInfo : ILoginRequest
+public class LoginInfo : LoginRequest
{
#region 属性
- /// <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 ProductKey { get; set; }
@@ -18,22 +18,22 @@ public class LoginInfo : ILoginRequest
/// <summary>产品密钥</summary>
public String ProductSecret { get; set; }
- /// <summary>实例。应用可能多实例部署,ip@proccessid</summary>
- public String ClientId { get; set; }
+ ///// <summary>实例。应用可能多实例部署,ip@proccessid</summary>
+ //public String ClientId { 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; }
+ ///// <summary>本地UTC时间</summary>
+ //public Int64 Time { get; set; }
#endregion
}
\ No newline at end of file
diff --git a/IoTCore/Models/PingInfo.cs b/IoTCore/Models/PingInfo.cs
index ca2fbaa..9b2c1a8 100644
--- a/IoTCore/Models/PingInfo.cs
+++ b/IoTCore/Models/PingInfo.cs
@@ -3,40 +3,40 @@
namespace NewLife.IoT.Models;
/// <summary>心跳信息</summary>
-public class PingInfo : IPingRequest
+public class PingInfo : PingRequest
{
#region 属性
- /// <summary>内存大小</summary>
- public UInt64 Memory { get; set; }
+ ///// <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 Double 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; }
+ ///// <summary>延迟。ms毫秒</summary>
+ //public Int32 Delay { get; set; }
#endregion
}
\ No newline at end of file
diff --git a/IoTEdge/HttpDevice.cs b/IoTEdge/HttpDevice.cs
index ab556d5..05d1d98 100644
--- a/IoTEdge/HttpDevice.cs
+++ b/IoTEdge/HttpDevice.cs
@@ -1,6 +1,6 @@
-using NewLife.IoT.Models;
+using NewLife;
+using NewLife.IoT.Models;
using NewLife.IoT.ThingModels;
-using NewLife.Log;
using NewLife.Model;
using NewLife.Remoting.Clients;
using NewLife.Remoting.Models;
@@ -15,17 +15,24 @@ public class HttpDevice : ClientBase
/// <summary>产品编码。从IoT管理平台获取</summary>
public String ProductKey { get; set; }
+ /// <summary>产品密钥</summary>
+ public String ProductSecret { get; set; }
+
private readonly ClientSetting _setting;
#endregion
#region 构造
- public HttpDevice() => Prefix = "Device/";
-
public HttpDevice(ClientSetting setting) : base(setting)
{
+ // 设置动作,开启下行通知
+ Features = Features.Login | Features.Logout | Features.Ping | Features.Notify | Features.Upgrade | Features.PostEvent;
+ SetActions("Device/");
+ Actions[Features.CommandReply] = "Thing/ServiceReply";
+
_setting = setting;
ProductKey = setting.ProductKey;
+ ProductSecret = setting.DeviceSecret;
}
#endregion
@@ -34,6 +41,8 @@ public class HttpDevice : ClientBase
{
var provider = ServiceProvider ??= ObjectContainer.Provider;
+ PasswordProvider = new SaltPasswordProvider { Algorithm = "md5", SaltTime = 60 };
+
// 找到容器,注册默认的模型实现,供后续InvokeAsync时自动创建正确的模型对象
var container = ModelExtension.GetService<IObjectContainer>(provider) ?? ObjectContainer.Current;
if (container != null)
@@ -50,14 +59,15 @@ public class HttpDevice : ClientBase
}
#endregion
- #region 登录注销
+ #region 登录
public override ILoginRequest BuildLoginRequest()
{
- var request = base.BuildLoginRequest();
- if (request is LoginInfo info)
- {
- info.ProductKey = ProductKey;
- }
+ var request = new LoginInfo();
+ FillLoginRequest(request);
+
+ request.ProductKey = ProductKey;
+ request.ProductSecret = ProductSecret;
+ request.Name = Environment.MachineName;
return request;
}
@@ -66,21 +76,11 @@ public class HttpDevice : ClientBase
#region 心跳
public override IPingRequest BuildPingRequest()
{
- var request = base.BuildPingRequest();
- if (request is PingInfo info)
- {
-
- }
+ var request = new PingInfo();
+ FillPingRequest(request);
return request;
}
-
- public override Task<Object> CommandReply(CommandReplyModel model) => InvokeAsync<Object>("Thing/ServiceReply", new ServiceReplyModel
- {
- Id = model.Id,
- Status = (ServiceStatus)model.Status,
- Data = model.Data,
- });
#endregion
#region 数据
@@ -88,7 +88,7 @@ public class HttpDevice : ClientBase
/// <returns></returns>
public async Task PostDataAsync()
{
- if (Tracer != null) DefaultSpan.Current = null;
+ //if (Tracer != null) DefaultSpan.Current = null;
using var span = Tracer?.NewSpan("PostData");
try
diff --git a/IoTEdge/IoTEdge.csproj b/IoTEdge/IoTEdge.csproj
index 0be314a..cb8c529 100644
--- a/IoTEdge/IoTEdge.csproj
+++ b/IoTEdge/IoTEdge.csproj
@@ -21,16 +21,16 @@
<ItemGroup>
<PackageReference Include="NewLife.BACnet" Version="1.0.2023.520-beta0022" />
- <PackageReference Include="NewLife.Core" Version="10.10.2024.601" />
- <PackageReference Include="NewLife.IoT" Version="2.2.2024.501" />
+ <PackageReference Include="NewLife.Core" Version="11.5.2025.501" />
+ <PackageReference Include="NewLife.IoT" Version="2.4.2025.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.MQTT" Version="2.0.2025.415" />
<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="NewLife.Stardust" Version="3.3.2025.506" />
<PackageReference Include="SmartA2" Version="1.1.2024.218" />
<PackageReference Include="SmartA4" Version="1.0.2023.606-beta1305" />
</ItemGroup>
diff --git a/IoTZero/Areas/IoT/Controllers/DeviceController.cs b/IoTZero/Areas/IoT/Controllers/DeviceController.cs
index 086cae9..c0fa0d2 100644
--- a/IoTZero/Areas/IoT/Controllers/DeviceController.cs
+++ b/IoTZero/Areas/IoT/Controllers/DeviceController.cs
@@ -1,5 +1,6 @@
using System.ComponentModel;
using IoT.Data;
+using NewLife;
using NewLife.Cube;
using NewLife.Log;
using NewLife.Web;
diff --git a/IoTZero/Areas/IoT/Controllers/DeviceGroupController.cs b/IoTZero/Areas/IoT/Controllers/DeviceGroupController.cs
index ddb050d..09190f7 100644
--- a/IoTZero/Areas/IoT/Controllers/DeviceGroupController.cs
+++ b/IoTZero/Areas/IoT/Controllers/DeviceGroupController.cs
@@ -1,5 +1,6 @@
using IoT.Data;
using Microsoft.AspNetCore.Mvc;
+using NewLife;
using NewLife.Cube;
using NewLife.Web;
using XCode.Membership;
diff --git a/IoTZero/Areas/IoT/Controllers/DeviceHistoryController.cs b/IoTZero/Areas/IoT/Controllers/DeviceHistoryController.cs
index 1da5870..3f5ada2 100644
--- a/IoTZero/Areas/IoT/Controllers/DeviceHistoryController.cs
+++ b/IoTZero/Areas/IoT/Controllers/DeviceHistoryController.cs
@@ -1,5 +1,7 @@
using IoT.Data;
+using NewLife;
using NewLife.Cube;
+using NewLife.Cube.Extensions;
using NewLife.Web;
using XCode.Membership;
@@ -9,6 +11,14 @@ namespace IoTZero.Areas.IoT.Controllers;
[Menu(60, true)]
public class DeviceHistoryController : ReadOnlyEntityController<DeviceHistory>
{
+ static DeviceHistoryController()
+ {
+ ListFields.RemoveField("Id");
+ ListFields.AddListField("Remark", null, "Success");
+
+ ListFields.TraceUrl();
+ }
+
protected override IEnumerable<DeviceHistory> Search(Pager p)
{
var deviceId = p["deviceId"].ToInt(-1);
diff --git a/IoTZero/Areas/IoT/Controllers/DeviceOnlineController.cs b/IoTZero/Areas/IoT/Controllers/DeviceOnlineController.cs
index 140afa0..2d4313c 100644
--- a/IoTZero/Areas/IoT/Controllers/DeviceOnlineController.cs
+++ b/IoTZero/Areas/IoT/Controllers/DeviceOnlineController.cs
@@ -1,6 +1,11 @@
-using IoT.Data;
+using System.ComponentModel;
+using IoT.Data;
+using Microsoft.AspNetCore.Mvc;
+using NewLife;
using NewLife.Cube;
using NewLife.Cube.ViewModels;
+using NewLife.Remoting.Extensions.Services;
+using NewLife.Remoting.Models;
using NewLife.Web;
using XCode.Membership;
@@ -11,11 +16,13 @@ namespace IoTZero.Areas.IoT.Controllers;
[IoTArea]
public class DeviceOnlineController : EntityController<DeviceOnline>
{
+ private readonly IDeviceService _deviceService;
+
static DeviceOnlineController()
{
//LogOnChange = true;
- //ListFields.RemoveField("Id", "Creator");
+ ListFields.RemoveField("Token");
ListFields.RemoveCreateField().RemoveRemarkField();
{
@@ -34,6 +41,8 @@ public class DeviceOnlineController : EntityController<DeviceOnline>
}
}
+ public DeviceOnlineController(IDeviceService deviceService) => _deviceService = deviceService;
+
/// <summary>高级搜索。列表页查询、导出Excel、导出Json、分享页等使用</summary>
/// <param name="p">分页器。包含分页排序参数,以及Http请求参数</param>
/// <returns></returns>
@@ -46,4 +55,56 @@ public class DeviceOnlineController : EntityController<DeviceOnline>
return DeviceOnline.Search(null, productId, start, end, p["Q"], p);
}
+
+ [DisplayName("检查更新")]
+ [EntityAuthorize((PermissionFlags)16)]
+ public async Task<ActionResult> CheckUpgrade()
+ {
+ var ts = new List<Task>();
+ foreach (var item in SelectKeys)
+ {
+ var online = DeviceOnline.FindById(item.ToInt());
+ if (online?.Device != null)
+ {
+ var cmd = new CommandModel
+ {
+ Command = "device/upgrade",
+ Expire = DateTime.UtcNow.AddSeconds(600),
+ };
+ ts.Add(_deviceService.SendCommand(online.Device, cmd, HttpContext.RequestAborted));
+ }
+ }
+
+ await Task.WhenAll(ts);
+
+ return JsonRefresh("操作成功!");
+ }
+
+ [DisplayName("执行命令")]
+ [EntityAuthorize((PermissionFlags)16)]
+ public async Task<ActionResult> Execute(String command, String argument)
+ {
+ if (GetRequest("keys") == null) throw new ArgumentNullException(nameof(SelectKeys));
+ if (command.IsNullOrEmpty()) throw new ArgumentNullException(nameof(command));
+
+ var ts = new List<Task<Int32>>();
+ foreach (var item in SelectKeys)
+ {
+ var online = DeviceOnline.FindById(item.ToInt());
+ if (online?.Device != null)
+ {
+ var cmd = new CommandModel
+ {
+ Command = command,
+ Argument = argument,
+ Expire = DateTime.UtcNow.AddSeconds(30),
+ };
+ ts.Add(_deviceService.SendCommand(online.Device, cmd, HttpContext.RequestAborted));
+ }
+ }
+
+ var rs = await Task.WhenAll(ts);
+
+ return JsonRefresh($"操作成功!下发指令{rs.Length}个,成功{rs.Count(e => e > 0)}个");
+ }
}
\ No newline at end of file
diff --git a/IoTZero/Areas/IoT/Controllers/ProductController.cs b/IoTZero/Areas/IoT/Controllers/ProductController.cs
index ec5857c..40c83e2 100644
--- a/IoTZero/Areas/IoT/Controllers/ProductController.cs
+++ b/IoTZero/Areas/IoT/Controllers/ProductController.cs
@@ -1,4 +1,5 @@
using IoT.Data;
+using NewLife;
using NewLife.Cube;
using NewLife.Web;
diff --git a/IoTZero/Areas/IoT/Views/DeviceOnline/_List_Search.cshtml b/IoTZero/Areas/IoT/Views/DeviceOnline/_List_Search.cshtml
new file mode 100644
index 0000000..fc05d4c
--- /dev/null
+++ b/IoTZero/Areas/IoT/Views/DeviceOnline/_List_Search.cshtml
@@ -0,0 +1,12 @@
+@using NewLife;
+@using NewLife.Web;
+@using XCode;
+@{
+ var fact = ViewBag.Factory as IEntityFactory;
+ var page = ViewBag.Page as Pager;
+}
+<div class="form-group">
+ <label for="productId" class="control-label">产品:</label>
+ @Html.ForDropDownList("productId", Product.FindAllWithCache(), page["productId"], "全部", true)
+</div>
+@await Html.PartialAsync("_Area2", "")
diff --git a/IoTZero/Areas/IoT/Views/DeviceOnline/_List_Toolbar_Batch.cshtml b/IoTZero/Areas/IoT/Views/DeviceOnline/_List_Toolbar_Batch.cshtml
new file mode 100644
index 0000000..efd35df
--- /dev/null
+++ b/IoTZero/Areas/IoT/Views/DeviceOnline/_List_Toolbar_Batch.cshtml
@@ -0,0 +1,21 @@
+@using NewLife.Common;
+@using System.Collections.Generic;
+@{
+ var set = ViewBag.PageSetting as PageSetting;
+ var page = ViewBag.Page as Pager;
+}
+@if (set.EnableSelect)
+{
+ <button type="button" class="btn btn-success btn-sm" data-action="action" data-url="@Url.Action("CheckUpgrade")" data-fields="keys" disabled>
+ 检查更新
+ </button>
+ <div class="form-group">
+ <label for="command" class="control-label">命令:</label>
+ @Html.TextBox("command", page["command"])
+ <label for="argument" class="control-label">参数:</label>
+ @Html.TextBox("argument", page["argument"])
+ </div>
+ <button type="button" class="btn btn-purple btn-sm" data-action="action" data-url="@Url.Action("Execute")" data-fields="keys,command,argument">
+ 执行命令
+ </button>
+}
\ No newline at end of file
diff --git a/IoTZero/Controllers/AppController.cs b/IoTZero/Controllers/AppController.cs
index 7796929..b23784d 100644
--- a/IoTZero/Controllers/AppController.cs
+++ b/IoTZero/Controllers/AppController.cs
@@ -23,8 +23,7 @@ public class AppController : BaseController
/// <summary>
/// 实例化应用管理服务
/// </summary>
- /// <param name="queue"></param>
- /// <param name="deviceService"></param>
+ /// <param name="serviceProvider"></param>
/// <param name="thingService"></param>
/// <param name="tracer"></param>
public AppController(IServiceProvider serviceProvider, ThingService thingService, ITracer tracer) : base(serviceProvider)
diff --git a/IoTZero/Controllers/DeviceController.cs b/IoTZero/Controllers/DeviceController.cs
index fdfe300..0af205f 100644
--- a/IoTZero/Controllers/DeviceController.cs
+++ b/IoTZero/Controllers/DeviceController.cs
@@ -7,6 +7,7 @@ using NewLife.IoT.ThingModels;
using NewLife.Log;
using NewLife.Remoting;
using NewLife.Remoting.Extensions;
+using NewLife.Remoting.Extensions.Services;
using NewLife.Remoting.Models;
namespace IoTZero.Controllers;
@@ -20,18 +21,18 @@ public class DeviceController : BaseDeviceController
/// <summary>当前设备</summary>
public Device Device { get; set; }
+ private readonly MyDeviceService _deviceService;
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="tracer"></param>
public DeviceController(IServiceProvider serviceProvider, ThingService thingService, ITracer tracer) : base(serviceProvider)
{
+ _deviceService = serviceProvider.GetRequiredService<IDeviceService>() as MyDeviceService;
_thingService = thingService;
_tracer = tracer;
}
@@ -47,13 +48,12 @@ public class DeviceController : BaseDeviceController
#endregion
#region 心跳
- /// <summary>设备心跳</summary>
+ /// <summary>心跳</summary>
/// <param name="request"></param>
/// <returns></returns>
- [HttpPost(nameof(Ping))]
- public override IPingResponse Ping([FromBody] IPingRequest request)
+ protected override IPingResponse OnPing(IPingRequest request)
{
- var rs = base.Ping(request);
+ var rs = base.OnPing(request);
var device = Device;
if (device != null && rs != null)
@@ -65,19 +65,6 @@ public class DeviceController : BaseDeviceController
}
#endregion
- #region 升级
- /// <summary>升级检查</summary>
- /// <returns></returns>
- [HttpGet(nameof(Upgrade))]
- public override IUpgradeInfo Upgrade()
- {
- var device = Device ?? throw new ApiException(ApiCode.Unauthorized, "节点未登录");
-
- //throw new NotImplementedException();
- return new UpgradeInfo { };
- }
- #endregion
-
#region 设备通道
/// <summary>获取设备信息,包括主设备和子设备</summary>
/// <returns></returns>
diff --git a/IoTZero/IoTSetting.cs b/IoTZero/IoTSetting.cs
index 4d34d69..68fc8b5 100644
--- a/IoTZero/IoTSetting.cs
+++ b/IoTZero/IoTSetting.cs
@@ -15,20 +15,6 @@ public class IoTSetting : Config<IoTSetting>, ITokenSetting
static IoTSetting() => Provider = new DbConfigProvider { UserId = 0, Category = "IoTServer" };
#endregion
- #region 属性
- ///// <summary>MQTT服务端口。默认1883</summary>
- //[Description("MQTT服务端口。默认1883")]
- //public Int32 MqttPort { get; set; } = 1883;
-
- ///// <summary>MQTT证书地址。设置了才启用安全连接,默认为空</summary>
- //[Description("MQTT证书地址。设置了才启用安全连接,默认为空")]
- //public String MqttCertPath { get; set; }
-
- ///// <summary>MMQTT证书密码</summary>
- //[Description("MQTT证书密码")]
- //public String MqttCertPassword { get; set; }
- #endregion
-
#region 设备管理
/// <summary>令牌密钥。用于生成JWT令牌的算法和密钥,如HS256:ABCD1234</summary>
[Description("令牌密钥。用于生成JWT令牌的算法和密钥,如HS256:ABCD1234")]
diff --git a/IoTZero/IoTZero.csproj b/IoTZero/IoTZero.csproj
index a000fd8..437661f 100644
--- a/IoTZero/IoTZero.csproj
+++ b/IoTZero/IoTZero.csproj
@@ -29,12 +29,12 @@
</ItemGroup>
<ItemGroup>
- <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" />
+ <PackageReference Include="NewLife.Cube.Core" Version="6.4.2025.513" />
+ <PackageReference Include="NewLife.IoT" Version="2.4.2025.501" />
+ <PackageReference Include="NewLife.MQTT" Version="2.0.2025.415" />
+ <PackageReference Include="NewLife.Redis" Version="6.2.2025.503" />
+ <PackageReference Include="NewLife.Remoting.Extensions" Version="3.3.2025.501" />
+ <PackageReference Include="NewLife.Stardust.Extensions" Version="3.3.2025.506" />
</ItemGroup>
<ItemGroup>
diff --git a/IoTZero/Program.cs b/IoTZero/Program.cs
index f8dede0..ba31b2f 100644
--- a/IoTZero/Program.cs
+++ b/IoTZero/Program.cs
@@ -1,13 +1,18 @@
using IoTZero;
using IoTZero.Services;
-using NewLife.Caching;
using NewLife.Cube;
using NewLife.Log;
+using NewLife.Reflection;
+using NewLife.Remoting.Extensions;
using XCode;
// 日志输出到控制台,并拦截全局异常
XTrace.UseConsole();
+#if DEBUG
+XTrace.Log.Level = NewLife.Log.LogLevel.Debug;
+#endif
+
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
@@ -20,21 +25,13 @@ var star = services.AddStardust(null);
var set = IoTSetting.Current;
services.AddSingleton(set);
-// 逐个注册每一个用到的服务,必须做到清晰明了
-services.AddSingleton<ThingService>();
-services.AddSingleton<DataService>();
-services.AddSingleton<QueueService>();
+// 注册Redis缓存提供者
+//services.AddSingleton<ICacheProvider, RedisCacheProvider>();
-// 注册IoT
+// 注册Remoting所必须的服务
services.AddIoT(set);
//services.AddRemoting(set);
-services.AddSingleton<ICache, MemoryCache>();
-
-// 后台服务
-services.AddHostedService<ShardTableService>();
-services.AddHostedService<DeviceOnlineService>();
-
// 启用接口响应压缩
services.AddResponseCompression();
@@ -45,24 +42,16 @@ services.AddCube();
var app = builder.Build();
-// 预热数据层,执行反向工程建表等操作
-EntityFactory.InitConnection("Membership");
-EntityFactory.InitConnection("Log");
-EntityFactory.InitConnection("Cube");
-EntityFactory.InitConnection("IoT");
-
// 使用Cube前添加自己的管道
if (app.Environment.IsDevelopment())
app.UseDeveloperExceptionPage();
else
app.UseExceptionHandler("/CubeHome/Error");
-app.UseResponseCompression();
+if (Environment.GetEnvironmentVariable("__ASPNETCORE_BROWSER_TOOLS") is null)
+ app.UseResponseCompression();
-app.UseWebSockets(new WebSocketOptions()
-{
- KeepAliveInterval = TimeSpan.FromSeconds(60),
-});
+app.UseRemoting();
// 使用魔方
app.UseCube(app.Environment);
@@ -73,7 +62,7 @@ app.MapControllerRoute(
name: "default",
pattern: "{controller=CubeHome}/{action=Index}/{id?}");
-app.RegisterService("AlarmServer", null, app.Environment.EnvironmentName);
+app.RegisterService(star.AppId, null, app.Environment.EnvironmentName);
app.Run();
diff --git a/IoTZero/Services/DeviceOnlineService.cs b/IoTZero/Services/DeviceOnlineService.cs
index edcd9d5..d5de1a6 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.Models;
using NewLife.Remoting.Extensions.Services;
using NewLife.Threading;
@@ -12,7 +13,7 @@ public class DeviceOnlineService : IHostedService
#region 属性
private TimerX _timer;
private readonly IDeviceService _deviceService;
- private readonly IoTSetting _setting;
+ private readonly ITokenSetting _setting;
private readonly ITracer _tracer;
#endregion
@@ -23,7 +24,7 @@ public class DeviceOnlineService : IHostedService
/// <param name="deviceService"></param>
/// <param name="setting"></param>
/// <param name="tracer"></param>
- public DeviceOnlineService(IDeviceService deviceService, IoTSetting setting, ITracer tracer)
+ public DeviceOnlineService(IDeviceService deviceService, ITokenSetting setting, ITracer tracer)
{
_deviceService = deviceService;
_setting = setting;
diff --git a/IoTZero/Services/IoTExtensions.cs b/IoTZero/Services/IoTExtensions.cs
index e8cfd5e..c1c6d8e 100644
--- a/IoTZero/Services/IoTExtensions.cs
+++ b/IoTZero/Services/IoTExtensions.cs
@@ -9,10 +9,27 @@ namespace IoTZero.Services;
/// <summary>IoT扩展</summary>
public static class IoTExtensions
{
+ /// <summary>添加IoT客户端服务端架构服务,支持客户端登录、心跳、更新以及指令下发等操作</summary>
+ /// <remarks>
+ /// 注册登录心跳等模型类,可在此扩展模型类,传输更多内容;
+ /// 注册IDeviceService服务,提供登录心跳等基础实现;
+ /// 注册TokenService令牌服务,提供令牌颁发与验证服务;
+ /// 注册密码提供者,用于通信过程中保护密钥,避免明文传输;
+ /// 注册缓存提供者的默认实现;
+ /// 注册节点在线后台服务,定时检查节点在线状态;
+ /// </remarks>
+ /// <param name="services"></param>
+ /// <param name="setting"></param>
+ /// <returns></returns>
public static IServiceCollection AddIoT(this IServiceCollection services, ITokenSetting setting)
{
ArgumentNullException.ThrowIfNull(setting);
+ // 逐个注册每一个用到的服务,必须做到清晰明了
+ services.AddSingleton<ThingService>();
+ services.AddSingleton<DataService>();
+ services.AddSingleton<QueueService>();
+
services.AddSingleton<IDeviceService, MyDeviceService>();
services.AddTransient<ILoginRequest, LoginInfo>();
@@ -21,6 +38,17 @@ public static class IoTExtensions
// 注册Remoting所必须的服务
services.AddRemoting(setting);
+ // 后台服务
+ services.AddHostedService<ShardTableService>();
+ services.AddHostedService<DeviceOnlineService>();
+
return services;
}
+
+ /// <summary>使用IoT客户端服务端架构服务,启用WebSocket</summary>
+ /// <param name="app"></param>
+ public static void UseIoT(this IApplicationBuilder app)
+ {
+ app.UseRemoting();
+ }
}
diff --git a/IoTZero/Services/MyDeviceService.cs b/IoTZero/Services/MyDeviceService.cs
index 9abcb0a..3323bf8 100644
--- a/IoTZero/Services/MyDeviceService.cs
+++ b/IoTZero/Services/MyDeviceService.cs
@@ -2,16 +2,16 @@
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.Models;
using NewLife.Remoting.Extensions.Services;
using NewLife.Remoting.Models;
+using NewLife.Remoting.Services;
using NewLife.Security;
using NewLife.Serialization;
using NewLife.Web;
-using LoginResponse = NewLife.Remoting.Models.LoginResponse;
namespace IoTZero.Services;
@@ -20,20 +20,21 @@ public class MyDeviceService : IDeviceService
{
private readonly ICacheProvider _cacheProvider;
private readonly ICache _cache;
+ private readonly ISessionManager _sessionManager;
private readonly IPasswordProvider _passwordProvider;
- private readonly IoTSetting _setting;
+ private readonly ITokenSetting _setting;
private readonly ITracer _tracer;
/// <summary>
/// 实例化设备服务
/// </summary>
/// <param name="passwordProvider"></param>
- /// <param name="dataService"></param>
/// <param name="cacheProvider"></param>
/// <param name="setting"></param>
/// <param name="tracer"></param>
- public MyDeviceService(IPasswordProvider passwordProvider, ICacheProvider cacheProvider, IoTSetting setting, ITracer tracer)
+ public MyDeviceService(ISessionManager sessionManager, IPasswordProvider passwordProvider, ICacheProvider cacheProvider, ITokenSetting setting, ITracer tracer)
{
+ _sessionManager = sessionManager;
_passwordProvider = passwordProvider;
_cacheProvider = cacheProvider;
_cache = cacheProvider.InnerCache;
@@ -41,7 +42,7 @@ public class MyDeviceService : IDeviceService
_tracer = tracer;
}
- #region 登录
+ #region 登录注销
/// <summary>
/// 设备登录验证,内部支持动态注册
/// </summary>
@@ -89,7 +90,7 @@ public class MyDeviceService : IDeviceService
//if (dv != null && !dv.Enable) throw new ApiException(99, "禁止登录");
- if (dv == null) throw new ApiException(ApiCode.Unauthorized, "节点鉴权失败");
+ if (dv == null) throw new ApiException(ApiCode.Unauthorized, "登录失败");
dv.Login(inf, ip);
@@ -100,7 +101,7 @@ public class MyDeviceService : IDeviceService
//SetChildOnline(dv, ip);
// 登录历史
- WriteHistory(dv, source + "设备鉴权", true, $"[{dv.Name}/{dv.Code}]鉴权成功 " + inf.ToJson(false, false, false), ip);
+ WriteHistory(dv, source + "登录", true, $"[{dv.Name}/{dv.Code}]登录成功 " + inf.ToJson(false, false, false), ip);
var rs = new LoginResponse
{
@@ -218,13 +219,14 @@ public class MyDeviceService : IDeviceService
}
#endregion
- #region 心跳
+ #region 心跳保活
/// <summary>心跳</summary>
- /// <param name="inf"></param>
+ /// <param name="device"></param>
+ /// <param name="request"></param>
/// <param name="token"></param>
/// <param name="ip"></param>
/// <returns></returns>
- public IOnlineModel Ping(IDeviceModel device, IPingRequest? request, String token, String ip)
+ public IOnlineModel Ping(IDeviceModel device, IPingRequest request, String token, String ip)
{
var dv = device as Device;
var inf = request as PingInfo;
@@ -245,11 +247,35 @@ public class MyDeviceService : IDeviceService
return olt;
}
+ /// <summary>设置设备的长连接上线/下线</summary>
+ /// <param name="device"></param>
+ /// <param name="online"></param>
+ /// <param name="token"></param>
+ /// <param name="ip"></param>
+ /// <returns></returns>
+ public IOnlineModel SetOnline(IDeviceModel device, Boolean online, String token, String ip)
+ {
+ if (device is Device dv)
+ {
+ // 上线打标记
+ var olt = GetOnline(dv, ip);
+ if (olt != null)
+ {
+ olt.WebSocket = online;
+ olt.Update();
+ }
+
+ return olt;
+ }
+
+ return null;
+ }
+
/// <summary></summary>
/// <param name="device"></param>
/// <param name="ip"></param>
/// <returns></returns>
- protected virtual DeviceOnline GetOnline(Device device, String ip)
+ public virtual DeviceOnline GetOnline(Device device, String ip)
{
var sid = $"{device.Id}@{ip}";
var olt = _cache.Get<DeviceOnline>($"DeviceOnline:{sid}");
@@ -266,7 +292,7 @@ public class MyDeviceService : IDeviceService
/// <param name="device"></param>
/// <param name="ip"></param>
/// <returns></returns>
- protected virtual DeviceOnline CreateOnline(Device device, String ip)
+ public virtual DeviceOnline CreateOnline(Device device, String ip)
{
var sid = $"{device.Id}@{ip}";
var olt = DeviceOnline.GetOrAdd(sid);
@@ -295,6 +321,41 @@ public class MyDeviceService : IDeviceService
}
#endregion
+ #region 升级更新
+ /// <summary>升级检查</summary>
+ /// <param name="channel">更新通道</param>
+ /// <returns></returns>
+ public IUpgradeInfo Upgrade(IDeviceModel device, String channel, String ip)
+ {
+ //return new UpgradeInfo();
+ return null;
+ }
+ #endregion
+
+ #region 下行通知
+ /// <summary>发送命令</summary>
+ /// <param name="device"></param>
+ /// <param name="command"></param>
+ /// <returns></returns>
+ public Task<Int32> SendCommand(IDeviceModel device, CommandModel command, CancellationToken cancellationToken) => _sessionManager.PublishAsync(device.Code, command, null, cancellationToken);
+ #endregion
+
+ #region 事件上报
+ /// <summary>命令响应</summary>
+ /// <param name="device"></param>
+ /// <param name="model"></param>
+ /// <param name="ip"></param>
+ /// <returns></returns>
+ public Int32 CommandReply(IDeviceModel device, CommandReplyModel model, String ip) => 0;
+
+ /// <summary>上报事件</summary>
+ /// <param name="device"></param>
+ /// <param name="events"></param>
+ /// <param name="ip"></param>
+ /// <returns></returns>
+ public Int32 PostEvents(IDeviceModel device, EventModel[] events, String ip) => 0;
+ #endregion
+
#region 辅助
/// <summary>
/// 颁发令牌
@@ -302,7 +363,7 @@ public class MyDeviceService : IDeviceService
/// <param name="name"></param>
/// <param name="set"></param>
/// <returns></returns>
- public TokenModel IssueToken(String name, IoTSetting set)
+ public TokenModel IssueToken(String name, ITokenSetting set)
{
// 颁发令牌
var ss = set.TokenSecret.Split(':');
@@ -359,7 +420,7 @@ public class MyDeviceService : IDeviceService
/// <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;
@@ -378,19 +439,6 @@ public class MyDeviceService : IDeviceService
return null;
}
- /// <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>
diff --git a/IoTZero/Services/ThingService.cs b/IoTZero/Services/ThingService.cs
index 6e333ad..b211b0c 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.Models;
using NewLife.Remoting.Extensions.Services;
using NewLife.Security;
@@ -16,7 +17,7 @@ public class ThingService
private readonly QueueService _queueService;
private readonly IDeviceService _deviceService;
private readonly ICacheProvider _cacheProvider;
- private readonly IoTSetting _setting;
+ private readonly ITokenSetting _setting;
private readonly ITracer _tracer;
static Snowflake _snowflake = new();
@@ -25,13 +26,11 @@ public class ThingService
/// </summary>
/// <param name="dataService"></param>
/// <param name="queueService"></param>
- /// <param name="ruleService"></param>
- /// <param name="segmentService"></param>
/// <param name="deviceService"></param>
/// <param name="cacheProvider"></param>
/// <param name="setting"></param>
/// <param name="tracer"></param>
- public ThingService(DataService dataService, QueueService queueService, IDeviceService deviceService, ICacheProvider cacheProvider, IoTSetting setting, ITracer tracer)
+ public ThingService(DataService dataService, QueueService queueService, IDeviceService deviceService, ICacheProvider cacheProvider, ITokenSetting setting, ITracer tracer)
{
_dataService = dataService;
_queueService = queueService;
@@ -294,6 +293,8 @@ public class ThingService
// 挂起等待。借助redis队列,等待响应
if (timeout > 1000)
{
+ await Task.Delay(1000);
+
throw new NotImplementedException();
}