NewLife/SmartA4

A4工业计算机增加一系列例程代码
大石头 authored at 2023-10-28 23:33:28
c9fdc08
Tree
1 Parent(s) cf76e1d
Summary: 31 changed files with 1494 additions and 0 deletions.
Modified +1 -0
Added +19 -0
Added +16 -0
Added +16 -0
Added +23 -0
Added +66 -0
Added +196 -0
Added +0 -0
Added +228 -0
Added +277 -0
Added +29 -0
Added +15 -0
Added +19 -0
Added +16 -0
Added +19 -0
Added +16 -0
Added +16 -0
Added +23 -0
Added +34 -0
Added +23 -0
Added +39 -0
Added +62 -0
Added +27 -0
Added +57 -0
Added +23 -0
Added +60 -0
Added +27 -0
Added +16 -0
Added +14 -0
Added +19 -0
Modified +98 -0
Modified +1 -0
diff --git a/.gitignore b/.gitignore
index 8b53321..2f08f4c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,4 @@ bld/
 *.nupkg
 /BinTest
 /BinUnitTest
+/Samples/DatabaseTest/Entity/Config
Added +19 -0
diff --git a/Samples/BuzzerTest/BuzzerTest.csproj b/Samples/BuzzerTest/BuzzerTest.csproj
new file mode 100644
index 0000000..62c0a75
--- /dev/null
+++ b/Samples/BuzzerTest/BuzzerTest.csproj
@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net7.0</TargetFramework>
+    <Company>新生命开发团队</Company>
+    <Copyright>©2002-2023 新生命开发团队</Copyright>
+    <VersionPrefix>1.0</VersionPrefix>
+    <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
+    <Version>$(VersionPrefix).$(VersionSuffix)</Version>
+    <FileVersion>$(Version)</FileVersion>
+    <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
+    <Deterministic>false</Deterministic>
+    <OutputPath>..\..\Bin\BuzzerTest</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+</Project>
Added +16 -0
diff --git a/Samples/BuzzerTest/OutputPort.cs b/Samples/BuzzerTest/OutputPort.cs
new file mode 100644
index 0000000..2487a24
--- /dev/null
+++ b/Samples/BuzzerTest/OutputPort.cs
@@ -0,0 +1,16 @@
+namespace SmartA4;
+
+/// <summary>输出口</summary>
+public class OutputPort
+{
+    /// <summary>文件路径</summary>
+    public String FileName { get; set; }
+
+    /// <summary>读取开关值</summary>
+    /// <returns></returns>
+    public Boolean Read() => File.ReadAllText(FileName)?.Trim() == "1";
+
+    /// <summary>写入开关值</summary>
+    /// <param name="value"></param>
+    public void Write(Boolean value) => File.WriteAllText(FileName, value ? "1" : "0");
+}
\ No newline at end of file
Added +16 -0
diff --git a/Samples/BuzzerTest/Program.cs b/Samples/BuzzerTest/Program.cs
new file mode 100644
index 0000000..32cc6b2
--- /dev/null
+++ b/Samples/BuzzerTest/Program.cs
@@ -0,0 +1,16 @@
+using SmartA4;
+
+var buzzer = new OutputPort { FileName = "/dev/buzzer" };
+
+for (var i = 0; i < 5; i++)
+{
+    // 响
+    buzzer.Write(true);
+
+    Thread.Sleep(500);
+
+    // 不响
+    buzzer.Write(false);
+
+    Thread.Sleep(500);
+}
\ No newline at end of file
Added +23 -0
diff --git a/Samples/DatabaseTest/DatabaseTest.csproj b/Samples/DatabaseTest/DatabaseTest.csproj
new file mode 100644
index 0000000..62bcd22
--- /dev/null
+++ b/Samples/DatabaseTest/DatabaseTest.csproj
@@ -0,0 +1,23 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net7.0</TargetFramework>
+    <Company>新生命开发团队</Company>
+    <Copyright>©2002-2023 新生命开发团队</Copyright>
+    <VersionPrefix>1.0</VersionPrefix>
+    <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
+    <Version>$(VersionPrefix).$(VersionSuffix)</Version>
+    <FileVersion>$(Version)</FileVersion>
+    <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
+    <Deterministic>false</Deterministic>
+    <OutputPath>..\..\Bin\DatabaseTest</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="NewLife.XCode" Version="11.10.2023.1012" />
+  </ItemGroup>
+
+</Project>
Added +66 -0
diff --git a/Samples/DatabaseTest/Entity/Model.xml b/Samples/DatabaseTest/Entity/Model.xml
new file mode 100644
index 0000000..a59995a
--- /dev/null
+++ b/Samples/DatabaseTest/Entity/Model.xml
@@ -0,0 +1,66 @@
+<?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/Model202309.xsd" Document="https://newlifex.com/xcode/model" xmlns="https://newlifex.com/Model202309.xsd">
+  <Option>
+    <!--类名模板。其中{name}替换为Table.Name,如{name}Model/I{name}Dto等-->
+    <ClassNameTemplate />
+    <!--显示名模板。其中{displayName}替换为Table.DisplayName-->
+    <DisplayNameTemplate />
+    <!--基类。可能包含基类和接口,其中{name}替换为Table.Name-->
+    <BaseClass>Entity</BaseClass>
+    <!--命名空间-->
+    <Namespace>DatabaseTest.Data</Namespace>
+    <!--输出目录-->
+    <Output>.\</Output>
+    <!--是否使用中文文件名。默认false-->
+    <ChineseFileName>True</ChineseFileName>
+    <!--用于生成Copy函数的参数类型。例如{name}或I{name}-->
+    <ModelNameForCopy />
+    <!--带有索引器。实现IModel接口-->
+    <HasIModel>True</HasIModel>
+    <!--可为null上下文。生成String?等-->
+    <Nullable>True</Nullable>
+    <!--数据库连接名-->
+    <ConnName>Test</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>
+    <!--魔方区域显示名-->
+    <DisplayName />
+    <!--魔方控制器输出目录-->
+    <CubeOutput />
+  </Option>
+  <Tables>
+    <Table Name="Member" Description="用户。用户帐号信息,以身份验证为中心,拥有多种角色,可加入多个租户">
+      <Columns>
+        <Column Name="Id" DataType="Int32" Identity="True" PrimaryKey="True" Description="编号" />
+        <Column Name="Name" DataType="String" Master="True" Nullable="False" Description="名称。登录用户名" />
+        <Column Name="Password" DataType="String" Length="200" Description="密码" />
+        <Column Name="DisplayName" DataType="String" Description="昵称" />
+        <Column Name="Sex" DataType="Int32" Description="性别。未知、男、女" Type="XCode.Membership.SexKinds" />
+        <Column Name="Mail" DataType="String" ItemType="mail" Description="邮件。支持登录" />
+        <Column Name="Mobile" DataType="String" ItemType="mobile" Description="手机。支持登录" />
+        <Column Name="Enable" DataType="Boolean" Description="启用" Category="登录信息" />
+        <Column Name="UpdateUser" DataType="String" Nullable="False" DefaultValue="''" Description="更新者" Model="False" Category="扩展" />
+        <Column Name="UpdateUserID" DataType="Int32" Description="更新用户" Model="False" Category="扩展" />
+        <Column Name="UpdateIP" DataType="String" Description="更新地址" Model="False" Category="扩展" />
+        <Column Name="UpdateTime" DataType="DateTime" Nullable="False" Description="更新时间" Model="False" Category="扩展" />
+        <Column Name="Remark" DataType="String" Length="500" Description="备注" Category="扩展" />
+      </Columns>
+      <Indexes>
+        <Index Columns="Name" Unique="True" />
+        <Index Columns="Mail" />
+        <Index Columns="Mobile" />
+        <Index Columns="UpdateTime" />
+      </Indexes>
+    </Table>
+  </Tables>
+</EntityModel>
\ No newline at end of file
Added +196 -0
diff --git a/Samples/DatabaseTest/Entity/Test.htm b/Samples/DatabaseTest/Entity/Test.htm
new file mode 100644
index 0000000..fa11111
--- /dev/null
+++ b/Samples/DatabaseTest/Entity/Test.htm
@@ -0,0 +1,196 @@
+<style>
+    table {
+        border-collapse: collapse;
+        border: 1px solid;
+        border-color: rgb(211, 202, 221);
+    }
+
+    table thead,
+    table tr {
+        border-top-width: 1px;
+        border-top-style: solid;
+        border-top-color: rgb(211, 202, 221);
+    }
+
+    table {
+        border-bottom-width: 1px;
+        border-bottom-style: solid;
+        border-bottom-color: rgb(211, 202, 221);
+    }
+
+    table td,
+    table th {
+        padding: 5px 10px;
+        font-size: 14px;
+        font-family: Verdana;
+        color: rgb(95, 74, 121);
+    }
+
+    table tr:nth-child(even) {
+        background: rgb(223, 216, 232)
+    }
+
+    table tr:nth-child(odd) {
+        background: #FFF
+    }
+</style>
+<h3>用户(Member)</h3>
+<table>
+    <thead>
+        <tr>
+            <th>名称</th>
+            <th>显示名</th>
+            <th>类型</th>
+            <th>长度</th>
+            <th>精度</th>
+            <th>主键</th>
+            <th>允许空</th>
+            <th>备注</th>
+        </tr>
+    </thead>
+    <tbody>
+        <tr>
+            <td>Id</td>
+            <td>编号</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td title="自增">AI</td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Name</td>
+            <td>名称</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td title="唯一索引">UQ</td>
+            <td>N</td>
+            <td>登录用户名</td>
+        </tr>
+
+        <tr>
+            <td>Password</td>
+            <td>密码</td>
+            <td>String</td>
+            <td>200</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>DisplayName</td>
+            <td>昵称</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Sex</td>
+            <td>性别</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td>未知、男、女</td>
+        </tr>
+
+        <tr>
+            <td>Mail</td>
+            <td>邮件</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>支持登录</td>
+        </tr>
+
+        <tr>
+            <td>Mobile</td>
+            <td>手机</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>支持登录</td>
+        </tr>
+
+        <tr>
+            <td>Enable</td>
+            <td>启用</td>
+            <td>Boolean</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>UpdateUser</td>
+            <td>更新者</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>UpdateUserID</td>
+            <td>更新用户</td>
+            <td>Int32</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>UpdateIP</td>
+            <td>更新地址</td>
+            <td>String</td>
+            <td>50</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>UpdateTime</td>
+            <td>更新时间</td>
+            <td>DateTime</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td>N</td>
+            <td></td>
+        </tr>
+
+        <tr>
+            <td>Remark</td>
+            <td>备注</td>
+            <td>String</td>
+            <td>500</td>
+            <td></td>
+            <td></td>
+            <td></td>
+            <td></td>
+        </tr>
+    </tbody>
+</table>
+<br></br>
Added +0 -0
diff --git a/Samples/DatabaseTest/Entity/xcodetool.exe b/Samples/DatabaseTest/Entity/xcodetool.exe
new file mode 100644
index 0000000..1604a23
Binary files /dev/null and b/Samples/DatabaseTest/Entity/xcodetool.exe differ
Added +228 -0
diff --git "a/Samples/DatabaseTest/Entity/\347\224\250\346\210\267.Biz.cs" "b/Samples/DatabaseTest/Entity/\347\224\250\346\210\267.Biz.cs"
new file mode 100644
index 0000000..3bade3a
--- /dev/null
+++ "b/Samples/DatabaseTest/Entity/\347\224\250\346\210\267.Biz.cs"
@@ -0,0 +1,228 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+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.Web.Script.Serialization;
+using System.Xml.Serialization;
+using NewLife;
+using NewLife.Data;
+using NewLife.Log;
+using NewLife.Model;
+using NewLife.Reflection;
+using NewLife.Threading;
+using NewLife.Web;
+using XCode;
+using XCode.Cache;
+using XCode.Configuration;
+using XCode.DataAccessLayer;
+using XCode.Membership;
+using XCode.Shards;
+
+namespace DatabaseTest.Data;
+
+public partial class Member : Entity<Member>
+{
+    #region 对象操作
+    static Member()
+    {
+        // 累加字段,生成 Update xx Set Count=Count+1234 Where xxx
+        //var df = Meta.Factory.AdditionalFields;
+        //df.Add(nameof(Sex));
+
+        // 过滤器 UserModule、TimeModule、IPModule
+        Meta.Modules.Add<UserModule>();
+        Meta.Modules.Add<TimeModule>();
+        Meta.Modules.Add<IPModule>();
+
+        // 单对象缓存
+        var sc = Meta.SingleCache;
+        sc.FindSlaveKeyMethod = k => Find(_.Name == k);
+        sc.GetSlaveKeyMethod = e => e.Name;
+    }
+
+    /// <summary>验证并修补数据,通过抛出异常的方式提示验证失败。</summary>
+    /// <param name="isNew">是否插入</param>
+    public override void Valid(Boolean isNew)
+    {
+        // 如果没有脏数据,则不需要进行任何处理
+        if (!HasDirty) return;
+
+        // 这里验证参数范围,建议抛出参数异常,指定参数名,前端用户界面可以捕获参数异常并聚焦到对应的参数输入框
+        if (Name.IsNullOrEmpty()) throw new ArgumentNullException(nameof(Name), "名称不能为空!");
+
+        // 建议先调用基类方法,基类方法会做一些统一处理
+        base.Valid(isNew);
+
+        // 在新插入数据或者修改了指定字段时进行修正
+        // 处理当前已登录用户信息,可以由UserModule过滤器代劳
+        /*var user = ManageProvider.User;
+        if (user != null)
+        {
+            if (!Dirtys[nameof(UpdateUserID)]) UpdateUserID = user.ID;
+        }*/
+        //if (!Dirtys[nameof(UpdateTime)]) UpdateTime = DateTime.Now;
+        //if (!Dirtys[nameof(UpdateIP)]) UpdateIP = ManageProvider.UserHost;
+
+        // 检查唯一索引
+        // CheckExist(isNew, nameof(Name));
+    }
+
+    ///// <summary>首次连接数据库时初始化数据,仅用于实体类重载,用户不应该调用该方法</summary>
+    //[EditorBrowsable(EditorBrowsableState.Never)]
+    //protected override void InitData()
+    //{
+    //    // InitData一般用于当数据表没有数据时添加一些默认数据,该实体类的任何第一次数据库操作都会触发该方法,默认异步调用
+    //    if (Meta.Session.Count > 0) return;
+
+    //    if (XTrace.Debug) XTrace.WriteLine("开始初始化Member[用户]数据……");
+
+    //    var entity = new Member();
+    //    entity.Name = "abc";
+    //    entity.Password = "abc";
+    //    entity.DisplayName = "abc";
+    //    entity.Sex = 0;
+    //    entity.Mail = "abc";
+    //    entity.Mobile = "abc";
+    //    entity.Enable = true;
+    //    entity.UpdateUser = "abc";
+    //    entity.UpdateUserID = 0;
+    //    entity.UpdateIP = "abc";
+    //    entity.UpdateTime = DateTime.Now;
+    //    entity.Remark = "abc";
+    //    entity.Insert();
+
+    //    if (XTrace.Debug) XTrace.WriteLine("完成初始化Member[用户]数据!");
+    //}
+
+    ///// <summary>已重载。基类先调用Valid(true)验证数据,然后在事务保护内调用OnInsert</summary>
+    ///// <returns></returns>
+    //public override Int32 Insert()
+    //{
+    //    return base.Insert();
+    //}
+
+    ///// <summary>已重载。在事务保护范围内处理业务,位于Valid之后</summary>
+    ///// <returns></returns>
+    //protected override Int32 OnDelete()
+    //{
+    //    return base.OnDelete();
+    //}
+    #endregion
+
+    #region 扩展属性
+    #endregion
+
+    #region 扩展查询
+    /// <summary>根据编号查找</summary>
+    /// <param name="id">编号</param>
+    /// <returns>实体对象</returns>
+    public static Member FindById(Int32 id)
+    {
+        if (id <= 0) return null;
+
+        //// 实体缓存
+        //if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.Id == id);
+
+        //// 单对象缓存
+        //return Meta.SingleCache[id];
+
+        return Find(_.Id == id);
+    }
+
+    /// <summary>根据名称查找</summary>
+    /// <param name="name">名称</param>
+    /// <returns>实体对象</returns>
+    public static Member FindByName(String name)
+    {
+        if (name.IsNullOrEmpty()) return null;
+
+        //// 实体缓存
+        //if (Meta.Session.Count < 1000) return Meta.Cache.Find(e => e.Name.EqualIgnoreCase(name));
+
+        // 单对象缓存
+        //return Meta.SingleCache.GetItemWithSlaveKey(name) as Member;
+
+        return Find(_.Name == name);
+    }
+
+    /// <summary>根据邮件查找</summary>
+    /// <param name="mail">邮件</param>
+    /// <returns>实体列表</returns>
+    public static IList<Member> FindAllByMail(String mail)
+    {
+        if (mail.IsNullOrEmpty()) return new List<Member>();
+
+        // 实体缓存
+        if (Meta.Session.Count < 1000) return Meta.Cache.FindAll(e => e.Mail.EqualIgnoreCase(mail));
+
+        return FindAll(_.Mail == mail);
+    }
+
+    /// <summary>根据手机查找</summary>
+    /// <param name="mobile">手机</param>
+    /// <returns>实体列表</returns>
+    public static IList<Member> FindAllByMobile(String mobile)
+    {
+        if (mobile.IsNullOrEmpty()) return new List<Member>();
+
+        // 实体缓存
+        if (Meta.Session.Count < 1000) return Meta.Cache.FindAll(e => e.Mobile.EqualIgnoreCase(mobile));
+
+        return FindAll(_.Mobile == mobile);
+    }
+    #endregion
+
+    #region 高级查询
+    /// <summary>高级查询</summary>
+    /// <param name="name">名称。登录用户名</param>
+    /// <param name="mail">邮件。支持登录</param>
+    /// <param name="mobile">手机。支持登录</param>
+    /// <param name="start">更新时间开始</param>
+    /// <param name="end">更新时间结束</param>
+    /// <param name="key">关键字</param>
+    /// <param name="page">分页参数信息。可携带统计和数据权限扩展查询等信息</param>
+    /// <returns>实体列表</returns>
+    public static IList<Member> Search(String name, String mail, String mobile, DateTime start, DateTime end, String key, PageParameter page)
+    {
+        var exp = new WhereExpression();
+
+        if (!name.IsNullOrEmpty()) exp &= _.Name == name;
+        if (!mail.IsNullOrEmpty()) exp &= _.Mail == mail;
+        if (!mobile.IsNullOrEmpty()) exp &= _.Mobile == mobile;
+        exp &= _.UpdateTime.Between(start, end);
+        if (!key.IsNullOrEmpty()) exp &= _.Name.Contains(key) | _.Password.Contains(key) | _.DisplayName.Contains(key) | _.Mail.Contains(key) | _.Mobile.Contains(key) | _.UpdateUser.Contains(key) | _.UpdateIP.Contains(key) | _.Remark.Contains(key);
+
+        return FindAll(exp, page);
+    }
+
+    // Select Count(Id) as Id,Mail From Member Where CreateTime>'2020-01-24 00:00:00' Group By Mail Order By Id Desc limit 20
+    static readonly FieldCache<Member> _MailCache = new FieldCache<Member>(nameof(Mail))
+    {
+        //Where = _.CreateTime > DateTime.Today.AddDays(-30) & Expression.Empty
+    };
+
+    /// <summary>获取邮件列表,字段缓存10分钟,分组统计数据最多的前20种,用于魔方前台下拉选择</summary>
+    /// <returns></returns>
+    public static IDictionary<String, String> GetMailList() => _MailCache.FindAllName();
+
+    // Select Count(Id) as Id,Mobile From Member Where CreateTime>'2020-01-24 00:00:00' Group By Mobile Order By Id Desc limit 20
+    static readonly FieldCache<Member> _MobileCache = new FieldCache<Member>(nameof(Mobile))
+    {
+        //Where = _.CreateTime > DateTime.Today.AddDays(-30) & Expression.Empty
+    };
+
+    /// <summary>获取手机列表,字段缓存10分钟,分组统计数据最多的前20种,用于魔方前台下拉选择</summary>
+    /// <returns></returns>
+    public static IDictionary<String, String> GetMobileList() => _MobileCache.FindAllName();
+    #endregion
+
+    #region 业务操作
+    #endregion
+}
Added +277 -0
diff --git "a/Samples/DatabaseTest/Entity/\347\224\250\346\210\267.cs" "b/Samples/DatabaseTest/Entity/\347\224\250\346\210\267.cs"
new file mode 100644
index 0000000..5c9fa74
--- /dev/null
+++ "b/Samples/DatabaseTest/Entity/\347\224\250\346\210\267.cs"
@@ -0,0 +1,277 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Runtime.Serialization;
+using System.Web.Script.Serialization;
+using System.Xml.Serialization;
+using NewLife;
+using NewLife.Data;
+using XCode;
+using XCode.Cache;
+using XCode.Configuration;
+using XCode.DataAccessLayer;
+
+namespace DatabaseTest.Data;
+
+/// <summary>用户。用户帐号信息,以身份验证为中心,拥有多种角色,可加入多个租户</summary>
+[Serializable]
+[DataObject]
+[Description("用户。用户帐号信息,以身份验证为中心,拥有多种角色,可加入多个租户")]
+[BindIndex("IU_Member_Name", true, "Name")]
+[BindIndex("IX_Member_Mail", false, "Mail")]
+[BindIndex("IX_Member_Mobile", false, "Mobile")]
+[BindIndex("IX_Member_UpdateTime", false, "UpdateTime")]
+[BindTable("Member", Description = "用户。用户帐号信息,以身份验证为中心,拥有多种角色,可加入多个租户", ConnName = "Test", DbType = DatabaseType.None)]
+public partial class Member
+{
+    #region 属性
+    private Int32 _Id;
+    /// <summary>编号</summary>
+    [DisplayName("编号")]
+    [Description("编号")]
+    [DataObjectField(true, true, false, 0)]
+    [BindColumn("Id", "编号", "")]
+    public Int32 Id { get => _Id; set { if (OnPropertyChanging("Id", value)) { _Id = value; OnPropertyChanged("Id"); } } }
+
+    private String _Name = null!;
+    /// <summary>名称。登录用户名</summary>
+    [DisplayName("名称")]
+    [Description("名称。登录用户名")]
+    [DataObjectField(false, false, false, 50)]
+    [BindColumn("Name", "名称。登录用户名", "", Master = true)]
+    public String Name { get => _Name; set { if (OnPropertyChanging("Name", value)) { _Name = value; OnPropertyChanged("Name"); } } }
+
+    private String? _Password;
+    /// <summary>密码</summary>
+    [DisplayName("密码")]
+    [Description("密码")]
+    [DataObjectField(false, false, true, 200)]
+    [BindColumn("Password", "密码", "")]
+    public String? Password { get => _Password; set { if (OnPropertyChanging("Password", value)) { _Password = value; OnPropertyChanged("Password"); } } }
+
+    private String? _DisplayName;
+    /// <summary>昵称</summary>
+    [DisplayName("昵称")]
+    [Description("昵称")]
+    [DataObjectField(false, false, true, 50)]
+    [BindColumn("DisplayName", "昵称", "")]
+    public String? DisplayName { get => _DisplayName; set { if (OnPropertyChanging("DisplayName", value)) { _DisplayName = value; OnPropertyChanged("DisplayName"); } } }
+
+    private XCode.Membership.SexKinds _Sex;
+    /// <summary>性别。未知、男、女</summary>
+    [DisplayName("性别")]
+    [Description("性别。未知、男、女")]
+    [DataObjectField(false, false, false, 0)]
+    [BindColumn("Sex", "性别。未知、男、女", "")]
+    public XCode.Membership.SexKinds Sex { get => _Sex; set { if (OnPropertyChanging("Sex", value)) { _Sex = value; OnPropertyChanged("Sex"); } } }
+
+    private String? _Mail;
+    /// <summary>邮件。支持登录</summary>
+    [DisplayName("邮件")]
+    [Description("邮件。支持登录")]
+    [DataObjectField(false, false, true, 50)]
+    [BindColumn("Mail", "邮件。支持登录", "", ItemType = "mail")]
+    public String? Mail { get => _Mail; set { if (OnPropertyChanging("Mail", value)) { _Mail = value; OnPropertyChanged("Mail"); } } }
+
+    private String? _Mobile;
+    /// <summary>手机。支持登录</summary>
+    [DisplayName("手机")]
+    [Description("手机。支持登录")]
+    [DataObjectField(false, false, true, 50)]
+    [BindColumn("Mobile", "手机。支持登录", "", ItemType = "mobile")]
+    public String? Mobile { get => _Mobile; set { if (OnPropertyChanging("Mobile", value)) { _Mobile = value; OnPropertyChanged("Mobile"); } } }
+
+    private Boolean _Enable;
+    /// <summary>启用</summary>
+    [Category("登录信息")]
+    [DisplayName("启用")]
+    [Description("启用")]
+    [DataObjectField(false, false, false, 0)]
+    [BindColumn("Enable", "启用", "")]
+    public Boolean Enable { get => _Enable; set { if (OnPropertyChanging("Enable", value)) { _Enable = value; OnPropertyChanged("Enable"); } } }
+
+    private String _UpdateUser = null!;
+    /// <summary>更新者</summary>
+    [Category("扩展")]
+    [DisplayName("更新者")]
+    [Description("更新者")]
+    [DataObjectField(false, false, false, 50)]
+    [BindColumn("UpdateUser", "更新者", "", DefaultValue = "''")]
+    public String UpdateUser { get => _UpdateUser; set { if (OnPropertyChanging("UpdateUser", value)) { _UpdateUser = value; OnPropertyChanged("UpdateUser"); } } }
+
+    private Int32 _UpdateUserID;
+    /// <summary>更新用户</summary>
+    [Category("扩展")]
+    [DisplayName("更新用户")]
+    [Description("更新用户")]
+    [DataObjectField(false, false, false, 0)]
+    [BindColumn("UpdateUserID", "更新用户", "")]
+    public Int32 UpdateUserID { get => _UpdateUserID; set { if (OnPropertyChanging("UpdateUserID", value)) { _UpdateUserID = value; OnPropertyChanged("UpdateUserID"); } } }
+
+    private String? _UpdateIP;
+    /// <summary>更新地址</summary>
+    [Category("扩展")]
+    [DisplayName("更新地址")]
+    [Description("更新地址")]
+    [DataObjectField(false, false, true, 50)]
+    [BindColumn("UpdateIP", "更新地址", "")]
+    public String? UpdateIP { get => _UpdateIP; set { if (OnPropertyChanging("UpdateIP", value)) { _UpdateIP = value; OnPropertyChanged("UpdateIP"); } } }
+
+    private DateTime _UpdateTime;
+    /// <summary>更新时间</summary>
+    [Category("扩展")]
+    [DisplayName("更新时间")]
+    [Description("更新时间")]
+    [DataObjectField(false, false, false, 0)]
+    [BindColumn("UpdateTime", "更新时间", "")]
+    public DateTime UpdateTime { get => _UpdateTime; set { if (OnPropertyChanging("UpdateTime", value)) { _UpdateTime = value; OnPropertyChanged("UpdateTime"); } } }
+
+    private String? _Remark;
+    /// <summary>备注</summary>
+    [Category("扩展")]
+    [DisplayName("备注")]
+    [Description("备注")]
+    [DataObjectField(false, false, true, 500)]
+    [BindColumn("Remark", "备注", "")]
+    public String? Remark { get => _Remark; set { if (OnPropertyChanging("Remark", value)) { _Remark = value; OnPropertyChanged("Remark"); } } }
+    #endregion
+
+    #region 获取/设置 字段值
+    /// <summary>获取/设置 字段值</summary>
+    /// <param name="name">字段名</param>
+    /// <returns></returns>
+    public override Object? this[String name]
+    {
+        get => name switch
+        {
+            "Id" => _Id,
+            "Name" => _Name,
+            "Password" => _Password,
+            "DisplayName" => _DisplayName,
+            "Sex" => _Sex,
+            "Mail" => _Mail,
+            "Mobile" => _Mobile,
+            "Enable" => _Enable,
+            "UpdateUser" => _UpdateUser,
+            "UpdateUserID" => _UpdateUserID,
+            "UpdateIP" => _UpdateIP,
+            "UpdateTime" => _UpdateTime,
+            "Remark" => _Remark,
+            _ => base[name]
+        };
+        set
+        {
+            switch (name)
+            {
+                case "Id": _Id = value.ToInt(); break;
+                case "Name": _Name = Convert.ToString(value); break;
+                case "Password": _Password = Convert.ToString(value); break;
+                case "DisplayName": _DisplayName = Convert.ToString(value); break;
+                case "Sex": _Sex = (XCode.Membership.SexKinds)value.ToInt(); break;
+                case "Mail": _Mail = Convert.ToString(value); break;
+                case "Mobile": _Mobile = Convert.ToString(value); break;
+                case "Enable": _Enable = value.ToBoolean(); break;
+                case "UpdateUser": _UpdateUser = Convert.ToString(value); break;
+                case "UpdateUserID": _UpdateUserID = value.ToInt(); break;
+                case "UpdateIP": _UpdateIP = Convert.ToString(value); break;
+                case "UpdateTime": _UpdateTime = value.ToDateTime(); break;
+                case "Remark": _Remark = Convert.ToString(value); break;
+                default: base[name] = value; break;
+            }
+        }
+    }
+    #endregion
+
+    #region 关联映射
+    #endregion
+
+    #region 字段名
+    /// <summary>取得用户字段信息的快捷方式</summary>
+    public partial class _
+    {
+        /// <summary>编号</summary>
+        public static readonly Field Id = FindByName("Id");
+
+        /// <summary>名称。登录用户名</summary>
+        public static readonly Field Name = FindByName("Name");
+
+        /// <summary>密码</summary>
+        public static readonly Field Password = FindByName("Password");
+
+        /// <summary>昵称</summary>
+        public static readonly Field DisplayName = FindByName("DisplayName");
+
+        /// <summary>性别。未知、男、女</summary>
+        public static readonly Field Sex = FindByName("Sex");
+
+        /// <summary>邮件。支持登录</summary>
+        public static readonly Field Mail = FindByName("Mail");
+
+        /// <summary>手机。支持登录</summary>
+        public static readonly Field Mobile = FindByName("Mobile");
+
+        /// <summary>启用</summary>
+        public static readonly Field Enable = FindByName("Enable");
+
+        /// <summary>更新者</summary>
+        public static readonly Field UpdateUser = FindByName("UpdateUser");
+
+        /// <summary>更新用户</summary>
+        public static readonly Field UpdateUserID = FindByName("UpdateUserID");
+
+        /// <summary>更新地址</summary>
+        public static readonly Field UpdateIP = FindByName("UpdateIP");
+
+        /// <summary>更新时间</summary>
+        public static readonly Field UpdateTime = FindByName("UpdateTime");
+
+        /// <summary>备注</summary>
+        public static readonly Field Remark = FindByName("Remark");
+
+        static Field FindByName(String name) => Meta.Table.FindByName(name);
+    }
+
+    /// <summary>取得用户字段名称的快捷方式</summary>
+    public partial class __
+    {
+        /// <summary>编号</summary>
+        public const String Id = "Id";
+
+        /// <summary>名称。登录用户名</summary>
+        public const String Name = "Name";
+
+        /// <summary>密码</summary>
+        public const String Password = "Password";
+
+        /// <summary>昵称</summary>
+        public const String DisplayName = "DisplayName";
+
+        /// <summary>性别。未知、男、女</summary>
+        public const String Sex = "Sex";
+
+        /// <summary>邮件。支持登录</summary>
+        public const String Mail = "Mail";
+
+        /// <summary>手机。支持登录</summary>
+        public const String Mobile = "Mobile";
+
+        /// <summary>启用</summary>
+        public const String Enable = "Enable";
+
+        /// <summary>更新者</summary>
+        public const String UpdateUser = "UpdateUser";
+
+        /// <summary>更新用户</summary>
+        public const String UpdateUserID = "UpdateUserID";
+
+        /// <summary>更新地址</summary>
+        public const String UpdateIP = "UpdateIP";
+
+        /// <summary>更新时间</summary>
+        public const String UpdateTime = "UpdateTime";
+
+        /// <summary>备注</summary>
+        public const String Remark = "Remark";
+    }
+    #endregion
+}
Added +29 -0
diff --git a/Samples/DatabaseTest/Program.cs b/Samples/DatabaseTest/Program.cs
new file mode 100644
index 0000000..ae6acf8
--- /dev/null
+++ b/Samples/DatabaseTest/Program.cs
@@ -0,0 +1,29 @@
+using DatabaseTest.Data;
+using NewLife.Log;
+using NewLife.Serialization;
+
+XTrace.UseConsole();
+
+// 新增用户插入数据库
+var user = new Member
+{
+    Name = "Stone",
+    DisplayName = "大石头",
+    Password = "123456",
+    Enable = true
+};
+user.Insert();
+
+// 查询用户
+var user2 = Member.FindByName("Stone");
+XTrace.WriteLine("FindByName: {0}", user2.ToJson(true));
+
+// 更新用户
+user2.DisplayName = "智能大石头";
+user2.Update();
+
+var user3 = Member.FindById(user2.Id);
+XTrace.WriteLine("FindByID: {0}", user3.DisplayName);
+
+// 删除用户
+user3.Delete();
\ No newline at end of file
Added +15 -0
diff --git a/Samples/KeyTest/InputPort.cs b/Samples/KeyTest/InputPort.cs
new file mode 100644
index 0000000..558076b
--- /dev/null
+++ b/Samples/KeyTest/InputPort.cs
@@ -0,0 +1,15 @@
+namespace SmartA4;
+
+/// <summary>输入口</summary>
+public class InputPort
+{
+    /// <summary>文件路径</summary>
+    public String FileName { get; set; }
+
+    FileStream _stream;
+    Stream GetStream() => _stream ??= File.OpenRead(FileName);
+
+    /// <summary>读取开关值</summary>
+    /// <returns></returns>
+    public Boolean Read() => GetStream().ReadByte() == '1';
+}
\ No newline at end of file
Added +19 -0
diff --git a/Samples/KeyTest/KeyTest.csproj b/Samples/KeyTest/KeyTest.csproj
new file mode 100644
index 0000000..fd3f49b
--- /dev/null
+++ b/Samples/KeyTest/KeyTest.csproj
@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net7.0</TargetFramework>
+    <Company>新生命开发团队</Company>
+    <Copyright>©2002-2023 新生命开发团队</Copyright>
+    <VersionPrefix>1.0</VersionPrefix>
+    <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
+    <Version>$(VersionPrefix).$(VersionSuffix)</Version>
+    <FileVersion>$(Version)</FileVersion>
+    <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
+    <Deterministic>false</Deterministic>
+    <OutputPath>..\..\Bin\KeyTest</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+</Project>
Added +16 -0
diff --git a/Samples/KeyTest/Program.cs b/Samples/KeyTest/Program.cs
new file mode 100644
index 0000000..d9a791c
--- /dev/null
+++ b/Samples/KeyTest/Program.cs
@@ -0,0 +1,16 @@
+using SmartA4;
+
+var key = new InputPort { FileName = "/dev/key" };
+
+var f = false;
+for (var i = 0; i < 100; i++)
+{
+    var rs = key.Read();
+    if (rs != f)
+    {
+        f = rs;
+        Console.WriteLine(f ? "按下" : "松开");
+    }
+
+    Thread.Sleep(100);
+}
\ No newline at end of file
Added +19 -0
diff --git a/Samples/LedTest/LedTest.csproj b/Samples/LedTest/LedTest.csproj
new file mode 100644
index 0000000..340b958
--- /dev/null
+++ b/Samples/LedTest/LedTest.csproj
@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net7.0</TargetFramework>
+    <Company>新生命开发团队</Company>
+    <Copyright>©2002-2023 新生命开发团队</Copyright>
+    <VersionPrefix>1.0</VersionPrefix>
+    <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
+    <Version>$(VersionPrefix).$(VersionSuffix)</Version>
+    <FileVersion>$(Version)</FileVersion>
+    <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
+    <Deterministic>false</Deterministic>
+    <OutputPath>..\..\Bin\LedTest</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+</Project>
Added +16 -0
diff --git a/Samples/LedTest/OutputPort.cs b/Samples/LedTest/OutputPort.cs
new file mode 100644
index 0000000..2487a24
--- /dev/null
+++ b/Samples/LedTest/OutputPort.cs
@@ -0,0 +1,16 @@
+namespace SmartA4;
+
+/// <summary>输出口</summary>
+public class OutputPort
+{
+    /// <summary>文件路径</summary>
+    public String FileName { get; set; }
+
+    /// <summary>读取开关值</summary>
+    /// <returns></returns>
+    public Boolean Read() => File.ReadAllText(FileName)?.Trim() == "1";
+
+    /// <summary>写入开关值</summary>
+    /// <param name="value"></param>
+    public void Write(Boolean value) => File.WriteAllText(FileName, value ? "1" : "0");
+}
\ No newline at end of file
Added +16 -0
diff --git a/Samples/LedTest/Program.cs b/Samples/LedTest/Program.cs
new file mode 100644
index 0000000..ff616d6
--- /dev/null
+++ b/Samples/LedTest/Program.cs
@@ -0,0 +1,16 @@
+using SmartA4;
+
+var led = new OutputPort { FileName = "/dev/led" };
+
+while (true)
+{
+    // 灭
+    led.Write(false);
+
+    Thread.Sleep(500);
+
+    // 亮
+    led.Write(true);
+
+    Thread.Sleep(500);
+}
Added +23 -0
diff --git a/Samples/NetClientTest/NetClientTest.csproj b/Samples/NetClientTest/NetClientTest.csproj
new file mode 100644
index 0000000..a307fdd
--- /dev/null
+++ b/Samples/NetClientTest/NetClientTest.csproj
@@ -0,0 +1,23 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net7.0</TargetFramework>
+    <Company>新生命开发团队</Company>
+    <Copyright>©2002-2023 新生命开发团队</Copyright>
+    <VersionPrefix>1.0</VersionPrefix>
+    <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
+    <Version>$(VersionPrefix).$(VersionSuffix)</Version>
+    <FileVersion>$(Version)</FileVersion>
+    <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
+    <Deterministic>false</Deterministic>
+    <OutputPath>..\..\Bin\NetClientTest</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="NewLife.Core" Version="10.6.2023.1001" />
+  </ItemGroup>
+
+</Project>
Added +34 -0
diff --git a/Samples/NetClientTest/Program.cs b/Samples/NetClientTest/Program.cs
new file mode 100644
index 0000000..d6b7edf
--- /dev/null
+++ b/Samples/NetClientTest/Program.cs
@@ -0,0 +1,34 @@
+using NewLife;
+using NewLife.Log;
+using NewLife.Net;
+
+// 网络客户端,一般跑在工控机上,充当硬件设备到服务器之间的桥梁
+
+XTrace.UseConsole();
+
+// 支持tcp/udp地址
+XTrace.WriteLine("请输入要连接的服务器:");
+var server = Console.ReadLine();
+if (server.IsNullOrEmpty()) server = "tcp://10.0.2.6:777";
+
+var uri = new NetUri(server);
+var client = uri.CreateRemote();
+client.Log = XTrace.Log;
+client.LogSend = true;
+client.LogReceive = true;
+
+// 在事件中接收数据
+client.Received += (s, e) =>
+{
+    XTrace.WriteLine("收到数据:{0}", e.Packet.ToStr());
+};
+client.Open();
+
+// 发送数据
+for (var i = 0; i < 10; i++)
+{
+    XTrace.WriteLine("请输入要发送的数据:");
+    var input = Console.ReadLine();
+
+    client.Send(input);
+}
Added +23 -0
diff --git a/Samples/NetServerTest/NetServerTest.csproj b/Samples/NetServerTest/NetServerTest.csproj
new file mode 100644
index 0000000..6d3003d
--- /dev/null
+++ b/Samples/NetServerTest/NetServerTest.csproj
@@ -0,0 +1,23 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net7.0</TargetFramework>
+    <Company>新生命开发团队</Company>
+    <Copyright>©2002-2023 新生命开发团队</Copyright>
+    <VersionPrefix>1.0</VersionPrefix>
+    <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
+    <Version>$(VersionPrefix).$(VersionSuffix)</Version>
+    <FileVersion>$(Version)</FileVersion>
+    <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
+    <Deterministic>false</Deterministic>
+    <OutputPath>..\..\Bin\NetServerTest</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="NewLife.Core" Version="10.6.2023.1001" />
+  </ItemGroup>
+
+</Project>
Added +39 -0
diff --git a/Samples/NetServerTest/Program.cs b/Samples/NetServerTest/Program.cs
new file mode 100644
index 0000000..eda299d
--- /dev/null
+++ b/Samples/NetServerTest/Program.cs
@@ -0,0 +1,39 @@
+using NewLife.Log;
+using NewLife.Net;
+
+// 网络服务端,一般跑在服务器或上位机上,用于接收工控机内客户端的数据
+
+XTrace.UseConsole();
+
+var server = new NetServer(777)
+{
+    Log = XTrace.Log,
+    SessionLog = XTrace.Log
+};
+
+// 新连接会话事件
+server.NewSession += (s, e) =>
+{
+    var uri = e.Session.Remote;
+    XTrace.WriteLine("新会话:{0}", uri);
+
+    var session = e.Session;
+    session.Send($"欢迎:{uri}");
+};
+
+// 在事件中接收数据
+server.Received += (s, e) =>
+{
+    var msg = e.Packet.ToStr();
+    XTrace.WriteLine("收到数据:{0}", msg);
+
+    // 倒序返回
+    var session = s as INetSession;
+    var cs = msg.Reverse().ToArray();
+    session.Send(new String(cs));
+};
+
+server.Start();
+
+// 等待退出
+Console.ReadLine();
\ No newline at end of file
Added +62 -0
diff --git a/Samples/Serial2NetClientTest/Program.cs b/Samples/Serial2NetClientTest/Program.cs
new file mode 100644
index 0000000..d614819
--- /dev/null
+++ b/Samples/Serial2NetClientTest/Program.cs
@@ -0,0 +1,62 @@
+using System.IO.Ports;
+using NewLife;
+using NewLife.Data;
+using NewLife.Log;
+using NewLife.Net;
+using SmartA4;
+
+// 客户端,本地连接串口,远程连接服务端
+
+internal class Program
+{
+    static SerialPort _serial;
+    static ISocketRemote _client;
+
+    private static void Main(string[] args)
+    {
+        XTrace.UseConsole();
+
+        var host = new A2();
+
+        // 配置并打开串口COM1
+        var serial = host.CreateSerial(1, 9600);
+        serial.DataReceived += OnReceiveSerial;
+        serial.Open();
+
+        // 服务器地址,可保存在配置文件中,支持tcp/udp地址
+        var server = "tcp://10.0.2.6:888";
+        var uri = new NetUri(server);
+        var client = uri.CreateRemote();
+        client.Log = XTrace.Log;
+        client.Received += OnReceiveSocket;
+        client.Open();
+
+        _serial = serial;
+        _client = client;
+
+        // 等待退出
+        Console.ReadLine();
+    }
+
+    static void OnReceiveSerial(Object sender, SerialDataReceivedEventArgs e)
+    {
+        // 等一会儿,等待数据接收完毕
+        Thread.Sleep(10);
+
+        var sp = sender as SerialPort;
+        var buf = new Byte[sp.BytesToRead];
+        var count = sp.Read(buf, 0, buf.Length);
+        if (count <= 0) return;
+
+        // 发送串口数据到服务器
+        var pk = new Packet(buf, 0, count);
+        _client.Send(pk);
+    }
+
+    static void OnReceiveSocket(Object sender, ReceivedEventArgs e)
+    {
+        // 接收到服务器数据,转发到串口
+        var pk = e.Packet;
+        _serial.Write(pk.Data, pk.Offset, pk.Count);
+    }
+}
\ No newline at end of file
Added +27 -0
diff --git a/Samples/Serial2NetClientTest/Serial2NetClientTest.csproj b/Samples/Serial2NetClientTest/Serial2NetClientTest.csproj
new file mode 100644
index 0000000..12db2fc
--- /dev/null
+++ b/Samples/Serial2NetClientTest/Serial2NetClientTest.csproj
@@ -0,0 +1,27 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net7.0</TargetFramework>
+    <Company>新生命开发团队</Company>
+    <Copyright>©2002-2023 新生命开发团队</Copyright>
+    <VersionPrefix>1.0</VersionPrefix>
+    <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
+    <Version>$(VersionPrefix).$(VersionSuffix)</Version>
+    <FileVersion>$(Version)</FileVersion>
+    <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
+    <Deterministic>false</Deterministic>
+    <OutputPath>..\..\Bin\Serial2NetClientTest</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="NewLife.Core" Version="10.6.2023.1001" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\SmartA4\SmartA4.csproj" />
+  </ItemGroup>
+
+</Project>
Added +57 -0
diff --git a/Samples/Serial2NetServerTest/Program.cs b/Samples/Serial2NetServerTest/Program.cs
new file mode 100644
index 0000000..e7646c4
--- /dev/null
+++ b/Samples/Serial2NetServerTest/Program.cs
@@ -0,0 +1,57 @@
+using NewLife.Log;
+using NewLife.Net;
+
+// 网络服务端,接收客户端数据
+
+XTrace.UseConsole();
+
+// 应用层连接字典
+var users = new Dictionary<String, Int32>();
+
+var server = new NetServer(888)
+{
+    Log = XTrace.Log,
+    SessionLog = XTrace.Log
+};
+
+// 新连接会话事件
+server.NewSession += (s, e) =>
+{
+    var uri = e.Session.Remote;
+    XTrace.WriteLine("新会话:{0}", uri);
+
+    // 记录该设备IP,后面通过IP找到对应会话并下发数据
+    var session = e.Session;
+    users[uri.Address + ""] = session.ID;
+};
+
+// 在事件中接收数据
+server.Received += (s, e) =>
+{
+    var msg = e.Packet.ToStr();
+    XTrace.WriteLine("收到数据:{0}", msg);
+
+    // 倒序返回
+    var session = s as INetSession;
+    var cs = msg.Reverse().ToArray();
+    session.Send(new String(cs));
+};
+
+server.Start();
+
+// 应用层向指定设备下发数据
+var ip = "10.0.2.6";
+if (users.TryGetValue(ip, out var id))
+{
+    // 根据ID找到对应会话,如果会话不存在,可能是设备已经断开
+    var session = server.GetSession(id);
+    if (session != null)
+    {
+        var msg = "Hello " + ip;
+        session.Send(msg);
+        XTrace.WriteLine("向[{0}]发送数据[{1}]", ip, msg);
+    }
+}
+
+// 等待退出
+Console.ReadLine();
\ No newline at end of file
Added +23 -0
diff --git a/Samples/Serial2NetServerTest/Serial2NetServerTest.csproj b/Samples/Serial2NetServerTest/Serial2NetServerTest.csproj
new file mode 100644
index 0000000..55a8f17
--- /dev/null
+++ b/Samples/Serial2NetServerTest/Serial2NetServerTest.csproj
@@ -0,0 +1,23 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net7.0</TargetFramework>
+    <Company>新生命开发团队</Company>
+    <Copyright>©2002-2023 新生命开发团队</Copyright>
+    <VersionPrefix>1.0</VersionPrefix>
+    <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
+    <Version>$(VersionPrefix).$(VersionSuffix)</Version>
+    <FileVersion>$(Version)</FileVersion>
+    <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
+    <Deterministic>false</Deterministic>
+    <OutputPath>..\..\Bin\Serial2NetServerTest</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="NewLife.Core" Version="10.6.2023.1001" />
+  </ItemGroup>
+
+</Project>
Added +60 -0
diff --git a/Samples/SerialTest/Program.cs b/Samples/SerialTest/Program.cs
new file mode 100644
index 0000000..1b29022
--- /dev/null
+++ b/Samples/SerialTest/Program.cs
@@ -0,0 +1,60 @@
+using System.IO.Ports;
+using NewLife;
+using NewLife.Log;
+
+internal class Program
+{
+    private static void Main(string[] args)
+    {
+        XTrace.UseConsole();
+
+        // 硬件串口对照表
+        // COM1:/dev/ttyS1
+        // COM2:/dev/ttyS2
+        // COM3:/dev/ttyS3
+        // COM4:/dev/ttyS4
+
+        // 列出所有串口
+        var ports = SerialPort.GetPortNames();
+        XTrace.WriteLine("串口列表[{0}]:", ports.Length);
+        foreach (var port in ports)
+        {
+            XTrace.WriteLine(port);
+        }
+
+        // 配置并打开串口COM1
+        var serial = new SerialPort
+        {
+            PortName = "/dev/ttyS1",
+            BaudRate = 9600,
+        };
+        // 注册数据接收事件
+        serial.DataReceived += OnReceive;
+        serial.Open();
+
+        // 发送数据
+        for (var i = 0; i < 10; i++)
+        {
+            XTrace.WriteLine("请输入要发送的数据:");
+            var input = Console.ReadLine();
+
+            serial.Write(input);
+        }
+
+        // 关闭串口
+        serial.Close();
+    }
+
+    static void OnReceive(Object sender, SerialDataReceivedEventArgs e)
+    {
+        // 等一会儿,等待数据接收完毕
+        Thread.Sleep(10);
+
+        var sp = sender as SerialPort;
+        var buf = new Byte[sp.BytesToRead];
+        sp.Read(buf, 0, buf.Length);
+
+        XTrace.WriteLine("收到数据:{0}", buf.ToStr());
+        //XTrace.WriteLine("收到数据:{0}", buf.ToHex());
+    }
+}
\ No newline at end of file
Added +27 -0
diff --git a/Samples/SerialTest/SerialTest.csproj b/Samples/SerialTest/SerialTest.csproj
new file mode 100644
index 0000000..ebd32de
--- /dev/null
+++ b/Samples/SerialTest/SerialTest.csproj
@@ -0,0 +1,27 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net7.0</TargetFramework>
+    <Company>新生命开发团队</Company>
+    <Copyright>©2002-2023 新生命开发团队</Copyright>
+    <VersionPrefix>1.0</VersionPrefix>
+    <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
+    <Version>$(VersionPrefix).$(VersionSuffix)</Version>
+    <FileVersion>$(Version)</FileVersion>
+    <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
+    <Deterministic>false</Deterministic>
+    <OutputPath>..\..\Bin\SerialTest</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="NewLife.Core" Version="10.6.2023.1001" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\SmartA4\SmartA4.csproj" />
+  </ItemGroup>
+
+</Project>
Added +16 -0
diff --git a/Samples/UsbPowerTest/OutputPort.cs b/Samples/UsbPowerTest/OutputPort.cs
new file mode 100644
index 0000000..2487a24
--- /dev/null
+++ b/Samples/UsbPowerTest/OutputPort.cs
@@ -0,0 +1,16 @@
+namespace SmartA4;
+
+/// <summary>输出口</summary>
+public class OutputPort
+{
+    /// <summary>文件路径</summary>
+    public String FileName { get; set; }
+
+    /// <summary>读取开关值</summary>
+    /// <returns></returns>
+    public Boolean Read() => File.ReadAllText(FileName)?.Trim() == "1";
+
+    /// <summary>写入开关值</summary>
+    /// <param name="value"></param>
+    public void Write(Boolean value) => File.WriteAllText(FileName, value ? "1" : "0");
+}
\ No newline at end of file
Added +14 -0
diff --git a/Samples/UsbPowerTest/Program.cs b/Samples/UsbPowerTest/Program.cs
new file mode 100644
index 0000000..30b3e13
--- /dev/null
+++ b/Samples/UsbPowerTest/Program.cs
@@ -0,0 +1,14 @@
+using SmartA4;
+
+// USB口电源控制,可用于控制外部USB设备上电。如风扇、灯光、水泵等
+var usb = new OutputPort { FileName = "/dev/usbpwr" };
+
+// 上电
+usb.Write(true);
+
+Thread.Sleep(500);
+
+// 断电
+usb.Write(false);
+
+Thread.Sleep(500);
Added +19 -0
diff --git a/Samples/UsbPowerTest/UsbPowerTest.csproj b/Samples/UsbPowerTest/UsbPowerTest.csproj
new file mode 100644
index 0000000..a7aa338
--- /dev/null
+++ b/Samples/UsbPowerTest/UsbPowerTest.csproj
@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net7.0</TargetFramework>
+    <Company>新生命开发团队</Company>
+    <Copyright>©2002-2023 新生命开发团队</Copyright>
+    <VersionPrefix>1.0</VersionPrefix>
+    <VersionSuffix>$([System.DateTime]::Now.ToString(`yyyy.MMdd`))</VersionSuffix>
+    <Version>$(VersionPrefix).$(VersionSuffix)</Version>
+    <FileVersion>$(Version)</FileVersion>
+    <AssemblyVersion>$(VersionPrefix).*</AssemblyVersion>
+    <Deterministic>false</Deterministic>
+    <OutputPath>..\..\Bin\UsbPowerTest</OutputPath>
+    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+    <ImplicitUsings>enable</ImplicitUsings>
+  </PropertyGroup>
+
+</Project>
Modified +98 -0
diff --git a/SmartA4.sln b/SmartA4.sln
index c71cf12..a84c640 100644
--- a/SmartA4.sln
+++ b/SmartA4.sln
@@ -18,6 +18,44 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartA4", "SmartA4\SmartA4.
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XUnitTest", "XUnitTest\XUnitTest.csproj", "{64BDB91E-0B31-4EDD-AD11-8D83A1A3CD82}"
 EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{3DB3FAF5-8F1A-44EB-A407-025A4AB19AB0}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LedTest", "Samples\LedTest\LedTest.csproj", "{B1632DC0-9AFF-4034-9234-32B9A0B8F2A4}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1.LED测试", "1.LED测试", "{315309F2-7427-4EDF-9854-105B2E24C684}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2.蜂鸣器测试", "2.蜂鸣器测试", "{8825FC53-DDAF-4F1E-8F3C-38A85C19731A}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3.USB电源测试", "3.USB电源测试", "{8862FCEB-8460-4E40-B8D5-A91EA1F2E3BA}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "4.按键测试", "4.按键测试", "{A3BF37DF-1078-41A2-82DF-4AE854096DF5}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuzzerTest", "Samples\BuzzerTest\BuzzerTest.csproj", "{88DF6E42-8776-4073-89BB-26666888BB5D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UsbPowerTest", "Samples\UsbPowerTest\UsbPowerTest.csproj", "{A027B945-184C-4EDD-95E1-038586B60FFD}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KeyTest", "Samples\KeyTest\KeyTest.csproj", "{6BE3AB6D-88A6-4493-B6BB-C725882B991D}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "5.串口", "5.串口", "{50F09D8D-EE42-467C-A366-E50676F157E0}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "6.网络", "6.网络", "{43CA44E0-92E8-4448-89A4-240AD34494A4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SerialTest", "Samples\SerialTest\SerialTest.csproj", "{65D7E24B-FAA3-4159-825F-8EE25316D52C}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "8.数据库", "8.数据库", "{C1A2B597-61C1-4A4B-AF34-A6E808B89522}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetClientTest", "Samples\NetClientTest\NetClientTest.csproj", "{A005D70E-BEC9-473C-AE26-F7C4217C5DC2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetServerTest", "Samples\NetServerTest\NetServerTest.csproj", "{3BC6E35A-A5E0-41E5-A8E8-3822BBAF22BD}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "7.串口转以太网", "7.串口转以太网", "{5BE54C41-A5D4-4255-986F-5FB9CE486BFE}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serial2NetClientTest", "Samples\Serial2NetClientTest\Serial2NetClientTest.csproj", "{DE468AF1-CA80-4B4C-88FD-2AE016C4F970}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serial2NetServerTest", "Samples\Serial2NetServerTest\Serial2NetServerTest.csproj", "{C26767B7-9386-4EF3-8625-FCD3BB403066}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DatabaseTest", "Samples\DatabaseTest\DatabaseTest.csproj", "{B049FA36-51A1-4384-A53C-F9D86E002D29}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -36,10 +74,70 @@ Global
 		{64BDB91E-0B31-4EDD-AD11-8D83A1A3CD82}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{64BDB91E-0B31-4EDD-AD11-8D83A1A3CD82}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{64BDB91E-0B31-4EDD-AD11-8D83A1A3CD82}.Release|Any CPU.Build.0 = Release|Any CPU
+		{B1632DC0-9AFF-4034-9234-32B9A0B8F2A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{B1632DC0-9AFF-4034-9234-32B9A0B8F2A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{B1632DC0-9AFF-4034-9234-32B9A0B8F2A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{B1632DC0-9AFF-4034-9234-32B9A0B8F2A4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{88DF6E42-8776-4073-89BB-26666888BB5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{88DF6E42-8776-4073-89BB-26666888BB5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{88DF6E42-8776-4073-89BB-26666888BB5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{88DF6E42-8776-4073-89BB-26666888BB5D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A027B945-184C-4EDD-95E1-038586B60FFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A027B945-184C-4EDD-95E1-038586B60FFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A027B945-184C-4EDD-95E1-038586B60FFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A027B945-184C-4EDD-95E1-038586B60FFD}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6BE3AB6D-88A6-4493-B6BB-C725882B991D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6BE3AB6D-88A6-4493-B6BB-C725882B991D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6BE3AB6D-88A6-4493-B6BB-C725882B991D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6BE3AB6D-88A6-4493-B6BB-C725882B991D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{65D7E24B-FAA3-4159-825F-8EE25316D52C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{65D7E24B-FAA3-4159-825F-8EE25316D52C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{65D7E24B-FAA3-4159-825F-8EE25316D52C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{65D7E24B-FAA3-4159-825F-8EE25316D52C}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A005D70E-BEC9-473C-AE26-F7C4217C5DC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A005D70E-BEC9-473C-AE26-F7C4217C5DC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A005D70E-BEC9-473C-AE26-F7C4217C5DC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A005D70E-BEC9-473C-AE26-F7C4217C5DC2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{3BC6E35A-A5E0-41E5-A8E8-3822BBAF22BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3BC6E35A-A5E0-41E5-A8E8-3822BBAF22BD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3BC6E35A-A5E0-41E5-A8E8-3822BBAF22BD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3BC6E35A-A5E0-41E5-A8E8-3822BBAF22BD}.Release|Any CPU.Build.0 = Release|Any CPU
+		{DE468AF1-CA80-4B4C-88FD-2AE016C4F970}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{DE468AF1-CA80-4B4C-88FD-2AE016C4F970}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{DE468AF1-CA80-4B4C-88FD-2AE016C4F970}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{DE468AF1-CA80-4B4C-88FD-2AE016C4F970}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C26767B7-9386-4EF3-8625-FCD3BB403066}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C26767B7-9386-4EF3-8625-FCD3BB403066}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C26767B7-9386-4EF3-8625-FCD3BB403066}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C26767B7-9386-4EF3-8625-FCD3BB403066}.Release|Any CPU.Build.0 = Release|Any CPU
+		{B049FA36-51A1-4384-A53C-F9D86E002D29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{B049FA36-51A1-4384-A53C-F9D86E002D29}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{B049FA36-51A1-4384-A53C-F9D86E002D29}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{B049FA36-51A1-4384-A53C-F9D86E002D29}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{B1632DC0-9AFF-4034-9234-32B9A0B8F2A4} = {315309F2-7427-4EDF-9854-105B2E24C684}
+		{315309F2-7427-4EDF-9854-105B2E24C684} = {3DB3FAF5-8F1A-44EB-A407-025A4AB19AB0}
+		{8825FC53-DDAF-4F1E-8F3C-38A85C19731A} = {3DB3FAF5-8F1A-44EB-A407-025A4AB19AB0}
+		{8862FCEB-8460-4E40-B8D5-A91EA1F2E3BA} = {3DB3FAF5-8F1A-44EB-A407-025A4AB19AB0}
+		{A3BF37DF-1078-41A2-82DF-4AE854096DF5} = {3DB3FAF5-8F1A-44EB-A407-025A4AB19AB0}
+		{88DF6E42-8776-4073-89BB-26666888BB5D} = {8825FC53-DDAF-4F1E-8F3C-38A85C19731A}
+		{A027B945-184C-4EDD-95E1-038586B60FFD} = {8862FCEB-8460-4E40-B8D5-A91EA1F2E3BA}
+		{6BE3AB6D-88A6-4493-B6BB-C725882B991D} = {A3BF37DF-1078-41A2-82DF-4AE854096DF5}
+		{50F09D8D-EE42-467C-A366-E50676F157E0} = {3DB3FAF5-8F1A-44EB-A407-025A4AB19AB0}
+		{43CA44E0-92E8-4448-89A4-240AD34494A4} = {3DB3FAF5-8F1A-44EB-A407-025A4AB19AB0}
+		{65D7E24B-FAA3-4159-825F-8EE25316D52C} = {50F09D8D-EE42-467C-A366-E50676F157E0}
+		{C1A2B597-61C1-4A4B-AF34-A6E808B89522} = {3DB3FAF5-8F1A-44EB-A407-025A4AB19AB0}
+		{A005D70E-BEC9-473C-AE26-F7C4217C5DC2} = {43CA44E0-92E8-4448-89A4-240AD34494A4}
+		{3BC6E35A-A5E0-41E5-A8E8-3822BBAF22BD} = {43CA44E0-92E8-4448-89A4-240AD34494A4}
+		{5BE54C41-A5D4-4255-986F-5FB9CE486BFE} = {3DB3FAF5-8F1A-44EB-A407-025A4AB19AB0}
+		{DE468AF1-CA80-4B4C-88FD-2AE016C4F970} = {5BE54C41-A5D4-4255-986F-5FB9CE486BFE}
+		{C26767B7-9386-4EF3-8625-FCD3BB403066} = {5BE54C41-A5D4-4255-986F-5FB9CE486BFE}
+		{B049FA36-51A1-4384-A53C-F9D86E002D29} = {C1A2B597-61C1-4A4B-AF34-A6E808B89522}
+	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {323831A1-A95B-40AB-B9AD-36A0BC10C2CB}
 	EndGlobalSection