任务说明补齐 23 份;设计器支持窗体改名

- 其余 21 个实验的 task.md:控件名照示例代码,"在平台上怎么试"和评分步骤一致,列出书上的印刷错误和控件名不一致
- 窗体改名:属性窗口里选中窗体可以改名称,类名、构造函数、new X()、Program.cs 和两个文件名一起改;方法名和字符串不变
- 11 个主窗体不叫 Form1 的实验,任务说明提示先改窗体名再双击生成 Load 事件
- 测试:表头只认分隔行上面那一行,数据行里出现"名称"不再被当成表头

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-26 13:55:39 +08:00
1 parent 90b739a2c7
commit d4b8bf9241
29 files changed
+1236 -31

No files matched your search

+64
View File
@@ -0,0 +1,64 @@
## 任务目标
做一个登录程序:登录窗体里输入用户名和密码,点"登录"检查账号是否存在;点"增加用户"打开第二个窗体,输入用户名和两遍密码后保存。用户数据放在一个公共的静态类里,两个窗体都能用。
练习重点:多个窗体(新建、Show、Close)、静态类和属性、数组和 for 循环、MessageBox。详细步骤见书 p2–7。
> 书上先用 Python 写了一遍登录程序,平台只支持 C#,这一部分看书了解即可。知识提炼里的控制台示例可以把代码放进按钮的 Click 里运行,`Console.WriteLine` 的输出显示在运行区下面;`Console.ReadLine` 读不到输入。
## 界面和控件名
登录窗体:
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `Login` | 登录 |
| 文本框 | `txtUserName` | 输入用户名(前面放标签 `LblUsername`) |
| 文本框 | `txtPassword` | 输入密码,PasswordChar 设为星号 *(前面放标签 `LblPwd`,文字"密码") |
| 按钮 | `btnOk` | 登录 |
| 按钮 | `btnCancel` | 取消 |
| 按钮 | `btnAdd` | 增加用户 |
用户名前面的标签 `LblUsername` 文字是"用户名称"。
> 窗体名:在设计器里点窗体空白处,把属性窗口的**名称**从 `Form1` 改成 `Login`,类名、文件名和 Program.cs 会跟着改。
增加用户窗体(点"+ 新建窗体",名字填 AddUser):
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `AddUser` | 增加用户 |
| 文本框 | `txtNewUser1` | 新用户名(前面放标签"用户名") |
| 文本框 | `txtPass1` | 密码,PasswordChar 设为星号 * |
| 文本框 | `txtPass2` | 确认密码,PasswordChar 设为星号 * |
| 按钮 | `btnOk` | 确定 |
| 按钮 | `btnCancel` | 取消 |
> 书上的控件表和代码对不上:表里写 `txtPwd`、`BtnOk`,增加用户窗体写 `txtUserName`、`txtPwd`、`txtCheckPwd`,代码里用的是上面这些名字。请按上面的表起名,自动评分按这些名字操作。
## 实现步骤
1. 点"+ 新建类",新建 `PublicData` 类,写 4 个静态成员:用户名数组 `SysUserName`、密码数组 `SysPwd`(都是 `new string[4]`,最多存 4 个用户),已存的用户数 `intNum`、当前登录用户的序号 `intUser`(这两个写成 `{ get; set; }` 属性)。
2. Login 窗体里声明 `public bool IsLogin = false;` 记录是否登录成功。
3. **登录窗体 btnOk 的 Click**:用 for 循环逐个比较 `PublicData.SysUserName[i]` 和 `SysPwd[i]`,用户名和密码都相同(且用户名不为空)时把 IsLogin 设为 true,提示"登录成功,欢迎进入!",记下 `intUser = i`,break 跳出循环;循环结束后 IsLogin 还是 false,就提示"无效的密码或用户名,请重试",并让密码框获得焦点(`Focus()`)。
4. **登录窗体 btnCancel 的 Click**:IsLogin 设为 false,`this.Close()` 关闭窗体。
5. **btnAdd 的 Click**:`new AddUser()` 创建增加用户窗体,再调用 `Show()` 显示出来。
6. **增加用户窗体 btnOk 的 Click**:
- `intNum` 小于 4 时,如果两次密码相同(`txtPass1.Text.Equals(txtPass2.Text)`)且不为空,就把用户名和密码存进两个数组的第 `intNum` 个位置,`intNum` 加 1,关闭窗体;否则提示"用户名或密码错误,请重新输入"。
- 已经存满 4 个,提示"用户名已满不能增加"。
7. **增加用户窗体 btnCancel 的 Click**:`this.Close()`。
## 和书上不一样的地方
> **增加用户窗体每次点击时新建**:书上在 Login 里写了全局变量 `AddUser addfrm = new AddUser();`,点按钮时只 `Show()`。窗体关闭后就被释放了,第二次点"增加用户"会出错,所以改成在 btnAdd 的 Click 里 `new AddUser()` 再 `Show()`。
> 书上登录成功后用到的变量 `change` 没有声明过,照抄会编译不过,这几行删掉即可。
> 程序刚启动时一个用户都没有,要先点"增加用户"加一个账号才能登录。
## 在平台上怎么试
1. 运行后点"增加用户",用户名填 zhangsan,两次密码都填 123,点"确定",窗体关闭。
2. 在登录窗体输入 zhangsan 和 123,点"登录",应提示"登录成功,欢迎进入!"。
3. 输入一个不存在的用户名或错误的密码,应提示"无效的密码或用户名,请重试"。
4. 再点"增加用户",两次密码填得不一样,点"确定",应提示"用户名或密码错误,请重新输入"。
+45
View File
@@ -0,0 +1,45 @@
## 任务目标
一个窗体里做两件事:上半部分是简易计算器,输入两个数、选运算符,点"="算出结果;下半部分点"显示",让两个中国人、两个美国人依次"说话",在文本框里输出各自说的语言。
练习重点:装箱与拆箱(object 和值类型互相转换)、类的继承、虚方法(virtual)和重写(override)、List 和 Dictionary。详细步骤见书 p13–16。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `Form1` | C#入门基础 |
| 分组框 | `groupBox1` | 简易计算器 |
| 文本框 | `txtNum1` | 第一个数 |
| 下拉框 | `cmboxType` | 运算符,Items 填 `+`、`-`、`*`、`/` 四项 |
| 文本框 | `txtNum2` | 第二个数 |
| 按钮 | `btnCalculate` | = |
| 文本框 | `txtResult` | 计算结果,ReadOnly 设为 True |
| 分组框 | `groupBox2` | 类的多态性 |
| 文本框 | `txtDispaly` | 输出说话内容,Multiline 设为 True |
| 按钮 | `btnDisplay` | 显示 |
> `txtDispaly` 是书上的拼法(不是 Display),代码和自动评分都用这个名字,照着写。书上的表格没写下拉框的 Items 和文本框的 Multiline,要自己在属性里设置。
## 实现步骤
1. 写一个方法 `public Object Result(Object a, Object b, String type)`:先把 a、b 拆箱成 float,用 switch 按 `+ - * /` 计算(其他情况结果为 0),最后把结果装箱成 Object 返回。
2. **btnCalculate 的 Click**:两个文本框都不为空时,用 `float.Parse` 把文字转成数,调用 `Result(a, b, cmboxType.Text)`,返回值拆箱成 float,显示到 `txtResult`。
3. 点"+ 新建类",新建 `People` 类,写一个虚方法 `public virtual void Speak(TextBox Txtbox, string name)`,把 name 追加到文本框的 Text 后面。
4. 新建 `Chinese` 类继承 People,重写 Speak:调用 `base.Speak`,传入 `name + "说的是汉语!\r\n"`。
5. 新建 `American` 类继承 People,同样重写,内容改成"说的是英语!"。
6. **btnDisplay 的 Click**:
- 建一个 `Dictionary<int, string>`,放入 1 张三、2 李四、3 Mary、4 Jimmy;
- 建一个 `List<People>`,先加 2 个 `new Chinese()`,再加 2 个 `new American()`;
- 遍历 List,对第 i 个对象调用 `Speak(this.txtDispaly, dicName[i + 1])`。
## 和书上不一样的地方
> 书上第 8 步写的是"双击'='按钮",其实应该双击"**显示**"按钮,生成 `btnDisplay_Click`。
> 书上排版把减号印成了长一点的"–",抄 `case "-"` 和下拉框的 Items 时要用键盘上的减号,否则选"-"时算不出来。
## 在平台上怎么试
1. 运行后输入 7 和 2,选"+",点"=",结果应为 9;改选"/"再点"=",结果应为 3.5。
2. 点"显示",文本框里应依次出现"张三说的是汉语!""李四说的是汉语!""Mary说的是英语!""Jimmy说的是英语!"四行。
+37
View File
@@ -0,0 +1,37 @@
## 任务目标
模拟校园信息发布:选好 LED 显示屏的串口并打开,在文本框里输入一段文字,点"推送"后实训台的 LED 屏和窗体上的 LED 控件都显示这段文字;推送失败时弹出出错信息。
练习重点:分组框(GroupBox)、下拉框的 Click 事件、用 `SerialPort.GetPortNames()` 列出串口、调用设备类库、自定义控件 LED。详细步骤见书 p23–27。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `Form1` | LED显示屏 |
| LED 显示屏 | `led` | 窗体上方的大块 LED 控件,在工具箱"物联网设备"里 |
| 分组框 | `groupBox1` | LED控制 |
| 下拉框 | `com_Port` | 串口(前面放标签"串口") |
| 按钮 | `btn_Open` | 打开 |
| 文本框 | `txt_SendText` | 要推送的文字(前面放标签"推送LED信息") |
| 按钮 | `btn_Send` | 推送 |
> 书上的表格把 LED 控件写成 `LED`,代码里用的是小写的 `led`,请按代码起名。
## 实现步骤
1. 平台已经引用好了 `DeviceCtrlLibrary.dll` 和 `WinFormsDeviceControlLibrary.dll`,不用像书上那样复制 dll、添加引用;LED 显示屏、灯泡、报警灯这些控件直接在工具箱的"物联网设备"里。
2. 代码开头加上 `using System.IO.Ports;`、`using DeviceCtrlLibrary;`、`using WinFormsDeviceControlLibrary;`。
3. **com_Port 的 Click**(在 ⚡ 事件里双击 Click):先清空下拉框,再用 foreach 把 `SerialPort.GetPortNames()` 的每个串口号加进去。这样每次点开下拉框都会刷新串口列表。
4. **btn_Open 的 Click**:调用 `DeviceCtrlHelper.ledHelper.OpenLedCom(com_Port.Text)` 打开选中的串口。
5. **btn_Send 的 Click**:调用 `DeviceCtrlHelper.ledHelper.SendLedString(txt_SendText.Text)`,把返回的字符串存到变量 res;如果 res 里含有 "Error"(`res.IndexOf("Error") >= 0`)就用 MessageBox 显示出来,否则调用 `led.setLedText(res)` 让窗体上的 LED 控件显示。
## 和书上不一样的地方
> 知识提炼的 try-catch 示例里,书上把 `catch (Exception ex)` 印成了 `Catch(EventArgs ex)`,照抄会编译不过:catch 要小写,括号里是异常类型 Exception。
## 在平台上怎么试
1. 运行后点开"串口"下拉框,选 **COM5**(LED 屏),点"打开"。
2. 输入"欢迎光临",点"推送",实训台的 **LED 显示屏**和窗体上的 LED 控件都应显示"欢迎光临"。
3. 重新运行,不点"打开"直接推送,应弹出"Error:发送失败"。
+40
View File
@@ -0,0 +1,40 @@
## 任务目标
从 ADAM-4017+ 模拟量采集器读出光照度、温度、湿度:选好串口点"开启",再点"获取",三个文本框显示传感器的当前值;"开启"按钮打开后变成"关闭",再点一次关闭串口。
练习重点:标签(Label)和按钮(Button)、窗体的 Load 事件、用 bool 变量记住串口是否打开、读取设备类库返回的数据。详细步骤见书 p36–39。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `frm_analog` | 模拟量采集 |
| 分组框 | `grp_show` | 数据显示 |
| 标签 | `lbl_light`、`lbl_temperature`、`lbl_humidity` | 光照度、温度、湿度 |
| 文本框 | `txt_light`、`txt_temperature`、`txt_humidity` | 显示光照度、温度、湿度的值 |
| 按钮 | `btn_get` | 获取 |
| 分组框 | `grp_config` | 配置 |
| 下拉框 | `com_port` | 串口 |
| 按钮 | `btn_Open` | 开启(打开后变成"关闭") |
> 窗体名:在设计器里点窗体空白处,把属性窗口的**名称**从 `Form1` 改成 `frm_analog`,类名、文件名和 Program.cs 会跟着改。先改名再双击窗体,生成的 Load 事件才叫 `frm_analog_Load`。
## 实现步骤
1. 代码开头加上 `using System.IO.Ports;`、`using DeviceCtrlLibrary;`、`using DeviceCtrlLibrary.Model;`(平台已经引用好了设备类库,不用复制 dll)。
2. 声明两个全局变量:`bool IsOpen = false;` 记录串口是否打开,`ADAM4017ResModel ResModel;` 存放读到的数据。
3. **frm_analog_Load**(窗体的 Load 事件,双击窗体空白处生成):清空 `com_port`,用 foreach 把 `SerialPort.GetPortNames()` 的串口号加进去。
4. **btn_Open 的 Click**:
- IsOpen 为 false 时:`DeviceCtrlHelper.adam4017Helper.Open(com_port.Text)` 打开串口,按钮文字改成"关闭",IsOpen 设为 true;
- 否则:`adam4017Helper.Close()` 关闭串口,按钮文字改回"开启",IsOpen 设为 false。
5. **btn_get 的 Click**:`ResModel = DeviceCtrlHelper.adam4017Helper.SetData();`,把 `ResModel.lightValue`、`temperatureValue`、`humidityValue` 分别填进三个文本框。
## 和书上不一样的地方
> 书上把 `IsOpen = true`、`IsOpen = false` 印成了 `True`、`False`(首字母大写),C# 的布尔值只能小写,照抄会编译不过。
## 在平台上怎么试
1. 运行后串口选 **COM2**(ADAM-4017+),点"开启",按钮应变成"关闭"。
2. 点"获取",三个文本框应显示实训台上的默认值:温度 25、湿度 60、光照度 500。
3. 在实训台上把温度拖到 31、光照拖到 300,再点"获取",文本框应变成新的数值。
+48
View File
@@ -0,0 +1,48 @@
## 任务目标
把任务 1 和任务 2 合起来:同时打开 ADAM-4017+ 和 LED 屏的串口,点"获取"读出光照度、温度、湿度,显示在文本框里,再拼成一句话推送到 LED 屏,例如"当前环境信息 光照度:500lx温度:25℃湿度:60%"。
练习重点:下拉框(ComboBox)和文本框(TextBox)、字符串拼接、一个按钮同时控制两个设备。详细步骤见书 p42–45。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `frm_LED` | LED推送模拟量 |
| LED 显示屏 | `led` | 窗体上方的 LED 控件 |
| 分组框 | `grp_show` | 数据显示 |
| 文本框 | `txt_light`、`txt_temperature`、`txt_humidity` | 显示光照度、温度、湿度的值(前面放标签) |
| 按钮 | `btn_get` | 获取 |
| 分组框 | `grp_config` | 配置 |
| 下拉框 | `com_adamPort` | 模拟量串口(ADAM-4017+) |
| 下拉框 | `com_ledPort` | LED串口 |
| 按钮 | `btn_open` | 开启(打开后变成"关闭") |
> 书上的表格写的是 `btn_Open`,代码里是 `btn_open`(小写 o);表格里也没有列 LED 控件,代码里叫 `led`。请按代码起名,自动评分按这些名字找控件。
> 窗体名:在设计器里点窗体空白处,把属性窗口的**名称**从 `Form1` 改成 `frm_LED`,类名、文件名和 Program.cs 会跟着改。先改名再双击窗体,生成的 Load 事件才叫 `frm_LED_Load`。
## 实现步骤
1. 代码开头加上 `using System.IO.Ports;`、`using DeviceCtrlLibrary;`、`using DeviceCtrlLibrary.Model;`。
2. 声明全局变量:`bool IsOpen = false;`、`ADAM4017ResModel ResModel;`、`String LED_MSG = "";`(要推送的文字)。
3. **frm_LED_Load**:清空两个下拉框,用一个 foreach 把串口号同时加进 `com_adamPort` 和 `com_ledPort`。
4. **btn_open 的 Click**:
- 没打开时:`adam4017Helper.Open(com_adamPort.Text)` 和 `ledHelper.OpenLedCom(com_ledPort.Text)` 打开两个串口,按钮改成"关闭",IsOpen 设为 true;
- 已打开时:`adam4017Helper.Close()` 和 `ledHelper.Close()` 关闭,按钮改回"开启",IsOpen 设为 false。
5. **btn_get 的 Click**:
- 先把 LED_MSG 清空,用 `adam4017Helper.SetData()` 读数据,填进三个文本框(和任务 2 一样);
- 把"当前环境信息"和三个值拼进 LED_MSG(光照度后面加 lx,温度加 ℃,湿度加 %);
- 调用 `ledHelper.SendLedString(LED_MSG)`,返回值里没有 "Error" 时,调用 `led.setLedText` 让窗体上的 LED 控件也显示。
## 和书上不一样的地方
> 书上关闭时调用的 `DeviceCtrlHelper.ledHelper.Close()`,教材配套的类库里其实没有这个方法。平台保留了它(什么也不做),书上的代码可以原样编译。
> 书上的 `True` / `False` 首字母大写是印刷错误,要写成小写的 `true` / `false`。
## 在平台上怎么试
1. 运行后"模拟量串口"选 **COM2**(ADAM-4017+),"LED串口"选 **COM5**(LED 屏),点"开启"。
2. 点"获取",文本框显示温度 25、湿度 60、光照度 500,实训台的 **LED 显示屏**上应出现这三个数。
3. 在实训台上把温度拖到 31,再点"获取",LED 屏上的温度应变成 31。
+38
View File
@@ -0,0 +1,38 @@
## 任务目标
做一个路灯遥控程序:选好 ZigBee 协调器的串口点"打开",然后选"开"单选按钮路灯亮,选"关"路灯灭,窗体上的灯泡跟着实训台上的灯一起变。
练习重点:if 语句、按钮文字切换、单选按钮(RadioButton)的 CheckedChanged 事件、自己定义一个方法。详细步骤见书 p53–56。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `Form1` | ZigBee路灯 |
| 灯泡 | `lamp1` | 窗体上显示路灯的亮灭(工具箱"物联网设备"里的**灯泡**) |
| 单选按钮 | `rb_Open` | 开(开灯) |
| 单选按钮 | `rb_Close` | 关(关灯) |
| 分组框 | `groupBox1` | 配置 |
| 标签 | `lbl_Lamp` | 路灯 |
| 下拉框 | `com_port` | ZigBee 协调器的串口 |
| 按钮 | `btn_open` | 打开(打开后变成"关闭") |
> 书上的表格写的是 `btn_Open`,代码里是 `btn_open`。名字区分大小写,请按代码写成 `btn_open`,否则自动评分找不到这个按钮。
## 实现步骤
1. 代码开头加上 `using System.IO.Ports;` 和 `using DeviceCtrlLibrary;`。
2. 写一个方法 `LampOnOff(int RelayNum, bool IsOpen)`:调用 `DeviceCtrlHelper.zigBeeHelper.OnOff` 控制实训台的路灯,再调用 `lamp1.OnOff` 让窗体上的灯泡跟着变。
3. **Form1_Load**:清空 `com_port`,用 `SerialPort.GetPortNames()` 把串口号加进去。
4. **btn_open 的 Click**:用 if 判断按钮文字——是"打开"就用 `zigBeeHelper.Open(com_port.Text)` 打开串口,文字改成"关闭";是"关闭"就 `zigBeeHelper.Close()`,文字改回"打开"。
5. **rb_Open 的 CheckedChanged**:调用 `LampOnOff(1, true)`。
6. **rb_Close 的 CheckedChanged**:调用 `LampOnOff(1, false)`。
## 和书上不一样的地方
> 书上第 12 步"关"按钮的事件方法名印成了 `rb_Open_CheckedChanged`,应该是 `rb_Close_CheckedChanged`。在设计器里双击"关"单选按钮,生成的名字就是对的。
## 在平台上怎么试
1. 运行后串口选 **COM4**(ZigBee 协调器),点"打开",按钮变成"关闭"。
2. 选"开",窗体上的灯泡和实训台的**照明灯 1** 应一起亮;选"关",一起灭。
+42
View File
@@ -0,0 +1,42 @@
## 任务目标
做一个围墙入侵监测程序:点"开启"后,每 2 秒读一次红外对射传感器,在窗体上显示当前时间和"发现入侵"或"正常";再点一次"关闭",停止检测。
练习重点:定时器(Timer)的 Tick 事件、用一个 bool 变量记住开关状态、foreach 语句。详细步骤见书 p73–76。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `Form1` | 红外对射 |
| 标签 | `lbl_port` | 环境串口 |
| 下拉框 | `com_port` | ADAM-4150 的串口 |
| 按钮 | `btn_open` | 开启(打开后变成"关闭") |
| 分组框 | `groupBox1` | 数据显示 |
| 标签 | `lbl_time` | 时间 |
| 文本框 | `txt_time` | 显示当前时间 |
| 标签 | `lbl_even` | 入侵 |
| 文本框 | `txt_even` | 显示"发现入侵"或"正常" |
| 定时器 | `timer1` | Interval 设为 2000(2 秒) |
> 书上的表格里没有分组框,看书上的界面图,"时间""入侵"两行放在"数据显示"分组框里。
## 实现步骤
1. 代码开头加上 `using System.IO.Ports;` 和 `using DeviceCtrlLibrary;`。
2. 声明全局变量 `bool IsOpen = false;`,记住串口有没有打开。
3. **Form1_Load**:清空 `com_port`,用 foreach 把 `SerialPort.GetPortNames()` 里的串口号逐个加进去。
4. **btn_open 的 Click**:先把 `IsOpen` 取反;
- 变成 true 时:打开 ADAM-4150 串口(`adam4150Helper.Open(com_port.Text)`),启动 `timer1`,按钮文字改成"关闭";
- 变成 false 时:关闭串口,停止 `timer1`,按钮文字改回"开启"。
5. **timer1 的 Tick**:把 `DateTime.Now` 显示到 `txt_time`;读 `adam4150Helper.SetData().infraredValue`,是 `true` 就在 `txt_even` 显示"发现入侵",否则显示"正常"。
## 和书上不一样的地方
> 书上的全局变量印成了 `boolIsOpen = false;`,`bool` 和 `IsOpen` 之间要有空格,照抄会编译不过。
## 在平台上怎么试
1. 运行后串口选 **COM3**(ADAM-4150),点"开启",按钮变成"关闭",约 2 秒后时间框开始显示时间。
2. 在实训台顶棚点**红外对射**(光束断开,显示"有人闯入"),几秒内入侵框应显示"发现入侵";再点一次恢复光束,应变回"正常"。
3. 点"关闭",按钮变回"开启",时间框不再更新。
+43
View File
@@ -0,0 +1,43 @@
## 任务目标
做一个校园监控程序:输入摄像机 IP 点"开",图片框里实时显示摄像机拍到的画面;用四个方向键转动摄像机;点"拍照"把当前画面保存成图片。
练习重点:图片框(PictureBox)、给设备的事件绑定处理方法(`+=`)、static 变量、文件夹和图片的保存。详细步骤见书 p78–81。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `Form1` | 网络摄像机 |
| 图片框 | `pictureBox1` | 显示画面;背景色设为黑色,显示方式选"拉伸填满"(StretchImage) |
| 标签 | `label1` | 摄像机IP |
| 文本框 | `txt_ip` | 输入摄像机的 IP |
| 按钮 | `btn_open` | 开(打开后变成"关") |
| 按钮 | `btn_up` | ↑(向上转) |
| 按钮 | `btn_down` | ↓(向下转) |
| 按钮 | `btn_left` | ←(向左转) |
| 按钮 | `btn_right` | →(向右转) |
| 按钮 | `btn_camera` | 拍照 |
## 实现步骤
1. 代码开头加上 `using System.IO;` 和 `using DeviceCtrlLibrary;`。
2. 声明 4 个全局变量:拍照保存路径 `CameraPath`(static,程序所在目录下的 `Camera/` 文件夹)、摄像机 IP `IpCameraIPAddress`(static)、`bool IsOpen`、存当前画面的 `Image ReadImage`。
3. 写一个方法 `ipCameraHelper_ImageRead(ImageReadEvenArgs e)`:把 `e.img` 存进 `ReadImage`,再显示到 `pictureBox1`。
4. **Form1_Load**:`Camera` 文件夹不存在就用 `Directory.CreateDirectory` 建一个;用 `+=` 把第 3 步的方法绑定到 `DeviceCtrlHelper.ipCameraHelper.ImageRead` 事件上。
5. **btn_open 的 Click**:先把 `IsOpen` 取反;
- 打开时:取 `txt_ip` 里的 IP,调用 `ipCameraHelper.Open(IP, "admin", "")` 连接,再 `Start()` 开始传画面,按钮文字改成"关";
- 关闭时:`Stop()` 停止传画面,按钮文字改回"开"。
6. **四个方向键的 Click**:分别调用 `ipCameraHelper.PanUp()`、`PanDown()`、`PanLeft()`、`PanRight()`。
7. **btn_camera 的 Click**:`ReadImage` 不是 null 时,用当前时间做文件名,`ReadImage.Save(...)` 存成 .jpg 到 `CameraPath`。
## 和书上不一样的地方
> 书上第 6 步只写了 `using System.IO.Ports;`,可是 `Directory` 要用 `using System.IO;`,不加会编译不过。这个任务不用串口,`System.IO.Ports` 可以不写。
## 在平台上怎么试
1. 运行后在 IP 框里填 **192.168.2.50**(填别的 IP 也能连上),点"开",按钮变成"关",图片框里出现东围墙的画面,实训台顶棚的**网络摄像头**亮起。
2. 点四个方向键,画面应跟着转动:每点一次转一格,左右各 3 格、上下各 1 格,转到头就不再动。
3. 点"拍照",运行区下方出现"程序保存的图片",点开能看到刚拍的照片(只在这次运行中保留)。
4. 再点"关",按钮变回"开",画面停止更新。
+42
View File
@@ -0,0 +1,42 @@
## 任务目标
做一个报警灯控制程序:选好 ADAM-4150 的串口点"开启",点"开"报警灯亮,点"关"报警灯灭,窗体上的报警灯跟着实训台上的灯一起变。
练习重点:switch 语句、带参数的方法。详细步骤见书 p84–87。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `Form1` | 报警灯 |
| 报警灯 | `alarm1` | 窗体上显示报警灯的亮灭(工具箱"物联网设备"里的**报警灯**) |
| 按钮 | `btn_alarmOpen` | 开(打开报警灯) |
| 按钮 | `btn_alarmClose` | 关(关闭报警灯) |
| 分组框 | `groupBox1` | 配置 |
| 标签 | `label1` | 报警灯 |
| 下拉框 | `com_port` | ADAM-4150 的串口 |
| 按钮 | `btn_open` | 开启(打开后变成"关闭") |
> 书上的表格把报警灯控件的名称写成 `Alarm`,那是控件的类型名;代码里用的名字是 `alarm1`,请按代码写,否则自动评分找不到它。
## 实现步骤
1. 代码开头加上 `using System.IO.Ports;` 和 `using DeviceCtrlLibrary;`。
2. 声明全局变量 `bool IsOpen = false;`。
3. **Form1_Load**:和任务 1 一样,把串口号加进 `com_port`。
4. 写一个方法 `AlarmOnOff(string Device, bool IsOpen)`,用 switch 按设备名分支:
- `"Alarm"`:调用 `adam4150Helper.OnOff(0, IsOpen)` 控制接在 DO0 的报警灯,再调用 `alarm1.OnOff(IsOpen)` 改窗体上的报警灯;
- `"LED1"`、`"LED2"`:分别控制继电器 1、2;
- 其他(default):弹出"没有合适的接口程序!"。
- 每个分支最后都要有 `break;`。
5. **btn_open 的 Click**:把 `IsOpen` 取反,打开时 `adam4150Helper.Open(com_port.Text)`、文字改成"关闭",关闭时 `adam4150Helper.Close()`、文字改回"开启"。这个任务不用定时器。
6. **btn_alarmOpen、btn_alarmClose 的 Click**:分别调用 `AlarmOnOff("Alarm", true)`、`AlarmOnOff("Alarm", false)`。
## 和书上不一样的地方
> 书上的全局变量又印成了 `boolIsOpen = false;`,`bool` 和 `IsOpen` 之间要有空格,照抄会编译不过。
## 在平台上怎么试
1. 运行后串口选 **COM3**(ADAM-4150),点"开启",按钮变成"关闭"。
2. 点"开",窗体上的报警灯和实训台顶棚的**警示灯**应一起亮;点"关",一起灭。
+38
View File
@@ -0,0 +1,38 @@
## 任务目标
做一个火情监测窗体:选好串口点"开启"后,程序每 2 秒读一次火焰传感器,在"时间"框里显示当前时间,在"事件"框里显示"发现火情!"或"正常!";再点一次按钮(这时文字是"关闭")就停止采集。
练习重点:for 循环、带返回值的方法和 return 语句、DateTime.Now、定时器(Timer)。详细步骤见书 p91–94。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `Form1` | 火焰 |
| 标签 | `lbl_port` | 串口 |
| 下拉框 | `com_port` | ADAM-4150 的串口 |
| 按钮 | `btn_open` | 开启(打开后变成"关闭") |
| 分组框 | `groupBox1` | 数据显示 |
| 标签 | `lbl_time` | 时间 |
| 文本框 | `txt_time` | 显示当前时间 |
| 标签 | `lbl_even` | 事件 |
| 文本框 | `txt_even` | 显示有没有火情 |
| 定时器 | `timer1` | Interval 设为 2000(2 秒),Enabled 保持 False |
> 书上的表格里没有分组框,照图加一个 `groupBox1`(数据显示),把时间、事件两行放进去。
## 实现步骤
1. 代码开头加上 `using System.IO.Ports;` 和 `using DeviceCtrlLibrary;`,声明全局变量 `bool IsOpen = false;`。
2. **Form1_Load**:这次用 **for 循环**,从 0 数到 `SerialPort.GetPortNames().Length - 1`,把每个串口号加进 `com_port`。
3. **btn_open 的 Click**:先把 `IsOpen` 取反;打开时调用 `adam4150Helper.Open(com_port.Text)`、启动 `timer1`、按钮改成"关闭";关闭时调用 `Close()`、停止 `timer1`、按钮改回"开启"。
4. 写一个方法 `GetFireInfo()`,返回 `String`:`adam4150Helper.SetData().fireValue` 是 `true` 就 `return "发现火情!";`,否则 `return "正常!";`。
5. **timer1 的 Tick**:`txt_time` 显示 `DateTime.Now.ToString()`,`txt_even` 显示 `GetFireInfo()` 的返回值。
> 在设计器里双击 timer1 就会生成 `timer1_Tick`。
## 在平台上怎么试
1. 运行后串口选 **COM3**(ADAM-4150),点"开启",按钮变成"关闭",2 秒左右"时间"框开始走。
2. 在实训台上把**火焰传感器**点成"有火焰",几秒内"事件"框显示"发现火情!";点回"正常",显示"正常!"。
3. 点"关闭",按钮变回"开启","时间"框不再变化。
+43
View File
@@ -0,0 +1,43 @@
## 任务目标
和任务 1 一样监测灾情,这次读的是烟雾传感器,而且不用定时器,改用**线程**:线程里用 while 循环每 2 秒读一次烟雾传感器,显示时间和"发现烟情"或"正常"。点"关闭"时把线程挂起,再点"开启"让线程继续,关闭窗体时终止线程。
练习重点:while 循环、线程 Thread(Start、Suspend、Resume、Abort、Thread.Sleep)、窗体的 FormClosing 事件。详细步骤见书 p98–101。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `Form1` | 烟雾 |
| 标签 | `lbl_port` | 串口 |
| 下拉框 | `com_port` | ADAM-4150 的串口 |
| 按钮 | `btn_open` | 开启(打开后变成"关闭") |
| 分组框 | `groupBox1` | 数据显示 |
| 标签 | `lbl_time` | 时间 |
| 文本框 | `txt_time` | 显示当前时间 |
| 标签 | `lbl_even` | 事件 |
| 文本框 | `txt_even` | 显示有没有烟情 |
> 界面和任务 1 一样,但**没有定时器**。书上的表格里没有分组框,照图加一个 `groupBox1`(数据显示)。
## 实现步骤
1. 代码开头加上 `using System.IO.Ports;`、`using DeviceCtrlLibrary;`、`using System.Threading;`。
2. 声明全局变量:`bool IsOpen = false;`、`bool IsFirst = true;`(线程是不是第一次启动)、`Thread thd;`。
3. 写线程要运行的方法 `GetData()`:`while (true)` 循环里,`adam4150Helper.SetData().smokeValue` 是 `true` 就显示"发现烟情",否则显示"正常",两种情况都把当前时间写进 `txt_time`;每次循环最后 `Thread.Sleep(2000)`。
4. **Form1_Load**:这次用 **foreach** 把串口号加进 `com_port`;然后 `thd = new Thread(GetData);`,再写 `Control.CheckForIllegalCrossThreadCalls = false;`,允许线程直接改窗体上的控件。
5. **btn_open 的 Click**:先把 `IsOpen` 取反。
- 打开时:打开 ADAM-4150 串口,按钮改成"关闭";`IsFirst` 是 `true` 就把它改成 `false` 并 `thd.Start()`,否则 `thd.Resume()`;
- 关闭时:关闭串口,按钮改回"开启",`thd.Suspend()`。
6. **Form1 的 FormClosing**:终止线程(见下面"和书上不一样的地方")。在设计器里选中窗体,到 **⚡ 事件** 里双击 FormClosing 生成方法。
## 和书上不一样的地方
> **关闭窗体时先让线程继续**:书上 FormClosing 里直接 `thd.Abort();`。如果点过"关闭",线程正处于挂起状态,直接终止会出错(在真的 Visual Studio 里也一样)。要先判断 `thd.ThreadState == ThreadState.Suspended`,是的话先 `thd.Resume();`,再 `thd.Abort();`。
## 在平台上怎么试
1. 运行后串口选 **COM3**(ADAM-4150),点"开启",按钮变成"关闭","时间"框开始走。
2. 在实训台上把**烟雾传感器**点成"有烟雾",几秒内"事件"框显示"发现烟情";点回"正常",显示"正常"。
3. 点"关闭","时间"框应停住不动;再点"开启",时间接着走,烟雾状态也继续更新。
4. 点"关闭"之后直接关掉窗体,不应弹出错误。
+64
View File
@@ -0,0 +1,64 @@
## 任务目标
把任务 1、任务 2 合在一起:点"获取"时同时读火焰和烟雾传感器,显示在窗体上,把灾情推送到 LED 屏(实训台的屏和窗体上的 LED 控件都显示);每次发现灾情都记进一个二维数组,点"历史灾情信息"打开第二个窗体,在列表框里列出全部记录。
练习重点:二维数组、静态类成员、continue 语句、两个窗体、列表框(ListBox)。详细步骤见书 p104–107。
## 界面和控件名
主窗体 Form1:
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `Form1` | 火焰烟雾 |
| LED 显示屏 | `led1` | 窗体顶部,显示推送的灾情 |
| 分组框 | `groupBox2` | 数据采集 |
| 标签 | `lbl_time`、`lbl_fire`、`lbl_smoke` | 时间、火焰、烟雾 |
| 文本框 | `txt_time`、`txt_fire`、`txt_smoke` | 显示时间、火焰状态、烟雾状态 |
| 按钮 | `btn_get` | 获取 |
| 分组框 | `groupBox1` | 配置 |
| 标签 | `lbl_Ledport` | LED |
| 下拉框 | `com_ledPort` | LED 屏的串口 |
| 标签 | `lbl_4150port` | 环境采集 |
| 下拉框 | `com_environmentPort` | ADAM-4150 的串口 |
| 按钮 | `btn_Open` | 开启(打开后变成"关闭") |
| 按钮 | `OpenRecord` | 历史灾情信息 |
历史记录窗体 Form2:
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `Form2` | 历史灾情记录 |
| 列表框 | `lstbRecored` | 一行一条灾情记录 |
> 书上表 5-6 没列 `btn_get`、`OpenRecord`、`groupBox2`,照图加上。`lstbRecored` 的拼写就是这样(不是 Record),照着写。
## 实现步骤
1. 点 **+ 新建类**,建 `RecordClass`,写两个静态成员:`string[,] record = new string[50, 2]`(每条记录存时间和内容)、`int recoudeindex = 0`(下一条记录存在第几行)。
2. Form1 的代码开头加上 `using System.IO.Ports;`、`using DeviceCtrlLibrary;`、`using DeviceCtrlLibrary.Model;`;全局变量:`ADAM4150ResModel ResModel;`、`bool IsOpen = false;`、`string LED_MSG = "";`。
3. **Form1_Load**:清空两个下拉框,用 for 循环加串口号;遇到 `COM1`(电脑自带的串口)就 `continue` 跳过。
4. **btn_Open 的 Click**:打开时调用 `adam4150Helper.Open(com_environmentPort.Text)` 和 `ledHelper.OpenLedCom(com_ledPort.Text)`,按钮改成"关闭";关闭时关闭 ADAM-4150 串口,按钮改回"开启"。
5. **btn_get 的 Click**:
- 用 `adam4150Helper.SetData()` 读出数据存进 `ResModel`,清空 `LED_MSG`,显示当前时间,火焰、烟雾两个框先写"正常";
- `fireValue` 为 `true`:火焰框写"发现火情",`LED_MSG` 加上"发现火情",把时间和内容记进数组当前行,`recoudeindex` 加 1;
- `smokeValue` 为 `true`:同样处理,写的是"发现烟情";
- 用 `ledHelper.SendLedString(LED_MSG)` 推送到 LED 屏,返回的字符串里没有 "Error" 时,再用 `led1.setLedText(...)` 显示到窗体上。
6. 点 **+ 新建窗体**,建 Form2,放列表框 `lstbRecored`。
7. **OpenRecord 的 Click**:`new` 一个 Form2 并 `Show()`。
8. **Form2_Load**:for 循环从 0 到 `recoudeindex - 1`,把每条记录的"时间 + 内容"加进 `lstbRecored`。
## 和书上不一样的地方
> **烟雾记录写成了火情**:书上烟雾分支往数组里记的内容是" 发现火情",要改成" 发现烟情",否则历史记录里看不到烟情。
> **列表框的名字**:书上 Form2_Load 里写的是 `listBox1`,表 5-7 和界面里叫 `lstbRecored`,代码要写 `lstbRecored`。
> 书上烟雾分支有一行注释只写了一个 `/`,照抄会编译不过,要写成 `//`。
## 在平台上怎么试
1. 运行后"LED"选 **COM5**(LED 屏),"环境采集"选 **COM3**(ADAM-4150),点"开启"。
2. 在实训台上把**火焰传感器**点成"有火焰",点"获取":火焰框显示"发现火情"、烟雾框显示"正常",实训台的 LED 屏和窗体上的 LED 控件都显示"发现火情"。
3. 把火焰点回"正常"、**烟雾传感器**点成"有烟雾",再点"获取",火焰框显示"正常"、烟雾框显示"发现烟情"。
4. 点"历史灾情信息",列表里应有"发现火情"和"发现烟情"两条,每条前面是时间。
+46
View File
@@ -0,0 +1,46 @@
## 任务目标
选一个开始日期和结束日期,点"查询",从任务 2 存的 CampusEnvironment 表里查出这段时间内的记录,显示在表格里。
练习重点:select 语句和 where … between … and …、日期时间控件(DateTimePicker)、SqlDataAdapter 和 DataSet、DataGridView 绑定数据。详细步骤见书 p127–128。
## 先准备数据库
这个任务用任务 2 的**环境数据表**库和里面的 **CampusEnvironment** 表(ID、light、humidity、temperature、addtime)。表里要先有几条不同日期的记录,查询才看得出效果。
还没做任务 2,或者数据弄乱了,到右上角的**数据库**页面点**恢复实验初始数据**,选这个实验:库和表会自动建好,CampusEnvironment 里有 3 条记录,时间是 2024 年 1 月 16 日、18 日、23 日。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `Form1` | 查询校园灾情数据 |
| 标签 | `LblStartTime` | 开始日期 |
| 日期时间控件 | `StartTime` | 选开始日期 |
| 标签 | `LblEndTime` | 结束日期 |
| 日期时间控件 | `EndTime` | 选结束日期 |
| 按钮 | `btnCx` | 查询 |
| 分组框 | `groupBox4` | 历史记录 |
| 数据表格 | `dataGridView1` | 显示查到的记录 |
## 实现步骤
1. 引用 `System.Data.SqlClient` 和 `System.IO.Ports`。
2. 声明全局变量:连接字符串 `sqlConnectString`、`SqlConnection mySqlConnection`、`SqlCommand mySqlCmd`、`SqlDataAdapter sqldata`、`DataSet ds`。
3. **btnCx 的 Click**:
- 创建连接对象并打开;
- 写 select 语句:查出 ID、temperature、humidity、light、addtime,列名起中文别名(编号、温度、湿度、光照度、时间),条件是 `addtime BETWEEN '开始' AND '结束'`,开始、结束分别拼进 `StartTime.Value.ToString()` 和 `EndTime.Value.ToString()`;
- 用这条命令创建 SqlDataAdapter,`ds = new DataSet();`,`Fill(ds)`;
- `dataGridView1.DataSource = ds.Tables[0].DefaultView;`,最后关闭连接。
## 和书上不一样的地方
> **要先 new 一个 DataSet**:书上直接 `sqldata.Fill(ds)`,`ds` 还没有创建,运行时会报空引用错误。在 Fill 前面加一行 `ds = new DataSet();`。
> **连接字符串**:平台只看 `Initial Catalog=环境数据表`,服务器名、用户名和密码照书上写或写成 `Data Source=.` 都可以。
## 在平台上怎么试
1. 先按上面准备好数据库(用初始数据最好对照)。
2. 运行后开始日期选 **2024 年 1 月 15 日**,结束日期选 **2024 年 1 月 20 日**,点"查询":表格里应有编号 1、2 两条(16 日、18 日),没有 23 日的那条。
3. 改成 1 月 21 日到 1 月 31 日再点"查询":表格里只剩编号 3(23 日)。
+63
View File
@@ -0,0 +1,63 @@
## 任务目标
做一个"智慧教室"控制窗体:点"启用"后,程序每 2 秒读一次光照度、温度、湿度并显示出来,再按读数自动控制设备——光线暗就开灯、太亮就关灯;太热或太冷就开空调并调好温度,否则关空调。灯也可以点开关图片手动开关。
练习重点:滑动条(TrackBar)的 Minimum / Maximum / Value,图片框(PictureBox)显示项目资源里的图片,定时器 Tick 里的 if / else if 判断。详细步骤见书 p133–137。
## 先添加项目资源
开关图片用 `switch_on`(开)和 `switch_off`(关)。书上是"项目属性 → 资源 → 添加资源",平台上点代码上方的**项目资源**,在素材库里勾选这两张图(名字和书上一样,不能自己上传图片)。
加好后,图片框的**图片**属性可以直接选 `Resources.switch_off`;代码里写 `Properties.Resources.switch_on`。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `frm_control` | 智慧教室情景控制 |
| 分组框 | `grp_mode` | 模式选择 |
| 标签 | `lbl_mode` | 智慧教室将开启(启用后变成"智慧教室已开启") |
| 按钮 | `btn_apply` | 启用(启用后变成"关闭") |
| 分组框 | `grp_input` | 采集量 |
| 下拉框 | `com_import` | 采集器的串口 |
| 文本框 | `txt_light`、`txt_temperature`、`txt_humidity` | 光照度、温度、湿度 |
| 分组框 | `grp_status` | 状态栏 |
| 图片框 | `pic_airconditioneStatus` | 空调开关状态,图片选 switch_off |
| 滑动条 | `trb_airconditione` | 模拟空调温度:最小值 18,最大值 32,值 18 |
| 图片框 | `pic_lightsStatus` | 灯光开关状态,图片选 switch_off;点它手动开关灯 |
| 灯泡 | `lamp` | 窗体上显示灯的亮灭 |
| 定时器 | `timer` | 间隔 2000(2 秒) |
其余标签(串口、光照度、温度、湿度、空调、灯光、18℃、32℃)照书上的图摆好,名字不影响评分。
> 窗体名:在设计器里点窗体空白处,把属性窗口的**名称**从 `Form1` 改成 `frm_control`,类名、文件名和 Program.cs 会跟着改。先改名再双击窗体,生成的 Load 事件才叫 `frm_control_Load`。
## 实现步骤
1. 代码开头加上 `using System.IO.Ports;`、`using DeviceCtrlLibrary;`、`using DeviceCtrlLibrary.Model;`。
2. 声明全局变量:串口是否打开、灯是否打开、空调是否打开(三个 bool,都是 false),以及 `ADAM4017ResModel ResModel`。
3. **Load**:用 `SerialPort.GetPortNames()` 把串口号加进 `com_import`。
4. **btn_apply 的 Click**:
- 没打开时:用 `com_import.Text` 打开 `adam4017Helper` 和 `adam4150Helper`,按钮改成"关闭",`lbl_mode` 改成"智慧教室已开启",启动 `timer`;
- 已打开时:关闭两个采集器,停止 `timer`,文字改回"启用""智慧教室将开启"。
5. **timer 的 Tick**:
- `adam4017Helper.SetData()` 读出数据,把 lightValue、temperatureValue、humidityValue 填进三个文本框;
- 光照度(用 `Convert.ToInt16` 转成数字)**小于 400** 开灯:图片换成 switch_on,`lamp.OnOff(true)`,`adam4150Helper.OnOff(1, true)`,记下灯已打开;**大于 800** 关灯(反过来做一遍);
- 温度**高于 30℃**:空调图片换成 switch_on,`trb_airconditione.Value = 26`;**低于 5℃**:图片换成 switch_on,`Value = 25`;其他情况:图片换成 switch_off,`Value = 18`。
6. **pic_lightsStatus 的 Click**:灯开着就关(图片、灯泡、实训台照明灯一起关),关着就开。
## 和书上不一样的地方
> **手动开关灯的判断写反了**:书上 pic_lightsStatus_Click 写的是 `if (!IsLampOpen) { 关灯 }`,这样灯关着时点了还是关。应该是 `if (IsLampOpen) { 关灯 } else { 开灯 }`。
> **滑动条要设最小值 18、最大值 32**:书上的表格没写。滑动条默认最大值是 10,代码里把 Value 设成 26 时程序会出错。
> 书上表格里温度文本框写的是 `txt_temperation`,代码里用的是 `txt_temperature`,请按代码写,自动评分按 `txt_temperature` 找。
## 在平台上怎么试
1. 运行后串口选 **COM2**,点"启用",按钮变成"关闭"。两个采集器都用这个串口打开(书上就是这样写的)。
2. 在实训台上拖动光照、温度、湿度的滑块,2 秒内三个文本框应跟着变。
3. 把光照拖到 400 以下,窗体上的灯泡和实训台 ADAM-4150 的 **DO1 照明灯**应一起亮;拖到 800 以上应熄灭。
4. 把温度拖到 30℃ 以上,空调滑块应停在 26;拖到 5℃ 以下停在 25;其他温度回到 18。
5. 点灯光的开关图片,灯应该一下开、一下关。
+71
View File
@@ -0,0 +1,71 @@
## 任务目标
做一个"智慧寝室"控制窗体:点"开启情景模式"后,程序每秒显示当前时间和光照度、温度、湿度,并按时间段自动切换四种情景模式,控制灯光、窗帘和广播:
| 时间段 | 模式 | 灯光 | 窗帘(滑动条) | 广播 |
|---|---|---|---|---|
| 6:45–7:30 | 起床模式 | 开 | 5(半开) | 放铃声 |
| 7:30–21:00 | 上课模式 | 关 | 0(全开) | 关 |
| 21:00–22:00 | 回寝模式 | 开 | 10(全关) | 放铃声 |
| 其他时间 | 熄灯模式 | 关 | 10(全关) | 关 |
练习重点:DateTime 的比较,自己写方法(CheckMode、PlayMp3),用线程(Thread)播放声音,媒体播放器控件。详细步骤见书 p141–145。
## 先添加项目资源
点代码上方的**项目资源**,在素材库里勾选开关图片 `switch_on`、`switch_off` 和铃声 `ring`(就是书上的 ring.mp3,可以先试听)。名字和书上一样,不能自己上传。
广播图片框的**图片**属性选 `Resources.switch_off`。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `frm_control` | 智慧寝室情景控制 |
| 分组框 | `grp_mode` | 模式选择 |
| 文本框 | `txt_time` | 显示当前时间 |
| 标签 | `lbl_mode` | 显示现在是哪种模式(文字先空着) |
| 按钮 | `btn_apply` | 开启情景模式(开启后变成"停止情景模式") |
| 分组框 | `grp_input` | 采集量 |
| 下拉框 | `com_import` | 采集器的串口 |
| 文本框 | `txt_light`、`txt_temperature`、`txt_humidity` | 光照度、温度、湿度 |
| 分组框 | `grp_status` | 状态栏 |
| 滑动条 | `trb_curtain` | 模拟窗帘:0 全开,10 全关(最小值 0、最大值 10 用默认的就行) |
| 图片框 | `pic_boardcastStatus` | 广播状态,图片选 switch_off |
| 灯泡 | `lamp` | 窗体上显示灯的亮灭 |
| 定时器 | `timer` | 间隔 1000(1 秒) |
| 媒体播放器 | `axWindowsMediaPlayer1` | 播放铃声;可见设为 False |
其余标签(当前时间、串口、光照度、温度、湿度、窗帘、全开、全关、广播、灯光)照书上的图摆好,名字不影响评分。
媒体播放器在工具箱的"物联网设备"里,直接拖到窗体上就行,不用像书上小贴士那样去"选择工具箱项"添加 COM 组件。
> 窗体名:在设计器里点窗体空白处,把属性窗口的**名称**从 `Form1` 改成 `frm_control`,类名、文件名和 Program.cs 会跟着改。先改名再双击窗体,生成的 Load 事件才叫 `frm_control_Load`。
## 实现步骤
1. 代码开头加上 `using System.IO.Ports;`、`using DeviceCtrlLibrary;`、`using DeviceCtrlLibrary.Model;`、`using System.Threading;`。
2. 声明全局变量:串口、灯、广播是否打开(三个 bool),`ADAM4017ResModel ResModel`,`DateTime now`,以及四个时间点:起床 `DateTime.Today.AddHours(6.75)`、上课 `AddHours(7.5)`、回寝 `AddHours(21.0)`、熄灯 `AddHours(22.0)`。
3. **Load**:清空 `com_import`,把串口号加进去。
4. **btn_apply 的 Click**:没打开时打开 `adam4017Helper`、`adam4150Helper`,按钮改成"停止情景模式",启动 `timer`;打开着就反过来。
5. 写 **PlayMp3()** 方法:广播图片换成 switch_on,给媒体播放器的 `URL` 设铃声文件的路径(`AppDomain.CurrentDomain.BaseDirectory + "//Resources//ring.mp3"`),调用 `Ctlcontrols.play()`,`Thread.Sleep(4000)` 等 4 秒,图片换回 switch_off。
6. 写 **CheckMode()** 方法:用 `now` 和四个时间点比较,判断现在是哪种模式,`lbl_mode` 显示"智慧寝室正处于××模式",再按上面的表格控制:
- 开灯 / 关灯:`adam4150Helper.OnOff(1, ...)` 和 `lamp.OnOff(...)`,只在状态要变时才做;
- 放铃声:广播还没开时 `new Thread(PlayMp3).Start()`,让铃声在另一个线程里放,不卡住界面;关广播:图片换成 switch_off;
- 窗帘:设 `trb_curtain.Value`。
7. **timer 的 Tick**:`now = DateTime.Now`,`txt_time` 显示 `now.ToString("HH:mm:ss")`,读出数据填进三个文本框,最后调用 `CheckMode()`。
## 在平台上怎么用时钟和广播
- **时钟**在实训台上:填一个时刻点"拨到",程序里的 `DateTime.Now` 就从这个时刻往后走;还可以选 10×、60× 倍速,点"恢复真实时间"回到现在。不用等到早上 6:45 才能测试起床模式。
- 拨时间时**日期别动**:四个时间点是程序启动时按"今天"算好的,改了日期就对不上了。
- 程序放铃声时,实训台上的**广播**喇叭亮起,浏览器里能听到声音(电脑要开音量)。
## 在平台上怎么试
1. 运行后串口选 **COM2**,点"开启情景模式",按钮变成"停止情景模式",`txt_time` 每秒走一次,拖动实训台的光照、温度、湿度滑块,文本框跟着变。
2. 在实训台的时钟上拨到 **06:50:00**:应显示起床模式,ADAM-4150 的 **DO1 照明灯**亮,窗帘滑块到 5,广播响一声铃。
3. 拨到 **08:00:00**:上课模式,灯灭,窗帘到 0。
4. 拨到 **21:30:00**:回寝模式,灯亮,窗帘到 10,广播响铃。
5. 拨到 **23:00:00**:熄灯模式,灯灭,窗帘到 10。
6. 也可以拨到 6:44:50,看程序到 6:45 时自己切进起床模式。
+84
View File
@@ -0,0 +1,84 @@
## 任务目标
做一个"智慧会议室"预约窗体:程序一启动就每秒显示当前时间和光照度、温度、湿度、气压、空气质量。填好时、分、秒,选一种模式(会议、讨论、展示),点"预约";到了预约时间,程序自动开启这种模式,控制灯光、窗帘和主、客话筒:
| 模式 | 灯光 | 窗帘(滑动条) | 主话筒 | 客话筒 |
|---|---|---|---|---|
| 会议 | 开 | 0(全开) | 开 | 关 |
| 讨论 | 开 | 0(全开) | 开 | 开 |
| 展示 | 开 | 10(全关) | 开 | 关 |
勾选"拍照记录"时,预约时间到了还要打开网络摄像头,每 5 分钟存一张照片。点"退出"关掉所有设备。
练习重点:DateTime 的计算和比较(AddHours、AddMinutes、AddSeconds),switch 语句,用 `+=` 订阅事件。详细步骤见书 p148–153。
## 先添加项目资源
点代码上方的**项目资源**,在素材库里勾选 `switch_on`、`switch_off` 两张开关图片(名字和书上一样,不能自己上传)。三个开关图片框的**图片**属性都选 `Resources.switch_off`。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `frm_control` | 智慧会议室情景模式 |
| 分组框 | `grp_mode` | 模式选择 |
| 标签 | `lbl_time` | 当前时间:00:00:00(运行时显示当前时间) |
| 标签 | `lbl_mode` | 智慧会议室未开启情景模式 |
| 复选框 | `chk_record` | 拍照记录 |
| 文本框 | `txt_hour`、`txt_minute`、`txt_second` | 预约的时、分、秒,默认 13、20、00 |
| 标签 | `lbl_hour`、`lbl_minute`、`lbl_second` | 时、分、秒将会有场 |
| 下拉框 | `com_mode` | 选择模式,默认文字"会议" |
| 按钮 | `btn_reserve` | 预约 |
| 按钮 | `btn_exit` | 退出 |
| 分组框 | `grp_input` | 采集量 |
| 下拉框 | `com_import` | 采集器的串口 |
| 文本框 | `txt_light`、`txt_temperature`、`txt_humidity` | 光照度、温度、湿度 |
| 文本框 | `txt_airPressure`、`txt_airQuality` | 气压、空气质量 |
| 文本框 | `txt_camera` | 摄像头 IP 地址,默认 192.168.14.131 |
| 分组框 | `grp_status` | 状态栏 |
| 滑动条 | `trb_curtain` | 模拟窗帘:0 全开,10 全关 |
| 图片框 | `pic_lightsStatus` | 灯光开关,点它手动开关灯 |
| 灯泡 | `lamp` | 窗体上显示灯的亮灭 |
| 图片框 | `pic_mainMicrophone`、`pic_subMicrophone` | 主、客话筒开关,点它手动开关 |
| 定时器 | `timer` | 间隔 1000(1 秒) |
其余标签(串口、光照度、温度、湿度、气压、空气质量、摄像头IP地址、窗帘、全开、全关、灯光、话筒、主、客)照书上的图摆好,名字不影响评分。书上表格里还有 CO₂ 浓度一行,但代码里没有用到,可以不做。
> 窗体名:在设计器里点窗体空白处,把属性窗口的**名称**从 `Form1` 改成 `frm_control`,类名、文件名和 Program.cs 会跟着改。先改名再双击窗体,生成的 Load 事件才叫 `frm_control_Load`。
## 实现步骤
1. 代码开头加上 `using System.IO;`、`using System.IO.Ports;`、`using DeviceCtrlLibrary;`、`using DeviceCtrlLibrary.Model;`。
2. 声明全局变量:灯、主话筒、客话筒是否打开,模式是否已经开启(`IsInitialized`),`ADAM4017ResModel ResModel`,`DateTime now`,预约时间 `reserve`(先设成 `DateTime.Now.AddDays(1)`,也就是明天,免得一启动就"到时间"),摄像头读到的图片 `Image ReadImage = null`。
3. **Load**:
- 清空两个下拉框,把串口号加进 `com_import`,`com_mode` 加入"会议""讨论""展示";
- 启动 `timer`;
- 程序目录下没有 `Camera/` 文件夹就建一个(`Directory.Exists`、`Directory.CreateDirectory`);
- 用 `+=` 给 `ipCameraHelper.ImageRead` 绑定一个方法,在方法里把 `e.img` 存进 `ReadImage`。
4. **btn_reserve 的 Click**:`reserve = DateTime.Today`,再依次加上三个文本框里的时、分、秒(`Convert.ToInt16` 转成数字)。
5. 写 **ChooseMode()** 方法:`lbl_mode` 显示"智慧会议室已开启××模式",用 `switch (com_mode.Text)` 按上面的表格设置灯(图片、`lamp`、`adam4150Helper.OnOff(1, ...)`)、`trb_curtain.Value` 和两个话筒的图片。
6. **timer 的 Tick**:
- 用 `com_import.Text` 打开 `adam4017Helper`、`adam4150Helper`;
- `now = DateTime.Now`,`lbl_time` 显示 `HH:mm:ss`,读出数据填进五个文本框;
- `now >= reserve` 时:模式还没开启就调用 `ChooseMode()`,勾了"拍照记录"就打开并启动摄像头(`ipCameraHelper.Open(txt_camera.Text, "admin", "")`、`Start()`),然后记下已开启;分钟能被 5 整除且秒是 0 时,把 `ReadImage` 存到 `Camera/` 文件夹,文件名用当前时间。
7. **三个开关图片框的 Click**:灯、主话筒、客话筒各自开着就关、关着就开(灯还要控制 `lamp` 和实训台的照明灯)。
8. **btn_exit 的 Click**:停止 `timer`,`reserve` 改回明天,"拍照记录"设为不可用,关灯、关两个话筒,`trb_curtain.Value = 0`,`IsInitialized` 改回 false,关闭两个采集器和摄像头。
## 和书上不一样的地方
> **书上 Load 里没有把串口号加进 `com_import`**(注释写了,代码漏了),下拉框是空的,选不了串口。要补上 `foreach (string item in SerialPort.GetPortNames())` 那几行。
> 书上表格里温度文本框写的是 `txt_temperation`,代码里用的是 `txt_temperature`,请按代码写,自动评分按 `txt_temperature` 找。
## 在平台上怎么用时钟
- 实训台上的**时钟**可以拨到任意时刻:填好时刻点"拨到",程序里的 `DateTime.Now` 就从这个时刻往后走,点"恢复真实时间"回到现在。不用真的等到下午 1 点 20 分。
- 拨时间时**日期别动**:预约时间是按"今天"算的。
- 摄像头拍的照片会出现在运行区下方的"程序保存的图片"里,只在这次运行中保留。"每 5 分钟拍照"这一项自动评分不检查,由老师看代码评阅。
## 在平台上怎么试
1. 运行后串口选 **COM2**(程序一启动就在采集,不用点别的按钮)。在实训台上拖动光照、温度滑块;气压、空气质量在 ADAM-4017+ 那块点"Vin3–6 更多传感器"展开。几秒内文本框应跟着变。
2. 把时钟拨到 **13:19:54**;模式选"讨论",时分秒填 13、20、00,点"预约"。
3. 预约时间没到时,ADAM-4150 的 **DO1 照明灯**不亮;到 13:20:00 后,应显示"智慧会议室已开启讨论模式",照明灯亮,窗帘滑块到 0,主、客话筒都是开。
4. 点"退出",灯、话筒都关掉。想再试一次,重新运行程序。
+38
View File
@@ -0,0 +1,38 @@
## 任务目标
开始做项目 8 的"智慧校园管理平台":先做主界面。主界面没有标题栏,上面一条蓝色标题栏放"智慧校园"标题图,下面是校园背景图,背景上有"校园环境""校园安防""退出"三个图标;点"退出"关闭程序。后面四个任务都在这个程序上接着做。
练习重点:无边框窗体(FormBorderStyle)、面板(Panel)的停靠(Dock)、背景图片、图片框(PictureBox)和项目资源里的图片。详细步骤见书 p157–159。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `Main` | 主界面;FormBorderStyle 设为 None,StartPosition 设为 CenterScreen |
| 面板 | `panel1` | 顶部标题栏:停靠 Top,背景色 53, 147, 237 |
| 图片框 | `pictureBox1` | 放在 panel1 上,图片 `ico_title`("智慧校园"标题),SizeMode 设为 StretchImage |
| 面板 | `panel2` | 停靠 Fill,背景图片 `bg_home`(校园),背景排列 Stretch |
| 图片框 | `pic_environment` | "校园环境"入口,图片 `icon_02` |
| 图片框 | `pic_security` | "校园安防"入口,图片 `icon_03` |
| 图片框 | `pic_exit` | "退出",图片 `icon_04` |
> 三个图标放在 panel2 的背景图上,背景色都设成 **Transparent**,否则图标四周会露出一块灰色。
## 实现步骤
1. 点代码上方的**项目资源**,勾选 `ico_title`、`bg_home`、`icon_02`、`icon_03`、`icon_04` 五张图片。
2. 设置窗体:无边框、屏幕居中。
3. 拖两个面板:panel1 停靠 Top 当标题栏,里面放标题图;panel2 停靠 Fill,设背景图。
4. 在 panel2 上放三个图片框,按上表设名字、图片和透明背景。
5. 双击 `pic_exit`,在它的 Click 里写 `this.Close();`。窗体没有标题栏和关闭按钮,只能靠这个图标退出程序。
## 和书上不一样的地方
> **图片从项目资源里选**:书上第 2 步把配套资料里的 Resources 文件夹复制到工程里。平台上点**项目资源**,从素材库里勾选同名图片(平台自己画的,名字和尺寸与书上一致),设计器里"图片""背景图片"属性就能选到它们。
> **窗体名 Main**:在设计器里点窗体空白处,把属性窗口的**名称**从 `Form1` 改成 `Main`,类名、文件名和 Program.cs 会跟着改。任务 2 的自动评分要找到 Main 窗体。
## 在平台上怎么试
1. 运行后,运行区显示没有标题栏的主界面:蓝色标题栏上是"智慧校园",下面是校园背景和三个图标(窗体比运行区大时会按比例缩小,点**放大**看得更清楚)。
2. 点"退出"图标,程序应结束。
+56
View File
@@ -0,0 +1,56 @@
## 任务目标
在任务 1 的程序里新建"校园环境"窗体 `Environment`,把界面搭好:顶部标题栏和"<返回主窗体",中间是路灯场景的背景图,底部一栏显示火焰、烟雾、光照度、温度、湿度,右边是"路灯控制"和"LED推送"两组控件,左下方是显示一周温度的折线图。这个任务**只做界面**,采集数据、控制路灯、推送 LED 在任务 3 做。
练习重点:多窗体、面板的多层停靠(Top / Fill / Bottom / Right)、透明背景、复选框、图表(Chart)的系列和图例。详细步骤见书 p159–164。
> 接着任务 1 的程序做。为了能看到效果,这次先让主界面的"校园环境"能打开窗体、"<返回主窗体"能关闭窗体(书上放在后面的任务里)。
## 界面和控件名
| 控件 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 窗体 | `Environment` | 校园环境;FormBorderStyle 设为 None,StartPosition 设为 CenterScreen |
| 面板 | `panel1` | 顶部标题栏:停靠 Top,背景色 100, 116, 133,放标题图 `ico_title` |
| 面板 | `panel2` | 停靠 Fill,下面的面板都放在它里面 |
| 面板 | `panel3` | panel2 里停靠 Top,背景色 229, 229, 229 |
| 标签 | `lb_return` | < 返回主窗体(放在 panel3 上) |
| 面板 | `panel_bg` | panel3 下面停靠 Fill,背景图片 `day_02`(路灯没亮的场景),背景排列 Stretch |
| 面板 | `panel5` | panel_bg 里停靠 Bottom:环境数据栏 |
| 文本框 | `txt_fire`、`txt_smoke`、`txt_light`、`txt_temperature`、`txt_humidity` | 火焰、烟雾、光照、温度、湿度(左边各配一个标签) |
| 按钮 | `btn_dataAnalyse` | 数据分析 |
| 按钮 | `btn_ledEnvironment1` | 推送天气信息(环境数据栏里的那个) |
| 面板 | `panel_form` | panel_bg 里停靠 Right,背景色 Transparent,背景图片 `bg_from2` |
| 分组框 | `groupBox2` | 路灯控制 |
| 复选框 | `ch_autoLamp` | 自动控制 |
| 文本框 | `txt_autoLight` | 设定开灯光照值(后面配标签"lx") |
| 图片框 | `pic_lampOpen` | 灯光控制的开关:背景色 Transparent,背景图片 `switch_off` |
| 分组框 | `groupBox3` | LED推送 |
| 文本框 | `txt_ledMsg` | LED信息(Multiline 设为 True) |
| 复选框 | `ch_autoLedFire` | 自动推送火情 |
| 按钮 | `btn_ledSendMsg` | 推送LED信息 |
| 按钮 | `btn_ledEnvironment2` | 推送天气信息(LED推送组里的那个) |
| 图表 | `chart1` | 放在 panel_bg 左下方,显示最高温度和平均温度 |
> 书上表 8-6、表 8-7 把四个按钮的"名称"写成了"数据分析""推送天气信息""推送LED信息",那是按钮上的文字。名称按上表写,任务 3 的代码和自动评分都用这些名字。
## 实现步骤
1. **项目资源**里再勾选 `day_02`、`bg_from2`、`switch_off`(任务 3 还要用 `day_01`、`switch_on`,可以一起勾上)。
2. 点 **+ 新建窗体**,建 `Environment`,设成无边框、屏幕居中,调到合适的大小。
3. 按"标题栏 → panel2 → panel3 → panel_bg"的顺序拖面板,设好停靠。先放的面板先占地方,放错了可以在属性里改停靠。
4. 在 panel_bg 上依次放底部的 panel5(环境数据栏)和右边的 panel_form,再把两组控件放进 panel_form 里的两个分组框。书上要求 panel_form 里的其他控件背景色也设成 Transparent,让背景图透出来。
5. 拖一个**图表**到 panel_bg 上,在属性窗口点**系列和图例**:
- 图例两个:`MaxLegend`、`AVGLegend`;
- 系列两个:`MaxSeries`(图表类型 Line,图例 MaxLegend,图例文字"最大值",X 值字段 `_Time`,Y 值字段 `MaxTemperature`)和 `AVGSeries`(Line,AVGLegend,"平均值",`_Time`,`AVGTemperature`)。
6. 主窗体 `pic_environment` 的 Click:`new Environment().Show();`。
7. `lb_return` 的 Click:`this.Close();`(只关掉这个窗体,主窗体还在)。
## 和书上不一样的地方
> **背景图片叫 `bg_from2`**:书上写的是 `bg_form2.png`,配套资料里的文件名是 `bg_from2`,素材库也用这个名字。
## 在平台上怎么试
1. 运行后在主界面点"校园环境",打开 Environment 窗体:背景是路灯场景,底部有五个文本框和"数据分析""推送天气信息",右边有"路灯控制""LED推送"两组,左下方有图表(现在还没有数据)。
2. 点"< 返回主窗体",Environment 关闭,主界面还在。
@@ -0,0 +1,76 @@
## 任务目标
让任务 2 做好的"校园环境"界面动起来:打开窗体后,后台线程每隔 5 秒多采集一次环境数据,显示在底部的文本框里并存进数据库;路灯可以点开关手动控制,也可以按光照度自动开关,开灯时背景换成亮灯的场景;能把天气信息、自己输入的文字推送到 LED 屏,勾选"自动推送火情"后有火情自动推送;点"数据分析"用折线图显示最近 7 天每天的最高温度和平均温度。
练习重点:自己写类(AppSet、SQLHelper、SQLBLL)、后台线程(Thread)和 SynchronizationContext 回到界面线程更新控件、参数化的 SQL 语句、图表绑定数据(DataSource + DataBind)、复选框的 CheckedChanged 事件。详细步骤见书 p164–174。
## 先准备数据库
这个任务用 **WisdomCampus** 库(书上配套资料里附带的库):EnvironmentData 表存环境数据,SecurityData 表存报警图片(任务 4、5 用),Users 表存用户。平台已经按书上的表结构建好,并预置了 7 天的环境数据,"数据分析"一打开就有图。
第一次运行这个实验时,平台会自动建好这个库;数据弄乱了,到右上角的**数据库**页面点**恢复实验初始数据**,选这个实验。
## 界面和控件名
界面在任务 2 已经做好,这次要写代码的控件:
| 窗体 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 主界面 | `Main` | Load 里打开所有设备的串口 |
| 主界面 | `pic_environment` | "校园环境":打开 Environment 窗体 |
| 校园环境 | `Environment` | Load 里启动采集线程,FormClosed 里停止线程 |
| 校园环境 | `txt_fire`、`txt_smoke` | 显示"发生火情"/"发生烟情"或"正常" |
| 校园环境 | `txt_light`、`txt_temperature`、`txt_humidity` | 显示光照、温度、湿度 |
| 校园环境 | `btn_dataAnalyse` | 数据分析 / 关闭图表 |
| 校园环境 | `chart1` | 最近 7 天的最高温度、平均温度 |
| 校园环境 | `btn_ledEnvironment1`、`btn_ledEnvironment2` | 推送天气信息(两个按钮做同一件事) |
| 校园环境 | `pic_lampOpen` | 灯光控制开关:点一下开灯,再点关灯 |
| 校园环境 | `ch_autoLamp` | 自动控制路灯 |
| 校园环境 | `txt_autoLight` | 设定开灯光照值:光照低于它就开灯 |
| 校园环境 | `txt_ledMsg` | 要推送的 LED 信息 |
| 校园环境 | `btn_ledSendMsg` | 推送LED信息 |
| 校园环境 | `ch_autoLedFire` | 自动推送火情 |
## 实现步骤
1. 点 **+ 新建类**,建 `AppSet`:四个串口(ADAM-4017+ 是 COM2、ADAM-4150 是 COM3、ZigBee 是 COM4、LED 屏是 COM5)、摄像机的 IP / 用户名 / 密码(任务 4 用)、数据库连接字符串,都写成 `public static string`。
2. 再建 `SQLHelpers/SQLHelper` 和 `SQLHelpers/SQLBLL` 两个类(带文件夹,命名空间是 `智慧校园.SQLHelpers`):
- SQLHelper:`getData` 执行查询返回 DataSet,`Exec` 执行增删改返回影响的行数(出错返回 −1),`SaveData` 用 SqlCommandBuilder 保存 DataTable;
- SQLBLL:`EnvironmentUpData` 把一次采集的数据 insert 进 EnvironmentData,`GetEnvironmentGraphData` 按天分组查出最近 7 天的最高温度和平均温度,`SecurityUpData`、`GetSecurityData` 存取报警图片(任务 4、5 用);
- 在 SQLBLL 文件里再写一个 `SQLRes` 类(Msg、Result 两个属性)当返回结果。
3. **Main_Load**:在 try 里依次打开 ADAM-4017+、ADAM-4150、ZigBee、LED 屏的串口(用 AppSet 里的串口号)。
4. **Environment_Load**:隐藏 chart1,创建线程运行 `Tick` 方法,记下 `WindowsFormsSynchronizationContext.Current`,启动线程。
5. **Tick()**:`while (true)` 循环,每次:
- 读 ADAM-4017+ 和 ADAM-4150 的数据;
- 用 `_main.Send(...)` 回到界面线程,把火焰、烟雾、光照、温度、湿度显示到文本框;
- 勾了自动控制时,光照低于设定值就 `LampOnOff(1, true)`,否则 `LampOnOff(1, false)`;
- 勾了自动推送火情时,火焰和烟雾都有推送"发生严重火灾",只有火焰推送"发现火情",只有烟雾推送"发现烟情";
- 调用 `SQLBLL.EnvironmentUpData(...)` 存数据,`Thread.Sleep(5000)`。
6. **LampOnOff(int RelayNum, bool IsOpen)**:调用 `zigBeeHelper.OnOff` 开关路灯,再用 `_main.Send` 换图片:开灯时开关换成 `switch_on`、背景换成 `day_01`,关灯时换回 `switch_off`、`day_02`。
7. **Environment_FormClosed**:停止采集线程。
8. **btn_dataAnalyse 的 Click**:文字是"数据分析"时,把 `GetEnvironmentGraphData()` 的结果给 `chart1.DataSource`,`DataBind()`,显示图表,文字改成"关闭图表";反过来隐藏图表、改回"数据分析"。
9. **两个"推送天气信息"按钮**:推送"当前温度:…℃ 湿度:…%"。
10. **pic_lampOpen 的 Click**:按 `IsLampOpen` 开灯或关灯,然后把 `IsLampOpen` 取反。
11. **btn_ledSendMsg 的 Click**:推送 `txt_ledMsg` 里的文字。
12. **txt_autoLight 的 TextChanged**:用 `double.TryParse` 把输入的数存进 `setLight`。
13. **ch_autoLamp 的 CheckedChanged**:`IsAuto_Lamp = ch_autoLamp.Checked;`
14. **ch_autoLedFire 的 CheckedChanged**:`IsAuto_FireMsg = ch_autoLedFire.Checked;`
## 和书上不一样的地方
> **第 23 步的控件名写错了**:书上说在 `ch_autoLamp` 的 CheckedChanged 里写 `IsAuto_FireMsg = ch_autoLedFire.Checked;`,应该是 **`ch_autoLedFire`** 的 CheckedChanged(双击"自动推送火情"复选框生成)。写到 ch_autoLamp 里,"自动推送火情"就不起作用。
> **连接字符串**:平台只看 `Initial Catalog=WisdomCampus`,服务器名、用户名和密码照书上写就行。
> 书上 `GetSecurityData` 最后一行印成了 `eturn ImgDictionary;`,要写成 `return`。
> 书上关窗体时用的 `thread.Suspend()` 在新版 .NET 里已经不能用,平台做了兼容,照书上写就行。
## 在平台上怎么试
1. 运行后点"校园环境"。在实训台上拖动温度、湿度、光照滑块,8 秒内文本框里的数应跟着变;点火焰、烟雾,"火焰""烟雾"应显示"发生火情""发生烟情"。
2. 点灯光控制的开关:实训台的**照明灯 1** 亮起,开关和背景换成开灯的图片;再点一次全部换回来。
3. 在"设定开灯光照值"里输入 400,勾选"自动控制"。把光照拖到 200,几秒内路灯自动亮;拖到 800,路灯自动熄灭。
4. 点"推送天气信息",实训台的 LED 屏显示"当前温度:26℃ 湿度:50%"这样的文字;在"LED信息"里输入文字,点"推送LED信息",LED 屏显示这段文字。
5. 勾选"自动推送火情",点实训台的火焰,LED 屏显示"发现火情";再点烟雾,显示"发生严重火灾"。
6. 点"数据分析",出现两条折线(最大值、平均值),按钮变成"关闭图表";再点一次图表隐藏。到**数据库**页面查询 EnvironmentData 表,能看到程序刚存进去的数据。
+58
View File
@@ -0,0 +1,58 @@
## 任务目标
在主界面上接通"校园安防":新建 `Security` 窗体,打开时连上网络摄像机显示实时画面,后台线程每秒检查一次红外对射,有人闯入时显示"发现入侵"并把当前画面存进数据库;四个方向键控制摄像机转动;"拍照"把画面存成图片文件、同时存进数据库;返回主窗体时关掉摄像机。"查看历史图像"按钮先放好,任务 5 再做。
练习重点:摄像机的 ImageRead 事件、后台线程、图片框的鼠标按下 / 松开事件(MouseDown / MouseUp)、换背景图片、保存图片文件。详细步骤见书 p174–180。
## 先准备数据库
用任务 3 的 **WisdomCampus** 库,入侵和拍照的图片存进 **SecurityData** 表(报警时间 EvenTime、图片 EvenImage),存的方法是任务 3 写好的 `SQLBLL.SecurityUpData`。还没有这个库或数据弄乱了,到**数据库**页面点**恢复实验初始数据**,选这个实验。
## 界面和控件名
接着任务 3 的程序做。Security 窗体顶部和 Environment 一样:标题栏 + "< 返回主窗体"。
| 窗体 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 主界面 | `pic_security` | "校园安防":打开 Security 窗体 |
| 校园安防 | `Security` | 无边框、屏幕居中 |
| 校园安防 | `panel1`、`panel3` | 标题栏(放标题图 `ico_title`)、返回栏,都停靠 Top |
| 校园安防 | `lb_return` | < 返回主窗体 |
| 校园安防 | `panel_bg_video` | 停靠 Fill,白色背景,下面的控件都放在它上面 |
| 校园安防 | `panel2` | 灰色(DimGray)的视频区 |
| 校园安防 | `pic_video` | 放在 panel2 上,显示摄像机画面;背景图片 `icon_monitor`,背景排列 Center |
| 校园安防 | `lb_infrared` | 放在 panel2 上,标签"入侵:"的右边,开始是 N/A,之后显示"发现入侵"或"正常" |
| 校园安防 | `panel_video_ctrl` | 方向盘:背景图片 `panel_video_ctrl`,背景排列 Stretch |
| 校园安防 | `pic_up`、`pic_down`、`pic_left`、`pic_right` | 放在方向盘上的四个方向键,背景图片 `btn_arrow_up_normal` 等,背景排列 Stretch |
| 校园安防 | `btn_camera` | 拍照 |
| 校园安防 | `btn_history` | 查看历史图像(任务 5 再写代码) |
## 实现步骤
1. **项目资源**里再勾选 `icon_monitor`、`panel_video_ctrl` 和四个方向键的 `btn_arrow_*_normal`、`btn_arrow_*_press`(共 8 张)。
2. 点 **+ 新建窗体** 建 `Security`,按上表搭界面。
3. 主窗体 `pic_security` 的 Click:`new Security().Show();`。
4. 在 Security 里引用 `System.IO`、`System.Threading`、`DeviceCtrlLibrary`、`智慧校园.SQLHelpers`,声明字段:入侵标志 `IsInfrared`、图片文件夹 `ImgDir`(程序目录下的 Camera 文件夹)、线程、SynchronizationContext、当前画面 `img`。
5. 写 **ipCameraHelper_ImageRead(ImageReadEvenArgs e)**:把 `e.img` 记到 `img`,并显示到 `pic_video`。
6. 写 **tick()**:`while (true)` 循环,读 ADAM-4150 的 `infraredValue`,用 `_main.Send` 把"发现入侵"或"正常"显示到 `lb_infrared`;有入侵且有画面时调用 `SQLBLL.SecurityUpData(img)`;`Thread.Sleep(1000)`。
7. **Security_Load**:打开摄像机(AppSet 里的 IP、用户名、密码),挂上 ImageRead 事件,`Start()`;启动 tick 线程;Camera 文件夹不存在就建一个。
8. **Security_FormClosed**:摄像机 `Stop()`、`Close()`,停止线程。
9. **btn_camera 的 Click**:把 `pic_video.Image` 存成 `ImgDir + 年月日_时分秒 + ".jpg"`,再调用 `SQLBLL.SecurityUpData` 存进数据库。
10. **四个方向键**:在 ⚡ 事件里双击"鼠标按下"和"鼠标松开"生成方法。按下时换成 `_press` 图片并调用 `PanUp()`(或 PanDown / PanLeft / PanRight),松开时换回 `_normal` 图片并再调用一次同一个方法。
11. **lb_return 的 Click**:`this.Close();`。
## 和书上不一样的地方
> **摄像机每次调用转一格**:平台的摄像机左右 7 格、上下 3 格,开始在正中,`PanUp()` 等方法每调用一次转一格。书上按下、松开各调用一次,所以点一下方向键会转两格。
> **面板的名字**:示例(取自配套工程)里白色的大面板叫 `panel_bg_video`、灰色视频区叫 `panel2`,书上叫 `panel_bg`、`panel_bg_video`。面板的名字不评分,照书上起名也可以。
> 书上的路径 `"Camera\\"` 照写就行,平台会换成服务器上能用的路径。
## 在平台上怎么试
1. 运行后点"校园安防",打开 Security 窗体,几秒内 `pic_video` 里出现摄像机画面(校园东围墙)。
2. 点实训台的**红外对射**遮挡光束,"入侵:"后面显示"发现入侵",画面里出现翻墙的人;再点一次恢复,显示"正常"。
3. 点四个方向键,画面跟着转;松开后方向键换回原来的图片。把鼠标放在实训台的**网络摄像头**上能看到云台的位置。
4. 点"拍照",运行区下面的"程序保存的图片"里多出一张 Camera 文件夹下的照片(文件名是拍照的日期和时间);到**数据库**页面查询 SecurityData 表,能看到拍照和入侵时存的记录。
5. 点"< 返回主窗体",Security 关闭,摄像机也关闭。
+54
View File
@@ -0,0 +1,54 @@
## 任务目标
做"历史报警记录"窗体 `SecurityCameraData`:在校园安防界面点"查看历史图像"打开它,按开始时间、结束时间从数据库里查出这段时间拍下的照片(拍照和入侵时存的),一次显示一张和它的拍摄时间,用左右箭头前后翻;查不到就显示"NO DATA"。
练习重点:ShowDialog 打开对话框式的窗体、日期时间选择器(DateTimePicker)、Dictionary 和 List 集合、运行时改控件的父容器(Parent)和位置(Location)。详细步骤见书 p181–185。
## 先准备数据库
照片在 **WisdomCampus** 库的 **SecurityData** 表里,任务 4 的拍照和入侵检测会往里存;初始数据里这张表是空的,先在校园安防界面拍几张照片再查。查询用任务 3 写好的 `SQLBLL.GetSecurityData(开始日期, 结束日期)`,它返回"拍摄时间 → 照片"的 Dictionary。
## 界面和控件名
接着任务 4 的程序做。
| 窗体 | 名称(Name) | 文字(Text)和用途 |
|---|---|---|
| 校园安防 | `btn_history` | 查看历史图像:打开 SecurityCameraData 窗体 |
| 历史报警记录 | `SecurityCameraData` | 无边框、屏幕居中 |
| 历史报警记录 | `panel1` | 标题栏:背景色 100, 116, 133,左边标签 `label1`"历史报警记录"(白字) |
| 历史报警记录 | `pic_close` | 标题栏右边的关闭按钮:背景图片 `windowstate_closed_hover`,背景排列 Center |
| 历史报警记录 | `panel_toolBar` | 工具栏:背景色 229, 229, 229 |
| 历史报警记录 | `dateTimePicker1`、`dateTimePicker2` | 开始时间、结束时间(左边各配一个标签) |
| 历史报警记录 | `btn_get` | 查询 |
| 历史报警记录 | `pic_camera` | 显示照片:背景色 64, 64, 64 |
| 历史报警记录 | `lb_noData` | NO DATA(红字,背景色 64, 64, 64,字号 48),叠在 pic_camera 中间 |
| 历史报警记录 | `lb_time` | TIME(红字,背景色 64, 64, 64),叠在 pic_camera 右下角,显示拍摄时间 |
| 历史报警记录 | `pic_up`、`pic_down` | 左、右翻页箭头:图片 `arrow_left_normal`、`arrow_right_normal`,SizeMode 设为 CenterImage |
> 注意 `pic_up` 是**左**箭头(上一张)、`pic_down` 是**右**箭头(下一张),名字照书上写。
## 实现步骤
1. **项目资源**里再勾选 `windowstate_closed_hover`、`arrow_left_normal`、`arrow_right_normal`。
2. 点 **+ 新建窗体** 建 `SecurityCameraData`,按上表搭界面。
3. 校园安防 `btn_history` 的 Click:`new SecurityCameraData().ShowDialog();`。
4. 引用 `DeviceCtrlLibrary`、`智慧校园.SQLHelpers`,声明字段:照片集合 `ImgDictionary`(`Dictionary<string, Image>`)、当前下标 `index = -1`、当前照片 `img`、时间列表 `Keys`(`List<string>`)。
5. 写 **ImageChanged(char UpOrDown = '+')**:`'+'` 时下标加 1、`'-'` 时减 1,超过最后一张回到第一张、小于 0 回到最后一张;按 `Keys[index]` 从集合里取出照片显示到 `pic_camera`,时间显示到 `lb_time`。
6. 写 **GetData()**:用两个日期时间选择器的 Value 调用 `SQLBLL.GetSecurityData`,下标设回 −1,清空 `Keys`;没有照片就显示 `lb_noData` 并返回,有照片就隐藏它,把所有时间加进 `Keys`,调用 `ImageChanged()` 显示第一张。
7. **SecurityCameraData_Load**:把 `pic_up`、`pic_down` 的 Parent 设成 `pic_camera`,位置分别设为 (20, 0) 和 (560, 0),然后调用 `GetData()`。
8. **btn_get 的 Click**:`GetData();`
9. **pic_up 的 Click**:`ImageChanged('-');` **pic_down 的 Click**:`ImageChanged();`
10. **pic_close 的 Click**:`this.Close();`
## 和书上不一样的地方
> **每次查询前清空 Keys**:书上的 GetData 没有 `Keys.Clear();`,查第二次时 Keys 里还留着上次的时间,翻页时照片和时间就对不上了。在下标设回 −1 的地方加上 `Keys.Clear();`。
## 在平台上怎么试
1. 在实训台的**时钟**里拨到 9:00:00,运行后打开校园安防,等画面出来点"拍照";再拨到 10:30:00,再拍一张。
2. 点"查看历史图像",打开历史报警记录窗体:两个日期默认都是今天,显示第一张照片,右下角时间是 9:00 左右,"NO DATA"不显示。
3. 点右箭头显示 10:30 那张,再点回到第一张;点左箭头往回翻。
4. 开始时间选 2024-01-01,点"查询",还能查到今天的照片;结束时间再改成 2024-01-31,点"查询",显示"NO DATA"。
5. 点右上角的关闭按钮,回到校园安防界面。