Files
IOT_SIM/docs/教材摘要/2-项目3后半到项目6前半.md
xuyueandClaude Opus 5.5 98be8331bc 第 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>
2026-10-22 10:15:14 +08:00

1123 lines
73 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 《物联网编程与应用 第2版》摘要 第二部分(PDF 66–125)
**页码对照**:PDF 66–114 对应书页 63–111(PDF = 书页 + 3)。书页 112(项目5 后的空白页)似乎没有扫描进来,所以从 PDF 115 起是 PDF = 书页 + 2(PDF 115 = 书 113 项目6 首页,PDF 125 = 书 123)。
**范围开头**:从 **项目3 任务2【知识提炼】** 中间开始,具体是知识点"1. RadioButton 单选按钮"的示例代码后半段(PDF 66 / 书 63)。项目3 的项目概述、任务1 全部、任务2 的任务描述和任务实施都在前一段范围里。
**范围结尾**:停在 **项目6 任务2【任务实施】第15步** `btnDelete_Click` 的 `catch` 块里面(PDF 125 / 书 123)。
---
## 项目3 智慧校园系统——智慧路灯控制模块(续)
### 任务2【知识提炼】(续)PDF 66–73 / 书 63–70
#### 1. RadioButton 单选按钮(示例后半段,PDF 66–67 / 书 63–64)
- 第3步:选中窗体,在属性窗口点"事件"按钮,找到 `Load` 事件,双击进入代码窗口:
```csharp
private void Form1_Load(object sender, EventArgs e)
{
this.radioButton1.Checked = true;//设置radioButton1默认选中状态
}
//设置字体颜色方法
private void type_method(String value)
{
switch (value)
{
case "Black": this.label1.ForeColor = Color.Black; this.label1.Text = "字体颜色[黑色]"; break;
case "Red": this.label1.ForeColor = Color.Red; this.label1.Text = "字体颜色[红色]"; break;
case "Blue": this.label1.ForeColor = Color.Blue; this.label1.Text = "字体颜色[蓝色]"; break;
case "Yellow": this.label1.ForeColor = Color.Yellow; this.label1.Text = "字体颜色[黄色]"; break;
default: break;
}
}
```
- 第4步:选中4个 RadioButton,找到"Click"事件写代码。**书里步骤写的是 Click,代码实际用的是 `radioButtonN_CheckedChanged`**:radioButton1→`type_method("Black")`,2→`"Red"`,3→`"Blue"`,4→`"Yellow"`。
- 第5步:运行效果见图3-7/3-8,窗体标题"设置字体颜色"。左侧4个单选按钮 黑色/红色/蓝色/黄色,右侧 label1 显示"字体颜色[黑色]"。
#### 2. 控件:CheckBox 复选框(PDF 67–69 / 书 64–66)
- 说明:CheckBox 提供一个选项(true/false 或 是/否)。它和 RadioButton 的区别是 CheckBox 可以多选组合,RadioButton 只能在互斥选项里选一个。`Appearance` 决定显示成常规外观还是按钮样式;`ThreeState` 决定是否支持三种状态;`Checked` 获取或设置是否选中。
- **表3-6 常见属性**:Appearance(外观)、AutoCheck(单击时 Checked/CheckState 和外观是否自动改变)、AutoSize、BackColor、BackgroundImage、CheckAlign(复选标记的对齐方式)、Checked、CheckState、Font、FontHeight、ForeColor、Image、ImageAlign、Location、Parent、ResizeRedraw、Text、TextAlign、ThreeState(是否允许3种状态)、Visible。
- **表3-7 常见事件**:CheckedChanged(Checked 属性的值改变时发生)、Click(单击控件时发生)。
- **示例"复选框CheckBox"**:保存在 `D:\物联网编程与应用\项目3\任务2\`,窗体见图3-9"字体状态设置"。
**表3-8 "字体状态设置"窗体控件**
| 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|---|
| Form1 | 窗体 | Text | 字体状态设置 | |
| checkBox_Bold | 复选框 | Text | 字体加粗 | |
| checkBox_Italic | 复选框 | Text | 字体倾斜 | |
| checkBox_Underline | 复选框 | Text | 字体下画线 | |
| checkBox_Strikeout | 复选框 | Text | 字体删除线 | |
| label1 | 标签 | Text | 普通:Hello_CheckBox | |
第3步:给4个 CheckBox 的"Click"事件加代码(**代码里实际是 CheckedChanged,控件名是 checkBox1–4,和表3-8 的 checkBox_Bold 等名字对不上**):
```csharp
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{ type_method(FontStyle.Bold, checkBox1.Checked); }
private void checkBox2_CheckedChanged(...) { type_method(FontStyle.Italic, checkBox2.Checked); }
private void checkBox3_CheckedChanged(...) { type_method(FontStyle.Underline, checkBox3.Checked); }
private void checkBox4_CheckedChanged(...) { type_method(FontStyle.Strikeout, checkBox4.Checked); }
private void type_method(FontStyle fs, bool status)//设置字体的[状态]方法
{
if (status)
this.label1.Font = new Font(label1.Font, label1.Font.Style | fs);
else
this.label1.Font = new Font(label1.Font, label1.Font.Style & ~fs);
}
```
运行效果见图3-10(加粗)和图3-11(加粗+下画线)。**模拟器需要支持** `new Font(Font, FontStyle)`、`FontStyle` 的位运算 `| & ~`,以及 Strikeout/Underline 的渲染。
#### 3. 数组(PDF 69–73 / 书 66–70)
- (1) 定义:相同类型的元素按顺序排列的集合;有数组名、下标、元素;C# 数组下标从0开始;声明时 `[]` 必须写在类型后面(`int[] table;`,不能写 `int table[]`);数组大小不属于类型的一部分(`int[] numbers; numbers = new int[10]; numbers = new int[20];`)。
- (2) 声明:一维 `int[] numbers;`、多维 `string[,] names;`、交错 `string[][] scores;`。声明之后必须实例化。
- (3) 创建:`new int[5]`、`new string[5,4]`;交错数组 `byte[][] scores = new byte[5][]; for(...) scores[x] = new byte[4];`;三维 `int[,,] buttons = new int[4,5,3];`;混合写法 `int[][,,][,] numbers;`。
示例 `Form1_Click`:建交错数组 `scores[i] = new byte[i + 3]`,然后 `MessageBox.Show("Length of row " + i + " is " + scores[i].Length);`。
- (4) 初始化:未初始化时元素取类型默认值,字段数组默认是 null。
- 一维:`new int[5] {1,2,3,4,5}`、`new int[] {...}`、`int[] numbers = {1,2,3,4,5};`、`string[] names = {"Matt","Joanne","Robert"};`
- 多维:`new int[3,2] {{1,2},{3,4},{5,6}}`、`string[,] siblings = {{"Mike","Amy"},{"Mary","Albert"}};`
- 交错:`new int[2][] { new int[] {2,3,4}, new int[] {5,6,7,8,9} }`,可以省略第一维大小,也可以省略 new。交错数组的元素没有初始化语法。
- (5) 访问元素:`numbers[4] = 5;`、`numbers[1, 1] = 5;`、`numbers[0][0] = 100; numbers[1][1] = 200;`
- (6) 数组是对象:基类是 `System.Array`;`int LengthOfNumbers = numbers.Length;`;还有排序、搜索、复制等方法。
- (7) 用 foreach 遍历数组:`int[] numbers = {4,5,6,1,2,3,-2,-1,0}; foreach (int i in numbers) MessageBox.Show(i.ToString());`。多维数组同样可以 foreach,`new int[3,2]{{9,99},{8,88},{7,77}}` 输出 `9 99 8 88 7 77`。多维数组用嵌套 for 能控制得更细。
### 项目3【能力拓展】PDF 73–74 / 书 70–71
(这一节在项目3 任务2 后面,是项目3 的最后一个能力拓展。)
**边想边做**:
> 制作"流水路灯控制"界面,如图3-12所示。
> 当程序运行时,在文本框中输入1和0组成的一组数(中间用","分隔),单击"开"按钮,则按1、0数值开启和关闭灯,1表示开,0表示关,每隔2s切换,实现流水灯。运行效果如图3-13所示。
- **参考界面(图3-12/3-13)**:窗体标题"ZigBee路灯"。左边是一个大灯泡(Lamp 控件,暗/亮两种状态)。右边 GroupBox"配置",里面有标签"路 灯"、串口下拉框(图中 COM3)、按钮"打开"(打开后变成"关闭")。下方有一个 TextBox(示例输入 `1,0,1,1,1,0,0,1`)和两个 RadioButton"开""关"。
- 提示1:用 `String.Split` 拆分字符串。例:
```csharp
String sstring = "ab+cd+ef+gh";
String [] spstring = sstring.Split(new char[] { '+' });
```
书上写"spstring[0]="ab", spstring[1]="cd", spstring[2]="ed", spstring[3]="gh""(**书里 [2] 印错了,应为 "ef"**)。
- 提示2:用条件运算符 `?:` 把字符"1""0"转成 True/False。例:`bool pBool= 5>3 ? True:False;`(**书里大写的 True/False 在 C# 里无法编译,应为 `true : false`**)。"5>3"成立就返回问号后第1个值,pBool 为 True,否则返回第2个值。
**自主探究**:
> 请为该流水路灯控制程序增加下列功能:
> 添加一个文本框TextBox(Name属性值为intervalTextbox)和一个命令按钮Button(name属性值为configButton,Text属性值为"设置")。文本框用来输入每次开关灯的时间间隔,在文本框输入好时间间隔后,单击"设置"按钮设置时间控件的时间间隔值为当前文本框输入的值,且"开""关"单选按钮可用,启动程序默认不可用,其他功能不变。
> 模拟器提示:参考项目里 `项目3/任务一/ZigBee路灯` 用的是 `DeviceCtrlHelper.zigBeeHelper.Open/Close/OnOff(RelayNum,bool)` 加 `lamp1.OnOff(bool)`(WinFormsDeviceControlLibrary.Lamp)。流水灯需要 Timer、Split,以及 RadioButton 的 Enabled。
### 项目3【项目评价】PDF 74–75 / 书 71–72
**1. 项目评价指标**(书里只有指标和要求,没有分值)
| 任务 | 评价指标 | 具体要求 |
|---|---|---|
| 任务1 | 窗体界面 | 界面布局合理,美观大方,符合使用者的习惯 |
| 任务1 | 功能实现 | 环境值能正确获取 |
| | | 环境值能正确显示 |
| | | 实时时间控制正常 |
| 任务2 | 窗体界面 | 界面布局合理,美观大方,符合使用者的习惯 |
| 任务2 | 功能实现 | 能正常采集红外传感器数据 |
| | | 环境值能正确推送 |
| | | 能实时联动路灯 |
| 职业素养 | 代码书写规范 | 应该遵从结构化的要求,采用缩进的格式 |
| | 注释书写规范 | 做到清晰、准确地描述内容。对于程序中复杂的部分必须有注释加以说明。注释量要适中,避免过多或过少导致阅读困难 |
| | 变量命名规范 | 选择有意义的英文进行命名,使变量名能清楚地表达其含义。尽量少使用人名、地名和汉语拼音。应使用那些约定俗成的缩写。多个单词组成的变量名,每个单词的首字母应该大写 |
| | 界面常用控件命名约定 | 窗体控件命名以控件名称缩写前3个字母开头,以具有代表含义的词来命名,其形式为:控件缩写(前3个字母)+命名(具有代表含义的单词) |
**2. 个人学习总结**:空白表,两栏"项目学习情况""心得与反思"(每个项目都有,后面不再重复)。
---
## 项目4 智慧校园系统——入侵信息监测模块(PDF 76–93 / 书 73–90)
**项目概述**:实时安防系统的入侵信息监测,是借助物联网技术把感应器、网络摄像机和警报装置嵌入各种安防监控设备,由计算机采集监测数据,并实现拍摄和报警。本项目采集红外对射入侵传感器的数据,经数字量采集器 ADAM4150 传到计算机,显示在程序窗体中;再用程序分别打开报警灯、控制网络摄像机拍照。
**学习目标**:
- 能理解C#重写函数的作用。
- 能正确实现数据的读取、摄像头图像的显示。
- 能理解静态、switch、foreach语句的作用,并能灵活使用。
- 形成良好的代码编写规范,具备良好的问题解决能力。
- 培养严谨、规范的编程思维和习惯,增强逻辑思维和抽象思维能力。
- 增强职业认识、提升职业素养、树立职业信心。
### 任务1 采集红外对射入侵传感器数据(PDF 76–81 / 书 73–78)
**任务描述**:红外对射入侵传感器由红外发射机和接收机组成,两者之间的光束被遮断时就进入报警状态。本任务模拟校园围墙安防系统,用 C# 把传感器采集的数据实时显示在窗体上。学生要学会 foreach,理解 foreach 和 for 的区别。
**任务实施步骤(压缩版)**:
1. ADAM4150 串口接 PC 串口,红外对射传感器接采集器 **DI3** 口。
2. 新建 WinForms 项目"红外对射采集",保存在 `D:\物联网编程与应用\项目4\任务1`。
3. 从配套资源中找到 ADAM4150 用的 `DeviceCtrlLibrary.dll`,把"API"文件夹下所有 dll 复制到 `...\红外对射采集\红外对射采集\bin\Debug`。
4. 添加引用 `DeviceCtrlLibrary.dll`。(**注:没有硬件设备的读者,请添加仿真动态库文件,无须添加"API"文件夹下的dll文件**,后面各任务都有这句注释。)
5. 按图4-1创建窗体(表4-1)。
6. `using System.IO.Ports; using DeviceCtrlLibrary;`
7. 全局变量 `bool IsOpen = false;`(书上印成 `boolIsOpen`,少了空格)。
8. `Form1_Load`:`com_port.Items.Clear(); foreach (string item in SerialPort.GetPortNames()) com_port.Items.Add(item);`
9. `btn_open_Click`:`IsOpen = !IsOpen;`。打开时调用 `DeviceCtrlHelper.adam4150Helper.Open(com_port.Text); timer1.Start(); btn_open.Text="关闭";`;关闭时调用 `adam4150Helper.Close(); timer1.Stop(); btn_open.Text="开启";`。
10. 选中 timer1,在属性窗口的事件栏(闪电图标)找到 Tick 事件(图4-2)。代码为 `txt_time.Text = DateTime.Now.ToString(); if (DeviceCtrlHelper.adam4150Helper.SetData().infraredValue) txt_even.Text = "发现入侵"; else txt_even.Text = "正常";`
11. 运行,选串口,点"开启",效果见图4-3(com1,2018-12-08 14:22:45,发现入侵)。
**表4-1 窗体界面控件属性**
| 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|---|
| Form1 | 窗体 | Text | 红外对射 | |
| com_port | 下拉列表框 | | | 显示串口 |
| btn_open | 按钮 | Text | 开启 | |
| txt_time | 文本框 | | | 显示时间 |
| txt_even | 文本框 | | | 显示入侵信息 |
| timer1 | 时间控件 | Interval | 2000 | |
| lbl_port | 标签 | Text | 环境串口 | |
| lbl_time | 标签 | Text | 时间 | |
| lbl_even | 标签 | Text | 入侵 | |
图里还有一个 GroupBox"数据显示",包着"时间""入侵"两行,表里没列出来。
**小贴士**:`DeviceCtrlHelper.adam4150Helper.SetData().infraredValue` 获取红外对射入侵传感器的数据,返回 bool,true 表示入侵,false 表示正常。
**知识提炼**:
- foreach 语句:列出集合中所有元素。用 in 分隔,左边是变量,右边是集合,每次循环从集合取下一个元素放进变量,取完为止。示例:`string StrMsg=""; int[] array = new int[]{1,2,3,4,5,6,7,8,9}; foreach (int i in array) { StrMsg = StrMsg + i.ToString(); } MessageBox.Show(StrMsg);`
**能力拓展·边想边做**(PDF 80–81):
> 通过"任务实施"的操作步骤,已经实现了获取红外对射入侵传感器数据的功能。本环节将实现该模块在有入侵警告时推送报警信息至LED显示屏,并具备警报解除功能。根据下列提示,实现上述功能。注:本题涉及串口服务器,需正确配置串口服务器与路由器来实现该功能。
- 提示1,**图4-4 检测流程图**:开始 → 判断"是否入侵"。否 → 显示正常信息 → 回到"是否入侵"。是 → 显示入侵信息 → 判断"是否推送"。是 → 生成报警信息并推送 → 修改推送状态 → 结束;否 → 结束。
- **图4-5 解除报警流程图**:开始 → 推送空白信息至LED → 修改推送状态 → 显示正常信息 → 结束。
- 提示2:根据流程图补全代码,写在 timer1 的 Tick 事件里并调试:
```csharp
private void timer1_Tick(object sender, EventArgs e)
{
txt_time.Text = DateTime.Now.ToString();
if(DeviceCtrlHelper.adam4150Helper.SetData().infraredValue)
{
txt_even.Text = "发现入侵";
if( ____ )
{
____
____ ) // 书上第二条空线以")"结尾,照原样保留
}
}
else
{
txt_even.Text = "正常";
}
}
```
**自主探究**:
> 修改该模块使其成为夜间门禁防盗报警系统,要具有如下功能,效果如图4-6所示。
> 1)有入侵则在界面上闪烁显示。
> 2)将入侵信息推送至LED显示屏上显示。
**参考界面(图4-6)**:标题"红外对射"。上方是"环境串口"下拉框(COM2)和"LED串口"下拉框(COM3),右侧有"关闭"和"解除报警"两个按钮。GroupBox"数据显示"里有时间(2017/1/17 15:37:00)、入侵(发现入侵),下面一个闪烁的标签"检测到入侵,请注意!"。
### 任务2 控制网络摄像机(PDF 81–87 / 书 78–84)
**任务描述**:网络摄像机是传统摄像机与网络视频技术结合的产品,可以通过网络访问和控制,实时监控现场。本任务模拟校园视频监控系统,用 C# 把摄像机的实时图像显示在窗体上,并实现上下左右转动和拍照。学生要学会 PictureBox 的用法和 static 的特点。
**任务实施步骤**:
1. 网络摄像机和 PC 都连到无线路由器,并配置好摄像机。
2. 新建项目"网络摄像机",保存在 `D:\物联网编程与应用\项目4\任务2`。
3. 把摄像机用的 DeviceCtrlLibrary.dll 和 API 下所有 dll 复制到 `...\网络摄像机\网络摄像机\bin\Debug`。
4. 添加引用(仿真库注释同上)。
5. 按图4-7创建窗体(表4-2):左边是大块黑色图片框;右边是"摄像机IP"文本框,下面是十字形方向键,中间是"开",再往下是"拍照"。
6. `using System.IO.Ports; using DeviceCtrlLibrary;`(**书里漏了 `using System.IO;`,可是 `Directory` 要用它;参考代码写的是 `using System.IO;`,没有 Ports**)。
7. 4个全局变量:
```csharp
static string CameraPath = AppDomain.CurrentDomain.BaseDirectory + "Camera/";//拍照保存路径
static string IpCameraIPAddress = "192.168.14.131";//网络摄像机IP
bool IsOpen = false;
Image ReadImage = null;//存储摄像机图片
```
8. `void ipCameraHelper_ImageRead(ImageReadEvenArgs e) { ReadImage = e.img; pictureBox1.Image = ReadImage; }`。类型名写的就是 `ImageReadEvenArgs`,原文如此,和库里一致。
9. `Form1_Load`:`if(!Directory.Exists(CameraPath)) Directory.CreateDirectory(CameraPath); DeviceCtrlHelper.ipCameraHelper.ImageRead += ipCameraHelper_ImageRead;//绑定图片显示事件`
10. `btn_open_Click`:`IsOpen=!IsOpen;`。打开时 `IpCameraIPAddress = txt_ip.Text; DeviceCtrlHelper.ipCameraHelper.Open(IpCameraIPAddress, "admin", "");//用户名admin,密码为空` 然后 `DeviceCtrlHelper.ipCameraHelper.Start(); btn_open.Text = "关";`;关闭时 `ipCameraHelper.Stop(); btn_open.Text = "开";`。
11–14. 方向键分别调用 `DeviceCtrlHelper.ipCameraHelper.PanUp()`、`PanDown()`、`PanLeft()`、`PanRight()`。
15. `btn_camera_Click`:`if (ReadImage != null) ReadImage.Save(CameraPath + DateTime.Now.ToString("yyyyMMdd_HH mm ss") + ".jpg");`(格式串里本来就带空格)。
16. 运行,填摄像机IP(图中是 192.168.2.50),点"开",效果见图4-8。
**表4-2 "网络摄像机"窗体界面控件属性**
| 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|---|
| Form1 | 窗体 | Text | 网络摄像机 | |
| pictureBox1 | 图片框 | BackColor / SizeMode | Black / StretchImage | 显示图像 |
| label1 | 标签 | Text | 摄像机IP | |
| txt_ip | 文本框 | | | 输入摄像机IP |
| btn_open | 按钮 | Text | 开 | 打开/关闭摄像机 |
| btn_up | 按钮 | Text | ↑ | 向上转 |
| btn_down | 按钮 | Text | ↓ | 向下转 |
| btn_left | 按钮 | Text | ← | 向左转 |
| btn_right | 按钮 | Text | → | 向右转 |
| btn_camera | 按钮 | Text | 拍照 | 截取图像 |
**知识提炼**:
1. PictureBox 图片框:能显示 .bmp、.ico、.wmf、.jpg、.gif。**表4-3 常见属性**:
- BorderStyle:边框样式,枚举(书里写成"Emun")。None 无边框,FixedSingle 单线边框,fixed3D 立体边框。
- Image:要显示的图片,也可以用 `Image.FromFile` 加载。
- SizeMode:显示模式,枚举。Normal 置于左上角,图片大于控件时被剪切;AutoSize 自动调整图片框大小,和图片一样大;CenterImage 图片框比图片大时居中,比图片小时剪切边沿;StretchImage 拉伸或收缩以适合图片框;zoom 按原比例缩放。
2. static:用 static 声明的成员属于类本身,不属于对象,不用实例化,直接通过类名调用。示例:`public class StaticClass { public static int x = 10; }`,然后在 `Form1_Load` 里 `MessageBox.Show(StaticClass.x.ToString());`。
**能力拓展·边想边做**:
> 通过"任务实施"的操作步骤,已经实现了摄像机显示、控制与抓拍的功能。本环节将为该模块添加每天定时抓拍的功能,固定时间间隔为:2h、1h、45min、30min、15min、5min。请根据下列提示,实现上述功能。
> 提示:根据之前所学利用if条件语句完善该代码,然后在程序中双击"开"按钮进入代码编写窗口,输入完整代码并调试。
```csharp
private void btn_open_Click(object sender, EventArgs e)
{
IsOpen = !IsOpen;//取反IsOpen变量,变更按下按钮后串口的打开或关闭状态
if (IsOpen)
{
IpCameraIPAddress = txt_ip.Text;
DeviceCtrlHelper.ipCameraHelper.Open(IpCameraIPAddress, "admin", "");//打开网络摄像机
DeviceCtrlHelper.ipCameraHelper.Start();//开启网络摄像机监控
btn_open.Text = "关";//将按钮文字改为"关"
if (comboBoxDateSelect.Text.Trim()=="2h")
{
timer1.Interval = 7200000;
}
else if (____)
{
timer1.Interval = 3600000;
}
else if (comboBoxDateSelect.Text.Trim() == "45min") // 书中这两行带下划线,是示范答案
{
timer1.Interval = 2700000;
}
else if ( ____ )
{
____
}
else if ( ____ )
{
____
}
timer1.Start();
}
else
{
DeviceCtrlHelper.ipCameraHelper.Stop();//关闭网络摄像机
btn_open.Text = "开";//将按钮文字改为"开"
timer1.Stop();
}
}
```
注意:一共列了6档间隔,骨架只有5个分支(5min 没有位置)。另外要新增 ComboBox `comboBoxDateSelect` 和 timer1,Tick 里应该做抓拍,书里都没给控件表,也没给 Tick 代码。
**自主探究**:
> 修改该模块使其成为智慧校园监控系统,在东围墙安装一台红外对射入侵传感器与一台网络摄像机,要具有如下功能,运行效果如图4-9所示。
> 1)实时监控围墙情况。
> 2)如发现入侵则自动抓拍。
**参考界面(图4-9)**:标题"网络摄像机",左边大图像区。右边三个 GroupBox:
- "设置":摄像机IP(192.168.2.90)、串口配置下拉框、"关闭"按钮。
- "入侵监控区":标签"发现入侵"。
- "控制区":↑ ← → ↓ 四个按钮。
### 任务3 控制报警灯(PDF 87–92 / 书 84–89)
**任务描述**:报警灯用在危险场所,靠闪光发出警示。发生事故、火灾等紧急情况时,控制器发出信号启动报警电路,灯就闪光报警。本任务模拟校园安防报警系统,用 C# 打开和关闭报警灯。学生要掌握 switch 语句。
**任务实施步骤**:
1. 采集器串口接 PC 串口,报警灯通过继电器接到采集器 **DO0** 口。
2. 新建"报警灯"项目,保存在 `D:\物联网编程与应用\项目4\任务3`。
3. **把自定义控件 `WinFormsDeviceControlLibrary.dll` 添加到工具箱。**
4. 把 ADAM4150 的 DeviceCtrlLibrary.dll 和 API 下的 dll 复制到 `...\报警灯\报警灯\bin\Debug`。
5. 添加引用(仿真库注释同上)。
6. 按图4-10建窗体(表4-4):左边是报警灯图,下面"开""关"两个按钮;右边 GroupBox"配置",里面是"报警灯"下拉框和"开启"按钮。
7. `using System.IO.Ports; using DeviceCtrlLibrary;`
8. `bool IsOpen = false;`(书上又印成 `boolIsOpen`)
9. `Form1_Load` 填充串口,和任务1 一样。
10. 定义方法:
```csharp
private void AlarmOnOff(string Device, bool IsOpen)
{
//利用switch语句打开或关闭继电器模块
switch(Device)
{
case "Alarm": DeviceCtrlHelper.adam4150Helper.OnOff(0, IsOpen);
alarm1.OnOff(IsOpen); break;
//开启或关闭继电器0模块,报警灯接入DO0,更改界面报警灯状态
case "LED1": DeviceCtrlHelper.adam4150Helper.OnOff(1, IsOpen); break;//开启或关闭继电器1模块
case "LED2": DeviceCtrlHelper.adam4150Helper.OnOff(2, IsOpen); break;//开启或关闭继电器2模块
default: MessageBox.Show("没有合适的接口程序!", "错误提示!"); break;//若为其他模块,则提示错误
}
}
```
11. `btn_open_Click`:只开关串口并改按钮文字("关闭"/"开启"),**没有 timer**。
12. `btn_alarmOpen_Click` 里调用 `AlarmOnOff("Alarm", true)`;13. `btn_alarmClose_Click` 里调用 `AlarmOnOff("Alarm", false)`。
14. 运行,选串口点"开启",再点"开""关"控制报警灯,见图4-11(灯变红亮)。
**表4-4 "报警灯"界面控件属性**
| 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|---|
| Form1 | 窗体 | Text | 报警灯 | |
| Alarm | 自定义控件 | | | 显示报警灯状态 |
| btn_alarmOpen | 按钮 | Text | 开 | |
| btn_alarmClose | 按钮 | Text | 关 | |
| groupBox1 | 组合框 | Text | 配置 | |
| label1 | 标签 | Text | 报警灯 | |
| com_port | 下拉列表框 | | | 显示串口 |
| btn_open | 按钮 | Text | 开启 | |
表里的控件名写的是"Alarm"(其实是控件类型名),代码和参考项目里的实例名都是 `alarm1`,类型是 `WinFormsDeviceControlLibrary.Alarm`。
**知识提炼**:switch 语句。
- 作用:类似 if,用于从一组互斥分支里选一个执行。格式为 `switch(表达式){ case "常量一":语句;break; … case "常量N":语句;break; default:语句;break; }`。
- 规则:每个 case 指定一个常数值,匹配上就跳进去执行,直到 break 跳出;都不匹配就执行 default;没有 default 就什么都不做,直接执行 switch 后面的语句。
- 示例 `Form1_Load`:`string grade="A"; switch(grade){ case "A":MessageBox.Show("您的成绩为:优秀");break; case "B":…"良好"; case "C":…"一般"; case "D":…"合格"; case "E":MessageBox.Show("您的成绩为:不合格,要好好努力了!");break; default: MessageBox.Show("无效的成绩等级!");break; }`
**能力拓展·边想边做**:
> 通过"任务实施"的操作步骤,已经实现了报警灯的打开与关闭功能。本环节为该模块添加有入侵警告时打开报警灯的功能。请根据下列提示,实现上述功能。图4-12所示为实现本功能的流程图,供参考。
**图4-12 监测流程图**:
- 开始 → 判断"是否入侵"。
- 是 → 判断"报警灯是否已打开"。否 → 打开报警灯 → 回到"是否入侵";是 → 结束。
- 否 → 判断"报警灯是否打开"。是 → 关闭报警灯 → 回到"是否入侵";否 → 结束。
提示:根据流程图补全代码,写在 timer1 的 Tick 事件里:
```csharp
private void timer1_Tick(object sender, EventArgs e)
{
if (DeviceCtrlHelper.adam4150Helper.SetData().infraredValue)
{
txt_even.Text = "发现入侵";
if( ____ )
{
____
AlarmState = true;
}
}
else
{
txt_even.Text = "正常";
if ( ____ )
{
____
AlarmState = false;
}
}
}
```
这里要额外加 txt_even、timer1 和 `bool AlarmState`,任务3 的控件表里都没有。
**自主探究**:
> 修改该模块使其成为智慧校园围墙监控报警系统,要具有如下功能,运行效果如图4-13所示。
> 1)实时检测入侵信息。
> 2)若有入侵则推送报警信息至LED显示屏。
> 3)若有入侵则打开报警灯。
**参考界面(图4-13)**:标题"报警灯",左边是报警灯。右边 GroupBox"配置":串口配置(COM2)、LED串口(COM3)、"关闭"按钮。下面 GroupBox"数据显示":入侵文本框(发现入侵)。
### 项目4【项目评价】PDF 92–93 / 书 89–90
| 任务 | 评价指标 | 具体要求 |
|---|---|---|
| 任务1 | 窗体界面 | 窗体控件正确,布局美观 |
| | 开关功能 | 按钮能打开、关闭串口;开启、停止定时器;更改按钮显示文字 |
| | 实时显示 | 能实时显示入侵数据 |
| 任务2 | 窗体界面 | 窗体控件正确,布局美观 |
| | 开关功能 | 能正确打开、关闭摄像机 |
| | 图像实时显示 | 能正确显示图像 |
| | 旋转控制功能 | 能控制摄像机上下左右旋转 |
| | 抓拍功能 | 能抓拍摄像机当前图像 |
| 任务3 | 窗体界面 | 窗体控件正确,布局美观 |
| | 开关功能 | 能正确打开、关闭报警灯 |
| 职业素养 | 代码书写规范 | 应该遵从结构化的要求,采用缩进的格式 |
| | 注释书写规范 | 做到清晰、准确地描述内容。对于程序中复杂的部分必须有注释加以说明。注释量要适中,避免过多或过少导致阅读困难 |
| | 变量命名规范 | 选择有意义的英文进行命名,使变量名能清楚地表达其含义。尽量少使用人名、地名和汉语拼音。应使用那些约定俗成的缩写。多个单词组成的变量名,每个单词的首字母应该大写 |
(没有分值。和项目3 比,少了"界面常用控件命名约定"这一行。)后面跟个人学习总结空表。
---
## 项目5 智慧校园系统——灾情信息监测模块(PDF 94–114 / 书 91–111)
**项目概述**:实时安防系统的灾情信息监测,是借助物联网技术把火焰、烟雾传感器嵌入各种安防监控设备,由计算机采集灾情数据,并实现推送和报警。本项目采集火焰传感器和烟雾传感器的数据,经数字量采集器 ADAM4150 传到计算机,显示在程序窗体中;再用程序把灾情信息推送到 LED 显示屏。
**学习目标**:
- 能正确理解for、while循环语句,并能使用循环语句解决简单问题。
- 能理解Datetime类和continue语句。
- 了解多线程的概念,学会简单的多线程编写。
- 形成良好的代码编写规范,培养代码优化和性能调优的能力。
- 培养严谨、规范的编程思维和习惯,增强逻辑思维和抽象思维能力。
- 增强职业认识,提升职业素养,树立职业信心。
### 任务1 采集火焰传感器数据(PDF 94–101 / 书 91–98)
**任务描述**:火焰传感器利用红外线对火焰非常敏感的特点,用特制的红外接收管检测火焰。本任务模拟校园安防的灾情监测系统,用 C# 把火焰传感器的数据实时显示在窗体上。学生要掌握 for 和 return 语句,了解 DateTime 类常见的属性和方法。
**任务实施步骤**:
1. ADAM4150 接 PC,火焰传感器接 **DI1**。
2. 新建"火焰传感器数据采集"项目,保存在 `D:\物联网编程与应用\项目5\任务1`。
3. 复制 dll 到 `...\火焰传感器数据采集\火焰传感器数据采集\bin\Debug`。
4. 添加引用(仿真库注释同上)。
5. 按图5-1建窗体(表5-1)。
6. `using System.IO.Ports; using DeviceCtrlLibrary;`
7. `bool IsOpen=false;`
8. `Form1_Load` **这次用 for 循环**:`for (int i=0; i<SerialPort.GetPortNames().Length; i++) { com_port.Items.Add(SerialPort.GetPortNames()[i].ToString()); }`
9. `btn_open_Click`:和项目4 任务1 一样(Open/Close 串口,"关闭"/"开启",timer1 Start/Stop)。
10. 自定义方法 `GetFireInfo`:
```csharp
private String GetFireInfo()
{
//判断火焰传感器返回bool值是否为true,若为true,则返回火情信息
if (DeviceCtrlHelper.adam4150Helper.SetData().fireValue)
{
return "发现火情!";
}
return "正常!";//无火情返回正常信息
}
```
11. timer1 的 Tick:`txt_time.Text = DateTime.Now.ToString(); txt_even.Text = GetFireInfo();`
12. 运行效果见图5-2(COM1,2017/1/17 13:13:16,发现火情!)。
**表5-1 火焰传感器数据采集界面控件属性**
| 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|---|
| Form1 | 窗体 | Text | 火焰 | |
| com_port | 下拉列表框 | | | 输入/显示串口 |
| btn_open | 按钮 | Text | 开启 | |
| txt_time | 文本框 | | | 显示时间 |
| txt_even | 文本框 | | | 显示入侵信息 |
| timer1 | 时间控件 | Interval | 2000 | |
| lbl_port | 标签 | Text | 串口 | |
| lbl_time | 标签 | Text | 时间 | |
| lbl_even | 标签 | Text | 事件 | |
图里还有 GroupBox"数据显示"。
**小贴士**:`DeviceCtrlHelper.adam4150Helper.SetData().fireValue` 获取火焰传感器数据,返回 bool,true 表示有火情,false 表示正常。
**知识提炼**:
1. for 循环:适合次数确定的循环,格式 `for(表达式1;表达式2;表达式3){语句;}`。表达式1 给循环变量赋初值;表达式2 是循环条件,为真就执行循环体,为假退出;表达式3 让循环变量自增或自减。示例求1到100的和:`int sum=0; for(int i=1;i<=100;i++){sum=sum+i;} MessageBox.Show("1+2+3+…+100="+sum.ToString());`
2. return 语句:结束当前方法,并返回 return 后面的值(可以不带值);void 方法可以省略 return。示例:`MessageBox.Show(Area(3.1,2.5).ToString()); private double Area(double x,double y){ return x * y; }`
3. DateTime 类。
- **表5-2 常见属性**:Now(当前日期和时间)、Today(当前日期)、Date(日期部分)、Day(当月第几天)、Month、Year、DayOfWeek(星期几)、DayOfYear(当年第几天)、Hour、Minute、Second。
示例:`DateTime dt=DateTime.Now; StrMsg+=dt.ToString()+"\n"; DateTime dt1=DateTime.Today; …dt.Date/Day/Month/Year/DayOfWeek/DayOfYear/Hour/Minute/Second…; MessageBox.Show(StrMsg);`
- **表5-3 常见方法**:
- AddDays / AddMonths / AddYears / AddHours / AddMinutes / AddSeconds:把指定的天数/月数/年数/小时数/分钟数/秒数加到当前实例上。
- IsLeapYear:判断指定年份是不是闰年。
- Parse:把日期时间字符串转成 DateTime。
- ToLongDateString / ToLongTimeString / ToShortDateString / ToShortTimeString:转成长日期、长时间、短日期、短时间字符串。
- ToString:转成字符串。
示例 `Form2_Load`:`dt.AddDays(10)`、`AddMonths(1)`、`AddYears(1)`、`AddHours(10)`、`AddMinutes(20)`、`AddSeconds(20)`,`string str="2016-12-12"; DateTime dt7=DateTime.Parse(str);`,再输出 `ToLongDateString()`、`ToLongTimeString()`、`ToShortDateString()`、`ToShortTimeString()`、`dt.ToString("yyyy*MM*dd")`。
- 模拟器注意:`MessageBox.Show` 要能显示 "\n" 换行;DateTime 的输出格式要和中文 Windows 一致(如 `2017/1/17 13:13:16`)。
**能力拓展·边想边做**:
> 通过"任务实施"的操作步骤,已经实现了实时获取火焰传感器数据的功能。本环节将使用多线程机制来实现该功能,为任务2做好准备。请根据下列提示,实现上述功能。
- 提示1,**图5-3 线程运行流程图**:开始 → 判断"是否检测到火焰",是 → 显示火情,否 → 显示正常 → 线程休眠2s → 回到判断(图上有"结束"框,但循环不会自己退出)。
- **图5-4 开关监测流程图**:开始 → 判断"串口是否打开"。
- 否 → 打开串口 → 判断"是否第一次运行线程",是 → 开启线程,否 → 继续已挂起的线程 → 结束。
- 是 → 挂起线程 → 结束。
- 提示2:进入代码窗口,在 btn_open_Click 事件里实现功能,线程方法见表5-4。**表5-4 线程方法**:
| 名称 | 说明 |
|---|---|
| Start() | 开启线程 |
| Resume() | 继续已挂起的线程 |
| Suspend() | 挂起线程 |
| Sleep(int32) | 线程休眠,Thread.Sleep(2000)为休眠2s |
| Abort() | 终止线程 |
- 提示3:找到 GetData() 方法,按流程图补全:
```csharp
private void GetData()
{
while ( ____ )
{
if ( ____ )
{
txt_time.Text=DateTime.Now.ToString();
txt_even.Text="发现火情";
}
else
{
txt_time.Text=DateTime.Now.ToString();
txt_even.Text="正常";
}
}
}
```
骨架里没有 `Thread.Sleep(2000)`,流程图里却有。
**自主探究**:
> 分别在教室A、B安装火焰传感器模拟智慧教室火焰检测系统,要具有如下功能,运行效果如图5-5所示。注:本题使用DI1与DI2接口,接入火焰传感器。
> 1)有火焰灾情时在界面上闪烁显示,并显示教室位置。
> 2)有火焰灾情时打开报警灯。
**参考界面(图5-5)**:标题"教室火焰检测"。顶部是"串口"下拉框(COM1)和"关闭"按钮。下面两个 GroupBox"教室A""教室B",每个里有标签"火焰状态"和状态文字("正常!" / "发现火情!")。
(注意 DI2 在任务实施里接的是烟雾传感器,这里两路都当火焰用,模拟器要能把 DI1、DI2 都设成火焰。)
### 任务2 采集烟雾传感器数据(PDF 101–106 / 书 98–103)
**任务描述**:烟雾传感器通过监测烟雾浓度来做火灾报警提示。本任务模拟校园安防的灾情监测系统,用线程把烟雾传感器的数据实时显示在窗体上。学生要掌握 while 语句,理解 while 和 for 各自的特点,初步了解线程 Thread 的概念和用法。
**任务实施步骤**:
1. 烟雾传感器接 **DI2**。
2. 新建"烟雾传感器数据采集"项目,保存在 `D:\物联网编程与应用\项目5\任务2`。
3. 复制 dll 到 `...\烟雾传感器数据采集\烟雾传感器数据采集\bin\Debug`。
4. 添加引用(仿真注释同上)。
5. 按图5-6建窗体(表5-5,**没有 timer**)。
6. `using System.IO.Ports; using DeviceCtrlLibrary; using System.Threading;//引用线程命名空间`
7. 全局变量:`bool IsOpen=false; bool IsFirst=true;//记录线程是否第一次打开 Thread thd;//定义Thread线程`
8. GetData:
```csharp
private void GetData()
{
//线程运行方法,不断循环运行
while (true)
{
if (DeviceCtrlHelper.adam4150Helper.SetData().smokeValue)//判断烟雾状态,True为发现烟雾灾情,False为正常状态
{
txt_time.Text = DateTime.Now.ToString();
txt_even.Text = "发现烟情";
}
else
{
txt_time.Text = DateTime.Now.ToString();
txt_even.Text = "正常";
}
Thread.Sleep(2000);//线程休眠2s
}
}
```
9. `Form1_Load`:用 foreach 填串口,然后 `thd = new Thread(GetData);//实例化线程,绑定线程运行方法` 和 `Control.CheckForIllegalCrossThreadCalls = false;//允许跨线程调用`。
10. `btn_open_Click`:
- `IsOpen=!IsOpen;`
- 打开时:`adam4150Helper.Open(com_port.Text); btn_open.Text="关闭"; if (IsFirst) { IsFirst=false; thd.Start(); } else { thd.Resume(); }`
- 关闭时:`adam4150Helper.Close(); btn_open.Text="开启"; thd.Suspend();`
11. 窗体 FormClosing 事件:`private void Form1_FormClosing(object sender, FormClosingEventArgs e) { thd.Abort();//终止线程 }`
12. 运行效果见图5-7(COM1,2016-12-15 17 10 29,正常)。
**表5-5 烟雾采集窗体界面控件属性**
| 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|---|
| Form1 | 窗体 | Text | 烟雾 | |
| com_port | 下拉列表框 | | | 输入/显示串口 |
| btn_open | 按钮 | Text | 开启 | |
| txt_time | 文本框 | | | 显示时间 |
| txt_even | 文本框 | | | 显示入侵信息 |
| lbl_port | 标签 | Text | 串口 | |
| lbl_time | 标签 | Text | 时间 | |
| lbl_even | 标签 | Text | 事件 | |
**模拟器需要支持**:
- `System.Threading.Thread` 的 Start、Suspend、Resume、Abort、Sleep,以及 `Control.CheckForIllegalCrossThreadCalls = false`(让后台线程直接改 UI 也不报错)。
- Suspend、Resume、Abort 在新版 .NET 里已过时,.NET Core/5+ 调 Abort 会抛 PlatformNotSupportedException,模拟器得自己模拟这几个行为。
- 参考项目的 FormClosing 和书不一样:`if(thd.ThreadState==ThreadState.Unstarted) thd.Abort();`。这种写法对已挂起的线程不 Abort;对没启动过的线程调 Abort,在原 .NET Framework 上也有问题。
**知识提炼**:
1. while 循环:条件为真就反复执行,直到条件为假,格式 `while(条件){语句;}`。示例求1到10的和:`int i = 1, sum = 0; while(i<11){ sum = sum + i; i = i + 1; } MessageBox.Show(sum.ToString());`
2. Thread 线程:.NET 是多线程环境,C# 程序从一个由系统自动创建的主线程开始,可以再创建额外线程。示例是一个控制台程序:
```csharp
class ThreadTest
{
static void Main(string[] args)
{
Thread thd = new Thread(PrintB);
thd.Start();
while (true) Console.Write("A");
}
static void PrintB ()
{
while (true) Console.Write("B");
}
}
```
**能力拓展·边想边做**:
> 通过"任务实施"的操作步骤,已经实现了获取烟雾传感器数据的功能。本环节将实现当有烟雾报警时,使用多线程在程序界面闪烁红色文字。请根据下列提示,实现上述功能。
提示1:根据以下提示,完善GetData()方法,进入代码编写窗口,输入代码。
```csharp
private void GetData()
{
while (true)
{
if (DeviceCtrlHelper.adam4150Helper.SetData().smokeValue)
{
txt_time.Text=DateTime.Now.ToString();
txt_even.Text="发现烟情";
btn_remove.Visible=true;
btn_open.Visible=true;
____ //挂起thd线程
____ //开启hdAlarm线程
}
else
{
txt_time.Text=DateTime.Now.ToString();
txt_even.Text="正常";
}
____ //调用Thread.Sleep(int)方法使线程休眠一段时间
}
}
```
提示2:根据以下提示,完善该代码。选择"Alarm"方法,进入代码编写窗口,输入代码。
```csharp
private void Alarm()
{
while( ____ )//设置while条件,使线程方法体循环运行
{
if (lbl_alarm.Visible == true)
{
lbl_alarm.Visible = false;
}
else
{
lbl_alarm.Visible =true;
}
____ //调用Thread.Sleep(int)方法,线程休眠一段时间,调试后设置合适的时间,使线程达到理想效果
}
}
```
提示3:根据以下提示,完善"btn_remove"按钮控件的"Click"事件,进入代码编写窗口,输入代码并调试。
```csharp
private void btn_remove_Click(object sender, EventArgs e)
{
txt_even.ForeColor = Color.Black;
____ //重新开启thd实时采集线程
btn_remove.Visible = false;
lbl_alarm.Visible = false;
____ //终止thdAlarm线程
}
```
还需要补的东西:btn_remove(解除报警按钮,默认隐藏)、lbl_alarm(红色闪烁标签)、`Thread thdAlarm = new Thread(Alarm)`。注释里的"hdAlarm"是原文笔误,指的就是 thdAlarm。
**自主探究**:
> 分别在教室A、B安装烟雾传感器模拟智慧教室烟雾检测系统,要具有如下功能,运行效果如图5-8所示。
> 1)有烟雾灾情时开启报警灯。
> 2)推送灾情位置和发现时间至LED显示屏。
**参考界面(图5-8)**:标题"烟雾"。上方"串口"(COM2)、"LED串口"(COM3)两个下拉框和"关闭"按钮。下面 GroupBox"教室A"(烟雾状态 / 发现烟情)和"教室B"(烟雾状态 / 正常)。
### 任务3 实时推送灾情信息(PDF 107–111 / 书 104–108)
**任务描述**:本任务模拟校园安防的灾情监测系统,结合任务1 和任务2,用 C# 把烟雾、火焰传感器的灾情信息推送到 LED 屏,用数组存储灾情信息并在窗体上显示。学生要进一步掌握数组,理解 continue 的作用,掌握 continue 和 break 的区别。
**任务实施步骤**:
1. 采集器串口和 LED 屏串口都接到**串口服务器**上;火焰、烟雾传感器分别接 DI1、DI2;正确配置串口服务器和路由器。
2. 新建"实时推送灾情信息"项目,保存在 `D:\物联网编程与应用\项目5\任务3`。
3. 复制 ADAM4150 的 dll 和 API 到 `...\实时推送灾情信息\实时推送灾情信息\bin\Debug`。
4. 添加引用(仿真注释同上)。
5. 新建 **"RecordClass" 类文件**:
```csharp
public static string[,] record = new string[50, 2] ;//定义一个二维数组,用于存储灾情信息的时间和内容
public static int recoudeindex = 0;//用于记录灾情信息的索引位置
```
参考项目里 RecordClass 是没有 namespace 的全局 `public class RecordClass`。
6. 按图5-9建窗体(表5-6):顶部是一条很宽的黑色 LED 显示控件 led1。下方 GroupBox"数据采集"里有时间、火焰、烟雾三个文本框和一个竖长的"获取"按钮。右边 GroupBox"配置"里有"L E D"下拉框、"环境采集"下拉框、"开启"按钮和"历史灾情信息"按钮。
7. `using System.IO.Ports; using DeviceCtrlLibrary; using DeviceCtrlLibrary.Model;//引用设备控制库下的Model命名空间`
8. 全局变量:`ADAM4150ResModel ResModel; bool IsOpen = false; string LED_MSG = "";//存储LED显示屏所需发送的文字信息`
9. `Form1_Load`:
```csharp
com_environmentPort.Items.Clear(); com_ledPort.Items.Clear();
//将系统串口名依次加入下拉列表框中,由于本题使用串口服务器,所以COM1计算机串口可不加入串口列表
for (int i = 0; i < SerialPort.GetPortNames().Length;i++ )
{
//判断计算机的默认串口,将其排除
if (SerialPort.GetPortNames()[i].ToUpper()=="COM1")
{
continue;//跳出此次循环
}
com_environmentPort.Items.Add(SerialPort.GetPortNames()[i].ToString());
com_ledPort.Items.Add(SerialPort.GetPortNames()[i].ToString());
}
```
参考项目没有排除 COM1,用两个 foreach 分别填充。书上的运行截图里 LED 用的却正是 COM1,自相矛盾。
10. `btn_Open_Click`:
- 打开时:`DeviceCtrlHelper.adam4150Helper.Open(com_environmentPort.Text);`、`DeviceCtrlHelper.ledHelper.OpenLedCom(com_ledPort.Text);`、`btn_Open.Text = "关闭";`
- 关闭时:只执行 `adam4150Helper.Close(); btn_Open.Text="开启";`,**LED 串口没有关**。
11. `btn_get_Click`("获取"按钮):
```csharp
ResModel = DeviceCtrlHelper.adam4150Helper.SetData();//获取数字量采集器ADAM 4150的最新数据
LED_MSG = "";
txt_time.Text = DateTime.Now.ToString();
txt_fire.Text = "正常"; txt_smoke.Text = "正常";
if (ResModel.fireValue)
{
txt_fire.Text = "发现火情";
LED_MSG += "发现火情";
RecordClass.record[RecordClass.recoudeindex, 0] = txt_time.Text;//记录灾情时间至数组
RecordClass.record[RecordClass.recoudeindex, 1] = " 发现火情";//记录灾情内容至数组
RecordClass.recoudeindex++;
}
if (ResModel.smokeValue)
{
txt_smoke.Text = "发现烟情";
LED_MSG += "发现烟情";
RecordClass.record[RecordClass.recoudeindex, 0] = txt_time.Text; /记录灾情时间至数组
RecordClass.record[RecordClass.recoudeindex, 1] = " 发现火情";//记录灾情内容至数组
RecordClass.recoudeindex++;
}
string ResStr = DeviceCtrlHelper.ledHelper.SendLedString(LED_MSG);
if (!(ResStr.IndexOf("Error") >= 0))
led1.setLedText(ResStr); //推送灾情信息至LED显示屏
```
这段有三个问题:
- 烟雾分支记录的内容写成了"发现火情",是 bug,参考项目里也一样。
- 烟雾分支的注释只有一个 `/`,书上的代码编译不过,参考项目正确。
- 数组只有 50 条,没有越界检查。
12. "历史灾情信息"按钮:`private void OpenRecord_Click(...) { Form2 fr2 = new Form2(); fr2.Show(); }`。这个按钮和"获取"按钮 btn_get 都不在表5-6 里。
13. 按图5-10建 Form2(表5-7)。
14. `Form2_Load`:`for (int i = 0; i < RecordClass.recoudeindex;i++ ) listBox1.Items.Add(RecordClass.record[i,0]+" "+RecordClass.record[i,1]);`。**书里代码用的是 listBox1,表5-7 和参考项目里都叫 lstbRecored。**
15. 运行:点"开启"后效果见图5-11,LED 区显示大字"发现火情",时间 2024/1/23 10 40.51,LED 用 COM1,环境采集用 COM2;点"历史灾情信息"后见图5-12,列表一行一条,如 `2024/1/23 10:41:30 发现火情`。
**表5-6 火焰烟雾采集窗体界面控件属性**
| 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|---|
| Form1 | 窗体 | Text | 火焰烟雾 | |
| com_ledPort | 下拉列表框 | | | 输入/显示LED串口 |
| com_environmentPort | 下拉列表框 | | | 输入/显示ADAM4150串口 |
| btn_Open | 按钮 | Text | 开启 | |
| txt_time | 文本框 | | | 显示时间 |
| txt_fire | 文本框 | | | 显示火焰信息 |
| txt_smoke | 文本框 | | | 显示烟雾信息 |
| groupBox1 | 组合框 | Text | 配置 | |
| lbl_Ledport | 标签 | Text | LED | |
| lbl_4150port | 标签 | Text | 环境采集 | |
| lbl_time | 标签 | Text | 时间 | |
| lbl_fire | 标签 | Text | 火焰 | |
| lbl_smoke | 标签 | Text | 烟雾 | |
| led1 | 自定义控件 | | | 信息显示在LED控件上 |
表里没有、参考项目里有的控件:
- btn_get(Text"获取")
- OpenRecord(Text"历史灾情信息")
- groupBox2(Text"数据采集")
- led1 的类型是 `WinFormsDeviceControlLibrary.LED`
**表5-7 历史灾情记录界面控件属性**
| 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|---|
| Form2 | 窗体 | Text | 历史灾情记录 | |
| lstbRecored | 列表框 | | | 显示灾情记录 |
**模拟器需要支持**:
- 多窗体 `new Form2().Show()`,以及多个类文件(RecordClass)。
- `DeviceCtrlHelper.ledHelper.OpenLedCom(port)` 和 `SendLedString(string)`。后者返回字符串,出错时字符串里含"Error"。
- `led1.setLedText(string)`。
- `ADAM4150ResModel` 的 fireValue 和 smokeValue。
**知识提炼**:continue 语句。
- 作用:结束本次循环,开始下一次循环。只能用在 while、do/while、for、foreach/in 的循环体里,用在别处会出错。
- 示例找出1到20内不能被3整除的数:`string StrMsg = ""; for (int i = 1; i <= 20; i++) { if (i % 3==0) continue; StrMsg += i.ToString() + "\n"; } MessageBox.Show(StrMsg);`
**能力拓展·边想边做**:
> 通过"任务实施"的操作步骤,已经实现了获取火焰及烟雾传感器数据的功能。本环节将实现实时获取相关数据,在发现灾情时推送信息至LED屏并打开报警灯,直至灾情解除。请根据下列提示,实现上述功能。
提示1:根据以下提示,完善"GetData"方法,进入代码编写窗口,输入完整代码并调试。
```csharp
private void GetData()
{
while (true)
{
if ( ____ )//判断是否有烟雾或火焰报警
{
txt_time.Text = DateTime.Now.ToString();
txt_even.Text = "发现灾情";
btn_remove.Visible = true;
if( ____ )//判断LED灯与报警灯的状态是否为false
{
____
____
LEDState = true;
AlarmLampState = true;
}
if(LedDisplay==false)
{
____ //打开LED显示屏串口
____ //推送灾情信息至LED显示屏
LedDisplay = true;
}
btn_open.Visible = true;
thd.Suspend();
}
else
{
txt_time.Text = DateTime.Now.ToString();
txt_even.Text = "正常";
}
Thread.Sleep(2000);
}
}
```
提示2:根据以下提示,完善"btn_remove"按钮控件的"Click"事件,进入代码编写窗口,输入完整代码并调试。
```csharp
private void btn_remove_Click(object sender, EventArgs e)
{
txt_even.ForeColor = Color.Black;
btn_remove.Visible = false;
lbl_alarm.Visible = false;
____ //重新开启thd实时采集线程
____ //结束thdAlarm线程
}
```
这道题沿用了任务2 的线程结构(thd、thdAlarm、txt_even、btn_remove、lbl_alarm),还要加 LEDState、AlarmLampState、LedDisplay 三个 bool。"LED灯"应该是接在 adam4150 DO1/DO2 上的 LED 灯(对应项目4 任务3 里 switch 的 "LED1""LED2"),不是 LED 显示屏。任务3 的控件表里这些都没有。
**自主探究**:
> 模拟智慧校园灾情监测系统的历史灾情显示功能,运行效果如图5-13所示,需具有如下功能:
> 1)在同一界面内利用数组记录灾情信息。
> 2)每隔10min将最新5条灾情信息推送至LED显示屏。
**参考界面(图5-13)**:标题"火焰烟雾"。左上是时间、火焰、烟雾三个文本框;右上 GroupBox"配置"有"L E D"(COM2)、"环境采集"(COM3)和"关闭"按钮。下面 GroupBox"历史记录"是一个大的多行列表,内容如 `发现火情 发现烟情 发现时间:2017/1/17 15.48:08`、`发现火情 发现时间:2017/1/17 15:48:10`……
### 项目5【项目评价】PDF 113 / 书 110
| 任务 | 评价指标 | 具体要求 |
|---|---|---|
| 任务1 | 窗体界面 | 窗体控件正确,布局美观 |
| | 开关功能 | 能正确打开、关闭串口;开启、停止定时器;更改按钮显示文字 |
| | 实时显示 | 能实时显示火焰传感器数据 |
| 任务2 | 窗体界面 | 窗体控件正确,布局美观 |
| | 开关功能 | 按钮能打开、关闭串口;开启、停止线程;更改按钮显示文字 |
| | 实时显示 | 能正确利用线程实时显示烟雾传感器数据 |
| 任务3 | 窗体界面 | 窗体控件正确,布局美观 |
| | 开关功能 | 能正确打开、关闭报警灯 |
| | 数组使用 | 能利用数组实时存储数据 |
| | 灾情实时显示 | 能实时显示火焰、烟雾传感器数据 |
| | 灾情推送 | 能正确推送灾情信息,并保证不重复推送 |
| 职业素养 | 代码书写规范 | 应该遵从结构化的要求,采用缩进的格式 |
| | 注释书写规范 | 做到清晰,准确地描述内容。对于程序中复杂的部分必须有注释加以说明。注释量要适中,避免过多或过少导致阅读困难 |
| | 变量命名规范 | 选择有意义的英文进行命名,使变量名能清楚地表达其含义。尽量少使用人名、地名和汉语拼音。应使用那些约定俗成的缩写。多个单词组成的变量名,每个单词的首字母应该大写 |
(没有分值。PDF 114 / 书 111 是个人学习总结空表。)
---
## 项目6 智慧校园系统——数据存储模块(PDF 115 起 / 书 113 起)
**项目概述**:数据是物联网的核心驱动力,数据存储是智慧校园系统开发的重要组成部分。本项目把各传感器采集的数据存进数据库,实现存储和分析处理。数据库用 Microsoft SQL Server 2008 R2,结合 C# 编程实现采集数据的存储、更新、删除、修改等基本操作。
**学习目标**:
- 理解数据库和数据表的基本概念,包括数据类型、表、字段、主键等概念。
- 了解数据库设计的流程和关键点。
- 掌握数据库SQL的基本语法。
- 能够通过数据库对各类型物联网传感器采集的数据进行存取。
- 能够在实际操作中连接摄像头,实现对摄像头开关和截图的操作。
- 树立数据安全保密意识,依规保护数据安全。
- 增强团队协作沟通能力,共同推动项目进展。
### 任务1 制作环境数据表(PDF 115–121 / 书 113–119)
**任务描述**:SQL Server 2008 R2 是微软的关系型数据库管理系统,它的数据库引擎为关系型数据和结构化数据提供更安全可靠的存储。本任务熟悉用数据库软件创建数据库和数据表,并能手动往表里添加、修改数据。学生要学会用 SQL Server 2008 R2 建库建表。
**任务实施(全程在 SSMS 里操作,没有 C# 代码)**:
1. 安装 SQL Server 2008 R2,打开 SQL Server Management Studio 并登录,主界面见图6-1(对象资源管理器里有:数据库、安全性、服务器对象、复制、管理、SQL Server 代理)。
2. 在"对象资源管理器"里右击"数据库",选"新建数据库",名称填 **"环境数据表"**,其余默认,点确定(图6-2/6-3)。新建数据库对话框里:所有者"<默认值>","使用全文索引"打勾;数据文件初始 3MB、按 1MB 增长、不限制;日志文件初始 1MB、按 10% 增长。
3. 展开"环境数据表"数据库,右击"表",选"新建表"(图6-4/6-5)。
4. 列名填 `light`、`temperature`、`humidity`,数据类型都选 `varchar(MAX)`,"允许 Null 值"打勾(图6-6)。
5. 点"保存",在"选择名称"对话框填表名 **"EnvironmentWork"**(图6-7)。
6. 右击表 EnvironmentWork,选"编辑前200行",就能直接录入数据(图6-8)。右键菜单里还有:新建表、设计、选择前1000行、编写表脚本为、查看依赖关系、全文索引、存储、策略、方面、启动PowerShell、报表、重命名、删除、刷新、属性。
7. 在 EnvironmentWork 里录入一周的环境数据,light 是光照、temperature 是温度、humidity 是湿度,内容自定。
**对应的建表 SQL(书里没有,模拟器可以内置)**:
```sql
CREATE DATABASE 环境数据表;
CREATE TABLE EnvironmentWork (light varchar(MAX) NULL, temperature varchar(MAX) NULL, humidity varchar(MAX) NULL);
-- 没有主键,也没有时间列
```
**知识提炼**:
1. 数据库(database,DB):长期存储在计算机内、有组织、可共享的数据集合,按一定的数据模型组织、描述和存储,冗余度小、数据独立性高、容易扩展,可以在一定范围内供多个用户共享。
2. 数据库管理系统(DBMS):用户和操作系统之间的一层数据管理软件(如 SQL 数据库管理系统),用来建立、使用和维护数据库,统一管理和控制数据库,保证安全性和完整性。
3. 数据表:数据库的组成部分,是实际存储数据的地方,每个数据库都有自己的数据表。表由行和列组成,建表就是定义列的过程。
4. 字段与记录:表里的每一行叫一条记录,表示所存物体各方面的属性;每个数据项叫字段,表示物体某方面的性质,比如学生信息里的学号、姓名、出生日期。
5. 主键:能唯一标识表中每一行,可以是一个字段,也可以是多个字段的组合,用来强制实体完整性。一个表只能有一个主键,主键列不能为空。例如学生表(学号、姓名、性别、班级)中学号唯一,就是主键。
6. SQL 数据类型:SQL Server 有 20 多种系统数据类型,插入的值超出允许范围就会报错。常用的有:
1) bit:只能是0、1或空值
2) tinyint:0~255,占1B
3) smallint:-32768~32767,占2字节
4) int:-2147483648~2147483647,占4B
5) money:-9220亿~9220亿,精确到货币单位的万分之一
6) float:-1.79×10^308~1.79×10^308,是近似值
7) smalldatetime:精确到1min
8) datetime:精确到1/300s
9) uniqueidentifier:GUID 全局唯一标识
10) char:定长、非统一编码,必须指定列长
11) varchar:变长,必须指定最大长度
12) text:最多 2^31-1(约20亿)个字符
13) nvarchar:统一编码、变长,能存4000个字符,占用字节翻倍
14) ntext:2^30-1(近10亿)个字符,占用字节翻倍
15) binary:最多8000B的定长二进制
16) varbinary:最多8000B的变长二进制,内容大小会变时用它
17) image:变长二进制,最多 2^31-1(约20亿)B
**能力拓展·边想边做**:
> 在数据库"环境数据表"中建立表"EnvironmentWorkNew",主键为id,整型,可以自增,"EnvironmentDay"为日期型date,"EnvironmentWork"为varchar(200),如图6-9所示。
图6-9 的表结构:id int 主键、不允许 Null;EnvironmentDay date 允许 Null;EnvironmentWork varchar(200) 允许 Null。自增要设 IDENTITY(1,1),对应 SQL 为 `CREATE TABLE EnvironmentWorkNew (id int IDENTITY(1,1) PRIMARY KEY, EnvironmentDay date NULL, EnvironmentWork varchar(200) NULL);`。
**自主探究**:
> 使用SQL语句,为新表"EnvironmentWorkNew"添加一行内容,"EnvironmentDay"的值为日期"2017年9月","EnvironmentWork"的值为"环境适宜"。
"2017年9月"不是完整的 date 值,参考答案应写成类似 `INSERT INTO EnvironmentWorkNew (EnvironmentDay, EnvironmentWork) VALUES ('2017-09-01', '环境适宜');`。模拟器的 SQL 引擎要支持 date 类型和 IDENTITY。
### 任务2 存储校园环境数据(PDF 121–125 / 书 119–123,范围在任务中间结束)
**任务描述**:动态采集环境数据,用程序代码存进 SQL 数据库。学生要了解 ADO.NET,学会表内容的添加、删除、修改。
**任务实施步骤**:
1. 在"环境数据表"库里新建 **"CampusEnvironment"** 表,共5个字段:自增主键 ID,light(光照度)、humidity(湿度)、temperature(温度)三个都用 `varchar(50)`,addtime 用 `Datetime`。对应 SQL:`CREATE TABLE CampusEnvironment (ID int IDENTITY(1,1) PRIMARY KEY, light varchar(50), humidity varchar(50), temperature varchar(50), addtime datetime);`
2. 把 ADAM4017 的串口直接接到 PC(**书上把 ADAM4017 叫成"数字量采集器",其实它是模拟量采集器**)。注:没有硬件的读者跳过这一步。
3. 新建"实时存储校园数据"项目,保存在 `D:\物联网编程与应用\项目6\任务2`。
4. 复制 ADAM4017 的 DeviceCtrlLibrary.dll 和 API 到 `...\实时存储校园数据\实时存储校园数据\bin\Debug\`。
5. 添加引用(仿真注释同上)。
6. 按图6-10建主窗体(表6-1),标题"校园灾情数据存储":
- 左上 GroupBox"配置":串口下拉框、"开启"按钮。
- 右上 GroupBox"数据显示":光照度、温度、湿度三个文本框,"获取""保存""修改""删除"四个按钮。
- 下方 GroupBox"历史记录":一个大 DataGridView。
7. 引用:
```csharp
using System.Data.SqlClient; //引用数据库操作库
using System.IO.Ports;
using DeviceCtrlLibrary;
using DeviceCtrlLibrary.Model;//引用ADAM4017ResModel
```
8. 7个全局变量:
```csharp
string sqlConnectString = @"Data Source= PC-20150908SCJF\SQLSERVER1026;Initial Catalog=环境数据表;User ID=sa;Password=123456";//数据库的连接字符串
SqlConnection mySqlConnection;
SqlCommand mySqlCmd;
SqlDataAdapter sqldata;
bool IsOpen = false;
ADAM4017ResModel ResModel;
DataSet ds;
```
**小贴士**:其中"PC-20150908SCJF\SQLSERVER1026"为自己SQL数据库的名称,也可以用"."号来表示本机默认数据库名称,该代码采用"sa"用户名来连接数据库,密码为"123456",请根据实际情况修改数据库名称和"sa"的密码。
9. `Form1_Load`:`mySqlConnection = new SqlConnection(sqlConnectString); mySqlConnection.Open();`,再用 foreach 填充 com_port。(**Load 里没调用 Databind(),所以刚启动时表格是空的。**)
10. `btn_Open_Click`:`if(!IsOpen){ DeviceCtrlHelper.adam4017Helper.Open(com_port.Text); btn_Open.Text="关闭"; IsOpen=True; } else { DeviceCtrlHelper.adam4017Helper.Close(); btn_Open.Text="开启"; IsOpen=False; }`(**书上的 True/False 是大写,引号也是全角 ”关闭”,原样都编译不过**)。
11. `btn_get_Click`(书上把 `private void` 印成了 `privatevoid`):`ResModel = DeviceCtrlHelper.adam4017Helper.SetData(); txt_light.Text = ResModel.lightValue; txt_humidity.Text = ResModel.humidityValue; txt_temperature.Text = ResModel.temperatureValue;`。这三个值都是 string。
12. 数据绑定函数 `Databind()`:
```csharp
mySqlConnection = new SqlConnection(sqlConnectString);
mySqlConnection.Open();
SqlCommand sqlcmd = new SqlCommand("select ID as '编号',temperature as '温度',humidity as '湿度',light as '光照度', addtime as '时间' from CampusEnvironment", mySqlConnection);
sqldata = new SqlDataAdapter(sqlcmd);
ds = new DataSet();
sqldata.Fill(ds);
dataGridView1.DataSource = ds.Tables[0].DefaultView;
```
**小贴士**:select语句主要用于从表中选取数据。
(每次调用都新开一个连接,从不关闭。)
13. `btn_save_Click`:
```csharp
string light = txt_light.Text; string temperature = txt_temperature.Text; string humidity = txt_humidity.Text;
try
{
mySqlCmd = new SqlCommand("insert into CampusEnvironment values (" + temperature + "," + humidity + "," + light + ", '"+DateTime.Now.ToString()+"')", mySqlConnection);
mySqlCmd.ExecuteNonQuery();
MessageBox.Show("保存成功!");
Databind();
}
catch (Exception ex) { MessageBox.Show("保存失败:"+ex.ToString()); }
```
**问题**:
- INSERT 没写列名,值的顺序是 temperature、humidity、light,第1步建表的列顺序却是 light、humidity、temperature,数据会存错列。除非学生建表时按 temperature、humidity、light 的顺序。模拟器的 SQL 引擎必须严格按列顺序插入,才能让这个错误暴露出来,或者在题目说明里提醒学生。
- 数值直接拼进 SQL、没加引号,存 varchar 列时靠隐式转换;值一旦不是数字就会报错。
- `DateTime.Now.ToString()` 生成的字符串要能被 SQL 解析成 datetime。
14. `btnUpdate_Click`:
```csharp
SqlCommandBuilder cmdb = new SqlCommandBuilder(sqldata);
sqldata.UpdateCommand = cmdb.GetUpdateCommand();
if (sqldata.Update(ds.Tables[0]) == 1) { MessageBox.Show("修改成功!"); Databind(); }
else { MessageBox.Show("修改失败!"); }
```
做法是让用户直接在 DataGridView 里改单元格,再提交。需要:DataGridView 可编辑并回写到 DataTable;SqlCommandBuilder 能根据带别名的 SELECT 和主键 ID 生成 UPDATE;`== 1` 表示只有恰好改了一行才算成功。
15. `btnDelete_Click`:
```csharp
try
{
mySqlCmd = new SqlCommand("delete from CampusEnvironment where id="+dataGridView1.CurrentRow.Cells[0].Value.ToString(), mySqlConnection);
mySqlCmd.ExecuteNonQuery();
MessageBox.Show("删除成功!");
Databind();
}
catch (Exception ex) //捕获数据库操作异常信息
{
```
**范围到这里结束**。后面的 catch 内容在下一段里,参考代码是 `MessageBox.Show("修改失败:" + ex.ToString());`。
**表6-1 校园数据存储窗体界面控件属性**
| 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|---|
| Form1 | 窗体 | Text | 校园灾情数据存储 | |
| groupBox1 | 组合框 | Text | 数据显示 | |
| groupBox2 | 组合框 | Text | 配置 | |
| groupBox3 | 组合框 | Text | 历史记录 | |
| txt_light | 文本框 | | | 输入/显示光照度值 |
| txt_temperature | 文本框 | | | 输入/显示温度值 |
| txt_humidity | 文本框 | | | 输入/显示湿度值 |
| btn_get | 按钮 | Text | 获取 | 控制数据获取 |
| btn_save | 按钮 | Text | 保存 | 保存数据到SQL数据库 |
| btnUpdate | 按钮 | Text | 修改 | 更新修改的数据 |
| btnDelete | 按钮 | Text | 删除 | 删除修改的数据 |
| btn_Open | 按钮 | Text | 开启 | 打开串口 |
| com_port | 下拉列表框 | | | 显示串口 |
| lbl_light | 标签 | Text | 光照度 | |
| lbl_temperature | 标签 | Text | 温度 | |
| lbl_humidity | 标签 | Text | 湿度 | |
| dataGridView1 | 数据网格控件 | | | 用于绑定数据库数据 |
---
## 书和参考项目的差异汇总(只列本段范围内的)
| 位置 | 书中 | 参考项目 / 实际情况 |
|---|---|---|
| 项目3 任务2 RadioButton 示例 | 步骤说"Click"事件 | 代码是 CheckedChanged(书里的示例,参考项目里没有) |
| 项目3 任务2 CheckBox 示例 | 表3-8 控件名 checkBox_Bold/Italic/Underline/Strikeout | 代码用 checkBox1–4 |
| 项目3 能力拓展 Split 提示 | spstring[2]="ed" | 应为 "ef" |
| 项目3 能力拓展 ?: 提示 | `5>3 ? True:False` | C# 要写小写 true/false |
| 项目4 任务1 | 表4-1 里标签名 lbl_port/lbl_time/lbl_even,没有 GroupBox | 参考项目是 label1/label2/label3,还有 groupBox1"数据显示" |
| 项目4 任务1/3 | `boolIsOpen = false;` | `bool IsOpen = false;` |
| 项目4 任务2 | `using System.IO.Ports;` | 参考项目是 `using System.IO;`(Directory 要用它) |
| 项目4 任务2 | label1 Text"摄像机IP" | 参考项目里是"摄像头IP" |
| 项目4 任务3 | 表4-4 控件名"Alarm" | 实例名 alarm1,类型 WinFormsDeviceControlLibrary.Alarm |
| 项目5 任务1/2 | 表5-1/5-5 标签名 lbl_port/lbl_time/lbl_even | 参考项目是 label1/2/3,加 groupBox1"数据显示" |
| 项目5 任务2 FormClosing | `thd.Abort();` | `if(thd.ThreadState==ThreadState.Unstarted) thd.Abort();` |
| 项目5 任务3 Form1_Load | for + continue 排除 COM1 | 两个 foreach,不排除 COM1 |
| 项目5 任务3 btn_get 烟雾分支 | 注释只有一个 `/`;内容记成" 发现火情" | 参考项目注释正确,但同样记成" 发现火情" |
| 项目5 任务3 Form2_Load | `listBox1.Items.Add` | 参考项目是 `lstbRecored.Items.Add`,和表5-7 一致 |
| 项目5 任务3 表5-6 | 没有 btn_get、OpenRecord、groupBox2 | 参考项目里有(获取 / 历史灾情信息 / 数据采集);Form1 里还有空的 com_ledPort_Click、com_environmentPort_Click、button1_Click |
| 项目6 任务1 | 全部是 SSMS 操作(建库"环境数据表"、建表 EnvironmentWork),没有代码 | 参考项目 `项目6/任务一` 却是一个 WinForms 程序:连接串 `Password=123`,往 CampusEnvironment 插入3个值、不含 addtime。它像是任务2 的早期版本,和书上的任务1 对不上 |
| 项目6 任务2 连接串 | `Data Source= PC-20150908SCJF\SQLSERVER1026;…Password=123456` | 参考项目是 `Data Source=.;…Password=123456` |
| 项目6 任务2 Form1_Load | 打开连接、填充 com_port,不调 Databind | 参考项目**只调 Databind(),不填 com_port**,串口下拉框一直是空的 |
| 项目6 任务2 Databind | 别名 `light as '光照度'` | 参考项目是 `'光照'`;表6-1 标签"光照度",参考项目 lbl_light 是"光照" |
| 项目6 任务2 | groupBox3 历史记录 | 参考项目叫 groupBox4 |
| 项目6 任务2 btn_Open | `IsOpen=True/False`,全角引号 | 参考项目是小写 true/false,代码正确 |
| 项目6 | 没有 SQL 脚本 | 配套资源里也没有 .sql/.mdf,模拟器要自己预置这些表 |
## 模拟器需要支持的、光看代码不明显的东西(本段范围)
- **WinForms 控件**:
- RadioButton、CheckBox:Checked 属性和 CheckedChanged 事件;CheckBox 还有 ThreeState、Appearance。
- PictureBox:SizeMode 的 StretchImage/Zoom 等模式,BackColor,`Image.FromFile`,`Image.Save(path)`。
- ListBox,DataGridView(可编辑,CurrentRow.Cells[0].Value)。
- GroupBox,Timer(Interval 最大要能到 7200000),以及 Visible、Enabled、ForeColor 的动态切换(用于闪烁效果)。
- `Font(Font, FontStyle)` 和 FontStyle 的位运算。
- 多个窗体(Form2.Show()),多个 .cs 文件(RecordClass,带 static 字段)。
- 窗体的 FormClosing 事件。
- MessageBox.Show(text, caption)。
- **自定义控件(WinFormsDeviceControlLibrary)**:`Alarm.OnOff(bool)`、`LED.setLedText(string)`,项目3 用 `Lamp.OnOff(bool)`。这个控件库要先"添加到工具箱"才能用。
- **设备 API(DeviceCtrlLibrary)**:
- adam4150Helper(数字量):
- 方法:`Open(port)`、`Close()`、`SetData()`、`OnOff(int DO, bool)`。
- SetData() 返回 ADAM4150ResModel,字段有 infraredValue(DI3)、fireValue(DI1)、smokeValue(DI2)。
- 继电器 DO0 是报警灯,DO1、DO2 在代码里叫 LED1、LED2。
- adam4017Helper(模拟量):`Open/Close/SetData()`,SetData() 返回 ADAM4017ResModel,有 lightValue、humidityValue、temperatureValue 三个 string。
- ipCameraHelper:
- 方法:`Open(ip, "admin", "")`、`Start()`、`Stop()`、`PanUp/PanDown/PanLeft/PanRight()`。
- 事件:`ImageRead += handler(ImageReadEvenArgs e)`,用 `e.img` 取图。
- 这个事件回调要能直接给 pictureBox1.Image 赋值(书里没做 Invoke)。
- ledHelper:`OpenLedCom(port)`;`SendLedString(msg)` 返回字符串,出错时含"Error"。
- 命名空间 `DeviceCtrlLibrary.Model`(ADAM4150ResModel、ADAM4017ResModel)。
- **串口和硬件**:
- `SerialPort.GetPortNames()` 要返回一组虚拟串口。项目5 任务3 会排除 COM1,书上的截图里却用了 COM1/COM2/COM3,所以至少要提供 COM1–COM3。
- 串口服务器、路由器的配置只是硬件准备步骤,模拟器里不用体现。
- 教室A/B 那类拓展题需要同时把 DI1、DI2 当两路火焰(或两路烟雾)传感器,所以模拟器里每个 DI 接什么传感器最好能配置。
- **线程**:
- Thread 的 Start、Suspend、Resume、Abort、Sleep,ThreadState。
- `Control.CheckForIllegalCrossThreadCalls = false`:后台线程直接改 UI 也要能生效。
- **数据库(ADO.NET + T-SQL 子集)**:
- ADO.NET 对象:SqlConnection(连接串).Open(),SqlCommand.ExecuteNonQuery(),SqlDataAdapter.Fill(DataSet),`ds.Tables[0].DefaultView` 作为 DataSource,`SqlCommandBuilder.GetUpdateCommand()` 和 `adapter.Update(DataTable)`(返回受影响行数)。
- SQL 语句:带中文别名的 `select ... as '编号' ...`,不写列名的 `insert into T values (...)`,`delete from T where id=...`。
- 类型和库名:varchar(MAX)、varchar(50)、varchar(200)、date、datetime、int IDENTITY 主键;中文数据库名"环境数据表"。
- 连接串:服务器名、sa 密码这些要能宽松接受,不管写的是 `.` 还是 `PC-…\SQLSERVER1026` 都映射到学生自己的沙箱库。
- 任务1 的 SSMS 操作(新建数据库、新建表、编辑前200行)需要一个简化的网页版"表设计器 + 数据编辑"界面,或者改成让学生写 SQL。