查看: 1210|回复: 4

[应用教程] [LattePanda] 使用C# 来做蓝牙4.0 iBeacon的门锁系统

[复制链接]
01.jpg
在学这篇教学前必须要先会一些事情,首先必须要会在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模块。

02.jpg

03.png

对应电路图,接在面包版上电路如下图
整体看起来会是像下图

04.png

换个角度再放一张图,如下

05.png

在电磁锁的部分基本上可以任意替换,在这里我将它锁在木板上,能够表示他有锁住或打开而已,其安装后的图如下(上方是一块木板连结铁块,下面则是一个「ㄇ」字形的木板链接电磁铁的线圈部分)。

06.png

2=====软件部分:=====
我使用的程序开发环境是Windows 10、Visual studio 2017、C#,那我们就直接进入程序部分。
程序我分成(1)接口、(2)初始化、(3)Serial连接、(4)开启backgroundWorker、(5)读取iBeacon信息、(6)iBeacon信息译码、(7)链接Arduino,这几个部分。

1.     界面
Form1中的接口配置如下图
07.jpg
在最上面有一个textBox 命名为 textbox_door
中间是一个listView 命名为 listView_door
按钮左边是 button_connect
按钮中间是button_findBeacon
按钮右边是button_loopFind
最下面的listBox 命名为 listBox_msg
在背景工具中有
serialPort 命名为 serialPort_beacon
backgroundWorker 命名为 backgroundWorker_findBeacon
timer 命名为 timer1

各个接口的命名可以参考下图:
08.jpg
命名如果不一样,请记得在程序中自行修改。

2.    初始化
初始化的部分先从 include开始:

[C#] 纯文本查看 复制代码
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.IO.Ports; //serial
using LattePanda.Firmata; //Arduino


以上是所有要用到的namespace。

在全局变量里:

[AppleScript] 纯文本查看 复制代码
Arduino arduino = new Arduino("COM3", 57600);
bool[] openlist = new bool[3];

(全局变量记得是写在 class Form1 里面,但是在一般函式外面)

Form1()建构式里:

[C#] 纯文本查看 复制代码
Arduino arduino = new Arduino("COM3", 57600);
bool[] openlist = new bool[3];
[C#] 纯文本查看 复制代码
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;
}

(这里面设定了一些接口的东西,一些初始值还有门锁状态)

3.   Serial连接
Serial 连接在C#中非常简单,我是用一个按钮,按下后链接Serial程序如下:

[C#] 纯文本查看 复制代码
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");
}

(这个COMUSB to URAT芯片的COM)

4.    开启backgroundWorker
首先是按钮触发寻找Beacon:

[C#] 纯文本查看 复制代码
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来持续触发,程序如下:

[C#] 纯文本查看 复制代码
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里所做的事情

[C#] 纯文本查看 复制代码
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[i] != '\0'; i++)
msg_read += buffer[i];

//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);
}
}
}
}

(这里的程序包含好多东西,上半部分是在处理Serial read的问题,并且在读取中有动态的「…」做显示,若没有收到资料则会有「time out」的信息出现。下半部分是将得到的信息做判断,判断要不要开门)

6 iBeacon信息译码
这里是「deCodeDISI」副函式的程序部分:

[C#] 纯文本查看 复制代码
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);
}

(这就是译码iBeacon的信息的部分)

7    连结Arduino
跟Arduino的部分非常简单,只有控制I/O的部分,D10脚位控制继电器、D5控制红色LED、D6控制绿色LED灯,子程序如下:
[C#] 纯文本查看 复制代码
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

太奢侈了,十几块的单片机就能做的事情,你用一千多的拿铁熊猫来做
回复 支持 1 反对 0

使用道具 举报

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

要视频要视频要视频
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
wifi气象站

硬件清单

btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2016 Comsenz Inc. Powered by Discuz! X3.4 Licensed

QQ