();
+}
\ No newline at end of file
diff --git a/Samples/IoTZero/Areas/IoT/Views/Device/_List_Search.cshtml b/Samples/IoTZero/Areas/IoT/Views/Device/_List_Search.cshtml
new file mode 100644
index 0000000..eadaed2
--- /dev/null
+++ b/Samples/IoTZero/Areas/IoT/Views/Device/_List_Search.cshtml
@@ -0,0 +1,14 @@
+@using NewLife;
+@using NewLife.Web;
+@using NewLife.Cube;
+@using XCode;
+@using IoT.Data;
+@{
+ var fact = ViewBag.Factory as IEntityFactory;
+ var page = ViewBag.Page as Pager;
+}
+
+
+ @Html.ForDropDownList("productId", Product.FindAllWithCache(), page["productId"], "全部", true)
+
+@await Html.PartialAsync("_DateRange")
\ No newline at end of file
diff --git a/Samples/IoTZero/Areas/IoT/Views/DeviceData/_List_Search.cshtml b/Samples/IoTZero/Areas/IoT/Views/DeviceData/_List_Search.cshtml
new file mode 100644
index 0000000..730699b
--- /dev/null
+++ b/Samples/IoTZero/Areas/IoT/Views/DeviceData/_List_Search.cshtml
@@ -0,0 +1,10 @@
+@using NewLife;
+@using NewLife.Web;
+@using NewLife.Cube;
+@using XCode;
+@using IoT.Data;
+@{
+ var fact = ViewBag.Factory as IEntityFactory;
+ var page = ViewBag.Page as Pager;
+}
+@await Html.PartialAsync("_DateRange")
\ No newline at end of file
diff --git a/Samples/IoTZero/Areas/IoT/Views/DeviceData/_List_Toolbar_Custom.cshtml b/Samples/IoTZero/Areas/IoT/Views/DeviceData/_List_Toolbar_Custom.cshtml
new file mode 100644
index 0000000..5115a96
--- /dev/null
+++ b/Samples/IoTZero/Areas/IoT/Views/DeviceData/_List_Toolbar_Custom.cshtml
@@ -0,0 +1,16 @@
+@using NewLife;
+@using NewLife.Web;
+@using NewLife.Cube;
+@using XCode;
+@using IoT.Data;
+@{
+ var fact = ViewBag.Factory as IEntityFactory;
+ var page = ViewBag.Page as Pager;
+ var url = page.GetBaseUrl(true, true, false, new[] { "sample" });
+}
+
diff --git a/Samples/IoTZero/Areas/IoT/Views/DeviceGroup/_List_Toolbar_Batch.cshtml b/Samples/IoTZero/Areas/IoT/Views/DeviceGroup/_List_Toolbar_Batch.cshtml
new file mode 100644
index 0000000..897a0ec
--- /dev/null
+++ b/Samples/IoTZero/Areas/IoT/Views/DeviceGroup/_List_Toolbar_Batch.cshtml
@@ -0,0 +1,9 @@
+@using NewLife.Common;
+@{
+ var user = ViewBag.User as IUser ?? User.Identity as IUser;
+ var fact = ViewBag.Factory as IEntityFactory;
+ var set = ViewBag.PageSetting as PageSetting;
+}
+
\ No newline at end of file
diff --git a/Samples/IoTZero/Areas/IoT/Views/DeviceHistory/_List_Search.cshtml b/Samples/IoTZero/Areas/IoT/Views/DeviceHistory/_List_Search.cshtml
new file mode 100644
index 0000000..730699b
--- /dev/null
+++ b/Samples/IoTZero/Areas/IoT/Views/DeviceHistory/_List_Search.cshtml
@@ -0,0 +1,10 @@
+@using NewLife;
+@using NewLife.Web;
+@using NewLife.Cube;
+@using XCode;
+@using IoT.Data;
+@{
+ var fact = ViewBag.Factory as IEntityFactory;
+ var page = ViewBag.Page as Pager;
+}
+@await Html.PartialAsync("_DateRange")
\ No newline at end of file
diff --git a/Samples/IoTZero/Areas/IoT/Views/_ViewImports.cshtml b/Samples/IoTZero/Areas/IoT/Views/_ViewImports.cshtml
new file mode 100644
index 0000000..e8fcb7a
--- /dev/null
+++ b/Samples/IoTZero/Areas/IoT/Views/_ViewImports.cshtml
@@ -0,0 +1,9 @@
+@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
+@using NewLife
+@using NewLife.Cube
+@using NewLife.Cube.Extensions
+@using NewLife.Reflection
+@using NewLife.Web
+@using XCode
+@using XCode.Membership
+@using IoT.Data
\ No newline at end of file
diff --git a/Samples/IoTZero/Areas/IoT/Views/_ViewStart.cshtml b/Samples/IoTZero/Areas/IoT/Views/_ViewStart.cshtml
new file mode 100644
index 0000000..ab02818
--- /dev/null
+++ b/Samples/IoTZero/Areas/IoT/Views/_ViewStart.cshtml
@@ -0,0 +1,6 @@
+@{
+ var theme = CubeSetting.Current.Theme;
+ if (String.IsNullOrEmpty(theme)) theme = "ACE";
+
+ Layout = "~/Views/" + theme + "/_Layout.cshtml";
+}
\ No newline at end of file
diff --git a/Samples/IoTZero/Clients/ClientSetting.cs b/Samples/IoTZero/Clients/ClientSetting.cs
new file mode 100644
index 0000000..749f03d
--- /dev/null
+++ b/Samples/IoTZero/Clients/ClientSetting.cs
@@ -0,0 +1,27 @@
+using System.ComponentModel;
+using NewLife.Configuration;
+
+namespace IoTEdge;
+
+/// 配置
+[Config("IoTClient")]
+public class ClientSetting : Config
+{
+ #region 属性
+ /// 服务端地址。IoT服务平台地址
+ [Description("服务端地址。IoT服务平台地址")]
+ public String Server { get; set; } = "http://localhost:1880";
+
+ /// 设备证书。在一机一密时手工填写,一型一密时自动下发
+ [Description("设备证书。在一机一密时手工填写,一型一密时自动下发")]
+ public String DeviceCode { get; set; }
+
+ /// 设备密钥。在一机一密时手工填写,一型一密时自动下发
+ [Description("设备密钥。在一机一密时手工填写,一型一密时自动下发")]
+ public String DeviceSecret { get; set; }
+
+ /// 产品证书。用于一型一密验证,对一机一密无效
+ [Description("产品证书。用于一型一密验证,对一机一密无效")]
+ public String ProductKey { get; set; } = "EdgeGateway";
+ #endregion
+}
\ No newline at end of file
diff --git a/Samples/IoTZero/Clients/ClientTest.cs b/Samples/IoTZero/Clients/ClientTest.cs
index 5143895..e94d63b 100644
--- a/Samples/IoTZero/Clients/ClientTest.cs
+++ b/Samples/IoTZero/Clients/ClientTest.cs
@@ -26,7 +26,7 @@ public static async Task Main(IServiceProvider serviceProvider)
Log = XTrace.Log,
};
- await device.LoginAsync();
+ await device.Login();
_device = device;
}
diff --git a/Samples/IoTZero/Clients/HttpDevice.cs b/Samples/IoTZero/Clients/HttpDevice.cs
new file mode 100644
index 0000000..bdb25f9
--- /dev/null
+++ b/Samples/IoTZero/Clients/HttpDevice.cs
@@ -0,0 +1,116 @@
+using NewLife;
+using NewLife.IoT.Models;
+using NewLife.IoT.ThingModels;
+using NewLife.Log;
+using NewLife.Remoting.Clients;
+using NewLife.Remoting.Models;
+using NewLife.Security;
+using LoginResponse = NewLife.Remoting.Models.LoginResponse;
+
+namespace IoTEdge;
+
+/// Http协议设备
+public class HttpDevice : HttpClientBase
+{
+ #region 属性
+ /// 产品编码。从IoT管理平台获取
+ public String ProductKey { get; set; }
+
+ private readonly ClientSetting _setting;
+ #endregion
+
+ #region 构造
+ public HttpDevice() => Prefix = "Device/";
+
+ public HttpDevice(ClientSetting setting) : base(setting.Server)
+ {
+ Prefix = "Device/";
+
+ _setting = setting;
+
+ ProductKey = setting.ProductKey;
+ }
+ #endregion
+
+ #region 登录注销
+ public override LoginRequest BuildLoginRequest()
+ {
+ var request = base.BuildLoginRequest();
+
+ return new LoginInfo
+ {
+ Code = request.Code,
+ Secret = request.Secret,
+ Version = request.Version,
+ ClientId = request.ClientId,
+
+ ProductKey = ProductKey,
+ //ProductSecret = _setting.DeviceSecret,
+ };
+ }
+
+ public override async Task Login()
+ {
+ var rs = await base.Login();
+
+ if (Logined && !rs.Secret.IsNullOrEmpty())
+ {
+ _setting.DeviceCode = rs.Code;
+ _setting.DeviceSecret = rs.Secret;
+ _setting.Save();
+ }
+
+ return rs;
+ }
+ #endregion
+
+ #region 心跳
+ public override PingRequest BuildPingRequest()
+ {
+ var request = base.BuildPingRequest();
+
+ return new PingInfo
+ {
+ };
+ }
+
+ public override Task