13771| 4
|
[LattePanda] 使用C# 来做蓝牙4.0 iBeacon的门锁系统 |
在学这篇教学前必须要先会一些事情,首先必须要会在Lattepanda上使用visualstudio 再来必须已经了解如何在visualstudio里使用C#控制Arduino的I/O 时间:3 小时 以上 成本:1,000~2,000元,不含Lattepanda 材料:Lattepanda,USB to UART模块*1,HM-10模块(2个以上)*1,电磁锁*1,5pin Realy*1,红色LED*1,绿色LED*1,220Ω电阻,跳线 难度:★★★★★★★☆☆☆(7/10) 前言 在这篇教学中会使用visualstudio的C#来开发简单的门锁系统,一般在蓝牙使用上都是使用蓝牙传输数据,但在这篇是使用蓝牙4.0中的iBeacon模式,在iBeacon模式中可以得知接收器与发射器的距离,而这个距离就可以当作我们决定要不要开门的条件,所以这样连按按钮的动作都可以省去。 正文 [请注意] 这边教学是必须有一些基础,首先前情提要中的Lattapanda上使用Visual Studio是基本,再来是必须要会开启Window Forms C#、会使用基本的工具箱,并且可以编译程序。最后是对于电路有基本的认识,能够认识基本电路符号,并且能将简单电路图在面包版上实现。 在这篇教学中分成硬件、软件两个部分,(1)硬件部分是电路的连接, (2)软件部分是Visual Studio C#的程序撰写 (1)=====硬件部分:===== 在硬件连接的电路图如下图。 使用的有 (1) 两个<LED>灯(显示门锁是开启还是关闭) (2) 一个12V的<LOCK>电磁锁 (3) 一个<USB to UART模块> (4) 两个<HM10>的BLE模块,一个安装至LattePanda,一个由使用者持有下图Lattepanda上左方USB孔上接上USB to UART模块(红色),可接上HM10 BLE模块。 对应电路图,接在面包版上电路如下图 整体看起来会是像下图 换个角度再放一张图,如下 在电磁锁的部分基本上可以任意替换,在这里我将它锁在木板上,能够表示他有锁住或打开而已,其安装后的图如下(上方是一块木板连结铁块,下面则是一个「ㄇ」字形的木板链接电磁铁的线圈部分)。 (2)=====软件部分:===== 我使用的程序开发环境是Windows 10、Visual studio 2017、C#,那我们就直接进入程序部分。 程序我分成(1)接口、(2)初始化、(3)Serial连接、(4)开启backgroundWorker、(5)读取iBeacon信息、(6)iBeacon信息译码、(7)链接Arduino,这几个部分。 1. 界面 Form1中的接口配置如下图 在最上面有一个textBox 命名为 textbox_door 中间是一个listView 命名为 listView_door 按钮左边是 button_connect 按钮中间是button_findBeacon 按钮右边是button_loopFind 最下面的listBox 命名为 listBox_msg 在背景工具中有 serialPort 命名为 serialPort_beacon backgroundWorker 命名为 backgroundWorker_findBeacon timer 命名为 timer1 各个接口的命名可以参考下图: 命名如果不一样,请记得在程序中自行修改。 2. 初始化 初始化的部分先从 include开始: [mw_shl_code=csharp,true]using System; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; using System.IO.Ports; //serial using LattePanda.Firmata; //Arduino [/mw_shl_code] 以上是所有要用到的namespace。 在全局变量里: [mw_shl_code=applescript,true]Arduino arduino = new Arduino("COM3", 57600); bool[] openlist = new bool[3]; [/mw_shl_code] (全局变量记得是写在 class Form1 里面,但是在一般函式外面) 在Form1()建构式里: [mw_shl_code=csharp,true]Arduino arduino = new Arduino("COM3", 57600); bool[] openlist = new bool[3]; [/mw_shl_code][mw_shl_code=csharp,true]public Form1() { InitializeComponent(); ColumnHeader header1, header2; header1 = new ColumnHeader(); header2 = new ColumnHeader(); header1.Text = "--Time--"; header1.TextAlign = HorizontalAlignment.Center; header1.Width = 90; header2.Text = "--ID--"; header2.TextAlign = HorizontalAlignment.Center; header2.Width = 70; listView_door.Columns.Add(header1); listView_door.Columns.Add(header2); //which ID can open the lock openlist[0] = false; openlist[1] = true; openlist[2] = true; //arduino setting arduino.pinMode(5, Arduino.OUTPUT);//pin of red LED arduino.pinMode(6, Arduino.OUTPUT);//pin of green LED arduino.pinMode(10, Arduino.OUTPUT);//pin of the lock //set the door close first doorOpen(false); textBox_door.BackColor = Color.Red; } [/mw_shl_code] (这里面设定了一些接口的东西,一些初始值还有门锁状态) 3. Serial连接 Serial 连接在C#中非常简单,我是用一个按钮,按下后链接Serial程序如下: [mw_shl_code=csharp,true]private void button_connect_Click(object sender, EventArgs e) { serialPort_beacon = new SerialPort("COM6", 9600, Parity.None, 8, StopBits.One); if (!serialPort_beacon.IsOpen) { try { serialPort_beacon.Open(); listBox_msg.Items.Add("Connect"); } catch { MessageBox.Show("Serial open error!"); } } else listBox_msg.Items.Add("Opened"); } [/mw_shl_code] (这个COM是USB to URAT芯片的COM角) 4. 开启backgroundWorker 首先是按钮触发寻找Beacon: [mw_shl_code=csharp,true]private void button_findBeacon_Click(object sender, EventArgs e) { //send DISC string msgSend = "AT+DISI?"; byte[] buffer = System.Text.Encoding.Default.GetBytes(msgSend); if (serialPort_beacon.IsOpen) if (backgroundWorker_findBeacon.IsBusy != true) {serialPort_beacon.Write(buffer, 0, buffer.Length); backgroundWorker_findBeacon.RunWorkerAsync(); } else MessageBox.Show("already finding"); else MessageBox.Show("Serial is close"); } [/mw_shl_code] (在这里是开启一个backgroundWorker,而这里只是单纯地寻找一次Beacon而已) 若是要持续寻找,可以开启一个timer来持续触发,程序如下: [mw_shl_code=csharp,true]private void button_loopFind_Click(object sender, EventArgs e) { timer1.Start(); } private void timer1_Tick(object sender, EventArgs e) { //send DISC string msgSend = "AT+DISI?"; byte[] buffer = System.Text.Encoding.Default.GetBytes(msgSend); if (serialPort_beacon.IsOpen) if (backgroundWorker_findBeacon.IsBusy != true) { serialPort_beacon.DiscardInBuffer(); serialPort_beacon.Write(buffer, 0, buffer.Length); backgroundWorker_findBeacon.RunWorkerAsync(); } else Console.Write("already finding"); else MessageBox.Show("Serial is close"); } [/mw_shl_code] (这里开一个Timer让我们可以每一段时间就开启backgroundWorker) 5 读取iBeacon信息 这里就是backgroundWorker里所做的事情 [mw_shl_code=csharp,true]private void backgroundWorker_findBeacon_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; char[] buffer = new char[256]; string msg_read = ""; bool stringCheck = false; //read data byte loopCount = 0; while (true) { if (serialPort_beacon.BytesToRead > 0) { //serial read to msg_read serialPort_beacon.Read(buffer, 0, buffer.Length); for (int i = 0; i < buffer.Length && buffer != '\0'; i++) msg_read += buffer; //if msg_read end with "OK+DISCE" then stop reading if (msg_read.Length > 8 && msg_read.IndexOf("OK+DISCE") != -1) { stringCheck = true; break; } } else { //timeout System.Threading.Thread.Sleep(500); loopCount++; string dot = ""; for (int i = 1; i <= loopCount; i++) dot = dot + ". "; if (loopCount > 1) this.Invoke((MethodInvoker)(() => listBox_msg.Items.RemoveAt(listBox_msg.Items.Count - 1))); if (loopCount > 15) break; this.Invoke((MethodInvoker)(() => listBox_msg.Items.Add(dot))); } } //if didn't read anything than prient "time out" if (msg_read == "") this.Invoke((MethodInvoker)(() => listBox_msg.Items.Add("Time out"))); else { if (stringCheck == false) //if have read something but not iBeacon info this.Invoke((MethodInvoker)(() => listBox_msg.Items.Add(msg_read))); else { Tuple<int[], int[], int[], int> result = deCodeDISI(msg_read, 2); this.Invoke((MethodInvoker)(() => listBox_msg.Items.Add("minor : " + result.Item2[0].ToString()))); this.Invoke((MethodInvoker)(() => listBox_msg.Items.Add("RSS : " + result.Item3[0].ToString()))); //if is close enough and it's in open list then open the lock if (result.Item3[0] > -40 && openlist[result.Item2[0]] == true) { this.Invoke((MethodInvoker)(() => textBox_door.BackColor = Color.Green)); ListViewItem item1 = new ListViewItem(DateTime.Now.ToShortTimeString()); item1.SubItems.Add(result.Item2[0].ToString()); this.Invoke((MethodInvoker)(() => listView_door.Items.Add(item1))); doorOpen(true); } else { this.Invoke((MethodInvoker)(() => textBox_door.BackColor = Color.Red)); doorOpen(false); } } } } [/mw_shl_code] (这里的程序包含好多东西,上半部分是在处理Serial read的问题,并且在读取中有动态的「…」做显示,若没有收到资料则会有「time out」的信息出现。下半部分是将得到的信息做判断,判断要不要开门) 6 iBeacon信息译码 这里是「deCodeDISI」副函式的程序部分: [mw_shl_code=csharp,true]private Tuple<int[], int[], int[], int> deCodeDISI(string serialData, int maxDiviceCount) { //OK+DISIS OK+DISC : Factory ID : iBeacon UUID : Major+Minor+Measured : MAC : RSSI OK+DISCE //OK+DISISOK+DISC:4C000215:74278BDAB64445208F0C720EAF059935:11110001C5:88C25532ED1E:-032OK+DISCE string DataRemain = serialData; int[] FactoryID = new int[maxDiviceCount]; string[] UUID = new string[maxDiviceCount]; int[] Major = new int[maxDiviceCount]; int[] Minor = new int[maxDiviceCount]; string[] MAC = new string[maxDiviceCount]; int[] RSSvalue = new int[maxDiviceCount]; DataRemain = DataRemain.Substring(0, serialData.IndexOf("OK+DISCE")); int count = 0; while (true) { int findNum = DataRemain.IndexOf(":"); if (findNum == -1) { Console.Write("deCode done!"); break; } else { //Factory ID (length 8) string FactoryID_str = DataRemain.Substring(findNum + 1, 8); DataRemain = DataRemain.Substring(findNum + 9); FactoryID[count] = Convert.ToInt32(FactoryID_str, 16); //iBeacon UUID findNum = DataRemain.IndexOf(":"); string UUID_str = DataRemain.Substring(findNum + 1, 32); DataRemain = DataRemain.Substring(findNum + 33); UUID[count] = UUID_str; //Major findNum = DataRemain.IndexOf(":"); string Major_str = DataRemain.Substring(findNum + 1, 4); DataRemain = DataRemain.Substring(findNum + 5); Major[count] = Convert.ToInt32(Major_str); //Minor string Minor_str = DataRemain.Substring(0, 4); DataRemain = DataRemain.Substring(findNum + 4); Minor[count] = Convert.ToInt32(Minor_str); //MAC findNum = DataRemain.IndexOf(":"); string MAC_str = DataRemain.Substring(findNum + 1, 12); DataRemain = DataRemain.Substring(findNum + 13); MAC[count] = MAC_str; //RSS findNum = DataRemain.IndexOf(":"); string RSS_str = DataRemain.Substring(findNum + 1, 4); DataRemain = DataRemain.Substring(findNum + 5); RSSvalue[count] = Convert.ToInt32(RSS_str); count++; } } return Tuple.Create(Major, Minor, RSSvalue, count); } [/mw_shl_code] (这就是译码iBeacon的信息的部分) 7 连结Arduino 跟Arduino的部分非常简单,只有控制I/O的部分,D10脚位控制继电器、D5控制红色LED、D6控制绿色LED灯,子程序如下: [mw_shl_code=csharp,true]private void doorOpen(bool open) { if (open == false)//door close { //lock the door and red LED on arduino.digitalWrite(10, Arduino.HIGH); arduino.digitalWrite(5, Arduino.HIGH); arduino.digitalWrite(6, Arduino.LOW); } else//door open { //unlock the door and green LED on arduino.digitalWrite(10, Arduino.LOW); arduino.digitalWrite(5, Arduino.LOW); arduino.digitalWrite(6, Arduino.HIGH); } } [/mw_shl_code] 软件部分就是以上程序了,其中包含了非常多的细节,像是处理Serial的部分,或是执行续(就是backgroundWorker)里的处理,为什么用Invoke,Tuple的用法,解碼的处理…等,这些初学的话建议复制副函式,直接用就好,里面是什么等对于程序更清楚后再慢慢回来看。 而当然这些副函式并不能称作完美,例如并没有对于不完整的iBeacon信息做处理的地方,只有稍微确认,和在信息结尾做非常简单的处理,在这里主要就是带大家入门,在深入的部分,就请大家自己研究咯~ 摄影:黃品叡,文章分类:教学 |
© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed