This projects will show you how to build a simple door lock using DFRobot's Lattepanda's Windows SBC, using C# (Visual Studio) to program. We use HM-10 BLE module as a beacon to calculate the distance between receiver and transmitter. We use this distance parameter to decide whether to open the door (trigger the solenoid) or not.
Let's start[請注意] 這邊教學是必須有一些基礎,首先前情提要中的Lattapanda上使用Visual Studio是基本,再來是必須要會開啟Window Forms C#、會使用基本的工具箱,並且可以編譯程式。最後是對於電路有基本的認識,能夠認識基本電路符號,並且能將簡單電路圖在麵包版上實現。
Components used in this project including:
- LED x 2: to indicate the door lock status.
- 12V solenoid lock x1: <LOCK>
- USB to UART adapter x1
- HM-10 BLE module x2: plug one on your Lattepanda, the other is held by user.on your模組,一個安裝至LattePanda,一個由使用者持有
Please plug USB to UART module (the red one) into Lattepanda's USB port, then connect HM10 BLE module to it.
Connect all components on bread對應電路圖,接在麵包版上電路如下圖. Finish as below:
You can use any Arduino-compatible solenoid module, just make sure the operation voltage is correct. 在電磁鎖的部分基本上可以任意替換,在這裡我將它鎖在木板上,能夠表示他有鎖住或打開而已,其安裝後的圖如下(上方是一塊木板連結鐵塊,下面則是一個「ㄇ」字形的木板連結電磁鐵的線圈部分)。
We use Visual Studio 2015 on Lattepanda's Windows 10. You can rebuild this project with ordinary PC and an Arduino if Lattepanda is not available.
Next we are going to discuss the code, including:
- interface
- initialize
- Serial connection
- backgroundWorker
- read iBeacon data
- iBeacon data decode
- connect to Arduino.
Component layout in Form1 is shown below.
Interface:
- textBox on top: textbox_door
- listView in the middle: listView_door
- left button: button_connect
- middle button: button_findBeacon
- right button: button_loopFind
- listBox at bottom: listBox_msg
Background:
- serialPort: serialPort_beacon
- backgroundWorker: backgroundWorker_findBeacon
- timer: timer1
Each component's name is shown below, you can give them different names, but don't forget to modify the code.
2. Initialize
Including libraries required:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.IO.Ports; //serial
using LattePanda.Firmata; //Arduino
Declaring two global variables: arduino and openlist, please notice that Lattepanda's Arduino is given COM port as COM3, but sometimes it may become COM4, 5. Please check its COM port in Windows Device Manager.
Arduino arduino = new Arduino("COM3", 57600);
bool[] openlist = new bool[3];
In Form1(), we declare all the interface, variables and the door lock status (doorOpen(false) means closed).
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 connection
It is quite easy to establish a serial connection in C#. Here we use [Connect] button to connect Lattepanda's Arduino chip when it is pressed:
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
First it will start to find Beacon nearby when [Find iBeacon] button is pressed. Here we only call backgroundWorker once to scan for 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而已)
If your want to scan for beacon continuously, can add a timer(timer1) to do that:
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");
}
5. read iBeacon info
這裡就是backgroundWorker裡所做的事情
(這裡的程式包含好多東西,上半部分是在處理Serial read的問題,並且在讀取中有動態的「…」做顯示,若沒有收到資料則會有「time out」的資訊出現。下半部分是將得到的資訊做判斷,判斷要不要開門)
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);
}
}
}
}
6. iBeacon data decode
這裡是「deCodeDISI」副函式的程式部分:
(這就是解碼iBeacon的資訊的部分)
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);
}
7. Connect to Arduino
We use dooOpen() function to decide what to do. If open variable is false, which means the door is closed, then switch on the relay, red LED on and green LED off. And do the opposite when the open variable is false, which means the door is opened.
The code is show below:
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資訊做處理的地方,只有稍微確認,和在資訊結尾做非常簡單的處理,在這裡主要就是帶大家入門,在深入的部分,就請大家自己研究瞜~
Reference- LattePanda 開機步驟與連接螢幕
- LattePanda 拿鐵熊貓教學:Arduino與Visual Studio環境設定
- LattePanda 拿鐵熊貓教學#1:LED 閃爍,使用Visual Studio
Comments