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
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>
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
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
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>
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
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>
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
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
+}
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
+}
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
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
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>
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
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>
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
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);
+}
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>
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);
+}
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>
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
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
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>
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
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>
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
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>
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
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);
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>
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