查看: 4475|回复: 0

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

[复制链接]
  • TA的每日心情

    2018-1-11 13:58
  • 签到天数: 8 天

    [LV.3]偶尔看看II

    发表于 2017-9-25 13:53 | 显示全部楼层 |阅读模式
    本帖最后由 dfrobot 于 2017-9-25 13:53 编辑

    1.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模块。

    2.jpg

    3.png

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

    4.png

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

    5.png

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

    6.png

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

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

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

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

    kittenblock中小学创客名师推荐的图形化编程软件

    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Windows.Forms;
    using System.IO.Ports; //serial
    using LattePanda.Firmata; //Arduino
    


    以上是所有要用到的namespace。

    在全局变量里:

    kittenblock中小学创客名师推荐的图形化编程软件

    Arduino arduino = new Arduino("COM3", 57600);
    bool[] openlist = new bool[3];
    

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

    Form1()建构式里:

    kittenblock中小学创客名师推荐的图形化编程软件

    Arduino arduino = new Arduino("COM3", 57600);
    bool[] openlist = new bool[3];
    

    kittenblock中小学创客名师推荐的图形化编程软件

    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程序如下:

    kittenblock中小学创客名师推荐的图形化编程软件

    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:

    kittenblock中小学创客名师推荐的图形化编程软件

    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来持续触发,程序如下:

    kittenblock中小学创客名师推荐的图形化编程软件

    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里所做的事情

    kittenblock中小学创客名师推荐的图形化编程软件

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

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

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

    kittenblock中小学创客名师推荐的图形化编程软件

    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灯,子程序如下:

    kittenblock中小学创客名师推荐的图形化编程软件

    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信息做处理的地方,只有稍微确认,和在信息结尾做非常简单的处理,在这里主要就是带大家入门,在深入的部分,就请大家自己研究咯~

    摄影:黃品叡,文章分类:教学



    打赏作者鼓励一下!
    您需要登录后才可以回帖 登录 | 立即注册  

    本版积分规则

    热门推荐

    【项目】基于Arduino Nano的多功能桌面感应垃圾桶
    【项目】基于Arduino Nano
    基于Arduino Nano的多功能桌面感应垃圾桶随着大学生活不断往前迈进,宿舍桌面上的杂物
    智能物联网花盆
    智能物联网花盆
    打坏了一个费时5小时 3D打印的花盆,耗时22小时 下载:Kittenblock软件,安装、运
    呼吸灯实验
    呼吸灯实验
    本文节选自《Arduino程序设计基础》 之前的章节已经介绍了多种方法控制LED,但单是开
    为开发板 Generic ESP8266 Module 编译时出错
    为开发板 Generic ESP8266
    第一次尝试Arduino UNO软串口通信,编译一直通不过,错误信息 “开发板 generic (平
    Arduino Uno 无法上传程序
    Arduino Uno 无法上传程序
    原来可以正常使用的Arduino Uno 板子突然不能上传程序了,硬件驱动正常,TX和RX指示灯
    Copyright   ©2015-2016  Arduino中文社区  Powered by©Discuz!   ( 蜀ICP备14017632号-3 )
    快速回复 返回顶部 返回列表