zbl 发表于 2017-9-11 18:56:56

[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_beaconbackgroundWorker 命名为 backgroundWorker_findBeacontimer 命名为 timer1
各个接口的命名可以参考下图:
命名如果不一样,请记得在程序中自行修改。
2.    初始化初始化的部分先从 include开始:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.IO.Ports; //serial
using LattePanda.Firmata; //Arduino


以上是所有要用到的namespace。
在全局变量里:
Arduino arduino = new Arduino("COM3", 57600);
bool[] openlist = new bool;

(全局变量记得是写在 class Form1 里面,但是在一般函式外面)
在Form1()建构式里:
Arduino arduino = new Arduino("COM3", 57600);
bool[] openlist = new bool;
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 = false;
openlist = true;
openlist = 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;
}

(这里面设定了一些接口的东西,一些初始值还有门锁状态)
3.   Serial连接Serial 连接在C#中非常简单,我是用一个按钮,按下后链接Serial程序如下:
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");
}

(这个COM是USB to URAT芯片的COM角)
4.    开启backgroundWorker首先是按钮触发寻找Beacon:
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");
}

(在这里是开启一个backgroundWorker,而这里只是单纯地寻找一次Beacon而已)若是要持续寻找,可以开启一个timer来持续触发,程序如下:
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");
}

(这里开一个Timer让我们可以每一段时间就开启backgroundWorker)
5   读取iBeacon信息
这里就是backgroundWorker里所做的事情
private void backgroundWorker_findBeacon_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;

char[] buffer = new char;
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.ToString())));
this.Invoke((MethodInvoker)(() => listBox_msg.Items.Add("RSS : " + result.Item3.ToString())));
//if is close enough and it's in open list then open the lock
if (result.Item3 > -40 && openlist] == true)
{
this.Invoke((MethodInvoker)(() => textBox_door.BackColor = Color.Green));
ListViewItem item1 = new ListViewItem(DateTime.Now.ToShortTimeString());
item1.SubItems.Add(result.Item2.ToString());
this.Invoke((MethodInvoker)(() => listView_door.Items.Add(item1)));
doorOpen(true);
}
else
{
this.Invoke((MethodInvoker)(() => textBox_door.BackColor = Color.Red));
doorOpen(false);
}
}
}
}

(这里的程序包含好多东西,上半部分是在处理Serial read的问题,并且在读取中有动态的「…」做显示,若没有收到资料则会有「time out」的信息出现。下半部分是将得到的信息做判断,判断要不要开门)
6 iBeacon信息译码这里是「deCodeDISI」副函式的程序部分:
private Tuple<int[], int[], int[], int> deCodeDISI(string serialData, int maxDiviceCount)
{

//OK+DISISOK+DISC : Factory ID : iBeacon UUID : Major+Minor+Measured : MAC : RSSIOK+DISCE
//OK+DISISOK+DISC:4C000215:74278BDAB64445208F0C720EAF059935:11110001C5:88C25532ED1E:-032OK+DISCE

string DataRemain = serialData;
int[] FactoryID = new int;
string[] UUID = new string;
int[] Major = new int;
int[] Minor = new int;
string[] MAC = new string;
int[] RSSvalue = new int;
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 = Convert.ToInt32(FactoryID_str, 16);

//iBeacon UUID
findNum = DataRemain.IndexOf(":");
string UUID_str = DataRemain.Substring(findNum + 1, 32);
DataRemain = DataRemain.Substring(findNum + 33);
UUID = UUID_str;

//Major
findNum = DataRemain.IndexOf(":");
string Major_str = DataRemain.Substring(findNum + 1, 4);
DataRemain = DataRemain.Substring(findNum + 5);
Major = Convert.ToInt32(Major_str);
//Minor
string Minor_str = DataRemain.Substring(0, 4);
DataRemain = DataRemain.Substring(findNum + 4);
Minor = Convert.ToInt32(Minor_str);

//MAC
findNum = DataRemain.IndexOf(":");
string MAC_str = DataRemain.Substring(findNum + 1, 12);
DataRemain = DataRemain.Substring(findNum + 13);
MAC = MAC_str;

//RSS
findNum = DataRemain.IndexOf(":");
string RSS_str = DataRemain.Substring(findNum + 1, 4);
DataRemain = DataRemain.Substring(findNum + 5);
RSSvalue = Convert.ToInt32(RSS_str);

count++;
}
}

return Tuple.Create(Major, Minor, RSSvalue, count);
}

(这就是译码iBeacon的信息的部分)
7    连结Arduino跟Arduino的部分非常简单,只有控制I/O的部分,D10脚位控制继电器、D5控制红色LED、D6控制绿色LED灯,子程序如下: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);
}
}


软件部分就是以上程序了,其中包含了非常多的细节,像是处理Serial的部分,或是执行续(就是backgroundWorker)里的处理,为什么用Invoke,Tuple的用法,解碼的处理…等,这些初学的话建议复制副函式,直接用就好,里面是什么等对于程序更清楚后再慢慢回来看。而当然这些副函式并不能称作完美,例如并没有对于不完整的iBeacon信息做处理的地方,只有稍微确认,和在信息结尾做非常简单的处理,在这里主要就是带大家入门,在深入的部分,就请大家自己研究咯~
摄影:黃品叡,文章分类:教学原文链接

dsweiliang 发表于 2017-10-9 09:43:16

太奢侈了,十几块的单片机就能做的事情,你用一千多的拿铁熊猫来做

lauren 发表于 2017-9-20 17:12:21

高级货,如果我带个特定的ibeacon设备可能走过去自动开门吗?

zbl 发表于 2017-9-21 15:27:43

lauren 发表于 2017-9-20 17:12
高级货,如果我带个特定的ibeacon设备可能走过去自动开门吗?

做个升级版!

1973742214 发表于 2017-9-23 12:01:08

要视频要视频要视频
页: [1]
查看完整版本: [LattePanda] 使用C# 来做蓝牙4.0 iBeacon的门锁系统