# 《物联网编程与应用 第2版》摘要 · 第1部分(PDF 第1–65页) > 页码换算:**书页 = PDF页 − 3**(PDF 4 = 书 p1)。扫描件里没有前言和二维码索引,只有目录。 > 本部分结束于 **PDF 65 = 书 p62**,即**项目3 任务2 的【知识提炼】中间**(RadioButton“设置字体颜色”示例,表3-5 之后)。下一位读者从 PDF 66 / 书 p63 接着读(应为该示例的代码、CheckBox 知识点、能力拓展,以及书 p71 的项目3评价)。 > 参考工程目录:`/home/xuyue/Projects/IOT/物联网编程与应用配套/物联网编程与应用/项目N/任务一|二|三`(文件夹用中文数字)。 ## 0. 目录(PDF 1–3) | 项目 / 任务 | 书页 | PDF页 | |---|---|---| | 项目1 C#入门基础 | 1 | 4 | |  任务1 C#引入 | 1 | 4 | |  任务2 C#面向对象特性 | 13 | 16 | |  项目评价 | 21 | 24 | | 项目2 智慧校园系统——环境监测模块 | 23 | 26 | |  任务1 LED显示屏的使用 | 23 | 26 | |  任务2 实时采集温度、湿度和光照度数据 | 36 | 39 | |  任务3 在LED显示屏中显示环境数据 | 42 | 45 | |  项目评价 | 51 | 54 | | 项目3 智慧校园系统——智慧路灯控制模块 | 53 | 56 | |  任务1 使用ZigBee技术控制路灯开关 | 53 | 56 | |  任务2 实现人体红外传感器和路灯联动 | 58 | 61 | |  项目评价 | 71 | 74 | | 项目4 入侵信息监测模块:任务1 采集红外对射入侵传感器数据 73;任务2 控制网络摄像机 78;任务3 控制报警灯 84;评价 89 | 73 | 76 | | 项目5 灾情信息监测模块:任务1 采集火焰传感器数据 91;任务2 采集烟雾传感器数据 98;任务3 实时推送灾情信息 104;评价 110 | 91 | 94 | | 项目6 数据存储模块:任务1 制作环境数据表 113;任务2 存储校园环境数据 119;任务3 查询校园安防数据 127;评价 131 | 113 | 116 | | 项目7 情景控制模块:任务1 实现智慧教室 133;任务2 实现智慧寝室 141;任务3 实现智慧会议室 148;评价 156 | 133 | 136 | | 项目8 智慧校园管理平台开发:任务1 制作管理平台的主界面 157;任务2 制作“校园环境”界面 159;任务3 实现“校园环境”数据采集及数据分析功能 164;任务4 实现“校园安防”功能模块 174;任务5 实现“校园安防”模块中显示历史图片的功能 181;评价 185 | 157 | 160 | | 参考文献 | 187 | 190(超出187页的扫描件,可能缺页) | (注意:扫描件只有187页,所以书页 ≥ 185 的内容可能不在 PDF 里。) --- ## 项目1 C#入门基础(书 p1–22 / PDF 4–25) **项目概述**:C# 是微软在 C 和 C++ 基础上衍生出的面向对象语言,基于 .NET。本项目先学 Python 示例程序,再逐步引入 C#,为后面的智慧校园开发做准备。 **学习目标** - 了解 C# 与 Python 的语法区别。 - 了解 C# 的语法规范。 - 理解属性和 this 关键字。 - 养成良好的代码编写规范。 - 增强职业认识,提升职业素养,树立职业信心。 ### 任务1 C#引入(书 p1–13 / PDF 4–16) **任务描述**:分别用 Python 和 C# 写一个登录程序,通过对比了解 C# 的命名空间、属性、this 等面向对象概念。 **任务实施** *1. Python 登录应用体验*(书 p2):控制台程序“登录”,用 `usernameLst = list()` 和 `pwdLst = list()` 保存用户。 - `def ligon()`:用 input 读入用户名和密码,`if username in usernameLst and pwd in pwdLst` 时打印“登录成功”,否则打印“登录失败”。 - `def addUser()`:读入新用户名并 append,读入新密码并 append,打印“添加用户成功!”。 - `while(True)`:提示“请输入您要操作的内容(1.登录;2.新增用户,输入q退出程序)”;输入 '1' 调 `ligon()`,'2' 调 `addUser()`,'q' 执行 break。 - 参考代码见 `项目1/任务一/python/登录.py`,内容与书一致(参考代码里少了 `q` 分支)。**模拟器如果要支持这一步,需要一个 Python 控制台(stdin/stdout)。** *2. C# 登录应用体验*(书 p2–7) 1. 新建 Windows 窗体应用程序,名称“登录”。 2. 把 Form1 改名为 Login,用工具箱搭出“登录”界面(图1-3:两个标签、两个文本框,按钮“登录 / 取消 / 增加用户”)。 3. 在“解决方案资源管理器”里选“添加 → 类”,新建 `PublicData`: ```csharp public class PublicData { public static string[] SysUserName = new string[4];//存储系统用户名 public static string[] SysPwd = new string[4];//存储系统用户密码 public static int intNum { get; set; }//当前数据存储的用户数量 public static int intUser { get; set; }//记录当前用户索引号 } ``` 4. 在 Login 中加全局变量 `AddUser addfrm = new AddUser();` 和 `public bool IsLogin = false;`。 5. 写 `btnOk_Click`:遍历 `PublicData.SysUserName`;用户名和密码都匹配且不为空时,令 `IsLogin=true`,弹 `MessageBox.Show("登录成功,欢迎进入!","登录")`,并记下 `intUser = i`。书里接着是 `if (change) { this.Hide(); change = false; }`,然后 break。都不匹配时提示“无效的密码或用户名,请重试”,并执行 `txtPassword.Focus()`。 6. 写 `btnCancel_Click`:`IsLogin=false; this.Close();` 7. 写 `btnAdd_Click`:`addfrm.Show();` 8. 添加 Windows 窗体 `addfrm`(图1-6“增加用户”界面)。 9. 写 addfrm 的 `btnOk_Click`:当 `intNum<4` 且 `txtPass1.Text.Equals(txtPass2.Text) && txtPass2.Text != ""` 时,把 `txtNewUser1.Text` 和 `txtPass1.Text` 存进数组,`intNum++`,再 `this.Close()`。否则提示“用户名或密码错误,请重新输入”。数组满了提示“用户名已满不能增加”(标题都是“新增用户”)。 10. 写 addfrm 的 `btnCancel_Click`:`this.Close();` **表1-1 “登录”界面控件的属性** | 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 | |---|---|---|---|---| | Login | 窗体 | Text | 登录 | | | LblUsername | 标签 | Text | 用户名称 | | | LblPwd | 标签 | Text | 密码 | | | txtUserName | 文本框 | | | 输入用户名 | | txtPwd | 文本框 | PasswordChar | * | 输入密码 | | BtnOk | 按钮 | Text | 登录 | | | BtnCancel | 按钮 | Text | 取消 | | | BtnAdd | 按钮 | Text | 增加用户 | | **表1-2 “增加用户”界面控件的属性** | 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 | |---|---|---|---|---| | addfrm | 窗体 | Text | 增加用户 | | | LblUsername | 标签 | Text | 用户名 | | | LblPwd | 标签 | Text | 密码 | | | LblCheckPwd | 标签 | Text | 确认密码 | | | txtUserName | 文本框 | | | 输入用户名 | | txtPwd | 文本框 | PasswordChar | * | 输入密码 | | txtCheckPwd | 文本框 | PasswordChar | * | 确认密码 | | BtnOk | 按钮 | Text | 确定 | | | BtnCancel | 按钮 | Text | 取消 | | **小贴士**(关于 VS IDE,模拟器需要有对应的界面) - “工具箱”面板在 VS 左侧,按栏目列出控件。双击控件就能加到窗体上,也可以拖上去。 - “属性”面板在 VS 右下方,用来设置控件属性,也用来管理控件事件。 **知识提炼**(书 p7–12) 1. **类**:类是创建对象的模板。用 `类修饰符 class 类名 { }` 声明,例如 `public class People { string name; int age; }`。静态类只能通过类名访问。继承写成 `class classChild : newClassName`。小贴士:继承时子类和基类之间必须用冒号。 2. **对象**:用 `new 类名()` 创建。示例 `Student` 类有字段 name、无参构造函数(name="unknown")、带参构造函数和 `SetName` 方法;最后用 `MessageBox.Show(student1.name + " " + student2.name)` 输出。 3. **命名空间**:用 `using 命名空间名称;` 引入。示例 `namespace TestN { class A { PrintMsg() → MessageBox.Show("物联网编程与应用") } }`,在 Form1_Load 中 `new A().PrintMsg()`。 4. **注释**:单行用 `//`,多行用 `/* */`。示例是控制台程序,用到 `Console.WriteLine` 和 `Console.ReadLine`。 5. **属性**:用 get/set 控制对私有字段的读写,至少要有其中一个。示例 Student 类:`School` 只读(默认值 "浙江**职业中学"),`Name` 只写,`Age` 可读写;`Display()` 用 Console.WriteLine 输出;Main 中用 `Console.Write("学校:"+stu1.School)`。 6. **this 关键字**:this 代表当前实例,静态方法中不能用。示例 `this.Age = age; this.Name = name;`。 **能力拓展** *边想边做*(书 p12–13):制作登录界面,登录成功后跳转,用属性读写,并在界面上显示当前登录的用户名。 提示:根据前面学的知识补全下面的代码,再输入完整代码并调试。 ```csharp public class Student { private string name="admin";//设置默认用户 private string pwd ="admin";//设置默认密码 // 声明Name属性,可进行读写 public string Name { set { name = value; } ____ //设置get属性 { ____ //返回name值 } } ____ // 声明pwd属性,可进行读写 { ____ //设置set属性 { ____ //赋值pwd值 } get { return pwd; } } } private void BtnLogin_Click(object sender, EventArgs e) { if (txtUsername.Text != "" &&txtPwd.Text != "")//判断输入内容是否为空 { if (txtUsername.Text == stu.Name&&txtPwd.Text == stu.Pwd) { ____ //实例化main窗体 ____ //将当前对象的stu对象赋值给main窗体对象的stu ____ //使用Show方法显示main窗体 ____ //使用Hide方法将当前窗体对象隐藏 } else { MessageBox.Show("登录失败,用户名或密码错误!", "登录"); } } else { MessageBox.Show("用户名或密码为空!", "登录"); } } ``` (参考答案大致为:`get` / `return name;` / `public string Pwd` / `set` / `pwd = value;` / `Main main = new Main();` / `main.stu = stu;` / `main.Show();` / `this.Hide();`。这里隐含要求:Login 窗体有字段 `Student stu = new Student();`,另有一个 `Main` 窗体,它有 public 字段 `stu`,并在界面上显示 `stu.Name`。书里没有给 Main 窗体的界面图或控件表。) *自主探究*(书 p13):把模块改成多用户登录系统,系统里有 10 个用户,要求: 1. 10 个用户能分别登录和退出。 2. 用对象存储用户数据,并用属性控制字段的读写。 ### 任务2 C#面向对象特性(书 p13–21 / PDF 16–24) **任务描述**:面向对象有封装、继承、多态三个特性。完成本任务后,学生能初步理解 C# 的装箱与拆箱、多态性,以及 List、Dictionary 的用法。 **任务实施** 1. 新建 Windows 窗体应用程序“C#基础”(存放在 项目1\任务2)。 2. 按图1-7 搭界面:groupBox1“简易计算器”里放 txtNum1、运算符下拉框、txtNum2、“=”按钮、结果框;groupBox2“类的多态性”里放一个多行文本框和“显示”按钮。 3. 写 `public Object Result(Object a, Object b, String type)`:先拆箱成 float,再 switch 处理 + - * /,default 返回 0,最后 `return (Object)result;`(装箱)。 4. “=”按钮 `btnCalculate_Click`:两个文本框都不为空时,`float.Parse` 两个数,`float res = (float)Result(a, b, cmboxType.Text)`,再令 `txtResult.Text = res.ToString()`。 5. 新建 `People` 类:`public virtual void Speak(TextBox Txtbox, string name) { Txtbox.Text += name; }` 6. 新建 `Chinese : People`,override 后执行 `base.Speak(Txtbox, name+"说的是汉语!\r\n")`。 7. 新建 `American : People`,override 后执行 `base.Speak(Txtbox, name+"说的是英语!\r\n")`。 8. 写“显示”按钮 `btnDisplay_Click`(书上第8步误写成“双击‘=’按钮”):先建 `Dictionary`,内容为 {1:"张三", 2:"李四", 3:"Mary", 4:"Jimmy"};再建 `List`,放 2 个 Chinese 和 2 个 American;最后遍历执行 `peoplelist[i].Speak(this.txtDispaly, dicName[i+1])`。 9. 运行:例如输入 1、+、2 后得 3;点“显示”后文本框输出“张三说的是汉语!/李四说的是汉语!/Mary说的是英语!/Jimmy说的是英语!”(图1-8)。 **表1-3 “C#入门基础”界面控件的属性** | 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 | |---|---|---|---|---| | Form1 | 窗体 | Text | C#入门基础 | | | groupBox1 | 组合框 | Text | 简易计算器 | | | txtNum1 | 文本框 | | | 输入数字1 | | txtNum2 | 文本框 | | | 输入数字2 | | cmboxType | 下拉列表框 | | | 选择运算符 | | btnCalculate | 按钮 | Text | = | 计算结果 | | txtResult | 文本框 | ReadOnly | True | 显示计算结果 | | groupBox2 | 组合框 | Text | 类的多态性 | | | txtDispaly | 文本框 | | | | | btnDisplay | 按钮 | Text | 显示 | | (书中没写,但模板必须有:`cmboxType.Items` = "+","-","*","/",`txtDispaly.Multiline = true`。参考工程里就是这样设置的。) **知识提炼**(书 p16–20,示例大多是**控制台程序**,用 Console.WriteLine / Console.Read) 1. **装箱和拆箱**:装箱把值类型转成引用类型(`Object obj = str;`),拆箱把引用类型转回值类型(`string s = (string)obj;`)。示例里用 `Console.WriteLine("...{0}...{1}", str, obj)` 做格式化输出。 2. **多态**: - 重载(overload):示例 `ClaArea.Area(double r)` 返回 `Math.PI*r*r`,`Area(double x, double y)` 返回 `x*y`。`Area(4)` 输出 50.26482,`Area(3,4)` 输出 12。 - 虚方法(virtual)与重写(override):`MyVirtualFun.Fun()` 输出“我是父类虚方法”,`OverrideFunction : MyVirtualFun` 重写后输出“我是子类方法”。 3. **List\**:表1-4 常见属性有 Count 和 Capacity(Capacity ≥ Count)。表1-5 常见方法有 Add、AddRange、Clear、Contains、CopyTo(T)、Find、IndexOf(T)、Insert、InsertRange、LastIndexOf(T)、Remove、Reverse()、Sort()、TrimExcess。示例依次调用:Add("A")、AddRange({"B","C","D"})、Capacity、Count、Contains("D")、Insert(2,"E")、CopyTo(array[10])、Remove("B")、Sort()、TrimExcess()、Clear()。 4. **Dictionary 类**:按键查找值,键必须唯一。格式 `Dictionary DemoDic = new Dictionary();`。示例用到 Add、按索引器取值 `Dic[2]`、ContainsKey(3)、Remove(2)、ContainsValue("张三")。 **能力拓展** *边想边做*(书 p20–21):“任务实施”中已经用过虚方法和 List。这里要定义形状父类,并用圆类实现其虚方法。按图1-9 创建界面:窗体标题“面积计算”;标签“请输入圆的半径:”加文本框 txtCircleR 和“添加”按钮;“显示面积”按钮;下方一个大的多行文本框 txtArea。按下面的提示实现功能。 1) 定义一个 Shape 类: ```csharp class Shape { ____ //定义虚方法Area,无返回值,参数为TextBox类型 { } } ``` 2) 定义一个 Circle 类,继承 Shape: ```csharp class Circle: Shape { double area; public Circle(double R) { ____ //实现构造函数,将R变量赋值给当前变量r } ____ //重写父类Area方法 { area= Math.PI____; //实现面积计算 txtArea.Text =txtArea.Text+ area.ToString()+"\r\n"; } } ``` 3) 在“添加”按钮的 Click 事件里按提示补全代码: ```csharp private void btnAdd_Click(object sender, EventArgs e) { Shape CirTemp; if (txtCircleR.Text != "") { ____; //实例化Circle类 ____; //将Shape对象加入list列表 txtCircleR.Text = ""; } else { MessageBox.Show("输入半径大小"); } } ``` (书中代码有缺陷:Circle 没有声明字段 `r`,需要补 `double r;`。Area 方法的参数名应为 `txtArea`。“显示面积”按钮的代码书里没给,应为遍历 list 并调用 `Area(txtArea)`。窗体还需要字段 `List list = new List();`。) *自主探究*(书 p21):定义形状类,在其中封装 X 和 Y 两个变量,并定义一个虚方法;再定义一个继承形状类的矩形类,实现该虚方法,输出 X×Y 的值。用 List 存 5 个矩形对象,循环输出矩形面积。 ### 项目1 项目评价(书 p21–22 / PDF 24–25) **1. 项目评价指标**(表中没有分值) | 任务 | 评价指标 | 具体要求 | |---|---|---| | 任务1 | 类与对象的概念 | 学会创建自定义类、实例化对象,理解类与对象 | | 任务1 | 窗体界面 | 窗体控件正确,布局美观 | | 任务1 | 单击事件 | 按钮单击事件的正确使用 | | 任务1 | 属性、this | 理解属性、this关键字 | | 任务2 | 拆箱与装箱 | 能正确使用拆箱与装箱 | | 任务2 | 多态性 | 重载、虚函数、重写的正确使用 | | 任务2 | List | 了解List的使用 | | 任务2 | Dictionary | 了解Dictionary的使用 | | 职业素养 | 代码书写规范 | 应该遵从结构化的要求,采用缩进的格式 | | 职业素养 | 注释书写规范 | 做到清晰、准确地描述内容。对于程序中复杂的部分必须有注释加以说明。注释量要适中,避免过多或过少导致阅读困难 | | 职业素养 | 变量命名规范 | 选择有意义的英文进行命名,使变量名能清楚地表达其含义。尽量少使用人名、地名和汉语拼音。应使用那些约定俗成的缩写。多个单词组成的变量名,每个单词的首字母应该大写 | **2. 个人学习总结**:只有两块空白,“项目学习情况:”和“心得与反思:”,供学生填写。 ### 项目1:书与参考工程的差异,以及模拟器要注意的点 - **任务1 控件命名不一致**。参考工程 `项目1/任务一/C#/frmlogin`: - 密码框叫 `txtPassword`(表1-1 写 txtPwd,但书上代码用的是 txtPassword)。 - 标签是 `label1/label2`,Text 为“用户名称(&U)”和“密码(&P)”(带助记符 &)。 - btnOk 的 Text 是“确定”(书上是“登录”)。 - Login 的 Load 事件 `frmExam5_8_Load` 预置了用户 `admin/admin`,书中没有这一步。没有它,第一次运行时没有可登录的用户。 - “增加用户”窗体类名是 `AddUser`(Name 为 "frmExam_5_82"),书上叫 addfrm。文本框是 `txtNewUser1/txtPass1/txtPass2`,与表1-2 的 txtUserName/txtPwd/txtCheckPwd 不一致,但和书上代码一致。**模板应以代码中用到的名字为准。** - **书中 `btnOk_Click` 用了未声明的变量 `change`**,属于书的错误。参考工程没有这个变量,另有一个 `btnChange_Click`,它先 `btnOk.PerformClick()`,登录成功再 `this.Hide()`。但设计器里没有 btnChange 按钮,这段是死代码。 - 任务1 需要**多窗体**支持:`new AddUser()`、`Show()`、`Hide()`、`Close()`;拓展练习还要跨窗体传对象(main.stu)。也要支持 `static` 类成员和自动属性。 - 任务2 参考工程中按钮叫 `BtnCalculate`(B 大写),handler 是 `BtnCalculate_Click`;书上是 btnCalculate。窗体 Text 为“c#入门基础”。 - 知识提炼的示例全是**控制台程序**(Console.WriteLine/Write/ReadLine/Read 和 `{0}` 格式化)。平台如果想让学生运行这些示例,需要一个控制台输出面板。 - 任务1 第一部分是 **Python** 控制台程序。 - 书中把运算符 "-" 排成了 "–"(en dash),抄写时要改回 ASCII 减号。 --- ## 项目2 智慧校园系统——环境监测模块(书 p23–52 / PDF 26–55) **项目概述**:LED 显示屏是校园的信息发布平台。本项目用温湿度传感器和光敏电阻传感器采集数据,并显示在校园 LED 显示屏上。 **学习目标** - 掌握串口通信的基本概念和工作原理。 - 了解 LED 显示屏的工作原理及数据推送方法。 - 掌握传感器的工作原理及数据采集方法。 - 熟悉温湿度传感器、光敏电阻传感器的工作原理。 - 理解模拟量采集器 ADAM4017 的功能及使用方法。 - 遵循代码编写规范,养成正确的变量命名与注释习惯,培养思维逻辑能力。 - 增强职业认识,提升职业素养,树立职业信心。 ### 任务1 LED显示屏的使用(书 p23–36 / PDF 26–39) **任务描述**:模拟智慧校园信息发布系统,用 C# 写程序通过串口把信息发送到 LED 显示屏。学生要学会向 LED 显示屏发送信息、使用 groupBox 控件,并了解 C# 的错误处理方法。 **任务实施** 1. 把 LED 显示屏通过串口接到 PC(没有硬件的读者跳过)。 2. 新建 Windows 窗体应用程序“LED显示屏”(项目2\任务1)。 3. 从配套资源中把 `DeviceCtrlLibrary.dll` 和 “API” 目录下的全部 dll 复制到 `bin\Debug`。API 目录有 ComLibrary.dll、ICS.Acquisition.dll、ICS.Common.dll、ICS.Models.dll、IPCameraDll.dll、LEDLibrary.dll、MyCamer.dll。 4. 添加引用 `DeviceCtrlLibrary.dll`。**没有硬件的读者添加仿真动态库文件,不用添加 API 文件夹里的 dll。** 5. 把自定义控件加入工具箱:右击工具箱“常规”,选“选择项”,点“浏览”,加载 `WinFormsDeviceControlLibrary.dll`。加载后“常规”下出现 4 个控件:**Alarm、Lamp、LED、ShowMsg**(图2-5)。 6. 按图2-6 搭界面:上方是大块 LED 控件;下方 groupBox“LED控制”中有“串口”下拉框、“打开”按钮、“推送LED信息”文本框和“推送”按钮。 7. 添加 using:`System.IO.Ports`、`DeviceCtrlLibrary`,以及 `WinFormsDeviceControlLibrary`(注释写“引用外部窗体类所在的命名空间”)。 8. `btn_Open_Click`:`DeviceCtrlHelper.ledHelper.OpenLedCom(com_Port.Text);` 9. `btn_Send_Click`:`string res = DeviceCtrlHelper.ledHelper.SendLedString(txt_SendText.Text);` 如果 `res.IndexOf("Error") >= 0`,执行 `MessageBox.Show(res)`,否则执行 `led.setLedText(res)`。 10. `com_Port_Click`:先 `com_Port.Items.Clear()`,再 `foreach (string item in SerialPort.GetPortNames()) com_Port.Items.Add(item);` 11. 运行:选串口(图中是 COM2),输入“欢迎学习物联网”,先点“打开”再点“推送”。仿真环境下 LED 控件会大字显示这段文字(图2-7)。 **表2-1 “LED显示屏”窗体中控件的属性** | 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 | |---|---|---|---|---| | Form1 | 窗体 | Text | LED显示屏 | | | LED | 自定义控件 | | | 显示LED推送的信息 | | groupBox1 | 组合框 | Text | LED控制 | 将功能控件放在同一个框里 | | Label1 | 标签 | Text | 串口 | | | Label2 | 标签 | Text | 推送LED信息 | | | btn_Open | 按钮 | Text | 打开 | 打开(关闭)串口 | | btn_Send | 按钮 | Text | 推送 | 向LED显示屏发送信息 | | com_Port | 下拉列表框 | | | 显示串口号 | | txt_SendText | 文本框 | | | 输入要发送的信息 | **小贴士(仿真相关)** - 书 p27 原文:“本项目使用的是硬件测试功能,若要使用仿真功能,请将资源文件下的 DeviceCtrlLibrary.dll 文件复制到 D:\物联网编程与应用\项目2\任务1\LED显示屏\LED显示屏\bin\Debug 文件夹下。”也就是说,**仿真版和硬件版用的是同名的 DeviceCtrlLibrary.dll,API 完全相同**,只是换一个 dll。 - 结果判断约定:SendLedString 失败时返回带 "Error" 的字符串,代码用 `IndexOf("Error")` 判断。 **知识提炼**(书 p28–34) 1. **串口通信及串口波特率**:串口即 Serial Port / COM 接口,按位顺序传输。接口标准有 RS-232C、RS-422、RS-423、RS-485,涉及 DTE 和 DCE。波特率例子:9600bit/s 约等于每秒 1200 字节。两端波特率必须一致,否则会出现乱码。 2. **SerialPort 类**: - 表2-2 常见属性:BaseStream、BaudRate、BytesToRead、BytesToWrite、CDHolding、CtsHolding、DataBits、Encoding、ParityReplace、ReadBufferSize、ReadTimeout、ReceivedBytesThreshold、StopBits、WriteBufferSize、WriteTimeout、PortName。 - 表2-3 常见方法:Close、Open、Read、ReadByte、ReadChar、ReadLine、ReadTo、Write、WriteLine。 - 发送示例:`new SerialPort()`,PortName="COM1",BaudRate=9600,Open,用 `Encoding.Unicode.GetBytes("ABC")` 转字节再转 Base64,WriteLine 后 Close。接收示例:ReadLine 后 `Convert.FromBase64String`,再 `Encoding.Unicode.GetString`,最后 MessageBox 显示。 3. **C# 基本数据类型**(表2-4):decimal、bool、char、byte、sbyte、short、int、long、ushort、uint、ulong、float、double、string,表中列出了各类型的位数和范围。 4. **变量**:区分大小写;以字母开头;只能由字母、数字、下划线和 @ 组成;不能与关键字或库函数同名(书中举 `char use;` 不合法、`char @use;` 合法,其实 use 不是 C# 关键字,这是书的错误)。作用域分字段、局部变量、循环变量。示例:全局 `int i=5`,`BtnShow_Click` 中弹出全局 i,再调 `showi()` 弹出局部 `i=1`。 5. **运算符**: - 表2-5 算术:+ - * / % ++ --。示例 a=8, b=3:a*b=24,a/b=2,a%b=2。 - 表2-6 比较:== != > < >= <=。 - 表2-7 逻辑:&& || !。 - 表2-8 赋值:= += -= *= /= %=。 6. **错误处理 try-catch**:除以 0 时程序会弹出“未经处理的异常…尝试除以零”的对话框(图2-8),加 try-catch 后改为 `MessageBox.Show("程序出现不可预知的错误:"+ex.Message)`。(书中误写为 `Catch(EventArgs ex)`,应为 `catch (Exception ex)`。) 7. **动态链接库文件**:DeviceCtrlLibrary.dll 和 WinFormsDeviceControlLibrary.dll 是编者提供的 dll,功能见“设备控制类库使用说明.doc”,使用前必须先加载、引用。 **能力拓展** *边想边做*(书 p34–36):做一个在 LED 上显示计算结果的小应用。参考界面图2-9,窗体标题“LED显示计算结果”: - 上方是 LED 控件。 - 中部 groupBox“简单计算器”:`[textBox1] + [textBox2] = [textBox3]` 和“计 算”按钮。 - 下部 groupBox“打开串口”:“串口”下拉框和“打开串口”按钮。 提示1:补全代码后,双击“计算”按钮,把代码输入代码编写窗口。 ```csharp private void btn_Calculation_Click(object sender, EventArgs e) { int add_1 = 0; //定义变量 int ____; //定义变量 int ____; //定义变量 int ____; //定义变量 string right = DeviceCtrlHelper.ledHelper.SendLedString("你答对了!"); string erro = DeviceCtrlHelper.ledHelper.SendLedString("你答错了!"); if (IsOpen) { if (textBox1.Text != "" && textBox2.Text != "" && textBox2.Text != "") { try { //将textBox1中的文本转换成数值 add_1 = int.Parse(textBox1.Text); add_2 = ____; add_3 = ____; counter = ____;//求和 if (counter == add_3) { if (right.IndexOf("Error") >= 0) { MessageBox.Show(right); //出错提示 } else { led.setLedText(right); //LED显示屏输出信息 MessageBox.Show("你答对了!"); } } else { if (erro.IndexOf("Error") >= 0) { ____; //出错提示 } else { ____; //LED显示屏输出信息 MessageBox.Show("你答错了!"); } } } catch (Exception) { //输入格式出错提醒 MessageBox.Show("请正确的格式。", "提醒"); } } else { //没有输入提醒 MessageBox.Show("请你输入!,", "提醒"); } } else { //没有设置串口提醒 MessageBox.Show("请设置好串口", "警告"); } } ``` (几点说明:第三个判断原文就是重复的 `textBox2.Text != ""`,应为 textBox3;变量名 add_2、add_3、counter 由后文推出;`IsOpen` 需要“打开串口”按钮来设置,书中没给这段代码;right/erro 在判断 IsOpen **之前**就调用了 SendLedString,串口未打开时仿真库可能返回 Error,也可能抛异常,模拟器需要定义这种行为。) 提示2:btn_Calculation 单击事件流程图(图2-10)的文字描述: - 开始,初始化变量,判断 IsOpen 是否为 true。 - 否:提示信息“串口参数没有设置好”,结束。 - 是:判断三个文本框是否都不为空。 - 否:提示信息“请你输入信息”,结束。 - 是:把三个文本框的文本转成数值,求和,再判断求和结果与输入的数据是否相等。 - 正确:判断串口通信是否正常。否则提示“串口出错信息”;正常则向 LED 屏输出正确信息,并弹出程序提示“你答对了”。 - 不正确:判断串口通信是否正常。否则提示“串口出错信息”;正常则向 LED 屏输出答错信息,并弹出程序提示“你答错了”。 - 所有分支都到“结束”。 *自主探究*(书 p36):给该模块增加乘法和除法计算功能。 ### 任务2 实时采集温度、湿度和光照度数据(书 p36–42 / PDF 39–45) **任务描述**:用 C# 写程序,把传感器采集的数据显示在计算机上。学生要学会使用 Label 标签和 Button 按钮。(有“扫码看视频”。) **任务实施** 1. 把模拟量采集器 ADAM4017 接到 PC 串口(没有的跳过)。 2. 新建 Windows 窗体应用程序“模拟量采集”(项目2\任务2)。 3. 把 DeviceCtrlLibrary.dll 和 API 目录下的全部 dll 复制到 bin\Debug。 4. 添加引用 DeviceCtrlLibrary.dll(没有硬件的读者添加仿真 dll,不加 API)。 5. 按图2-11 搭界面:左边 groupBox“数据显示”里有光照度、温度、湿度三组“标签+文本框”,右侧一个竖长的“获取”按钮;下方 groupBox“配置”里有“串口”下拉框和“开启”按钮。 6. 添加 using:`System.IO.Ports`、`DeviceCtrlLibrary`、`DeviceCtrlLibrary.Model`。 7. 定义全局变量 `bool IsOpen=false;` 和 `ADAM4017ResModel ResModel;`。 8. `frm_analog_Load`(窗体 Load 事件):清空 com_port,把 `SerialPort.GetPortNames()` 的结果全部加进去。 9. `btn_Open_Click`:如果 `!IsOpen`,执行 `DeviceCtrlHelper.adam4017Helper.Open(com_port.Text)`,按钮文字改为“关闭”,`IsOpen=true`;否则执行 `adam4017Helper.Close()`,按钮文字改为“开启”,`IsOpen=false`。 10. `btn_get_Click`:`ResModel = DeviceCtrlHelper.adam4017Helper.SetData();`,然后令 `txt_light.Text = ResModel.lightValue`、`txt_humidity.Text = ResModel.humidityValue`、`txt_temperature.Text = ResModel.temperatureValue`。 **小贴士**:ADAM4017 是研华科技的 16 位、8 通道模拟量输入模块,可以采集电压、电流等模拟量输入信号。 **表2-9 “模拟量采集”界面控件的属性** | 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 | |---|---|---|---|---| | frm_analog | 窗体 | Text | 模拟量采集 | | | grp_show | 组合框 | Text | 数据显示 | | | grp_config | 组合框 | Text | 配置 | | | txt_light | 文本框 | | | 输入/显示光照度值 | | txt_temperature | 文本框 | | | 输入/显示温度值 | | txt_humidity | 文本框 | | | 输入/显示湿度值 | | btn_get | 按钮 | Text | 获取 | 控制数据获取 | | btn_Open | 按钮 | Text | 开启 | 打开串口 | | com_port | 下拉列表框 | | | 显示串口 | | lbl_light | 标签 | Text | 光照度 | | | lbl_temperature | 标签 | Text | 温度 | | | lbl_humidity | 标签 | Text | 湿度 | | **知识提炼**(书 p39–41) 1. **Label 标签**:显示用户不能编辑的文本或图像,不能获得焦点。表2-10 常见属性有 Name、Text、Enabled、AutoSize、BackColor(可设为 Color.Transparent)、BorderStyle(None / FixedSingle / Fixed3D)、Visible、ForeColor、Font、Image、TextAlign(仅在 AutoSize=false 时有效)。 2. **Button 按钮**: - 表2-11 常见属性:Name、Text、Image、FlatStyle、DialogResult(OK、Cancel、Abort、Retry、Ignore、Yes、No)、Enabled、Visible、Font、BackColor、Image。 - 表2-12 常见事件(书中表头误写为“方法”):Click、MouseDown、MouseUp。 - 窗体的 **AcceptButton**(按 Enter 相当于点击该按钮)和 **CancelButton**(按 Esc 相当于点击该按钮)。 - 示例“Label显示示例”(项目2\任务2)见表2-13:Form1 的 Text 为“Label显示”;btnDisplay 是按钮,Text 为“显示”;label1 是标签。代码 `btnDisplay_Click` 中执行 `this.label1.Text = "显示内容";`,运行后 label1 显示“显示内容”(图2-12、2-13)。 **表2-13 “Label显示”界面控件的属性** | 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 | |---|---|---|---|---| | Form1 | 窗体 | Text | Label显示 | | | btnDisplay | 按钮 | Text | 显示 | | | label1 | 标签 | | | | **能力拓展** *边想边做*(书 p41–42) 1) 用标签和文本框实现图片的显示与隐藏(图2-14)。点“显示”按钮显示图片,点“隐藏”按钮隐藏图片,图片素材在资源文件“项目2\图片\”中。界面:窗体标题“练习”,大图区域(企鹅图片),下方“显示”和“隐藏”两个按钮。实现上可用 Label.Image 加 Visible,或用 PictureBox。**配套资源里没有“项目2\图片”文件夹,平台需要自备一张图片。** 2) 任务实施中已经能获取光照度、温度和湿度。要让模块实时获取这些环境值,可以用时间控件。按下面的提示实现: - 提示1:按表2-14 设置时间控件的关键属性(值由学生填)。 | 属性 | 值 | |---|---| | 名称 | timer1 | | Interval | ? | | Enabled | ? | 小贴士:Interval 表示多长时间执行一次 timer1_Tick 事件,单位是 ms,这里设多少比较合理? - 提示2:补全下面的代码。 ```csharp private void btn_get_Click(object sender,EventArgs e) { if(btn_get.Text=="获取") { timer1.Start(); //启动时间控件 btn_get.Text="____"; } else { timer1.____; btn_get.Text="____"; } } ``` - 提示3:参考“任务实施”中的代码,为 timer1 编写代码(即在 timer1_Tick 中调用 SetData 并刷新 3 个文本框)。 *自主探究*(书 p42):为模块增加以下功能: 1. 实时采集大气压的数值。 2. 每 2s 更新显示一次温度值、湿度值、光照度值和大气压值。 (仿真库的 `ADAM4017ResModel` 确实有 `airPressureValue`,还有 airQualityValue、CO2Value、windValue。`adam4017Helper.Open` 的签名为 `Open(string PortName, int Temperature_Port=0, int Humidity_Port=1, int Light_Port=2, int AirPressure_Port=3, int AirQuality_Port=4, int CO2_Port=5, int Wind_Port=6)`。所以这个练习在模拟器里可以做,模拟器应提供大气压数据。) ### 任务3 在LED显示屏中显示环境数据(书 p42–51 / PDF 45–54) **任务描述**:结合 LED 显示屏推送,把温湿度传感器和光敏传感器的数据推送到 LED 屏显示。学生要学会使用 comboBox 和 TextBox。(有“扫码看视频”。) **任务实施** 1. 把 ADAM4017 和 LED 显示屏接到串口服务器,再把串口服务器接到 PC。 2. 新建项目“LED推送模拟量”(项目2\任务3)。 3. 把自定义控件 WinFormsDeviceControlLibrary.dll 加入工具箱。 4. 复制 DeviceCtrlLibrary.dll 和 API 下的 dll 到 bin\Debug(没有硬件的读者用仿真 dll,不加 API)。 5. 添加引用 DeviceCtrlLibrary.dll。 6. 按图2-15 搭界面:顶部是黑色 LED 控件;左边 groupBox“数据显示”里有光照度、温度、湿度三个文本框和竖长的“获取”按钮;右边 groupBox“配置”里有“模拟量串口”下拉框、“LED串口”下拉框和“开启”按钮。 7. 添加 using:System.IO.Ports、DeviceCtrlLibrary、DeviceCtrlLibrary.Model。 8. 定义全局变量:`bool IsOpen=false;`、`ADAM4017ResModel ResModel;`、`String LED_MSG="";`。 9. `frm_LED_Load`:清空 com_adamPort 和 com_ledPort,用 foreach 把 GetPortNames 的结果同时加入两个下拉框。 10. `btn_Open_Click`:如果 `!IsOpen`,执行 `adam4017Helper.Open(com_adamPort.Text)` 和 `ledHelper.OpenLedCom(com_ledPort.Text)`,`btn_open.Text="关闭"`,`IsOpen=true`;否则执行 `adam4017Helper.Close()` 和 **`DeviceCtrlHelper.ledHelper.Close()`**,`btn_open.Text="开启"`,`IsOpen=false`。 11. `btn_get_Click`:先 `LED_MSG=""`,SetData 后填好 3 个文本框,再拼接 `LED_MSG += ("当前环境信息"+" "+"光照度:"+ResModel.lightValue+"lx温度:"+ResModel.temperatureValue+"℃湿度:"+ResModel.humidityValue+"%");` 然后 `String Res = DeviceCtrlHelper.ledHelper.SendLedString(LED_MSG);`,`if(!(Res.IndexOf("Error")>=0)) led.setLedText(Res);` **表2-15 “LED推送模拟量”界面控件的属性** | 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 | |---|---|---|---|---| | frm_LED | 窗体 | Text | LED推送模拟量 | | | grp_show | 组合框 | Text | 数据显示 | | | grp_config | 组合框 | Text | 配置 | | | txt_light | 文本框 | | | 输入/显示光照度值 | | txt_temperature | 文本框 | | | 输入/显示温度值 | | txt_humidity | 文本框 | | | 输入/显示湿度值 | | btn_get | 按钮 | Text | 获取 | 控制数据的获取 | | btn_Open | 按钮 | Text | 开启 | 打开串口 | | com_adamPort | 下拉列表框 | | | 显示串口 | | com_ledPort | 下拉列表框 | | | 显示串口 | (表中没有列 LED 控件,按代码它应叫 `led`;界面上也有光照度、温度、湿度和“模拟量串口”“LED串口”这些标签,表中同样没列。) **知识提炼**(书 p45–49) 1. **comboBox 下拉列表框**:点下三角或按 F4 展开,展开或折叠时触发 DropDownOpened / DropDownClosed。可以用 Items 静态添加,也可以用 `Items.Add` 动态添加,用 `Items.Clear` 清空。Text 属性是当前选中的内容,DropDownStyle 控制样式。 - 示例“下拉设置字体样式”(项目2\任务3),图2-16 窗体“字体样式”:标签“字体样式”加下拉框,下方 label2 显示“字体[宋体]”。 - `Form1_Load`:`String[] type = {"宋体","黑体","楷体"}`,foreach 后执行 `this.comboBox1.Items.Add(items)`,再 `SelectedIndex = 0`。 - `comboBox1_SelectedIndexChanged`(书上写“找到 Click 事件”,实际是 SelectedIndexChanged):switch 0/1/2 时执行 `this.label2.Font = new Font(comboBox1.Items[i].ToString(), 12); this.label2.Text = "字体["+ comboBox1.Items[i].ToString()+"]";` - 运行效果见图2-17,例如“字体[黑体]”。 **表2-16 各控件相关属性及命名** | 控件名称 | 控件类型 | 属性名 | 属性值 | |---|---|---|---| | Form1 | 窗体 | Text | 字体样式 | | label1 | 标签 | Text | 字体样式 | | label2 | 标签 | Text | 字体[字体名] | | comboBox1 | 下拉列表框 | Items | “宋体”“黑体”“楷体” | (表里已经静态设了 Items,Form1_Load 又动态加了一遍,会出现重复项,属于书的错误。模拟器要能渲染 宋体、黑体、楷体 字体,即 `new Font(name, size)`。) 2. **TextBox 文本框**: - 表2-17 常见属性:Text(默认最多 2048 字符,MultiLine 时 32KB)、MaxLength、MultiLine、HideSelection、ReadOnly、PasswordChar、ScrollBars、SelectionLength、SelectionStart、SelectedText、Lines、Modified、TextLength、WordWrap。 - 表2-18 常见方法:AppendText、Clear、Focus、Copy、Cut、Paste、Undo、ClearUndo、Select(start,length)、SelectAll。 - 表2-19 常见事件:GotFocus、LostFocus、TextChanged、Click。 - 示例“添加用户”(项目2\任务3),图2-18:账号、密码、“添加用户”按钮和多行“用户信息”框。`Form1_Load` 中执行 `this.tbox_Password.UseSystemPasswordChar = true;`。`btn_Add_Click` 中执行 `this.txtUserMessage.Text += "账号:"+user+"\t"+"密码:"+pass+"\r\n";`,书中 **user/pass 没有定义**,应先取 `tbox_Username.Text` 和 `tbox_Password.Text`。运行效果见图2-19,例如“账号:test 密码:123”。 **表2-20 各控件相关属性及命名** | 控件名称 | 控件类型 | 属性名 | 属性值 | |---|---|---|---| | Form1 | 窗体 | Text | 添加用户 | | label1 | 标签 | Text | 账号: | | label2 | 标签 | Text | 密码: | | label3 | 标签 | Text | 用户信息: | | tbox_Username | 文本框 | | | | tbox_Password | 文本框 | UseSystemPasswordChar | true | | txtUserMessage | 文本框 | | | | btn_Add | 按钮 | Text | 添加用户 | (txtUserMessage 应设 Multiline=true。) **能力拓展** *边想边做*(书 p49–50):制作用户登录界面,点“登录”按钮后用 messagebox 显示用户名和密码(图2-20:窗体“登录界面”,标题“用户登录界面”,用户名和密码各一个文本框,一个“登录”按钮)。提示:设置密码文本框的相关属性。 **表2-21 各控件的属性**(书中原表就有问题,照录) | 控件名称 | 控件类型 | 属性名 | 属性值 | |---|---|---|---| | Form1 | 窗体 | Text | 登录界面 | | txtUsername | 文本框 | Text | 用户名: | | txtPassword | 文本框 | Text | 密码: | | label_title | 标签 | Text | 用户登录界面 | | tbox_Username | 文本框 | | | | btnLogin | 按钮 | Text | 登录 | (txtUsername 和 txtPassword 实际应是**标签**;表中漏了 `tbox_Password`(文本框),代码里用到了它。) 补充代码: ```csharp private void btnLogin_Click(object sender, EventArgs e) { MessageBox.Show("用户名:"+this.tbox_Username.Text+"\r\n"+"密码:"+this.tbox_Password.Text); } private void Form1_Load(object sender, EventArgs e) { this.tbox_Password.UseSystemPasswordChar = true; //设置文本框的UseSystemPasswordChar属性为True } ``` 运行效果见图2-21:输入 test 和 ***,弹窗显示“用户名:test / 密码:123”。 *自主探究*(书 p50–51):增加功能:用户名为“admin”且密码为“123456”时提示“登录成功”,否则提示“登录失败”(图2-22、图2-23)。注意图中输入的是 user1,看起来与题目要求不一致。 ### 项目2 项目评价(书 p51–52 / PDF 54–55) **1. 项目评价指标**(没有分值) | 任务 | 评价指标 | 具体要求 | |---|---|---| | 任务1 | LED显示屏的使用 | 按照教程完成界面和功能 | | 任务1 | 制作LED显示计算结果 | 完成界面设计 | | 任务1 | 制作LED显示计算结果 | 完成程序功能设计 | | 任务2 | 窗体界面 | 界面布局合理,美观大方,符合使用者习惯 | | 任务2 | 功能实现 | 环境值能正确获取 | | 任务2 | 功能实现 | 环境值能正确显示 | | 任务2 | 功能实现 | 实时时间控制正常 | | 任务3 | 窗体界面 | 界面布局合理,美观大方,符合使用者习惯 | | 任务3 | 功能实现 | LED屏能正常显示 | | 任务3 | 功能实现 | 环境值能正确推送到LED屏 | | 任务3 | 功能实现 | 能实时推送信息 | | 职业素养 | 注释书写规范 | 做到清晰、准确地描述内容。对于程序中复杂的部分必须有注释加以说明。注释量要适中,避免过多或过少导致阅读困难 | | 职业素养 | 变量命名规范 | 选择有意义的英文进行命名,使变量名能清楚地表达其含义。尽量少使用人名、地名和汉语拼音。应使用那些约定俗成的缩写。多个单词组成的变量名,每个单词的首字母应该大写 | | 职业素养 | 界面常用控件命名约定 | 窗体控件命名以控件名称缩写前3个字母开头,以具有代表含义的词来命名,其形式为:控件缩写(前3个字母)+命名(具有代表含义的单词) | **2. 个人学习总结**:“项目学习情况:”和“心得与反思:”两块空白。 ### 项目2:书与参考工程的差异,以及模拟器要注意的点 - **串口列表的填充时机不同**:书中任务1 在 `com_Port_Click` 里填,任务2、3 在窗体 `Load` 里填;参考工程三个任务都在下拉框的 **Click** 事件里填(任务1 的 handler 名是 `com_port_Click`,绑定到 `com_Port.Click`)。**模拟器的 ComboBox 必须支持 Click 事件**,要在点击、展开之前触发,这样 Items 才能在展开前更新。`SerialPort.GetPortNames()` 需要返回虚拟串口列表,例如 COM1–COMn。 - **IsOpen 写法不同**:书中是 `bool IsOpen=false` 加 `if(!IsOpen){...IsOpen=True}`;参考工程是 `bool IsOpen = true`,先 `IsOpen = !IsOpen` 再判断,事件内容都包在 try/catch 中。书上写成了 `True/False`(首字母大写),C# 编译会报错,模拟器的编译错误提示要能指出这一点。 - **控件名差异**: - 任务2、3 参考工程的窗体名都是 Form1(书为 frm_analog / frm_LED)。 - 组合框名是 groupBox1/2(书为 grp_show / grp_config),标签是 label1–5(书为 lbl_*)。 - 任务3 的按钮在书上和参考工程里都叫 **`btn_open`**(小写 o),但表2-15 写的是 btn_Open。 - LED 自定义控件实例名为 **`led`**(类型 `WinFormsDeviceControlLibrary.LED`),表2-1 写的是“LED”。 - **`DeviceCtrlHelper.ledHelper.Close()`**:书中任务3 第10步调用了它,但参考工程没有调用,《设备控制类库使用说明》中 LED 部分也只列了 `OpenLedCom(string)` 和 `SendLedString(string)`,仿真 dll 很可能没有这个方法。模拟器可以二选一:提供一个空实现以兼容书上的代码,或者给出明确的编译错误。建议提供空实现。 - **SendLedString 的约定**:成功时返回传入的字符串,失败时返回 `"Error:发送失败"`(见使用说明)。LED 控件 `setLedText(string)` 负责显示,控件内部还有 `LedTimer`,推测会滚动显示。 - **工具箱里的 4 个自定义控件**:Alarm、Lamp、LED、ShowMsg(图2-5)。ShowMsg 在本范围内没有出现,从 dll 符号看它包含 txt_showMsg、btn_red、btn_green、btn_Clear 和 AddText,后续项目可能用到。 - 任务2 能力拓展需要 **Timer**(Interval、Enabled、Start/Stop、Tick)和图片显示(Label.Image 或 PictureBox,以及 Visible)。 - 任务3 知识提炼需要 `Font` 构造函数和中文字体,还需要 `UseSystemPasswordChar`、`\t` 制表符的显示,以及多行 TextBox。 - 知识提炼中的 SerialPort 直接收发示例(Base64 + Unicode)超出了“学生不学协议”的范围,可以只作阅读材料。如果要能运行,需要模拟 `new SerialPort()`、Open、WriteLine、ReadLine 和 Close。 --- ## 项目3 智慧校园系统——智慧路灯控制模块(书 p53–72 / PDF 56–75,本部分只覆盖到书 p62) **项目概述**:ZigBee 是一种近距离、低复杂度、低功耗、低速率、低成本的双向无线通信技术。智慧路灯控制模块借助 ZigBee 远程控制路灯开关。 **学习目标** - 了解智慧路灯控制模块在提升校园管理效率中的作用与重要性。 - 熟悉 ZigBee 协调器、串口服务器等硬件设备的连接与配置方法。 - 掌握 C# 在物联网编程中的应用,包括窗体应用程序的创建、控件的使用与事件处理等。 - 了解并学会使用 DeviceCtrlLibrary 等自定义控件及 API 接口。 - 具备环境监测与联动控制的能力。 - 具备良好的团队合作精神与沟通能力,能够与团队成员有效协作,共同完成任务。 - 面对智慧路灯控制系统开发中遇到的问题,能够独立思考、分析问题,并提出有效的解决方案。 ### 任务1 使用ZigBee技术控制路灯开关(书 p53–58 / PDF 56–61) **任务描述**:用 C# 写程序,通过 ZigBee 无线技术实现路灯的无线开关控制。学生要了解 ZigBee 技术,学会使用 if 语句。(有“扫码看视频”。) **任务实施** 1. 把 ZigBee 协调器的串口接到 PC(没有硬件的跳过)。 2. 新建项目“ZigBee控制路灯”,保存位置为 项目3\任务1。参考工程和后文路径都叫“ZigBee路灯”。 3. 把自定义控件 WinFormsDeviceControlLibrary.dll 加入工具箱。 4. 复制 DeviceCtrlLibrary.dll 和 API 下的 dll 到 bin\Debug。 5. 添加引用 DeviceCtrlLibrary.dll(没有硬件的读者用仿真 dll)。 6. 按图3-1 搭界面:左边是灯泡(Lamp 控件),下面有“开”“关”两个**单选按钮**;右边 groupBox“配置”里有“路灯”下拉框和“打开”按钮。 7. 添加 using:`System.IO.Ports`、`DeviceCtrlLibrary`。 8. 定义方法: ```csharp //控制开关灯 private void LampOnOff(int RelayNum, bool IsOpen) { DeviceCtrlHelper.zigBeeHelper.OnOff(RelayNum, IsOpen); lamp1.OnOff(IsOpen); } ``` 9. `Form1_Load`:清空 com_port,再加入 GetPortNames 的结果。 10. `btn_Open_Click`:如果 `btn_open.Text.Equals("打开")`,执行 `zigBeeHelper.Open(com_port.Text)` 并把按钮文字改为“关闭”;`else if (btn_open.Text.Equals("关闭"))` 时执行 `zigBeeHelper.Close()` 并改回“打开”。 11. “开”单选按钮的 CheckedChanged:`rb_Open_CheckedChanged` 中调用 `LampOnOff(1, true)`。 12. “关”单选按钮的 CheckedChanged:调用 `LampOnOff(1, false)`。书中这个方法名**误写为 rb_Open_CheckedChanged**,应为 `rb_Close_CheckedChanged`。 **表3-1 “ZigBee路灯”界面控件的属性** | 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 | |---|---|---|---|---| | Form1 | 窗体 | Text | ZigBee路灯 | | | groupBox1 | 组合框 | Text | 配置 | | | lamp1 | 自定义路灯控件 | | | | | rb_Open | 单选按钮 | Text | 开 | 开灯 | | rb_Close | 单选按钮 | Text | 关 | 关灯 | | com_port | 下拉列表框 | | | 获取PC串口 | | btn_Open | 按钮 | Text | 打开 | 打开串口 | | lbl_Lamp | 标签 | Text | 路灯 | | **知识提炼**(书 p56–57):**if 语句** - 两种形式:`if(expression) statement` 和 `if(expression) statement else statement`。 - 示例:`if(i>1) y=x/i; else { x=i; y=x*x; }`。 - 悬空 else:没有大括号时,else 与最近的、还没有 else 的 if 关联;加大括号后 else 属于外层 if。 **能力拓展** *边想边做*(书 p57–58) 1) **闰年判断**:在文本框输入年份,点“判断”按钮。是闰年则在右边标签显示“是闰年”,否则显示“不是闰年”。界面见图3-2 到图3-4:窗体标题“闰年判断”,标签“请输入年份”,一个文本框,右侧标签初始为“是否闰年?”,下方“判断”按钮。示例:1700 显示“不是闰年”,2000 显示“是闰年”。 - 提示1:字符型数值不能直接赋给数值型变量,要先转换,如 `int a = int.Parse("12")` 或 `int a = Convert.ToInt16("12")`。 - 提示2:闰年条件是能被 4 整除但不能被 100 整除,或者能被 400 整除。 2) **路灯按时间自动开关**:要求 18:00 到次日 7:00 开灯,其余时间关灯。用时间控件 Timer(从工具箱选取)完成,每分钟判断一次;程序运行后,打开串口时开启时间控件。 - 提示1:按表3-2 设置时间控件的关键属性(值留空由学生填)。 | 属性 | 值 | |---|---| | 名称 | timer1 | | Interval | ____ | | Enabled | ____ | - 提示2:补全代码,再双击时间控件 timer1 进入代码窗口输入。 ```csharp private void timer1_Tick(object sender, EventArgs e) { int h = DateTime.Now.Hour;//获取当前系统时间小时数 if( ____ ) //判断时间 ____ //开关灯 else ____ //开关灯 } ``` 参考答案:Interval=60000;`h >= 18 || h < 7`;`LampOnOff(1, true);` / `LampOnOff(1, false);`。**模拟器要用到 `DateTime.Now`,最好允许在模拟器里设置或加速虚拟时钟,否则学生白天测不到开灯。** *自主探究*(书 p58):为模块增加以下功能: 1. 实时采集大气压的数值。 2. 根据大气压控制路灯开关。 ### 任务2 实现人体红外传感器和路灯联动(书 p58–? / PDF 61–?,本部分读到书 p62) **任务描述**:人体红外传感器基于多普勒原理,人靠近感应区域时,开关会无接触、无压力、无火花地迅速发出电气指令。本任务模拟楼梯灯:有人经过时楼梯灯亮,无人时自动熄灭。学生要学会使用单选按钮和复选框,理解数组的作用。(有“扫码看视频”。) **任务实施** 1. 把数字量采集器 ADAM4150 和 ZigBee 协调器接到串口服务器,再把串口服务器接到 PC。 2. 新建项目“人体红外ZigBee联动”(项目3\任务2)。 3. 添加自定义控件 WinFormsDeviceControlLibrary.dll。 4. 复制 DeviceCtrlLibrary.dll 和 API 下的 dll 到 bin\Debug。 5. 添加引用 DeviceCtrlLibrary.dll(没有硬件的读者用仿真 dll)。 6. 再添加一次自定义控件(书中这一步与第3步重复)。 7. 按图3-5 搭界面:左边是灯泡(lamp1),下方“开”“关”两个按钮;右边 groupBox“配置”里有“路灯”下拉框、“环境采集”下拉框、“人体开关”复选框和“打开”按钮。 8. 添加 using:`System.IO.Ports`、`DeviceCtrlLibrary`。 9. `Form1_Load`:分别清空 com_lamp 和 com_environment,并用 GetPortNames 填充。 10. `LampOnOff(int RelayNum, bool IsOpen)`:与任务1 相同,即 `zigBeeHelper.OnOff` 加 `lamp1.OnOff`。 11. `btn_open_Click`:与任务1 相同,按 Text 在“打开”和“关闭”之间切换,用的是 `zigBeeHelper.Open(com_lamp.Text)` 和 `Close()`。 12. `ch_bodyInfrared_Click`(复选框“人体开关”的 **Click** 事件): - 如果 `ch_bodyInfrared.Checked`:执行 `DeviceCtrlHelper.adam4150Helper.Open(com_environment.Text)`,`timer1.Start()`,并把 `btn_lampOpen.Enabled` 和 `btn_lampClose.Enabled` 设为 false(禁用手动开关)。 - 否则:执行 `adam4150Helper.Close()`,`timer1.Stop()`,两个按钮的 Enabled 设回 true。 13. `btn_lampOpen_Click`:调用 `LampOnOff(1, true)`。 14. `btn_lampClose_Click`:调用 `LampOnOff(1, false)`。书中第14步标题误写为选中“开”按钮。 15. `timer1_Tick`(书上写成“双击 timer1 进入 Click 事件”,实际是 Tick): ```csharp //时间控件实时判断是否感应到人,如果感应到人则开路灯,否则关闭 private void timer1_Tick(object sender, EventArgs e) { if(DeviceCtrlHelper.adam4150Helper.SetData().bodyInfraredValue) LampOnOff(1, true); else LampOnOff(1, false); } ``` **表3-3 “人体红外ZigBee联动”界面控件的属性** | 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 | |---|---|---|---|---| | Form1 | 窗体 | Text | 人体红外ZigBee联动 | | | groupBox1 | 组合框 | Text | 配置 | | | lbl_lamp | 标签 | Text | 路灯 | | | lbl_environment | 标签 | Text | 环境采集 | | | com_lamp | 下拉列表框 | | | 显示路灯串口 | | com_environment | 下拉列表框 | | | 显示环境串口 | | ch_bodyInfrared | 复选框 | Text | 人体开关 | 控制人体红外传感器是否启动 | | btn_Open | 按钮 | Text | 打开 | 打开串口 | | lamp1 | 自定义路灯控件 | | | | | btn_lampOpen | 按钮 | Text | 开 | 手动控制开灯 | | btn_lampClose | 按钮 | Text | 关 | 手动控制关灯 | | timer1 | 时间控件 | Interval | 5000 | | | timer1 | 时间控件 | Enabled | False | | **知识提炼**(书 p61– ,本部分读到 p62) 1. **RadioButton 单选按钮**:可以显示文本、Image 或两者。同一容器(Form、GroupBox、Panel)中的 RadioButton 构成一组,组内互斥;CheckBox 可以多选。Checked 获取或设置状态,Appearance 可以把外观设成切换式按钮或标准选项按钮。表3-4 常见属性:Appearance、AllowDrop、AutoCheck、AutoSize、BackColor、BackgroundImage、BackgroundImageLayout、Bottom、CanFocus、CanSelect、Checked、Enabled、Focused、ForeColor、FontHeight、Image、Text、TextAlign、Visible。 - **单选按钮示例**“单选按钮”(项目3\任务2),图3-6 窗体“设置字体颜色”:左侧竖排 4 个单选按钮(黑色、红色、蓝色、黄色),右侧 label1 显示“字体颜色[黑色]”。 **表3-5 “设置字体颜色”窗体界面各控件相关属性及命名** | 控件名称 | 控件类型 | 属性名 | 属性值 | |---|---|---|---| | Form1 | 窗体 | Text | 设置字体颜色 | | radioButton_Black | 单选按钮 | Text | 黑色 | | radioButton_Red | 单选按钮 | Text | 红色 | | radioButton_Blue | 单选按钮 | Text | 蓝色 | | radioButton_Yellow | 单选按钮 | Text | 黄色 | | label1 | 标签 | Text | 字体颜色[颜色名] | **>>> 本部分在此结束(PDF 65 / 书 p62)。下一页(PDF 66 / 书 p63)应接着给出该示例的代码步骤,然后是 CheckBox 复选框知识点、任务2 的能力拓展和自主探究,以及项目3 评价(书 p71 / PDF 74)。<<<** ### 项目3(到书 p62 为止):书与参考工程的差异,以及模拟器要注意的点 - **任务1 控件不同**:书中用单选按钮 `rb_Open/rb_Close` 和 CheckedChanged 开关灯;参考工程 `项目3/任务一/ZigBee路灯` 用的是**按钮** `btn_lampOpen/btn_lampClose` 和 Click,与任务2 一样。参考工程没有 rb_*,groupBox 名为 `groupbox1`(小写 b),标签 `label1` 的 Text 为“路 灯”。另外书中表3-1 写 `btn_Open`,代码写 `btn_open`,参考工程也是 `btn_open`。 - 用单选按钮做开关时要注意:CheckedChanged 在选中和取消选中时**都会**触发。rb_Open 被取消(因为选了 rb_Close)时,如果只写 `LampOnOff(1,true)` 就会出问题。书上的写法只在“两个 handler 各自绑定、不判断 Checked”的情况下“恰好能用”:选“关”时两个事件都会触发,先触发谁决定最终状态。**模拟器必须按真实 WinForms 顺序触发 CheckedChanged**:先是被取消的那个,再是被选中的那个。这样书上的代码会在“开灯”和“关灯”各执行一次后停在正确状态。 - 参考工程中串口列表在 `com_lamp_Click` / `com_environment_Click`(ComboBox 的 Click)中填充,书中则在 Form1_Load 中填充。参考工程的所有 handler 都包了 try/catch。 - 参考工程的 timer1 是 Interval=5000,Enabled 为默认 false,与表3-3 一致。 - `zigBeeHelper.OnOff(RelayNum, IsOpen)`:使用说明写继电器通道为 0~1,书和参考工程都用 1。`zigBeeHelper.Close()` 在使用说明里误拼为 `Colse()`,仿真 dll 里实际是 `Close`。 - `adam4150Helper.SetData()` 返回 `ADAM4150ResModel`,包含 bool 型的 bodyInfraredValue、fireValue、smokeValue、infraredValue。`adam4150Helper.Open(string PortName, int BodyInfrared_Port=0, int Fire_Port=1, int Smoke_Port=2, int Infrared_Port=3)`;另有 `adam4150Helper.OnOff(int DO, bool IsOpen)`(DO 0~4)。**模拟器要提供“人体红外”的虚拟触发开关,学生用它模拟有人经过。** - `Lamp` 控件的方法是 `lamp1.OnOff(bool)`,图片资源在 `开发文档/图片资源/控件图片/LampOn.png` 和 `LampOff.png`。 - 复选框用的是 **Click** 事件而不是 CheckedChanged。Click 触发时 Checked 已经切换过了,模拟器要保证这个时序。 - 任务1 能力拓展用到 `DateTime.Now.Hour`;自主探究“根据大气压控制路灯”要用 adam4017 的 airPressureValue(string 类型,要 Parse),所以需要同时打开 ADAM4017 和 ZigBee 两个虚拟串口。