- 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>
73 KiB
《物联网编程与应用 第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事件,双击进入代码窗口: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 等名字对不上):
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拆分字符串。例:书上写"spstring[0]="ab", spstring[1]="cd", spstring[2]="ed", spstring[3]="gh""(书里 [2] 印错了,应为 "ef")。String sstring = "ab+cd+ef+gh"; String [] spstring = sstring.Split(new char[] { '+' }); - 提示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 的区别。
任务实施步骤(压缩版):
- ADAM4150 串口接 PC 串口,红外对射传感器接采集器 DI3 口。
- 新建 WinForms 项目"红外对射采集",保存在
D:\物联网编程与应用\项目4\任务1。 - 从配套资源中找到 ADAM4150 用的
DeviceCtrlLibrary.dll,把"API"文件夹下所有 dll 复制到...\红外对射采集\红外对射采集\bin\Debug。 - 添加引用
DeviceCtrlLibrary.dll。(注:没有硬件设备的读者,请添加仿真动态库文件,无须添加"API"文件夹下的dll文件,后面各任务都有这句注释。) - 按图4-1创建窗体(表4-1)。
using System.IO.Ports; using DeviceCtrlLibrary;- 全局变量
bool IsOpen = false;(书上印成boolIsOpen,少了空格)。 Form1_Load:com_port.Items.Clear(); foreach (string item in SerialPort.GetPortNames()) com_port.Items.Add(item);btn_open_Click:IsOpen = !IsOpen;。打开时调用DeviceCtrlHelper.adam4150Helper.Open(com_port.Text); timer1.Start(); btn_open.Text="关闭";;关闭时调用adam4150Helper.Close(); timer1.Stop(); btn_open.Text="开启";。- 选中 timer1,在属性窗口的事件栏(闪电图标)找到 Tick 事件(图4-2)。代码为
txt_time.Text = DateTime.Now.ToString(); if (DeviceCtrlHelper.adam4150Helper.SetData().infraredValue) txt_even.Text = "发现入侵"; else txt_even.Text = "正常"; - 运行,选串口,点"开启",效果见图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 事件里并调试:
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 的特点。
任务实施步骤:
- 网络摄像机和 PC 都连到无线路由器,并配置好摄像机。
- 新建项目"网络摄像机",保存在
D:\物联网编程与应用\项目4\任务2。 - 把摄像机用的 DeviceCtrlLibrary.dll 和 API 下所有 dll 复制到
...\网络摄像机\网络摄像机\bin\Debug。 - 添加引用(仿真库注释同上)。
- 按图4-7创建窗体(表4-2):左边是大块黑色图片框;右边是"摄像机IP"文本框,下面是十字形方向键,中间是"开",再往下是"拍照"。
using System.IO.Ports; using DeviceCtrlLibrary;(书里漏了using System.IO;,可是Directory要用它;参考代码写的是using System.IO;,没有 Ports)。- 4个全局变量:
static string CameraPath = AppDomain.CurrentDomain.BaseDirectory + "Camera/";//拍照保存路径 static string IpCameraIPAddress = "192.168.14.131";//网络摄像机IP bool IsOpen = false; Image ReadImage = null;//存储摄像机图片 void ipCameraHelper_ImageRead(ImageReadEvenArgs e) { ReadImage = e.img; pictureBox1.Image = ReadImage; }。类型名写的就是ImageReadEvenArgs,原文如此,和库里一致。Form1_Load:if(!Directory.Exists(CameraPath)) Directory.CreateDirectory(CameraPath); DeviceCtrlHelper.ipCameraHelper.ImageRead += ipCameraHelper_ImageRead;//绑定图片显示事件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()。btn_camera_Click:if (ReadImage != null) ReadImage.Save(CameraPath + DateTime.Now.ToString("yyyyMMdd_HH mm ss") + ".jpg");(格式串里本来就带空格)。- 运行,填摄像机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 | 拍照 | 截取图像 |
知识提炼:
- PictureBox 图片框:能显示 .bmp、.ico、.wmf、.jpg、.gif。表4-3 常见属性:
- BorderStyle:边框样式,枚举(书里写成"Emun")。None 无边框,FixedSingle 单线边框,fixed3D 立体边框。
- Image:要显示的图片,也可以用
Image.FromFile加载。 - SizeMode:显示模式,枚举。Normal 置于左上角,图片大于控件时被剪切;AutoSize 自动调整图片框大小,和图片一样大;CenterImage 图片框比图片大时居中,比图片小时剪切边沿;StretchImage 拉伸或收缩以适合图片框;zoom 按原比例缩放。
- static:用 static 声明的成员属于类本身,不属于对象,不用实例化,直接通过类名调用。示例:
public class StaticClass { public static int x = 10; },然后在Form1_Load里MessageBox.Show(StaticClass.x.ToString());。
能力拓展·边想边做:
通过"任务实施"的操作步骤,已经实现了摄像机显示、控制与抓拍的功能。本环节将为该模块添加每天定时抓拍的功能,固定时间间隔为:2h、1h、45min、30min、15min、5min。请根据下列提示,实现上述功能。 提示:根据之前所学利用if条件语句完善该代码,然后在程序中双击"开"按钮进入代码编写窗口,输入完整代码并调试。
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 语句。
任务实施步骤:
- 采集器串口接 PC 串口,报警灯通过继电器接到采集器 DO0 口。
- 新建"报警灯"项目,保存在
D:\物联网编程与应用\项目4\任务3。 - 把自定义控件
WinFormsDeviceControlLibrary.dll添加到工具箱。 - 把 ADAM4150 的 DeviceCtrlLibrary.dll 和 API 下的 dll 复制到
...\报警灯\报警灯\bin\Debug。 - 添加引用(仿真库注释同上)。
- 按图4-10建窗体(表4-4):左边是报警灯图,下面"开""关"两个按钮;右边 GroupBox"配置",里面是"报警灯"下拉框和"开启"按钮。
using System.IO.Ports; using DeviceCtrlLibrary;bool IsOpen = false;(书上又印成boolIsOpen)Form1_Load填充串口,和任务1 一样。- 定义方法:
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;//若为其他模块,则提示错误 } } btn_open_Click:只开关串口并改按钮文字("关闭"/"开启"),没有 timer。btn_alarmOpen_Click里调用AlarmOnOff("Alarm", true);13.btn_alarmClose_Click里调用AlarmOnOff("Alarm", false)。- 运行,选串口点"开启",再点"开""关"控制报警灯,见图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 事件里:
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 类常见的属性和方法。
任务实施步骤:
- ADAM4150 接 PC,火焰传感器接 DI1。
- 新建"火焰传感器数据采集"项目,保存在
D:\物联网编程与应用\项目5\任务1。 - 复制 dll 到
...\火焰传感器数据采集\火焰传感器数据采集\bin\Debug。 - 添加引用(仿真库注释同上)。
- 按图5-1建窗体(表5-1)。
using System.IO.Ports; using DeviceCtrlLibrary;bool IsOpen=false;Form1_Load这次用 for 循环:for (int i=0; i<SerialPort.GetPortNames().Length; i++) { com_port.Items.Add(SerialPort.GetPortNames()[i].ToString()); }btn_open_Click:和项目4 任务1 一样(Open/Close 串口,"关闭"/"开启",timer1 Start/Stop)。- 自定义方法
GetFireInfo:private String GetFireInfo() { //判断火焰传感器返回bool值是否为true,若为true,则返回火情信息 if (DeviceCtrlHelper.adam4150Helper.SetData().fireValue) { return "发现火情!"; } return "正常!";//无火情返回正常信息 } - timer1 的 Tick:
txt_time.Text = DateTime.Now.ToString(); txt_even.Text = GetFireInfo(); - 运行效果见图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 表示正常。
知识提炼:
- 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()); - return 语句:结束当前方法,并返回 return 后面的值(可以不带值);void 方法可以省略 return。示例:
MessageBox.Show(Area(3.1,2.5).ToString()); private double Area(double x,double y){ return x * y; } - 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() 方法,按流程图补全:
骨架里没有
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 的概念和用法。
任务实施步骤:
- 烟雾传感器接 DI2。
- 新建"烟雾传感器数据采集"项目,保存在
D:\物联网编程与应用\项目5\任务2。 - 复制 dll 到
...\烟雾传感器数据采集\烟雾传感器数据采集\bin\Debug。 - 添加引用(仿真注释同上)。
- 按图5-6建窗体(表5-5,没有 timer)。
using System.IO.Ports; using DeviceCtrlLibrary; using System.Threading;//引用线程命名空间- 全局变量:
bool IsOpen=false; bool IsFirst=true;//记录线程是否第一次打开 Thread thd;//定义Thread线程 - GetData:
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 } } Form1_Load:用 foreach 填串口,然后thd = new Thread(GetData);//实例化线程,绑定线程运行方法和Control.CheckForIllegalCrossThreadCalls = false;//允许跨线程调用。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();
- 窗体 FormClosing 事件:
private void Form1_FormClosing(object sender, FormClosingEventArgs e) { thd.Abort();//终止线程 } - 运行效果见图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 上也有问题。
知识提炼:
- while 循环:条件为真就反复执行,直到条件为假,格式
while(条件){语句;}。示例求1到10的和:int i = 1, sum = 0; while(i<11){ sum = sum + i; i = i + 1; } MessageBox.Show(sum.ToString()); - Thread 线程:.NET 是多线程环境,C# 程序从一个由系统自动创建的主线程开始,可以再创建额外线程。示例是一个控制台程序:
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()方法,进入代码编写窗口,输入代码。
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"方法,进入代码编写窗口,输入代码。
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"事件,进入代码编写窗口,输入代码并调试。
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 的区别。
任务实施步骤:
- 采集器串口和 LED 屏串口都接到串口服务器上;火焰、烟雾传感器分别接 DI1、DI2;正确配置串口服务器和路由器。
- 新建"实时推送灾情信息"项目,保存在
D:\物联网编程与应用\项目5\任务3。 - 复制 ADAM4150 的 dll 和 API 到
...\实时推送灾情信息\实时推送灾情信息\bin\Debug。 - 添加引用(仿真注释同上)。
- 新建 "RecordClass" 类文件:
参考项目里 RecordClass 是没有 namespace 的全局
public static string[,] record = new string[50, 2] ;//定义一个二维数组,用于存储灾情信息的时间和内容 public static int recoudeindex = 0;//用于记录灾情信息的索引位置public class RecordClass。 - 按图5-9建窗体(表5-6):顶部是一条很宽的黑色 LED 显示控件 led1。下方 GroupBox"数据采集"里有时间、火焰、烟雾三个文本框和一个竖长的"获取"按钮。右边 GroupBox"配置"里有"L E D"下拉框、"环境采集"下拉框、"开启"按钮和"历史灾情信息"按钮。
using System.IO.Ports; using DeviceCtrlLibrary; using DeviceCtrlLibrary.Model;//引用设备控制库下的Model命名空间- 全局变量:
ADAM4150ResModel ResModel; bool IsOpen = false; string LED_MSG = "";//存储LED显示屏所需发送的文字信息 Form1_Load:参考项目没有排除 COM1,用两个 foreach 分别填充。书上的运行截图里 LED 用的却正是 COM1,自相矛盾。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()); }btn_Open_Click:- 打开时:
DeviceCtrlHelper.adam4150Helper.Open(com_environmentPort.Text);、DeviceCtrlHelper.ledHelper.OpenLedCom(com_ledPort.Text);、btn_Open.Text = "关闭"; - 关闭时:只执行
adam4150Helper.Close(); btn_Open.Text="开启";,LED 串口没有关。
- 打开时:
btn_get_Click("获取"按钮):这段有三个问题: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 条,没有越界检查。
- "历史灾情信息"按钮:
private void OpenRecord_Click(...) { Form2 fr2 = new Form2(); fr2.Show(); }。这个按钮和"获取"按钮 btn_get 都不在表5-6 里。 - 按图5-10建 Form2(表5-7)。
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。- 运行:点"开启"后效果见图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"方法,进入代码编写窗口,输入完整代码并调试。
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"事件,进入代码编写窗口,输入完整代码并调试。
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# 代码):
- 安装 SQL Server 2008 R2,打开 SQL Server Management Studio 并登录,主界面见图6-1(对象资源管理器里有:数据库、安全性、服务器对象、复制、管理、SQL Server 代理)。
- 在"对象资源管理器"里右击"数据库",选"新建数据库",名称填 "环境数据表",其余默认,点确定(图6-2/6-3)。新建数据库对话框里:所有者"<默认值>","使用全文索引"打勾;数据文件初始 3MB、按 1MB 增长、不限制;日志文件初始 1MB、按 10% 增长。
- 展开"环境数据表"数据库,右击"表",选"新建表"(图6-4/6-5)。
- 列名填
light、temperature、humidity,数据类型都选varchar(MAX),"允许 Null 值"打勾(图6-6)。 - 点"保存",在"选择名称"对话框填表名 "EnvironmentWork"(图6-7)。
- 右击表 EnvironmentWork,选"编辑前200行",就能直接录入数据(图6-8)。右键菜单里还有:新建表、设计、选择前1000行、编写表脚本为、查看依赖关系、全文索引、存储、策略、方面、启动PowerShell、报表、重命名、删除、刷新、属性。
- 在 EnvironmentWork 里录入一周的环境数据,light 是光照、temperature 是温度、humidity 是湿度,内容自定。
对应的建表 SQL(书里没有,模拟器可以内置):
CREATE DATABASE 环境数据表;
CREATE TABLE EnvironmentWork (light varchar(MAX) NULL, temperature varchar(MAX) NULL, humidity varchar(MAX) NULL);
-- 没有主键,也没有时间列
知识提炼:
- 数据库(database,DB):长期存储在计算机内、有组织、可共享的数据集合,按一定的数据模型组织、描述和存储,冗余度小、数据独立性高、容易扩展,可以在一定范围内供多个用户共享。
- 数据库管理系统(DBMS):用户和操作系统之间的一层数据管理软件(如 SQL 数据库管理系统),用来建立、使用和维护数据库,统一管理和控制数据库,保证安全性和完整性。
- 数据表:数据库的组成部分,是实际存储数据的地方,每个数据库都有自己的数据表。表由行和列组成,建表就是定义列的过程。
- 字段与记录:表里的每一行叫一条记录,表示所存物体各方面的属性;每个数据项叫字段,表示物体某方面的性质,比如学生信息里的学号、姓名、出生日期。
- 主键:能唯一标识表中每一行,可以是一个字段,也可以是多个字段的组合,用来强制实体完整性。一个表只能有一个主键,主键列不能为空。例如学生表(学号、姓名、性别、班级)中学号唯一,就是主键。
- SQL 数据类型:SQL Server 有 20 多种系统数据类型,插入的值超出允许范围就会报错。常用的有:
- bit:只能是0、1或空值
- tinyint:0~255,占1B
- smallint:-32768~32767,占2字节
- int:-2147483648~2147483647,占4B
- money:-9220亿~9220亿,精确到货币单位的万分之一
- float:-1.79×10^308~1.79×10^308,是近似值
- smalldatetime:精确到1min
- datetime:精确到1/300s
- uniqueidentifier:GUID 全局唯一标识
- char:定长、非统一编码,必须指定列长
- varchar:变长,必须指定最大长度
- text:最多 2^31-1(约20亿)个字符
- nvarchar:统一编码、变长,能存4000个字符,占用字节翻倍
- ntext:2^30-1(近10亿)个字符,占用字节翻倍
- binary:最多8000B的定长二进制
- varbinary:最多8000B的变长二进制,内容大小会变时用它
- 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,学会表内容的添加、删除、修改。
任务实施步骤:
- 在"环境数据表"库里新建 "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); - 把 ADAM4017 的串口直接接到 PC(书上把 ADAM4017 叫成"数字量采集器",其实它是模拟量采集器)。注:没有硬件的读者跳过这一步。
- 新建"实时存储校园数据"项目,保存在
D:\物联网编程与应用\项目6\任务2。 - 复制 ADAM4017 的 DeviceCtrlLibrary.dll 和 API 到
...\实时存储校园数据\实时存储校园数据\bin\Debug\。 - 添加引用(仿真注释同上)。
- 按图6-10建主窗体(表6-1),标题"校园灾情数据存储":
- 左上 GroupBox"配置":串口下拉框、"开启"按钮。
- 右上 GroupBox"数据显示":光照度、温度、湿度三个文本框,"获取""保存""修改""删除"四个按钮。
- 下方 GroupBox"历史记录":一个大 DataGridView。
- 引用:
using System.Data.SqlClient; //引用数据库操作库 using System.IO.Ports; using DeviceCtrlLibrary; using DeviceCtrlLibrary.Model;//引用ADAM4017ResModel - 7个全局变量:
小贴士:其中"PC-20150908SCJF\SQLSERVER1026"为自己SQL数据库的名称,也可以用"."号来表示本机默认数据库名称,该代码采用"sa"用户名来连接数据库,密码为"123456",请根据实际情况修改数据库名称和"sa"的密码。
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; Form1_Load:mySqlConnection = new SqlConnection(sqlConnectString); mySqlConnection.Open();,再用 foreach 填充 com_port。(Load 里没调用 Databind(),所以刚启动时表格是空的。)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 是大写,引号也是全角 ”关闭”,原样都编译不过)。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。- 数据绑定函数
Databind():小贴士:select语句主要用于从表中选取数据。 (每次调用都新开一个连接,从不关闭。)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; btn_save_Click:问题: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。
btnUpdate_Click:做法是让用户直接在 DataGridView 里改单元格,再提交。需要:DataGridView 可编辑并回写到 DataTable;SqlCommandBuilder 能根据带别名的 SELECT 和主键 ID 生成 UPDATE;SqlCommandBuilder cmdb = new SqlCommandBuilder(sqldata); sqldata.UpdateCommand = cmdb.GetUpdateCommand(); if (sqldata.Update(ds.Tables[0]) == 1) { MessageBox.Show("修改成功!"); Databind(); } else { MessageBox.Show("修改失败!"); }== 1表示只有恰好改了一行才算成功。btnDelete_Click:范围到这里结束。后面的 catch 内容在下一段里,参考代码是try { mySqlCmd = new SqlCommand("delete from CampusEnvironment where id="+dataGridView1.CurrentRow.Cells[0].Value.ToString(), mySqlConnection); mySqlCmd.ExecuteNonQuery(); MessageBox.Show("删除成功!"); Databind(); } catch (Exception ex) //捕获数据库操作异常信息 {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)。
- adam4150Helper(数字量):
- 串口和硬件:
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。
- ADO.NET 对象:SqlConnection(连接串).Open(),SqlCommand.ExecuteNonQuery(),SqlDataAdapter.Fill(DataSet),