- 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>
65 KiB
《物联网编程与应用 第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)
- 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)
- 完整插入
- 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'; - delete 删除语句:
delete from 表名 [where 条件];。示例:delete from user where username='张三';、delete top (5) from user;、delete from user; - ADO.NET 对象模型简介:ADO.NET 由「.NET 数据提供程序」和「数据集 DataSet」两部分组成。表6-2 ADO.NET 核心组件:
| 对象 | 说明 | 特点 |
|---|---|---|
| Connection | 建立与特定数据库的连接 | 可以自己创建,也可由其他对象自动产生(如 DataAdapter) |
| Command | 对数据源执行命令 | 通过 Connection 对象下达命令,Connection 的指向决定操作对象 |
| DataAdapter | 从数据源中检索数据并填充 DataSet 中的表 | DataAdapter 填充到 DataSet 中的是断开连接的脱机数据 |
| DataReader | 从数据库中检索只读的数据流 | DataReader 读取数据必须在维持数据库连接的前提下进行 |
| DataSet | 在内存中缓存从数据源中检索到的数据 | 独立于其他数据源 |
- 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()后连接才能做其他操作。
- SqlConnection:表示一个打开的 SQL Server 连接,不能被继承;OLE DB 数据源要用 OleDbConnection。有两个构造函数:
能力拓展
边想边做(书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 两个控件。
任务实施
- 按图6-13「查询图片窗体界面」建 Form1(标题「查询校园灾情数据」):顶部一行「开始日期 [DateTimePicker] 结束日期 [DateTimePicker] [查询]」,下面「历史记录」组框里放 DataGridView。
表6-3 查询图片窗体界面控件属性:
| 控件名称 | 控件类型 | 属性名 | 属性值 | 控件功能 |
|---|---|---|---|---|
| LblStartTime | 标签 | Text | 开始日期 | 开始日期 |
| LblEndTime | 标签 | Text | 结束日期 | 结束日期 |
| StartTime | 日期时间控件 | Datetime | 时间 | 起始时间 |
| EndTime | 日期时间控件 | Datetime | 时间 | 结束时间 |
| btnCx | 按钮 | Text | 查询 | 查询 |
| groupBox4 | 组合框 | Text | 历史记录 | 组合框 |
| dataGridView1 | 数据网格控件 |
- 引用
System.Data.SqlClient、System.IO.Ports。 - 声明 5 个全局变量:连接字符串
sqlConnectString、SqlConnection mySqlConnection、SqlCommand mySqlCmd、SqlDataAdapter sqldata、DataSet ds。 - 写 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,最后关闭连接。 - 运行,选好起止日期后点「查询」,结果见图6-14(示例数据为 2024/1/16–2024/1/23 共 9 行)。
知识提炼(书128–130)
- 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 模拟)。
任务实施
- 把 4017(模拟量采集器)和 4150(数字量采集器)的串口接到 PC(没有硬件可以跳过)。
- 新建 WinForms 项目「教室情景模式」,保存到
D:\物联网编程与应用\项目7\任务1\。 - 把 ADAM4017、ADAM4150 需要的
DeviceCtrlLibrary.dll和「API」文件夹里的所有 dll 复制到...\教室情景模式\bin\Debug\。 - 添加引用 DeviceCtrlLibrary.dll。没有硬件的读者改为添加「仿真动态库文件」,不需要 API 文件夹里的 dll。
- 项目属性 → 资源 →「添加资源」,加入
switch_off.png、switch_on.png。 - 按图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。)
- 引用
System.IO.Ports、DeviceCtrlLibrary、DeviceCtrlLibrary.Model。 - 定义
bool IsOpen、IsLampOpen、IsairconditioneOpen,以及ADAM4017ResModel ResModel。 - frm_control_Load:用
SerialPort.GetPortNames()填充 com_import。 - btn_apply_Click:第一次点击打开 4017/4150 串口,按钮改为「关闭」,lbl_mode 改为「智慧教室已开启」,启动 timer;再次点击关闭串口、停止 timer、恢复文字。
- timer_Tick:调用
adam4017Helper.SetData()采集并显示。光照 <400 时开灯(图片 switch_on、lamp.OnOff(true)、adam4150Helper.OnOff(1,true)),>800 时关灯。温度 >30 时开空调、trb=26;<5 时开空调、trb=25;其他情况关空调、trb=18。 - pic_lightsStatus_Click:手动切换灯。
知识提炼
- 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 时也会触发) |
- 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 事件。
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」→ 确定,然后从工具箱把控件拖到窗体上。
- 引用
System.IO.Ports、DeviceCtrlLibrary、DeviceCtrlLibrary.Model、System.Threading。 - 定义变量:
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)。 - Load:清空 com_import,填入串口名。
- btn_apply_Click:在「开启情景模式」和「停止情景模式」之间切换,同时开/关 4017、4150 和 timer。
- 写
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)。 - 写
PlayMp3():图片切到 on,执行axWindowsMediaPlayer1.URL = AppDomain.CurrentDomain.BaseDirectory + "//Resources//ring.mp3"; axWindowsMediaPlayer1.Ctlcontrols.play();,然后Thread.Sleep(4000),图片切回 off。 - timer_Tick:
now = DateTime.Now,txt_time.Text = now.ToString("HH:mm:ss"),采集并显示数据,然后调用 CheckMode()。
知识提炼
- PictureBox 控件:可以加载 BMP、ICO、WMF、JPEG、GIF。表7-6 常见属性:Image(要显示的图像)、SizeMode(图像显示模式)。
- 自定义方法:语法为
<Access Specifier> <Return Type> <Method Name>(Parameter List) { Method Body },书中逐一解释了访问修饰符、返回类型(不返回值时用 void)、方法名(唯一,区分大小写)、参数列表和方法主体。示例:
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 事件代码。
private void timerAlarm_Tick(object sender, EventArgs e)
{
IsAlarmOpen = ____; // IsAlarmOpen为报警灯状态变量,每次进行切换
alarm.OnOff(____);//切换界面报警灯状态
}
提示2:根据下列代码的提示,修改完善 CheckMode 方法的代码。
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₂ 那一行。
- 引用
System.IO、System.IO.Ports、DeviceCtrlLibrary、DeviceCtrlLibrary.Model。 - 定义变量:
bool IsLightsOpen、IsmainMicrophoneOpen、IssubMicrophoneOpen、IsInitialized;ADAM4017ResModel ResModel;DateTime now;DateTime reserve = DateTime.Now.AddDays(1)(默认设为明天,防止出现「假预约」);Image ReadImage = null。 - Load:清空 com_import 和 com_mode,com_mode 加入「会议」「讨论」「展示」,启动 timer。如果
BaseDirectory + "Camera/"不存在就创建。然后DeviceCtrlHelper.ipCameraHelper.ImageRead += ipCameraHelper_ImageRead;绑定监控事件。 void ipCameraHelper_ImageRead(ImageReadEvenArgs e) { ReadImage = e.img; }- btn_reserve_Click:
reserve = DateTime.Today.AddHours(时).AddMinutes(分).AddSeconds(秒)(逐行调用 Convert.ToInt16)。 12–14. pic_lightsStatus / pic_mainMicrophone / pic_subMicrophone 的 Click:手动开关切换。 ChooseMode():lbl_mode.Text = "智慧会议室已开启" + com_mode.Text + "模式",然后 switch 三种模式设置灯、窗帘(0 开 / 10 关)、主客传声器图片。- 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")。 - 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 事件
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.SleepADAM4017ResModel的字段: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)
任务描述:主界面提供所有功能的入口,要求简洁、易学、好用。包含两个入口:校园环境、校园安防。
任务实施
- 新建 WinForms 项目「智慧校园」,保存到
D:\物联网编程与应用\项目8\任务1\。 - 把「综合实训」文件夹下的 Resources 文件夹复制到
...\智慧校园\bin\Debug,并在解决方案中导入。参考工程对应的是项目8/Resources。 - 设置窗体属性。表8-1 窗体属性:
| 属性名 | 属性值 |
|---|---|
| Name | Main |
| FormBorderStyle | none |
| StartPosition | CenterScreen |
-
加两个 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。
- panel1:BackColor = 53,147,237,Dock = Top;里面放一个 PictureBox,SizeMode = StretchImage,Image =
-
在 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 |
- pic_exit 的 Click:
this.Close();
任务2 制作「校园环境」界面(书159–164 / PDF 160–165)
任务描述:实时采集温度、湿度等气象信息并推送到 LED 屏;监测火焰、烟雾等安防信息,一旦有险情第一时间推送到 LED 预警;根据光照度自动开关路灯;对历史气象数据做分析比较。
任务实施(本任务只做界面)
- 新建窗体 Environment,并调整大小。表8-5 Environment 窗体属性:
| 属性名 | 属性值 |
|---|---|
| Name | Environment |
| FormBorderStyle | none |
| StartPosition | CenterScreen |
-
布局 Panel:
- panel1:BackColor = 100,116,133,Dock = Top,内放 PictureBox(StretchImage,
ico_title.png) - panel2:Dock = Fill,panel2 里再放 panel3(Dock = Top)
效果见图8-3。
- panel1:BackColor = 100,116,133,Dock = Top,内放 PictureBox(StretchImage,
-
panel3:BackColor = 229,229,229,上面放标签
lb_return,Text =「<返回主窗体」(图8-4)。 -
panel3 下方放
panel_bg:Dock = Fill,BackgroundImageLayout = Stretch,BackgroundImage =day_02.png(带路灯的建筑图,图8-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「推送天气信息」)。)
- 在 panel_bg 上放
panel_form:BackColor = Transparent,BackgroundImage =bg_form2.png,Dock = Right(图8-7)。参考资源里的文件名是bg_from2.png,书上写成了 bg_form2。 - 在 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信息)。)
- 在 panel_bg 上加 Chart 图表控件 chart1:
- Legends 加两项:
MaxLegend、AVGLegend - Series 设置(图8-9、图8-10「Series 集合编辑器」):
- Legends 加两项:
| 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/智慧校园/智慧校园/ 里都有,这里只列步骤)
- 导入数据采集相关 dll;新建
AppSet类和SQLHelpers文件夹,文件夹里放SQLBLL、SQLHelper两个类。 - 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.SqlConnSaveData(sql, DataTable):SqlDataAdapter + SqlCommandBuilder + UpdategetData(sql, params SqlParameter[])返回 DataSetgetData(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
- 在 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 里。 - 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。 - Environment_Load:
chart1.Visible=false;thread = new Thread(new ThreadStart(Tick));_main = WindowsFormsSynchronizationContext.Current;thread.Start()。 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)
- 采集 4017,
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。- Environment_FormClosed:
thread.Suspend(); thread = null; - btn_dataAnalyse_Click:按钮文字为「数据分析」时,
chart1.DataSource = SQLBLL.GetEnvironmentGraphData(); chart1.DataBind(); chart1.Visible = true;,并把按钮改为「关闭图表」;为「关闭图表」时反过来。 - 两个「推送天气信息」按钮:
SendLedString("当前温度:" + temperatureValue + "℃ 湿度:" + humidityValue + "%")。 - pic_lampOpen_Click:根据 IsLampOpen 调用 LampOnOff 切换,然后
IsLampOpen = !IsLampOpen。 - 「推送LED信息」按钮:
SendLedString(txt_ledMsg.Text)。 - txt_autoLight_TextChanged:
double.TryParse(txt_autoLight.Text, out setLight)。 - ch_autoLamp_CheckedChanged:
IsAuto_Lamp = ch_autoLamp.Checked; - 书上写「在 ch_autoLamp 的 CheckedChanged 事件中输入」
IsAuto_FireMsg = ch_autoLedFire.Checked;。这里书上写错了,应该是 ch_autoLedFire 的 CheckedChanged。
任务4 实现「校园安防」功能模块(书174–180 / PDF 175–181)
任务描述:实时监控,有人入侵时摄像机抓拍,把非法入侵人员拍照记录下来。
任务实施
- 添加窗体
Security.cs和SecurityCameraData.cs,Security 的顶部按任务2的方法布局(图8-12:标题栏 +「<返回主窗体」)。 - 加
panel_bg:Dock = Fill,BackColor = White。 - panel_bg 上加
panel_bg_video:BackColor = DimGray。 - panel_bg_video 上加 PictureBox
pic_video:BackgroundImage =icon_monitor.png,BackgroundImageLayout = Center。 - panel_bg_video 上再加 Label「入侵:」和 Label
lb_infrared(Text = 「N/A」),效果见图8-13。 - panel_bg 上加
panel_video_ctrl:BackgroundImage =panel_video_ctrl.png(圆形方向盘底图),Layout = Stretch。 - 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)。 - panel_bg 上加按钮
btn_camera(Text「拍照」)、btn_history(Text「查看历史图像」),完整效果见图8-15。 - 引用 System.IO、System.Threading、DeviceCtrlLibrary、智慧校园.SQLHelpers。
- 字段:
bool IsInfrared;string ImgDir = AppDomain.CurrentDomain.BaseDirectory + "Camera\\";Thread thread;SynchronizationContext _main;Image img = null。 tick()是 while(true) 循环:执行IsInfrared = adam4150Helper.SetData().infraredValue;通过_main.Send设置lb_infrared.Text = IsInfrared ? "发现入侵" : "正常";若有入侵且 img 不为 null,执行SQLBLL.SecurityUpData(img);然后Thread.Sleep(1000)。ipCameraHelper_ImageRead(ImageReadEvenArgs e):img = e.img; pic_video.Image = e.img;(在相机回调线程里直接设置 UI)。- Security_Load:
ipCameraHelper.Open(AppSet.Camera_Ip, Camera_Uid, Camera_Pwd),ImageRead += ...,Start();启动线程;ImgDir 不存在时创建。 - Security_FormClosed:
ipCameraHelper.Stop(); ipCameraHelper.Close(); thread.Suspend(); thread = null; - 「拍照」按钮:
pic_video.Image.Save(ImgDir + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".jpg"); SQLBLL.SecurityUpData(pic_video.Image); - 「查看历史图片」按钮:
new SecurityCameraData().ShowDialog();17–20. 四个方向图片的 MouseDown / MouseUp:MouseDown 时 BackgroundImage 换成btn_arrow_xx_press并调用ipCameraHelper.PanUp()(或 PanDown / PanLeft / PanRight);MouseUp 时换回_normal,并再调用一次同一个 Pan 方法。书上和参考代码都这样写,可能这个 API 是开始/停止切换式的。 - 主窗体 pic_security 的 Click:
new Security().Show(); - lb_return 的 Click:
this.Close();
任务5 实现「校园安防」模块中显示历史图片的功能(书181–185 / PDF 182–186)
任务描述:从数据库中查出非法入侵时拍下的照片,按时间快速定位目标人物。
任务实施
- SecurityCameraData 窗体:StartPosition = CenterScreen,FormBorderStyle = none。
- 加
Panel1:BackColor = 100,116,133,Dock = Top。上面放 label1(Text「历史报警记录」,ForeColor = White)和 PictureBoxpic_close(BackgroundImage =windowstate_closed_hover.png,Layout = Center),效果见图8-16。 - Panel1 下方加
panel_toolBar:BackColor = 229,229,229。上面放两个标签(「开始时间」「结束时间」)、dateTimePicker1、dateTimePicker2,以及按钮btn_get(Text「查询」),效果见图8-17。 - 加 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。 - 在 pic_camera 上加两个 PictureBox
pic_up、pic_down:Image 分别为arrow_left_normal.png、arrow_right_normal.png,SizeMode = CenterImage(图8-19:左右翻页箭头)。 - 引用 DeviceCtrlLibrary、智慧校园.SQLHelpers。
- 字段:
Dictionary<string, Image> ImgDictionary、int index = -1、Image img、List<string> Keys。 ImageChanged(char UpOrDown = '+'):按 '+' 或 '-' 移动 index 并循环取模,然后TryGetValue(Keys[index], out img),pic_camera.Image = img; lb_time.Text = Keys[index];。GetData():用 dateTimePicker1/2 的 Value 调用SQLBLL.GetSecurityData(...),index 置为 -1;没有数据时显示 lb_noData 并 return,否则隐藏 lb_noData,把所有 key 加进 Keys,再调用 ImageChanged()。 注意:Keys 每次查询都没有 Clear,重复查询会越积越多,书上原样如此。- 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(); - btn_get 的 Click:
GetData(); - pic_up 的 Click:
ImageChanged('-'); - pic_down 的 Click:
ImageChanged(); - 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 需要做方言转换
- T-SQL 函数
-
新出现的设备 API(之前的项目里没有):
DeviceCtrlHelper.zigBeeHelper.Open(com)、.OnOff(relayNum, bool)(路灯继电器)DeviceCtrlHelper.ledHelper.OpenLedCom(com)、.SendLedString(string)(LED 屏,模拟器应显示一块 LED 屏)adam4150Helper.SetData()返回ADAM4150ResModel,字段fireValue、smokeValue、infraredValue都是 boolipCameraHelper.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.Valuedouble.TryParse(..., out ...)- 文本框的 TextChanged 和复选框的 CheckedChanged 事件
- 多窗体:
-
任务3 的 Tick 循环:每 5.5 秒采集一次,每次都写入数据库(EnvironmentUpData);图表取最近 7 天的每日最高温和平均温。模拟器要能生成足够的历史数据,图表才有内容。