第 0 阶段:教材项目 2 任务 1 在网页中原样运行

- runtime:仿真 WinForms 子集(Form、Button、Label、TextBox、ComboBox、
  GroupBox、Timer、MessageBox、界面线程消息循环、跨线程检查)、
  教材设备类库(DeviceCtrlHelper、LED 控件)、运行器(常驻编译服务 + 运行模式)
- server:Bun + Hono,WebSocket 会话,每次运行一个进程,消息转发
- web:Vue + Naive UI,WinForms 风格窗体渲染、MessageBox、实训台 LED 屏
- 端到端测试与 100 个程序并发的压力测试
- 教材逐任务摘要(docs/教材摘要)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-22 10:15:14 +08:00
commit 98be8331bc
71 files changed
+7919

No files matched your search

@@ -0,0 +1,857 @@
# 《物联网编程与应用 第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<int,string>`,内容为 {1:"张三", 2:"李四", 3:"Mary", 4:"Jimmy"};再建 `List<People>`,放 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\<T\>**:表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<key,value> DemoDic = new Dictionary<key,value>();`。示例用到 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<Shape> list = new List<Shape>();`。)
*自主探究*(书 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 两个虚拟串口。
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,882 @@
# 《物联网编程与应用 第2版》摘要 第3部分(PDF 第126–187页)
## 页码对照
| PDF 页 | 书页 | 内容 |
|---|---|---|
| 126–127 | 124–125 | 项目6 任务2(中途开始)末尾:步骤16运行结果;知识提炼 1–4 |
| 128–129 | 126–127 | 项目6 任务2 知识提炼5、能力拓展;项目6 任务3 开始 |
| 130–132 | 128–130 | 项目6 任务3 实施 + 知识提炼 |
| 133 | 131 | 项目6 项目评价 |
| 134 | 133(无页码) | 项目7 首页 + 任务1 任务描述(书第132页应是空白页,扫描件里没有) |
| 135–141 | 134–140 | 项目7 任务1 |
| 142–149 | 141–148 | 项目7 任务2(PDF 149 / 书148 上半页是任务2的自主探究) |
| 149–156 | 148–155 | 项目7 任务3 |
| 157 | 156 | 项目7 项目评价 |
| 158 | 157(无页码) | 项目8 首页 + 任务1 任务描述 |
| 159–160 | 158–159 | 项目8 任务1;任务2 开始 |
| 160–165 | 159–164 | 项目8 任务2 |
| 165–175 | 164–174 | 项目8 任务3(书174上半页) |
| 175–181 | 174–180 | 项目8 任务4 |
| 182–186 | 181–185 | 项目8 任务5 + 项目评价(书185) |
| 187 | 186 | 项目8 项目评价(续)+ 个人学习总结 |
页码规则:PDF 126–133 时,书页 = PDF − 2;PDF 134–187 时,书页 = PDF − 1。
**扫描件到 PDF 187(书186)就结束了,没有「参考文献」页,也没有封底。**
---
## 项目6 智慧校园系统——数据存储模块(只覆盖结尾部分)
### 任务2(中途开始;任务名称在我负责的范围之前)
PDF 126 开头是「修改」按钮 Click 代码的结尾 `MessageBox.Show("修改失败:" + ex.ToString()); } }`,接着是**步骤16)运行该程序**,图6-11「运行结果」。窗体标题是「校园灾情数据存储」,各区域如下:
- 「配置」组:串口下拉框(COM1)+「关闭」按钮
- 「数据显示」组:光照度/温度/湿度文本框,「获取」「保存」「修改」「删除」按钮
- 「历史记录」组:DataGridView,列为 编号/温度/湿度/光照度/时间
从项目评价表看,这个任务的内容是显示光照度、温度、湿度,并把数据存进数据库。参考工程是 `项目6/任务二/校园数据存储`,与书上一致:btn_Open / btn_get / btn_save / btnUpdate / btnDelete。
#### 知识提炼(书124–126)
1. **insert 插入语句**:`insert into 表名 (列1, 列2, ...) values (值1, 值2, ...);`。示例表 user 有 id(自增主键)、username varchar(50)、birthday date 三个字段:
- 完整插入 `insert into user (id, username, birthday) values (1, '张三', '1999-09-09');`
- 部分插入 `insert into user (username) values ('李四');`(id 自增)
- 简写插入 `insert into user values ('王五', '1910-10-10');`(按字段顺序填写,跳过自增 id)
2. **update 更新语句**:`update 表名 set 列1=新值1, 列2=新值2, ... [where 条件];`。示例:`update user set birthday='2016-01-01' where username='张三';`;还有 T-SQL 特有写法 `update top (3) user set birthday='2000-01-01';`
3. **delete 删除语句**:`delete from 表名 [where 条件];`。示例:`delete from user where username='张三';`、`delete top (5) from user;`、`delete from user;`
4. **ADO.NET 对象模型简介**:ADO.NET 由「.NET 数据提供程序」和「数据集 DataSet」两部分组成。表6-2 ADO.NET 核心组件:
| 对象 | 说明 | 特点 |
|---|---|---|
| Connection | 建立与特定数据库的连接 | 可以自己创建,也可由其他对象自动产生(如 DataAdapter) |
| Command | 对数据源执行命令 | 通过 Connection 对象下达命令,Connection 的指向决定操作对象 |
| DataAdapter | 从数据源中检索数据并填充 DataSet 中的表 | DataAdapter 填充到 DataSet 中的是断开连接的脱机数据 |
| DataReader | 从数据库中检索只读的数据流 | DataReader 读取数据必须在维持数据库连接的前提下进行 |
| DataSet | 在内存中缓存从数据源中检索到的数据 | 独立于其他数据源 |
5. **ADO.NET 中的 SQL 操作类**
- SqlConnection:表示一个打开的 SQL Server 连接,不能被继承;OLE DB 数据源要用 OleDbConnection。有两个构造函数:`SqlConnection()` 和 `SqlConnection(String 连接字符串)`。用完后关闭。
- SqlCommand:执行 T-SQL 语句或存储过程。有四个构造函数:`()`、`(String)`、`(String, SqlConnection)`、`(String, SqlConnection, SqlTransaction)`。
- SqlDataReader:从上往下读取只读数据。必须通过 `SqlCommand.ExecuteReader()` 创建,不能直接用构造函数。使用期间连接被它独占,必须调用 `Close()` 后连接才能做其他操作。
#### 能力拓展
**边想边做**(书126):
> 通过「任务实施」的操作步骤,对已经实现了的环境数据添加一个图片框,以获取监控画面,并将环境数据与监控画面每5min添加入数据库。环境数据界面如图6-12所示。
图6-12「环境数据界面」,窗体标题「校园灾情数据存储」:
- 左上「数据显示」组:光照度、温度、湿度 3 个文本框;「获取」「保存」按钮
- 左中「配置」组:串口下拉框 +「开启」按钮
- 右侧「监控图像采集」组:一块大图片框
- 底部「历史记录」组:DataGridView(空)
**自主探究**(书127):
> 在「边想边做」的基础上,为程序增加下列功能:添加一个文本框 TextBox(Name 属性值为 lightValue)用于输入更新条件,当表中「light」的值超过 TextBox 的值时,将数据添加入数据表。
### 任务3 查询校园安防数据(书127–130 / PDF 129–132)
**任务描述**:在任务1、任务2的基础上,实现灾情数据按时间段查询。学生通过这个任务熟悉 select 语句以及 DateTimePicker、DataGridView 两个控件。
**任务实施**
1. 按图6-13「查询图片窗体界面」建 Form1(标题「查询校园灾情数据」):顶部一行「开始日期 [DateTimePicker] 结束日期 [DateTimePicker] [查询]」,下面「历史记录」组框里放 DataGridView。
表6-3 查询图片窗体界面控件属性:
| 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|---|
| LblStartTime | 标签 | Text | 开始日期 | 开始日期 |
| LblEndTime | 标签 | Text | 结束日期 | 结束日期 |
| StartTime | 日期时间控件 | Datetime | 时间 | 起始时间 |
| EndTime | 日期时间控件 | Datetime | 时间 | 结束时间 |
| btnCx | 按钮 | Text | 查询 | 查询 |
| groupBox4 | 组合框 | Text | 历史记录 | 组合框 |
| dataGridView1 | 数据网格控件 | | | |
2. 引用 `System.Data.SqlClient`、`System.IO.Ports`。
3. 声明 5 个全局变量:连接字符串 `sqlConnectString`、`SqlConnection mySqlConnection`、`SqlCommand mySqlCmd`、`SqlDataAdapter sqldata`、`DataSet ds`。
4. 写 btnCx_Click:打开连接,执行 `select ID as '编号',temperature as '温度',humidity as '湿度',light as '光照度',addtime as '时间' from CampusEnvironment where addtime BETWEEN '<StartTime.Value>' AND '<EndTime.Value>'`,用 `SqlDataAdapter.Fill(ds)` 填充,再 `dataGridView1.DataSource = ds.Tables[0].DefaultView`,最后关闭连接。
5. 运行,选好起止日期后点「查询」,结果见图6-14(示例数据为 2024/1/16–2024/1/23 共 9 行)。
**知识提炼**(书128–130)
1. **select 查询语句**:`SELECT 列名称 FROM 表名称` 或 `SELECT * FROM 表名称`。小贴士:SQL 对大小写不敏感。示例:`SELECT * FROM user`、`SELECT id,username FROM user`。表6-4 where 子句运算符:
| 运算符 | 描述 | 实例 |
|---|---|---|
| = | 等于 | 查询表中姓名为张三的生日数据:`SELECT * FROM user where username='张三'` |
| <> | 不等于。在 SQL 的一些版本中可写成 != | 查询表中姓名不是张三的生日数据:`SELECT * FROM user where username<>'张三'` |
| > | 大于 | 查询表中生日为2000年后的数据:`SELECT * FROM user where year(birthday)>2000` |
| < | 小于 | 查询表中生日为2000年前的数据:`... where year(birthday)<2000` |
| >= | 大于等于 | 查询表中生日为2000年(含2000)后的数据:`... where year(birthday)>=2000` |
| <= | 小于等于 | 查询表中生日为2000年(含2000)前的数据:`... where year(birthday)<=2000` |
| between | 在某个范围内 | 查询表中生日为2000—2006年之间的数据:`... where year(birthday) between 2000 in 2006`(书上原文就是 `in`,正确写法应为 `and`) |
| like | 搜索某种模式 | 查询表中姓张的数据:`SELECT * FROM user where username like '张%'` |
逻辑运算符:AND 要求所有条件都成立,OR 只要一个条件成立即可。
2. **SqlDataAdapter 类**:DataSet 与 SQL Server 之间的桥接器,通过 Fill(让 DataSet 跟数据源一致)和 Update(让数据源跟 DataSet 一致)工作。
3. **DataSet 类**:内存中的临时数据库,包含 DataTable、DataView。第 i 行第 j 列的访问方式是 `ds.Tables[0].Rows[i].ItemArray[j]`,索引从 0 开始。
4. **DataGridView 控件**:表格形式显示和编辑数据;绑定时设置 DataSource,数据源含多个表时再设置 DataMember。
5. **DateTimePicker 控件**:Format 枚举(DateTimePickerFormat)可取 Long / Short / Time / Custom,选 Custom 时用 CustomFormat 字符串;Text 返回文本,Value 返回 DateTime;MinDate / MaxDate 限制范围;点下箭头弹出日历;ShowUpDown 显示上下箭头。三种常用设置:
- 只显示时间:Format=Time,ShowUpDown=true
- 只显示时分(如 7:30):Format=Custom,CustomFormat=`HH:mm`
- 显示日期和时间:Format=Custom,CustomFormat=`yyyy:MM:dd HH:mm:ss`(书上原文如此)
任务3没有「能力拓展」。
### 项目6 项目评价(书131)
| 任务 | 评价指标 | 具体要求 |
|---|---|---|
| 任务1 | 功能实现 | 能正确登录数据库;能正确新建数据库;能正确添加数据 |
| 任务2 | 窗体界面 | 界面布局合理,美观大方,符合使用者的习惯 |
| 任务2 | 功能实现 | 能正确显示光照度、温度、湿度;能利用数组正确储存数据 |
| 任务3 | 窗体界面 | 界面布局合理、美观大方,符合使用者的习惯 |
| 任务3 | 功能实现 | 能实现按日期查询灾情数据 |
| 职业素养 | 代码书写规范 | 应该遵从结构化的要求,采用缩进的格式 |
| 职业素养 | 注释书写规范 | 做到清晰、准确地描述内容。对于程序中复杂的部分必须有注释加以说明。注释量要适中,避免过多或过少导致阅读困难 |
| 职业素养 | 变量命名规范 | 选择有意义的英文进行命名,使变量名能大致清楚地表达其含义。尽量少使用人名、地名和汉语拼音。应使用那些约定俗成的缩写。多个单词组成的变量名,每个单词的首字母应该大写 |
| 职业素养 | 界面常用控件命名约定 | 窗体控件命名以控件名称缩写前3个字母开头,以具有代表含义的词来命名,其形式为:控件缩写(前3个字母)+命名(具有代表含义的单词) |
| 职业素养 | 数据库规范 | 符合数据库命名、设计规范,做到SQL编码优化规范 |
评价表**没有分值**,只有指标和要求,后面跟「个人学习总结」(项目学习情况 / 心得与反思,空白待填)。项目7、项目8 的评价表格式相同。
### 项目6 书与参考代码的差异 / 模拟器要点
- **任务3 书上代码漏了 `ds = new DataSet();`**,直接 `sqldata.Fill(ds)` 会抛空引用异常。参考代码 `项目6/任务三/校园数据存储/Form1.cs` 里有这一行。
- 列别名:书上是 `light as '光照度'`,参考代码是 `light as '光照'`。
- 连接字符串:书上是 `Data Source= PC-20150908SCJF\SQLSERVER1026;Initial Catalog=环境数据表;User ID=sa;Password=123456`,参考代码是 `Data Source=.`。数据库名「环境数据表」,表名 CampusEnvironment,字段 ID / temperature / humidity / light / addtime。
- 模拟器的 SQL 层需要支持:`BETWEEN '日期字符串' AND '日期字符串'`(拼接的是 `DateTime.ToString()`,即中文区域格式 `2024/1/16 14:42:00`)、`as '中文别名'`、`top (n)` 用在 update/delete 上、`year()` 函数、`like '张%'`。另外要支持 SqlCommandBuilder + `DataAdapter.Update`(任务2的「修改」功能),以及 DataGridView 绑定 `DataView`。
- 书上的 SQL 知识点用的是 T-SQL 方言(`top (n)`、`year()`)。如果后端用 SQLite,需要转换或模拟这些语法。
---
## 项目7 智慧校园系统——情景控制模块(书133–156)
**项目概述**:情景控制就是用智能化技术实现自动控制,集成摄像头、温湿度传感器、语音识别器、智能电器等设备。本项目模拟智慧教室、智慧宿舍、智慧会议室三个场景,采集光照度、温湿度等数据,根据时间和环境联动灯光、窗帘、广播。
**学习目标**:
- 理解程序项目文件的结构
- 能根据需求设计交互 UI 窗体,并实现与数据的联动
- 能用 Button、Timer、TrackBar、PictureBox 等组件对物联网设备做简单控制
- 培养分析和解决问题的能力
- 养成耐心细致、严谨扎实的工作作风
- 增强职业认同感,提升职业素养,树立职业自信心
### 任务1 实现智慧教室(书133–140 / PDF 134–141)
**任务描述**:在上课模式下,根据光照度自动开关灯,根据温度自动开关空调(空调用 TrackBar 模拟)。
**任务实施**
1. 把 4017(模拟量采集器)和 4150(数字量采集器)的串口接到 PC(没有硬件可以跳过)。
2. 新建 WinForms 项目「教室情景模式」,保存到 `D:\物联网编程与应用\项目7\任务1\`。
3. 把 ADAM4017、ADAM4150 需要的 `DeviceCtrlLibrary.dll` 和「API」文件夹里的所有 dll 复制到 `...\教室情景模式\bin\Debug\`。
4. 添加引用 DeviceCtrlLibrary.dll。**没有硬件的读者改为添加「仿真动态库文件」,不需要 API 文件夹里的 dll。**
5. 项目属性 → 资源 →「添加资源」,加入 `switch_off.png`、`switch_on.png`。
6. 按图7-1建窗体(标题「智慧教室情景控制」):
- 顶部「模式选择」组:标签「智慧教室将开启」+「启用」按钮
- 左侧「采集量」组:串口下拉框,光照度/温度/湿度文本框
- 右侧「状态栏」组:「空调」+ 开关图片 + TrackBar(两端标 18℃ / 32℃);「灯光」+ 开关图片 + 灯泡控件
表7-1 窗体控件属性:
| 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|---|
| frm_control | 窗体 | Text | 智慧教室情景控制 | |
| lbl_mode | 标签 | Text | 智慧教室将开启 | |
| btn_apply | 按钮 | Text | 启用 | 开启、关闭模式 |
| grp_input | 组合框 | Text | 采集量 | |
| lbl_import | 标签 | Text | 串口 | |
| lbl_light | 标签 | Text | 光照度 | |
| lbl_temperation | 标签 | Text | 温度 | |
| lbl_humidity | 标签 | Text | 湿度 | |
| com_import | 下拉列表框 | | | 设置采集串口 |
| txt_light | 文本框 | | | 显示光照度 |
| txt_temperation | 文本框 | | | 显示温度 |
| txt_humidity | 文本框 | Text | | 显示湿度 |
| grp_status | 组合框 | Text | 状态栏 | |
| lbl_light | 标签 | Text | 灯光 | |
| lbl_airconditione | 标签 | Text | 空调 | |
| lamp | 灯泡 | | | 模拟电灯开关 |
| trb_airconditione | 滑动条 | | | 模拟空调温度 |
| lbl_lbl_airconditionmin | 标签 | Text | 18℃ | |
| lbl_airconditionmax | 标签 | Text | 32℃ | |
| pic_lightsStatus | 图片框 | Image | Switch_off.png | 控制并显示电灯状态 |
| pic_airconditioneStatus | 图片框 | Image | Switch_off.png | 显示空调状态 |
| timer | 时间控件 | Interval | 2000 | 实现实时采集 |
(表中有书上原样的问题:lbl_light 出现了两次,「lbl_lbl_airconditionmin」也是原文;后两行文本框名书上写作 txt_temperation,代码里用的是 txt_temperature。)
7. 引用 `System.IO.Ports`、`DeviceCtrlLibrary`、`DeviceCtrlLibrary.Model`。
8. 定义 `bool IsOpen`、`IsLampOpen`、`IsairconditioneOpen`,以及 `ADAM4017ResModel ResModel`。
9. frm_control_Load:用 `SerialPort.GetPortNames()` 填充 com_import。
10. btn_apply_Click:第一次点击打开 4017/4150 串口,按钮改为「关闭」,lbl_mode 改为「智慧教室已开启」,启动 timer;再次点击关闭串口、停止 timer、恢复文字。
11. timer_Tick:调用 `adam4017Helper.SetData()` 采集并显示。光照 <400 时开灯(图片 switch_on、`lamp.OnOff(true)`、`adam4150Helper.OnOff(1,true)`),>800 时关灯。温度 >30 时开空调、trb=26;<5 时开空调、trb=25;其他情况关空调、trb=18。
12. pic_lightsStatus_Click:手动切换灯。
**知识提炼**
1. TrackBar 控件:横向或纵向拖动来设置值,类似系统音量条。
表7-2 TrackBar 常见属性:
| 属性 | 说明 |
|---|---|
| Orientation | 获取或设置轨道方向,默认 Horizontal(横向,左右拖动),可改为 Vertical(纵向,上下拖动) |
| TickFrequency | 获取或设置滑块旁刻度的间隔,默认为1 |
| TickStyle | 获取或设置刻度位置,默认 BottomRight(在底部,从左往右递增),可改为 TopLeft(在顶部,从右往左递增),还有 Both(两种都要)、None(无刻度) |
| Enabled | 获取或设置滑块是否可拖动,默认 true,可改为 false 表示不能拖动 |
| Maximum | 滑块条最大端的值,默认10 |
| Minimum | 滑块条最小端的值,默认0 |
| LargeChange | 长距离移动(鼠标单击轨道让滑块靠近)时每次改变的值,默认5 |
| SmallChange | 鼠标拖动滑块或键盘方向键移动时每跳的最小值,默认1 |
| Value | 当前滑块所表示的值,默认0 |
| Visible | 滑动条是否可见,默认 true,可改为 false 表示隐藏 |
表7-3 TrackBar 常见事件:
| 事件 | 说明 |
|---|---|
| Scoll(原文拼写,应为 Scroll) | 用户拖动滑块、按下箭头或单击轨道改变值时触发 |
| ValueChanged | 该事件仅在用户通过拖动滑块更改值时触发(书上原文;实际上代码修改 Value 时也会触发) |
2. **TrackBar 显示案例**(书中独立的小练习,适合做成平台题目):新建「TrackBar显示案例」项目,保存到 `D:\物联网编程与应用\项目7\任务1\`。窗体(图7-2)中间是一个 TrackBar,左右两端标签 0 和 10,下方一个标签显示当前值。
表7-4「TrackBar显示」界面控件的属性:
| 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|---|
| Form1 | 窗体 | Text | TrackBar显示 | |
| trackBar1 | 滑动条 | | | |
| label1 | 标签 | Text | 0 | |
| label2 | 标签 | Text | 10 | |
| label3 | 标签 | Text | 0 | 显示当前TrackBar控件对应的数值 |
代码:`private void trackBar1_Scroll(...) { label3.Text = Convert.ToString(trackBar1.Value); }`。运行后拖动滑块,label3 显示 6 等数值(图7-3)。
**能力拓展**
**边想边做**(书139–140):
> 通过「任务实施」的操作步骤,已经实现了智慧教室上课模式的基本功能。现在在此基础上实现考试模式。通过添加考试模式,要求在考试模式下窗帘全开(用 TrackBar 模拟开关,0 为全开,10 为全关),光照度不够 200 时开启电灯。请根据下列提示,实现上述功能。
> 提示:根据以下提示,完善 timer_Tick 事件。
```csharp
private void timer_Tick(object sender, EventArgs e)
{
ResModel = DeviceCtrlHelper.adam4017Helper.SetData();//采集数据
txt_light.Text = ResModel.lightValue;
txt_temperature.Text = ResModel.temperatureValue;
txt_humidity.Text = ResModel.humidityValue;
if(com_mode.Text=="上课模式")//上课模式,与任务实施部分相同
{
if (Convert.ToInt16(ResModel.lightValue) < 200)//光照度过低则打开灯
{
pic_lightsStatus.Image = Properties.Resources.switch_on;
IsLampOpen = true;
lamp.OnOff(true);//打开界面灯
DeviceCtrlHelper.adam4150Helper.OnOff(1,true);//打开1通道灯
}
else if (Convert.ToInt16(ResModel.lightValue) > 800 )
{
pic_lightsStatus.Image = Properties.Resources.switch_off;
IsLampOpen = false;
lamp.OnOff(false);//关闭界面灯
DeviceCtrlHelper.adam4150Helper.OnOff(1, false);//关闭1通道灯
}
if (Convert.ToInt16(ResModel.temperatureValue) > 30 )
{
pic_airconditioneStatus.Image = Properties.Resources.switch_on;
IsairconditioneOpen = true;
trb_airconditione.Value = 26;
}
else if (Convert.ToInt16(ResModel.temperatureValue) < 5)
{
pic_airconditioneStatus.Image = Properties.Resources.switch_on;
IsairconditioneOpen = true;
trb_airconditione.Value = 25;
}
else
{
pic_airconditioneStatus.Image = Properties.Resources.switch_off;
IsairconditioneOpen = false;
trb_airconditione.Value = 18;
}
}
else if (____)// 判断com_mode控件选择的"考试模式"
{
____; //将窗帘设置为0全开(trackBar 控件名为trb_curtain)
if (____)//光照度低于200则打开灯
{
pic_lightsStatus.Image = Properties.Resources.switch_on;
IsLampOpen = true;
lamp.OnOff(true);//打开界面灯
DeviceCtrlHelper.adam4150Helper.OnOff(1, true);//打开1通道灯
}
else if (____) //光照度大于400
{
pic_lightsStatus.Image = Properties.Resources.switch_off;
IsLampOpen = false;
lamp.OnOff(false);//关闭界面灯
DeviceCtrlHelper.adam4150Helper.OnOff(1, false);//关闭1通道灯
}
}
}
```
说明:这道练习隐含要求在窗体上新增下拉框 `com_mode`(选项「上课模式」「考试模式」)和窗帘滑动条 `trb_curtain`。另外,提示代码中上课模式的开灯阈值是 <200,任务实施里是 <400;提示代码的上课模式分支漏了 `adam4150Helper.OnOff(1,false)`,书上原样如此。
**自主探究**(书140):
> 在「边想边做」的基础上,为程序增加以下功能:若当前环境实时温度低于5℃,则自动关闭窗户(使用 TrackBar 控件模拟)并打开空调,设置为25℃。
### 任务2 实现智慧寝室(书141–148 / PDF 142–149)
**任务描述**:寝室有四种情景模式,根据采集的光照、温湿度和定时自动切换:
- 起床模式:开灯光,开广播放音乐,窗帘拉到半开
- 上课模式:关灯光和广播,窗帘全开
- 熄灯模式:关灯光、广播和窗帘
- 回寝模式:开灯光,关窗帘,开广播放音乐
**任务实施**
1–4. 与任务1相同,项目名「寝室情景模式」,保存到 `D:\物联网编程与应用\项目7\任务2\`。没有硬件就用仿真 dll。
5. 添加资源 `switch_off.png`、`switch_on.png`、`ring.mp3`。
6. 按图7-4建窗体(标题「智慧寝室情景控制」):
- 顶部「模式选择」组:「当前时间」文本框 +「开启情景模式」按钮
- 左侧「采集量」组:串口、光照度、温度、湿度
- 右侧「状态栏」组:「窗帘」TrackBar(两端标「全开」「全关」)、「广播」开关图片、「灯光」灯泡
表7-5 窗体界面控件属性:
| 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|---|
| frm_control | 窗体 | Text | 智慧寝室情景控制 | |
| grp_mode | 组合框 | Text | 模式选择 | |
| lbl_time | 标签 | Text | 当前时间 | |
| txt_time | 文本框 | | | 显示时间 |
| lbl_mode | 标签 | Text | | 显示当前情景模式状态 |
| btn_apply | 按钮 | Text | 开启情景模式 | 控制模式开关 |
| grp_input | 组合框 | Text | 采集量 | |
| lbl_import | 标签 | Text | 串口 | |
| lbl_light | 标签 | Text | 光照度 | |
| lbl_temperation | 标签 | Text | 温度 | |
| lbl_humidity | 标签 | Text | 湿度 | |
| com_import | 下拉列表框 | | | 设置采集串口 |
| txt_light | 文本框 | | | 显示光照度 |
| txt_temperation | 文本框 | | | 显示温度 |
| txt_humidity | 文本框 | Text | | 显示湿度 |
| grp_status | 组合框 | Text | 状态栏 | |
| lbl_light | 标签 | Text | 灯光 | |
| lbl_curtain | 标签 | Text | 窗帘 | |
| lbl_boardcast | 标签 | Text | 广播 | |
| lamp | 灯泡 | | | 界面电灯开关 |
| trb_curtain | 滑动条 | | | 模拟窗帘开关 |
| lbl_curtainopen | 标签 | Text | 全开 | |
| lbl_curtainclose | 标签 | Text | 全关 | |
| pic_boardcastStatus | 图片框 | Image | Switch_off.png | 显示广播状态 |
| timer | 时间控件 | Interval | 1000 | 实现实时采集 |
| axWindowsMediaPlayer1 | Windows媒体播放器控件 | Visible | False | 用于播放mp3音乐 |
**小贴士(axWindowsMediaPlayer 的添加方法)**:在工具箱中右键 →「选择项」→「选择工具箱项」→「COM组件」标签,勾选「Windows Media Player」→ 确定,然后从工具箱把控件拖到窗体上。
7. 引用 `System.IO.Ports`、`DeviceCtrlLibrary`、`DeviceCtrlLibrary.Model`、`System.Threading`。
8. 定义变量:`bool IsOpen`、`IsLampOopen`(原文拼写)、`IsBoardcastOpen`;`ADAM4017ResModel ResModel`;`DateTime now`;四个时间点 `GetUp = DateTime.Today.AddHours(6.75)`(6:45)、`ClassBegin = AddHours(7.5)`(7:30)、`ClassEnd = AddHours(21.0)`、`Sleep = AddHours(22.0)`。
9. Load:清空 com_import,填入串口名。
10. btn_apply_Click:在「开启情景模式」和「停止情景模式」之间切换,同时开/关 4017、4150 和 timer。
11. 写 `CheckMode()` 方法:按时间段 GetUp≤now<ClassBegin / ClassBegin≤now<ClassEnd / ClassEnd≤now<Sleep / 其他,分别对应起床 / 上课 / 回寝 / 熄灯模式。每种模式设置 lbl_mode 文字(「智慧寝室正处于起床模式」等)、灯(4150 通道1 + lamp)、广播(`new Thread(PlayMp3).Start()` 或把图片切到 off),以及 trb_curtain.Value(起床 5,上课 0,回寝 10,熄灯 10)。
12. 写 `PlayMp3()`:图片切到 on,执行 `axWindowsMediaPlayer1.URL = AppDomain.CurrentDomain.BaseDirectory + "//Resources//ring.mp3"; axWindowsMediaPlayer1.Ctlcontrols.play();`,然后 `Thread.Sleep(4000)`,图片切回 off。
13. timer_Tick:`now = DateTime.Now`,`txt_time.Text = now.ToString("HH:mm:ss")`,采集并显示数据,然后调用 CheckMode()。
**知识提炼**
1. PictureBox 控件:可以加载 BMP、ICO、WMF、JPEG、GIF。表7-6 常见属性:Image(要显示的图像)、SizeMode(图像显示模式)。
2. 自定义方法:语法为 `<Access Specifier> <Return Type> <Method Name>(Parameter List) { Method Body }`,书中逐一解释了访问修饰符、返回类型(不返回值时用 void)、方法名(唯一,区分大小写)、参数列表和方法主体。示例:
```csharp
public int ADD(int x, int y)
{
Int s; // 原文就是大写 Int,属于书上的笔误
s=x+y;
return s;
}
```
**能力拓展**
**边想边做**(书147):
> 通过「任务实施」的操作步骤,已经实现了智慧寝室各个模式的基本功能。在此基础上为程序添加报警灯(Alarm)和定时器(timer)控件,定时器时间间隔为 0.5s,报警灯默认情况下 Visible 为 false,请完善 timerAlarm_Tick 事件,使在熄灯模式下温度超过 32℃ 时启动界面报警闪烁功能。
提示1:根据下列代码的提示,完善 timer_Tick 事件代码。
```csharp
private void timerAlarm_Tick(object sender, EventArgs e)
{
IsAlarmOpen = ____; // IsAlarmOpen为报警灯状态变量,每次进行切换
alarm.OnOff(____);//切换界面报警灯状态
}
```
提示2:根据下列代码的提示,修改完善 CheckMode 方法的代码。
```csharp
private void CheckMode()//切换各个模式
{
//上述代码省略,具体参照任务实施部分代码
else
{
lbl_mode.Text = "智慧寝室正处于熄灯模式";
if (IsLampOopen == true)
{
IsLampOopen = false;
DeviceCtrlHelper.adam4150Helper.OnOff(1, false);
lamp.OnOff(false);
}
if (IsBoardcastOpen == true)
{
IsBoardcastOpen = false;
pic_boardcastStatus.Image = Properties.Resources.switch_off;
}
trb_curtain.Value = 10;
if (____)//温度较高开启报警
{
alarm.Visible = true;//设置报警灯控件alarm为显示状态
____//开启报警灯闪烁定时器timerAlarm启动
}
else
{
____//设置报警灯控件alarm为不显示状态
timerAlarm.Stop();//停止报警灯闪烁定时器timerAlarm
}
}
}
```
这道题要用到教材类库的 **Alarm 控件**(名为 `alarm`),它有 `OnOff(bool)` 方法和 `Visible` 属性;还要第二个 Timer `timerAlarm`,Interval=500。
**自主探究**(书148):
> 在光照度和温湿度的基础上,实现对气压(airPressure)和空气质量(airQuality)变化的监测,打开关闭的窗户并启动排风系统。
### 任务3 实现智慧会议室(书148–155 / PDF 149–156)
**任务描述**:会议室有会议、讨论、展示三种情景。采集光照度、温度、湿度、空气质量等数据,按预设值控制灯光、窗帘、广播。预约时间到达后:
- 会议模式:开灯光、窗帘、主传声器,关客传声器
- 讨论模式:开灯光、窗帘、主传声器、客传声器
- 展示模式:开灯光、主传声器,关窗帘和客传声器
窗帘用滑块条模拟,主、客传声器用控件模拟。
**任务实施**
1–4. 与前面相同,项目名「会议模式」,保存到 `D:\物联网编程与应用\项目7\任务3\`,复制 dll 到 `...\会议室情景模式\bin\Debug\`。书上项目名和文件夹名不一致,原文如此。
5. 添加资源 `switch_off.png`、`switch_on.png`。
6. 按图7-5建窗体(标题「智慧会议室情景模式」):
- 顶部「模式选择」组:当前时间「19:46:41」、标签「智慧会议室未开启情景模式」、复选框「拍照记录」;一行「[13]时[20]分[00]秒将会有场 [会议▼] [预约] [退出]」
- 左侧「采集量」组:串口、光照度、温度、湿度、气压、空气质量,以及「摄像头IP地址」文本框 192.168.14.131
- 右侧「状态栏」组:窗帘 TrackBar(全开/全关)、灯光开关图片 + 灯泡、「传声器」两个开关(主、客)
表7-7 窗体界面控件属性:
| 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|---|
| frm_control | 窗体 | Text | 智慧会议室情景模式 | |
| grp_mode | 组合框 | Text | 模式选择 | |
| lbl_time | 标签 | Text | 当前时间:00:00:00 | |
| lbl_mode | 标签 | Text | 智慧会议室未开启情景模式 | |
| chk_record | 复选框 | Text | 拍照记录 | 设置拍照开关 |
| txt_hour | 文本框 | Text | 13 | 设置预约时间的小时数 |
| txt_minute | 文本框 | Text | 20 | 设置预约时间的分钟数 |
| txt_second | 文本框 | Text | 00 | 设置预约时间的秒数 |
| lbl_hour | 标签 | Text | 时 | |
| lbl_minute | 标签 | Text | 分 | |
| lbl_second | 标签 | Text | 秒将会有场 | |
| com_mode | 下拉列表框 | Text | 会议 | 选择模式 |
| btn_reserve | 按钮 | Text | 预约 | 预约模式 |
| btn_exit | 按钮 | Text | 退出 | 退出模式 |
| grp_input | 组合框 | Text | 采集量 | |
| lbl_import | 标签 | Text | 串口 | |
| com_import | 下拉列表框 | | | 设置采集串口 |
| lbl_light | 标签 | Text | 光照度 | |
| lbl_temperation | 标签 | Text | 温度 | |
| lbl_humidity | 标签 | Text | 湿度 | |
| lbl_airPressure | 标签 | Text | 气压 | |
| lbl_airQuality | 标签 | Text | 空气质量 | |
| lbl_CO2 | 标签 | Text | CO₂浓度 | |
| txt_light | 文本框 | | | 显示光照度 |
| txt_temperation | 文本框 | | | 显示温度 |
| txt_humidity | 文本框 | Text | | 显示湿度 |
| txt_airPressure | 文本框 | Text | | 显示气压 |
| txt_airQuality | 文本框 | Text | | 显示空气质量 |
| txt_CO2 | 文本框 | Text | | 显示CO₂浓度 |
| lbl_camera | 标签 | Text | 摄像头IP地址 | |
| txt_camera | 文本框 | Text | 192.168.14.131 | 设置摄像头IP地址 |
| grp_status | 组合框 | Text | 状态栏 | |
| lbl_light | 标签 | Text | 灯光 | |
| lbl_curtain | 标签 | Text | 窗帘 | |
| lbl_boardcast | 标签 | Text | 传声器 | |
| lamp | 灯泡 | | | 模拟电灯开关 |
| trb_curtain | 滑动条 | | | 模拟窗帘开关 |
| lbl_curtainopen | 标签 | Text | 全开 | |
| lbl_curtainclose | 标签 | Text | 全关 | |
| pic_lightsStatus | 图片框 | Image | Switch_off.png | 控制并显示电灯状态 |
| pic_mainMicrophone | 图片框 | Image | Switch_off.png | 控制并显示主传声器状态 |
| pic_subMicrophone | 图片框 | Image | Switch_off.png | 控制并显示客传声器状态 |
| timer | 时间控件 | Interval | 2000 | 实现实时采集 |
注意:图7-5 截图里没有 CO₂ 那一行。
7. 引用 `System.IO`、`System.IO.Ports`、`DeviceCtrlLibrary`、`DeviceCtrlLibrary.Model`。
8. 定义变量:`bool IsLightsOpen`、`IsmainMicrophoneOpen`、`IssubMicrophoneOpen`、`IsInitialized`;`ADAM4017ResModel ResModel`;`DateTime now`;`DateTime reserve = DateTime.Now.AddDays(1)`(默认设为明天,防止出现「假预约」);`Image ReadImage = null`。
9. Load:清空 com_import 和 com_mode,com_mode 加入「会议」「讨论」「展示」,启动 timer。如果 `BaseDirectory + "Camera/"` 不存在就创建。然后 `DeviceCtrlHelper.ipCameraHelper.ImageRead += ipCameraHelper_ImageRead;` 绑定监控事件。
10. `void ipCameraHelper_ImageRead(ImageReadEvenArgs e) { ReadImage = e.img; }`
11. btn_reserve_Click:`reserve = DateTime.Today.AddHours(时).AddMinutes(分).AddSeconds(秒)`(逐行调用 Convert.ToInt16)。
12–14. pic_lightsStatus / pic_mainMicrophone / pic_subMicrophone 的 Click:手动开关切换。
15. `ChooseMode()`:`lbl_mode.Text = "智慧会议室已开启" + com_mode.Text + "模式"`,然后 switch 三种模式设置灯、窗帘(0 开 / 10 关)、主客传声器图片。
16. timer_Tick:每次 Tick 都执行 `adam4017Helper.Open(com_import.Text)` 和 `adam4150Helper.Open(...)`,然后显示时间、采集光照/温度/湿度/气压(airPressureValue)/空气质量(airQualityValue)。当 `now >= reserve` 时:如果还没初始化,就 ChooseMode();若 chk_record 勾选,则 `ipCameraHelper.Open(txt_camera.Text, "admin", "")` 加 `ipCameraHelper.Start()`;然后 IsInitialized=true。在 `now.Second == 0 && now.Minute % 5 == 0` 时,执行 `ReadImage.Save(BaseDirectory + "Camera/" + DateTime.Now.ToString("yyyyMMdd_HH mm ss") + ".jpg")`。
17. btn_exit_Click:停止 timer,reserve 设为明天,`chk_record.Enabled=false`,关灯,`trb_curtain.Value=0`(注释写的是「关闭窗帘」,但 0 实际是全开),关主客传声器,IsInitialized=false,关闭 4150、4017,`ipCameraHelper.Stop()`。
**知识提炼**:`+=` 在 C# 中有两种用法:一是复合赋值(`i+=1` 即 `i=i+1`);二是注册/订阅事件,如 `winForm.Closed += new EventHandler(child_Closed);`,变量句柄类型为 EventHandler。用 `-=` 取消订阅。事件发生时,处理程序集合中的所有方法依次调用。
**能力拓展**
**边想边做**(书155):
> 通过「任务实施」的操作步骤,已经实现了智慧会议室的基本配置。现在修改程序:通过增加一个文本框控件 txt_duration 获取预约时长(单位 min),实现预约时间内设备的开启,预约时间结束后则关闭设备。请根据下列提示,实现上述功能。
> 提示:根据以下提示,完善 timer_Tick 事件
```csharp
private void timer_Tick(object sender, EventArgs e)
{
//以上代码忽略,详见任务实施timer_Tick事件代码
if (now >= reserve && ____)//是否在预约时间段内,txt_duration的Text属性接收预约时长
{
if (!IsInitialized)
{
ChooseMode();//进入选择的模式
//此部分代码忽略,详见任务实施timer_Tick事件代码
}
}
else//预约时间外关闭设备
{
____//停止timer定时器
____//修改reserve变量为明天,防止初始值小于当前时间的假预约情况
chk_record.Enabled = false;
pic_lightsStatus.Image = Properties.Resources.switch_off;//转换灯光图片
lamp.OnOff(false);//关闭界面灯光
DeviceCtrlHelper.adam4150Helper.OnOff(1, false);//关闭1通道灯
trb_curtain.Value = 0;//关闭窗帘
pic_mainMicrophone.Image = Properties.Resources.switch_off;//关闭主传声器
pic_subMicrophone.Image = Properties.Resources.switch_off;//关闭客传声器
IsInitialized = false;//初始化状态
DeviceCtrlHelper.adam4150Helper.Close();
DeviceCtrlHelper.adam4017Helper.Close();//关闭串口
DeviceCtrlHelper.ipCameraHelper.Stop();//关闭视频监控
}
}
```
(原文印刷错误:`lam•p.OnOff(false)` 应为 `lamp.OnOff(false)`。另外这道题的逻辑有缺陷:预约时间到达之前,timer 也会走进 else 分支把 timer 停掉。)
**自主探究**(书155):
> 在「边想边做」的基础上,为程序增加下列功能:当开启情景模式时,若时间早于 6 时或晚于 18 时,则窗帘自动关闭,打开照明灯。
### 项目7 项目评价(书156)
| 任务 | 评价指标 | 具体要求 |
|---|---|---|
| 任务1 | 窗体界面 | 窗体控件正确,布局美观 |
| 任务1 | 数据显示 | 能实时显示环境数据 |
| 任务1 | 自动控制 | 能根据环境数据的变化实时控制智能设备 |
| 任务2 | 窗体界面 | 窗体控件正确,布局美观 |
| 任务2 | 数据显示 | 能实时显示环境数据 |
| 任务2 | 自动控制 | 能根据时间段的变化判断寝室情景实时控制智能设备 |
| 任务3 | 窗体界面 | 窗体控件正确,布局美观 |
| 任务3 | 数据显示 | 能实时显示环境数据 |
| 任务3 | 实现预约时段后开启自动控制功能 | 能根据预约时间控制智能设备 |
| 任务3 | 监控抓取 | 能定时实现监控数据的抓取 |
| 职业素养 | 代码书写规范 | 应该遵从结构化的要求,采用缩进的格式 |
| 职业素养 | 注释书写规范 | 做到清晰,准确地描述内容。对于程序中复杂的部分必须有注释加以说明。注释量要适中,避免过多或过少导致阅读困难 |
| 职业素养 | 变量命名规范 | 选择有意义的英文进行命名,使变量名能清楚地表达其含义。尽量少使用人名、地名和汉语拼音。应使用那些约定俗成的缩写。多个单词组成的变量名,每个单词的首字母应该大写 |
后面是「个人学习总结」(项目学习情况 / 心得与反思)。表格没有分值。
### 项目7 书与参考代码的差异 / 模拟器要点
- **任务1 pic_lightsStatus_Click 的判断条件,书上写反了**:书上是 `if (!IsLampOpen) { 关灯 } else { 开灯 }`,参考代码 `项目7/任务一/任务1/教室情景模式/教室情景模式/教室情景模式.cs` 是 `if (IsLampOpen) { 关灯 }`。按书上写,点了灯不会亮。
- 任务1参考工程里的窗体文件叫 `教室情景模式.cs`,不是 Form1.cs。参考 Designer 中 `trb_airconditione` 的 Minimum=18、Maximum=32、Value=18,书上表格没写这几个值,起始模板要加上。timer.Interval 两边都是 2000。
- 任务2:`PlayMp3` 在后台线程里直接改 `pic_boardcastStatus.Image` 并调用 WMP 控件。书上和参考代码都没有设置 `CheckForIllegalCrossThreadCalls=false`,也没有用 Invoke(Release 版本能跑,调试时会报跨线程异常)。模拟器要么允许跨线程访问 UI,要么自动封送到 UI 线程。参考 Designer 里多了一个 `alarm1`(`WinFormsDeviceControlLibrary.Alarm`,尺寸 7×6),可能是给「边想边做」预留的。
- **模拟器需要支持的控件和 API**(从代码本身看不太出来):
- `WinFormsDeviceControlLibrary.Lamp`:`OnOff(bool)`
- `WinFormsDeviceControlLibrary.Alarm`:`OnOff(bool)`、Visible(任务2拓展用)
- **AxWindowsMediaPlayer(COM 控件)**:`URL` 属性、`Ctlcontrols.play()`,要能播放资源里的 `ring.mp3`,可以简化为提示音或「广播中」动画
- `Properties.Resources.switch_on/switch_off`(png 图片资源)
- `System.Threading.Thread`、`Thread.Sleep`
- `ADAM4017ResModel` 的字段:`lightValue`、`temperatureValue`、`humidityValue`、`airPressureValue`、`airQualityValue`,都是 string。参考代码注释里还提到 `CO2Value`,书上表格有 txt_CO2,但代码里从没赋值
- `adam4150Helper.OnOff(通道, bool)`
- `ipCameraHelper.Open(ip, uid, pwd)`、`.Start()`、`.Stop()`、`ImageRead` 事件(参数类型 `ImageReadEvenArgs`,字段 `.img`,类型为 Image)、`Image.Save(path)`;以及 `Directory.Exists`、`Directory.CreateDirectory`、`AppDomain.CurrentDomain.BaseDirectory`
- 任务2 依赖真实时钟(6:45 / 7:30 / 21:00 / 22:00),任务3 依赖预约时间和「每5分钟整点拍照」。模拟器最好提供**可调的模拟时钟**,否则学生很难测试各种模式
- 任务3:书上和参考代码的 Load 都**没有往 com_import 填串口名**(注释里写了但代码没做),而 timer 从 Load 起就在跑,并且每个 Tick 都 Open 一次串口。参考代码 timer.Interval=1000,书上表格是 2000。两边都没有给 `txt_CO2` 赋值。ChooseMode 只改了图片,没有同步 IsLightsOpen 等状态变量,所以模式切换后手动点开关时第一次点击状态会对不上(书上和参考代码都这样)。
---
## 项目8 智慧校园管理平台开发(书157–186)
**项目概述**:综合前面7个项目,做一个智慧校园管理平台,对校园物联网设备和信息化做监控和管理,方便管理部门实时监控、处理突发事件,界面要友好。
**学习目标**:
- 理解平台各功能模块的用途及模块间的相互关系
- 掌握各界面设计以及图标绘制的方法
- 能根据实际应用需求完成物联网应用界面设计
- 能对传感器采集的数据做简单分析
- 能识别系统开发中的常见错误与系统故障,掌握基本排查方法
- 养成严谨细致的工作作风和认真负责的工作态度
- 培养坚韧不拔、勇于面对困难的品质
本项目各任务**都没有「知识提炼」和「能力拓展」**,只有任务描述和任务实施,最后是项目评价。
### 任务1 制作管理平台的主界面(书157–159 / PDF 158–160)
**任务描述**:主界面提供所有功能的入口,要求简洁、易学、好用。包含两个入口:校园环境、校园安防。
**任务实施**
1. 新建 WinForms 项目「智慧校园」,保存到 `D:\物联网编程与应用\项目8\任务1\`。
2. 把「综合实训」文件夹下的 Resources 文件夹复制到 `...\智慧校园\bin\Debug`,并在解决方案中导入。参考工程对应的是 `项目8/Resources`。
3. 设置窗体属性。表8-1 窗体属性:
| 属性名 | 属性值 |
|---|---|
| Name | Main |
| FormBorderStyle | none |
| StartPosition | CenterScreen |
4. 加两个 Panel:
- panel1:BackColor = 53,147,237,Dock = Top;里面放一个 PictureBox,SizeMode = StretchImage,Image = `Resources/ico_title.png`(「智慧校园」标题图)
- panel2:Dock = Fill,BackgroundImageLayout = Stretch,BackgroundImage = `Resources/bg_home.png`(校园建筑照片)
效果见图8-1。
5. 在 panel2 上放 3 个 PictureBox(效果见图8-2,三个方形图标:校园环境/树、校园安防/盾牌、退出/箭头):
表8-2 校园环境图片框属性:
| 属性名 | 属性值 |
|---|---|
| Name | pic_environment |
| BackColor | Transparent |
| Image | 智慧校园.Properties.Resources.icon_02 |
表8-3 校园安防图片框属性:
| 属性名 | 属性值 |
|---|---|
| Name | pic_security |
| BackColor | Transparent |
| Image | 智慧校园.Properties.Resources.icon_03 |
表8-4 退出图片框属性:
| 属性名 | 属性值 |
|---|---|
| Name | pic_exit |
| BackColor | Transparent |
| Image | 智慧校园.Properties.Resources.icon_04 |
6. pic_exit 的 Click:`this.Close();`
### 任务2 制作「校园环境」界面(书159–164 / PDF 160–165)
**任务描述**:实时采集温度、湿度等气象信息并推送到 LED 屏;监测火焰、烟雾等安防信息,一旦有险情第一时间推送到 LED 预警;根据光照度自动开关路灯;对历史气象数据做分析比较。
**任务实施**(本任务只做界面)
1. 新建窗体 Environment,并调整大小。表8-5 Environment 窗体属性:
| 属性名 | 属性值 |
|---|---|
| Name | Environment |
| FormBorderStyle | none |
| StartPosition | CenterScreen |
2. 布局 Panel:
- panel1:BackColor = 100,116,133,Dock = Top,内放 PictureBox(StretchImage,`ico_title.png`)
- panel2:Dock = Fill,panel2 里再放 panel3(Dock = Top)
效果见图8-3。
3. panel3:BackColor = 229,229,229,上面放标签 `lb_return`,Text =「<返回主窗体」(图8-4)。
4. panel3 下方放 `panel_bg`:Dock = Fill,BackgroundImageLayout = Stretch,BackgroundImage = `day_02.png`(带路灯的建筑图,图8-5)。
5. 在 panel_bg 上放 `panel5`(Dock = Bottom),控件见表8-6,界面见图8-6:一行「环境数据 火焰[ ] 烟雾[ ] 光照度[ ] 温度[ ] 湿度[ ] [数据分析] [推送天气信息]」。
表8-6 窗体控件:
| 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|
| 文本框 | Name | txt_fire | 显示火焰值文本框 |
| 文本框 | Name | txt_smoke | 显示烟雾值文本框 |
| 文本框 | Name | txt_light | 显示光照度值文本框 |
| 文本框 | Name | txt_temperature | 显示温度值文本框 |
| 文本框 | Name | txt_humidity | 显示湿度值文本框 |
| 按钮 | Name | 数据分析 | 数据分析按钮 |
| 按钮 | Name | 推送天气信息 | 推送天气信息按钮 |
| 其余均为标签 | | | |
(书上把按钮的「Name」写成了中文文字。参考代码中的实际名字是 `btn_dataAnalyse`(Text「数据分析」)和 `btn_ledEnvironment1`(Text「推送天气信息」)。)
6. 在 panel_bg 上放 `panel_form`:BackColor = Transparent,BackgroundImage = `bg_form2.png`,Dock = Right(图8-7)。参考资源里的文件名是 `bg_from2.png`,书上写成了 bg_form2。
7. 在 panel_form 上做两个模块(图8-8):
- 「路灯控制」组:复选框「自动控制」、「设定开灯光照度值 [ ] lx」、「灯光控制 [开关]」
- 「LED推送」组:「LED信息」多行文本框、复选框「自动推送火情」、按钮「推送LED信息」「推送天气信息」
表8-7 panel_form 里面主要控件的属性设置:
| 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|
| 复选框 | Name | ch_autoLamp | 自动控制复选框 |
| 复选框 | Text | 自动控制 | |
| 文本框 | Name | txt_autoLight | 设置控制开灯的光照度值 |
| 图片框 | Name | pic_lampOpen | 灯光控制图片框 |
| 图片框 | BackColor | Transparent | 灯光控制图片框 |
| 图片框 | BackgroundImage | 智慧校园.Properties.Resources.switch_off | 灯光控制图片框 |
| 复选框 | Name | ch_autoLedFire | 自动推送火情复选框 |
| 复选框 | Text | 自动推送火情 | |
| 按钮 | Name | 推送天气信息 | 推送天气信息按钮 |
| 按钮 | Name | 推送LED信息 | 推送LED信息按钮 |
| 文本框 | Name | txt_ledMsg | LED信息文本框 |
| 其余控件 | BackColor | Transparent | |
(参考代码中按钮名为 `btn_ledEnvironment2`(推送天气信息)和 `btn_ledSendMsg`(推送LED信息)。)
8. 在 panel_bg 上加 **Chart 图表控件 chart1**:
- Legends 加两项:`MaxLegend`、`AVGLegend`
- Series 设置(图8-9、图8-10「Series 集合编辑器」):
| Series | XValueMember | YValueMembers | ChartArea | ChartType | IsVisibleInLegend | Legend | LegendText |
|---|---|---|---|---|---|---|---|
| MaxSeries | _Time | MaxTemperature | ChartArea1 | Line | True | MaxLegend | 最大值 |
| AVGSeries | _Time | AVGTemperature | ChartArea1 | Line | True | AVGLegend | 平均值 |
另外 XValueType=Auto,YValuesPerPoint=1,YValueType=Auto。完整效果见图8-11:左下方是折线图(Y 轴 0–100,X 轴 1–8,图例「平均值」「最大值」),右侧是控制面板。
### 任务3 实现「校园环境」数据采集及数据分析功能(书164–174 / PDF 165–175)
**任务描述**:实现数据库的查询分析,并把结果显示到界面上。
**任务实施**(代码在参考工程 `项目8/智慧校园/智慧校园/` 里都有,这里只列步骤)
1. 导入数据采集相关 dll;新建 `AppSet` 类和 `SQLHelpers` 文件夹,文件夹里放 `SQLBLL`、`SQLHelper` 两个类。
2. AppSet.cs 内容:串口 `ADAM4017_COM="COM2"`、`ADAM4150_COM="COM3"`、`LED_COM="COM5"`、`ZigBee_COM="COM4"`;摄像机 `Camera_Ip="192.168.1.100"`、`Camera_Uid="admin"`、`Camera_Pwd=""`;`SqlConn="Data Source=.;Initial Catalog=WisdomCampus;User ID=sa;Password=123"`。
**小贴士**:运行前先配置好数据库。在「综合实训」文件夹找到数据库文件 WisdomCampus,附加到数据库中;设置用 **sa 用户登录、密码 123**;导入程序运行需要的库文件;按串口设置连接好对应串口。
3–4. SQLHelper(引用 System.Data.SqlClient、System.Drawing、System.IO):
- `strConn = AppSet.SqlConn`
- `SaveData(sql, DataTable)`:SqlDataAdapter + SqlCommandBuilder + Update
- `getData(sql, params SqlParameter[])` 返回 DataSet
- `getData(SqlCommand)`
- `Exec(sql, params SqlParameter[])`:ExecuteNonQuery,异常时返回 −1
5–6. SQLBLL:
- `SecurityUpData(Image)`:用 MemoryStream 把图片存成 Jpeg 字节,执行 `INSERT INTO SecurityData (EvenTime,EvenImage) VALUES (@time, @img)`
- `GetSecurityData(DateTime startDate, DateTime endDate)` 返回 `Dictionary<string, Image>`:执行 `SELECT * FROM SecurityData WHERE EvenTime between @time_start and @time_end`,end 拼成「年/月/日 23:59:59」,然后 `Image.FromStream(new MemoryStream((byte[])...))`。书上这段有笔误 `eturn ImgDictionary;`
- `EnvironmentUpData(bool fire, bool smoke, string light, string temperature, string humidity)`:执行 `INSERT INTO EnvironmentData (Fire, Smoke, Temperature, Humidity, Light, _Time) VALUES (@fire,@smoke,@temperature,@humidity,@light,@time)`
- `GetEnvironmentGraphData()`:执行 `SELECT TOP 7 CONVERT(varchar(10),_Time,120) AS _Time, ROUND(Max(Temperature),2) AS MaxTemperature, ROUND(AVG(Temperature),2) AS AVGTemperature FROM EnvironmentData GROUP BY CONVERT(varchar(10),_Time,120) ORDER BY CONVERT(varchar(10),_Time,120) DESC`
7. 在 SQLBLL 文件中加 `class SQLRes { string Msg; bool Result; }`。
8–9. 主窗体引用 DeviceCtrlLibrary。Main_Load 中依次执行 `adam4017Helper.Open(AppSet.ADAM4017_COM)`、`adam4150Helper.Open(AppSet.ADAM4150_COM)`、`zigBeeHelper.Open(AppSet.ZigBee_COM)`、`ledHelper.OpenLedCom(AppSet.LED_COM)`,全部包在 try/catch 里。
10. pic_environment_Click:`new Environment().Show();`
11–12. Environment 引用 DeviceCtrlLibrary、DeviceCtrlLibrary.Model、智慧校园.SQLHelpers、System.Threading;字段有 `Thread thread`、`SynchronizationContext _main`、`ADAM4017ResModel ResModel_4017`、`ADAM4150ResModel ResModel_4150`、`double environmentLight, setLight`、`bool IsAuto_Lamp, IsAuto_FireMsg, IsLampOpen`。
13. Environment_Load:`chart1.Visible=false`;`thread = new Thread(new ThreadStart(Tick))`;`_main = WindowsFormsSynchronizationContext.Current`;`thread.Start()`。
14. `Tick()` 是一个 while(true) 循环:
- 采集 4017,`Thread.Sleep(500)`,再采集 4150
- 通过 `_main.Send(_ => {...}, null)` 回到 UI 线程更新显示:`txt_fire = fireValue ? "发生火情" : "正常"`,`txt_smoke = smokeValue ? "发生烟情" : "正常"`,以及光照、温度、湿度
- 自动路灯:`double.TryParse(lightValue, out environmentLight)`;当 IsAuto_Lamp 为真且 environmentLight < setLight 时 `LampOnOff(1,true)`,否则 `LampOnOff(1,false)`
- 自动推送火情:火且烟时推送「发生严重火灾」,只有火时推送「发现火情」,只有烟时推送「发现烟情」,都用 `ledHelper.SendLedString`
- 调用 `SQLBLL.EnvironmentUpData(...)` 上传数据,然后 `Thread.Sleep(5000)`
15. `LampOnOff(int RelayNum, bool IsOpen)`:调用 `zigBeeHelper.OnOff(RelayNum, IsOpen)`,再用 `_main.Send` 切换背景:开灯时 `panel_bg.BackgroundImage = day_01`、`pic_lampOpen.BackgroundImage = switch_on`,关灯时换成 `day_02` 和 `switch_off`。
16. Environment_FormClosed:`thread.Suspend(); thread = null;`
17. btn_dataAnalyse_Click:按钮文字为「数据分析」时,`chart1.DataSource = SQLBLL.GetEnvironmentGraphData(); chart1.DataBind(); chart1.Visible = true;`,并把按钮改为「关闭图表」;为「关闭图表」时反过来。
18. 两个「推送天气信息」按钮:`SendLedString("当前温度:" + temperatureValue + "℃ 湿度:" + humidityValue + "%")`。
19. pic_lampOpen_Click:根据 IsLampOpen 调用 LampOnOff 切换,然后 `IsLampOpen = !IsLampOpen`。
20. 「推送LED信息」按钮:`SendLedString(txt_ledMsg.Text)`。
21. txt_autoLight_TextChanged:`double.TryParse(txt_autoLight.Text, out setLight)`。
22. ch_autoLamp_CheckedChanged:`IsAuto_Lamp = ch_autoLamp.Checked;`
23. 书上写「在 ch_autoLamp 的 CheckedChanged 事件中输入」`IsAuto_FireMsg = ch_autoLedFire.Checked;`。这里书上写错了,应该是 **ch_autoLedFire** 的 CheckedChanged。
### 任务4 实现「校园安防」功能模块(书174–180 / PDF 175–181)
**任务描述**:实时监控,有人入侵时摄像机抓拍,把非法入侵人员拍照记录下来。
**任务实施**
1. 添加窗体 `Security.cs` 和 `SecurityCameraData.cs`,Security 的顶部按任务2的方法布局(图8-12:标题栏 +「<返回主窗体」)。
2. 加 `panel_bg`:Dock = Fill,BackColor = White。
3. panel_bg 上加 `panel_bg_video`:BackColor = DimGray。
4. panel_bg_video 上加 PictureBox `pic_video`:BackgroundImage = `icon_monitor.png`,BackgroundImageLayout = Center。
5. panel_bg_video 上再加 Label「入侵:」和 Label `lb_infrared`(Text = 「N/A」),效果见图8-13。
6. panel_bg 上加 `panel_video_ctrl`:BackgroundImage = `panel_video_ctrl.png`(圆形方向盘底图),Layout = Stretch。
7. panel_video_ctrl 上加 4 个 PictureBox:`pic_up`、`pic_down`、`pic_left`、`pic_right`,BackgroundImage 分别为 `btn_arrow_up_normal.png`、`btn_arrow_down_normal.png`、`btn_arrow_left_normal.png`、`btn_arrow_right_normal.png`,Layout = Stretch(图8-14)。
8. panel_bg 上加按钮 `btn_camera`(Text「拍照」)、`btn_history`(Text「查看历史图像」),完整效果见图8-15。
9. 引用 System.IO、System.Threading、DeviceCtrlLibrary、智慧校园.SQLHelpers。
10. 字段:`bool IsInfrared`;`string ImgDir = AppDomain.CurrentDomain.BaseDirectory + "Camera\\"`;`Thread thread`;`SynchronizationContext _main`;`Image img = null`。
11. `tick()` 是 while(true) 循环:执行 `IsInfrared = adam4150Helper.SetData().infraredValue`;通过 `_main.Send` 设置 `lb_infrared.Text = IsInfrared ? "发现入侵" : "正常"`;若有入侵且 img 不为 null,执行 `SQLBLL.SecurityUpData(img)`;然后 `Thread.Sleep(1000)`。
12. `ipCameraHelper_ImageRead(ImageReadEvenArgs e)`:`img = e.img; pic_video.Image = e.img;`(在相机回调线程里直接设置 UI)。
13. Security_Load:`ipCameraHelper.Open(AppSet.Camera_Ip, Camera_Uid, Camera_Pwd)`,`ImageRead += ...`,`Start()`;启动线程;ImgDir 不存在时创建。
14. Security_FormClosed:`ipCameraHelper.Stop(); ipCameraHelper.Close(); thread.Suspend(); thread = null;`
15. 「拍照」按钮:`pic_video.Image.Save(ImgDir + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".jpg"); SQLBLL.SecurityUpData(pic_video.Image);`
16. 「查看历史图片」按钮:`new SecurityCameraData().ShowDialog();`
17–20. 四个方向图片的 MouseDown / MouseUp:MouseDown 时 BackgroundImage 换成 `btn_arrow_xx_press` 并调用 `ipCameraHelper.PanUp()`(或 PanDown / PanLeft / PanRight);MouseUp 时换回 `_normal`,并**再调用一次同一个 Pan 方法**。书上和参考代码都这样写,可能这个 API 是开始/停止切换式的。
21. 主窗体 pic_security 的 Click:`new Security().Show();`
22. lb_return 的 Click:`this.Close();`
### 任务5 实现「校园安防」模块中显示历史图片的功能(书181–185 / PDF 182–186)
**任务描述**:从数据库中查出非法入侵时拍下的照片,按时间快速定位目标人物。
**任务实施**
1. SecurityCameraData 窗体:StartPosition = CenterScreen,FormBorderStyle = none。
2. 加 `Panel1`:BackColor = 100,116,133,Dock = Top。上面放 label1(Text「历史报警记录」,ForeColor = White)和 PictureBox `pic_close`(BackgroundImage = `windowstate_closed_hover.png`,Layout = Center),效果见图8-16。
3. Panel1 下方加 `panel_toolBar`:BackColor = 229,229,229。上面放两个标签(「开始时间」「结束时间」)、`dateTimePicker1`、`dateTimePicker2`,以及按钮 `btn_get`(Text「查询」),效果见图8-17。
4. 加 PictureBox `pic_camera`:BackColor = 64,64,64。上面放标签 `lb_noData`(Text「NO DATA」,ForeColor = Red,BackColor = 64,64,64,字号 48)和标签 `lb_time`(Text「TIME」,ForeColor = Red,BackColor = 64,64,64),效果见图8-18。
5. 在 pic_camera 上加两个 PictureBox `pic_up`、`pic_down`:Image 分别为 `arrow_left_normal.png`、`arrow_right_normal.png`,SizeMode = CenterImage(图8-19:左右翻页箭头)。
6. 引用 DeviceCtrlLibrary、智慧校园.SQLHelpers。
7. 字段:`Dictionary<string, Image> ImgDictionary`、`int index = -1`、`Image img`、`List<string> Keys`。
8. `ImageChanged(char UpOrDown = '+')`:按 '+' 或 '-' 移动 index 并循环取模,然后 `TryGetValue(Keys[index], out img)`,`pic_camera.Image = img; lb_time.Text = Keys[index];`。
9. `GetData()`:用 dateTimePicker1/2 的 Value 调用 `SQLBLL.GetSecurityData(...)`,index 置为 -1;没有数据时显示 lb_noData 并 return,否则隐藏 lb_noData,把所有 key 加进 Keys,再调用 ImageChanged()。
注意:Keys 每次查询都没有 Clear,重复查询会越积越多,书上原样如此。
10. Load:`pic_up.Parent = pic_camera; pic_down.Parent = pic_camera; pic_up.Location = new Point(20,0); pic_down.Location = new Point(560,0); GetData();`
11. btn_get 的 Click:`GetData();`
12. pic_up 的 Click:`ImageChanged('-');`
13. pic_down 的 Click:`ImageChanged();`
14. pic_close 的 Click:`this.Close();`
### 项目8 项目评价(书185–186)
| 任务 | 评价指标 | 具体要求 |
|---|---|---|
| 任务1 | 窗体界面 | 界面布局合理,美观大方,符合使用者的习惯 |
| 任务1 | 功能实现 | 实现退出功能 |
| 任务2 | 窗体界面 | 界面布局合理,美观大方,符合使用者的习惯 |
| 任务2 | 功能实现 | 路灯能手动、自动控制;实现多个LED信息推送功能;图表能正确显示数据 |
| 任务3 | 窗体界面 | 界面布局合理,美观大方,符合使用者的习惯 |
| 任务3 | 功能实现 | 人体入侵有文字提示;摄像机能正常打开并可调整方向;摄像机能拍照并保存图片 |
| 任务4 | 窗体界面 | 界面布局合理,美观大方,符合使用者的习惯 |
| 任务4 | 功能实现 | 能根据日期查找历史照片并显示;照片能根据图片按钮切换 |
| 任务5 | 窗体界面 | 界面布局合理,美观大方,符合使用者的习惯 |
| 任务5 | 功能实现 | 能正确查找时间段内的历史图片并显示 |
| 职业素养 | 界面设计规范 | 总体原则以用户为中心,有良好的视觉特征,为用户提供直接而直观的入口,界面美观简洁,风格统一 |
| 职业素养 | 代码书写规范 | 应该遵从结构化的要求,采用缩进的格式 |
| 职业素养 | 注释书写规范 | 做到清晰、准确地描述内容。对于程序中复杂的部分必须有注释加以说明。注释量要适中,避免过多或过少导致阅读困难 |
| 职业素养 | 变量命名规范 | 选择有意义的英文进行命名,使变量名能清楚地表达其含义。尽量少使用人名、地名和汉语拼音。应使用那些约定俗成的缩写。多个单词组成的变量名,每个单词的首字母应该大写 |
| 职业素养 | 界面常用控件命名约定 | 窗体控件命名以控件名称缩写前3个字母开头,以具有代表含义的词来命名,其形式为:控件缩写(前3个字母)+命名(具有代表含义的单词) |
| 职业素养 | 电工安全操作规范 | 所有路灯安装操作必须断电,电线接头必须用绝缘包布包扎好,规范使用各类工具 |
注意:这张评价表的任务编号和正文错开了一位。表中「任务2」包含了正文任务2(界面)和任务3(路灯、LED、图表)的功能;表中「任务3」对应正文的**任务4 安防**;表中「任务4」「任务5」都在说正文的**任务5 历史图片**。表格没有分值,后面是「个人学习总结」。
### 项目8 书与参考代码的差异 / 模拟器要点
- **参考代码不完整**(`项目8/智慧校园/智慧校园/Environment.cs`):
- `btn_ledSendMsg_Click`(书步骤20:推送 txt_ledMsg)是**空方法**
- `ch_autoLamp_CheckedChanged`(书步骤22)是**空方法**
- `ch_autoLedFire` 没有绑定任何 CheckedChanged 处理程序(书步骤23)
所以参考程序里「自动控制」和「自动推送火情」两个复选框不起作用。做平台时以书为准补全。
- 书上步骤23的控件名写错了(ch_autoLamp 应为 ch_autoLedFire);表8-6、8-7 把按钮的 Name 写成了中文。实际名字是:btn_dataAnalyse、btn_ledEnvironment1、btn_ledEnvironment2、btn_ledSendMsg。
- 资源文件名:书上写 `bg_form2.png`,参考资源中是 `bg_from2.png`(另有 `bg_from1.png`)。其他资源都在 `项目8/Resources`:`ico_title`、`bg_home`、`icon_02/03/04`、`day_01/02`、`switch_on/off`、`icon_monitor`、`panel_video_ctrl`、`btn_arrow_*_normal/press`、`arrow_left/right_normal`、`windowstate_closed_hover`、`pic_big`。
- 数据库:`项目8/DataBase/WisdomCampus.mdf`。表 `EnvironmentData` 的字段为 Fire (bit)、Smoke (bit)、Temperature、Humidity、Light、_Time;表 `SecurityData` 的字段为 EvenTime、EvenImage (varbinary/image)。模拟器的 SQL 层需要支持:
- **T-SQL 函数** `CONVERT(varchar(10), _Time, 120)`、`ROUND`、`MAX`、`AVG`、`GROUP BY`、`TOP 7`
- 参数化查询 `@param`(`SqlParameter`、`params SqlParameter[]`)
- 二进制图片列(byte[] ↔ Image,经 MemoryStream,`Image.Save(ms, ImageFormat.Jpeg)`、`Image.FromStream`)
- `SqlCommandBuilder`
- 若后端是 SQLite,CONVERT 和 TOP 需要做方言转换
- **新出现的设备 API**(之前的项目里没有):
- `DeviceCtrlHelper.zigBeeHelper.Open(com)`、`.OnOff(relayNum, bool)`(路灯继电器)
- `DeviceCtrlHelper.ledHelper.OpenLedCom(com)`、`.SendLedString(string)`(LED 屏,模拟器应显示一块 LED 屏)
- `adam4150Helper.SetData()` 返回 `ADAM4150ResModel`,字段 `fireValue`、`smokeValue`、`infraredValue` 都是 bool
- `ipCameraHelper.Open(ip, uid, pwd)` / `Start()` / `Stop()` / `Close()` / `PanUp/PanDown/PanLeft/PanRight()` / `ImageRead` 事件
- 设备串口固定在 AppSet:4017=COM2、4150=COM3、ZigBee=COM4、LED=COM5,摄像机 IP 为 192.168.1.100,用户 admin,密码为空
- **新出现的 WinForms 特性**:
- 多窗体:`new Environment().Show()`、`new SecurityCameraData().ShowDialog()`、`this.Close()`
- 无边框窗体 FormBorderStyle=None,自己做标题栏
- Panel 的 Dock(Top/Fill/Bottom/Right)嵌套布局
- BackgroundImage + BackgroundImageLayout(Stretch/Center)
- BackColor=Transparent、RGB 颜色值、`Control.Parent` 动态改父容器、`Location = new Point(...)`
- PictureBox 的 MouseDown/MouseUp 事件,SizeMode 为 StretchImage/CenterImage
- **System.Windows.Forms.DataVisualization.Charting.Chart**:Legends、Series、XValueMember/YValueMembers、DataSource + DataBind、ChartType=Line
- 后台 `Thread` + `SynchronizationContext`(`WindowsFormsSynchronizationContext.Current`、`_main.Send(_ => {...}, null)`)
- `thread.Suspend()`(已过时的 API,书上和参考代码都在用,模拟器要么支持,要么提示改用标志位退出)
- `Dictionary<string, Image>`、`List<string>`
- `DateTimePicker.Value`
- `double.TryParse(..., out ...)`
- 文本框的 TextChanged 和复选框的 CheckedChanged 事件
- 任务3 的 Tick 循环:每 5.5 秒采集一次,每次都写入数据库(EnvironmentUpData);图表取最近 7 天的每日最高温和平均温。模拟器要能生成足够的历史数据,图表才有内容。