PHP
MySQL
iBeacon
swift4

iBeaconを自動的にMySQLに記録する(Swift4)


この記事はなにか

スマホ(iPhone)が特定のiBeaconを感知した場合、MySQLに自動記録するiOSアプリの作り方です。

流れ:iOS(Swift4)⇔PHP⇔MySQL

出退勤や出入店の自動登録を想定し、狭い場所に設置できるビーコンを使用します。

image.png


完成イメージ

アプリ画面のイメージです。

ダウンロードして位置情報さえ承認しておけば勝手に記録されていきます。


バージョン情報


  • PHP 7.1

  • MySQL 5.6.23

  • swift4 (xcode更新したらswift5に切り替わりましたが大丈夫そうです)


MySQLを準備しよう

今回はシンプルに時間と共にビーコンを近づいたら1、離れたら0と記録することにして、下記のようなSQLを実行します。

CREATE TABLE beacon (

id int NOT NULL AUTO_INCREMENT,
data datetime NOT NULL,
flag int NOT NULL,
CONSTRAINT beacon_pk PRIMARY KEY (id)
);

SQLを実行するとこのようなものができます。


PHPを準備しよう

次にMySQLとiOSアプリをつなぐPHPを準備します。

構造は下記のとおりです。

├── api

│   └── recordbeacon.php
└── includes
├── Config.php
├── DbConnect.php
└── DbOperation.php

MySQLのDBとテーブルを設定します。


includes/Config.php

<?php

define('DB_USERNAME', 'AAAAAAAA');
define('DB_PASSWORD', 'BBBBBBBB');
define('DB_HOST', 'CCCCCCC');
define('DB_NAME', 'DDDDDDD');
?>

次にデータベースと接続するファイルを2つ作成します。


includes/DbConnect.php

<?php

class DbConnect
{
private $conn;
function __construct()
{
}
function connect()
{
require_once 'Config.php';
$this->conn = new mysqli(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_NAME);
if (mysqli_connect_errno()) {
echo "Failed to connect to MySQL: " . mysqli_connect_error();
}
return $this->conn;
}
}
?>


includes/DbOperation.php

<?php

class DbOperation
{
private $conn;

//Constructor
function __construct()
{
require_once dirname(__FILE__) . '/Config.php';
require_once dirname(__FILE__) . '/DbConnect.php';
// opening db connection
$db = new DbConnect();
$this->conn = $db->connect();
}

//Function to create a new data
public function beacon($date, $flag)
{
$stmt = $this->conn->prepare("INSERT INTO beacon(date, flag) values(?, ?)");
$stmt->bind_param("si", $date, $flag);
$result = $stmt->execute();
$stmt->close();
if ($result) {
return true;
} else {
return false;
}
}
}
?>


最後に、値をデータベースに挿入するファイルを作成します。


api/recordbeacon.php

<?php

//creating response array
$response = array();

if($_SERVER['REQUEST_METHOD']=='POST'){

//getting values
$date = $_POST['date'];
$flag = $_POST['flag'];

//including the db operation file
require_once '../includes/DbOperation.php';

$db = new DbOperation();

//inserting values
if($db->beacon($date,$flag)){
$response['error']=false;
$response['message']='Data added successfully';
}else{

$response['error']=true;
$response['message']='Could not add data';
}

}else{
$response['error']=true;
$response['message']='You are not authorized';
}
echo json_encode($response);
?>


以上でPHPのファイルは完成です。


サーバーにデプロイしよう

今回作成したPHP、MySqlをAWSなどお好きなレンタルサーバーへデプロイしてください。


POSTできるか確認しよう

サーバーにアップし、POSTできるか確認のためにPOSTMANが使えます。

なお、確認できるようであればどのような方法でも大丈夫です。

POSTMANダウンロード

成功すれば何かしらリスポンスが帰ってくると思います。

image.png


受信するためのビーコンを準備しよう

iPhoneかiPadにビーコン発信用のアプリをダウンロードして発信します。

「Beacon入門」というアプリが個人的にはおすすめです。

iPadやiPhoneがない、ビーコンの発信機を持っていないという場合にMacからビーコンを発信する着想に行き着くと思います。

但し、現時点ではMacOSがmojaveだと難しいようです…

最近のmacでblenoが使えない2019219

mojave以下では下記記事が参考になるかと思います。

MacでiBeaconを発信したかったけど苦労した話 | Qiita


Swiftを準備しよう

Xcodeでswiftアプリを新規で用意します(名前はご自由に)

Info.plistにて下記2つを追加します。


  • [Privacy - Location Always and When In Use Usage Description]

  • [Privacy - Location When In Use Usage Description]

image.png

ViewController.swiftに下記コードを書きます。


ViewController.swift


import UIKit
import CoreLocation

class ViewController: UIViewController, CLLocationManagerDelegate {

//ビーコン用の変数定義
var myLocationManager:CLLocationManager!
var myBeaconRegion:CLBeaconRegion!
var beaconUuids: NSMutableArray!
var beaconDetails: NSMutableArray!

//受信したいビーコンのUUIDを下記に記載(最大20個?)
let UUIDList = [
"48534442-4C45-4144-80C0-1800FFFFFFFF",
// この数値はUUIDに応じて変更ください。
]

//regionInOut...登録日時の表示
@IBOutlet weak var dateTimeNow: UILabel!

//regionInOut...領域内外の表示(outside/inside)
@IBOutlet weak var regionInOut: UILabel!

//regionNumber...ビーコン領域内外(0 or 1)の表示
@IBOutlet weak var regionNumber: UILabel!

//distanceBeacon...ビーコンとの距離表示
@IBOutlet weak var distanceBeacon: UILabel!

//UUIDの表示
@IBOutlet weak var uuidNumber: UILabel!

//情報の登録をするためのPHPのURL
let URL_SAVE_DATA = "https://yourserver/MyWebService/api/recordbeacon.php"

//日時の記録(日本時間)
func datejapan(){
let f = DateFormatter()
f.dateStyle = .short
f.timeStyle = .medium
print(f.string(from: Date()))
}
//MySQLデータベースに登録するファンクション
func Register() {
//created NSURL
let requestURL = URL(string: URL_SAVE_DATA)

//creating NSMutableURLRequest
let request = NSMutableURLRequest(url: requestURL!)

//setting the method to post
request.httpMethod = "POST"

//日時の記録(日本時間)
let f = DateFormatter()
f.dateStyle = .short
f.timeStyle = .medium
print(f.string(from: Date()))

let Number = regionNumber.text
let Now = dateTimeNow.text

//creating the post parameter by concatenating the keys and values from text field
let postParameters = "date="+Now!+"&flag="+Number!;

//adding the parameters to request body
request.httpBody = postParameters.data(using: String.Encoding.utf8)

//creating a task to send the post request
let task = URLSession.shared.dataTask(with: request as URLRequest) {
data, response, error in

if error != nil {
print("error is \(String(describing: error))")
return
}

do { //parsing the response
let myJSON = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary //converting resonse to NSDictionary

//parsing the json
if let parseJSON = myJSON {

//creating a string
var msg : String!

//getting the json response
msg = parseJSON["message"] as! String?

//printing the response
print(msg)

}
} catch {
print(error)
}

}
//executing the task
task.resume()

}

//以下、ビーコン側の処理
override func viewDidLoad() {
super.viewDidLoad()

myLocationManager = CLLocationManager()
// バックグランドモードで使用する場合YESにする必要あり?(追加)
myLocationManager.allowsBackgroundLocationUpdates = true;
myLocationManager.delegate = self
myLocationManager.desiredAccuracy = kCLLocationAccuracyBest
myLocationManager.distanceFilter = 1
let status = CLLocationManager.authorizationStatus()
print("CLAuthorizedStatus: \(status.rawValue)");
if(status == .notDetermined) {
myLocationManager.requestAlwaysAuthorization()
}
beaconUuids = NSMutableArray()
beaconDetails = NSMutableArray()
}

private func startMyMonitoring() {
for i in 0 ..< UUIDList.count {
let uuid: NSUUID! = NSUUID(uuidString: "\(UUIDList[i].lowercased())")
let identifierStr: String = "abcde\(i)"
myBeaconRegion = CLBeaconRegion(proximityUUID: uuid as UUID, identifier: identifierStr)
myBeaconRegion.notifyEntryStateOnDisplay = false
myBeaconRegion.notifyOnEntry = true
myBeaconRegion.notifyOnExit = true
myLocationManager.startMonitoring(for: myBeaconRegion)
uuidNumber.text = UUIDList[0]
}
}

func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
//日時の記録(日本時間)
let f = DateFormatter()
f.dateStyle = .short
f.timeStyle = .medium
print(f.string(from: Date()))
dateTimeNow.text = f.string(from: Date())
print("didChangeAuthorizationStatus");
switch (status) {
case .notDetermined:
print("not determined")
break
case .restricted:
print("restricted")
break
case .denied:
print("denied")
break
case .authorizedAlways:
print("authorizedAlways")
startMyMonitoring()
break
case .authorizedWhenInUse:
print("authorizedWhenInUse")
startMyMonitoring()
break
}
}

func locationManager(_ manager: CLLocationManager, didStartMonitoringFor region: CLRegion) {
manager.requestState(for: region);
}

func locationManager(_ manager: CLLocationManager, didDetermineState state: CLRegionState, for region: CLRegion) {
//日時の記録(日本時間)
let f = DateFormatter()
f.dateStyle = .short
f.timeStyle = .medium
print(f.string(from: Date()))
switch (state) {
case .inside:

//開始:regionNumberに表示(領域内=1)
regionNumber.text = "1"
dateTimeNow.text = f.string(from: Date())
regionInOut.text = "inside"

Register()
print("iBeacon inside")

manager.startRangingBeacons(in: region as! CLBeaconRegion)
break;
case .outside:

//regionNumberに表示(領域外=0)
regionNumber.text = "0"
dateTimeNow.text = f.string(from: Date())
regionInOut.text = "outside"

Register()
print("iBeacon outside")
break;
case .unknown:

//regionNumberに表示(領域外=0)
regionNumber.text = "0"
dateTimeNow.text = f.string(from: Date())
regionInOut.text = "outside"

Register()
print("iBeacon unknown")
break;
}
}

func locationManager(_ manager: CLLocationManager, didRangeBeacons beacons: [CLBeacon], in region: CLBeaconRegion) {
beaconUuids = NSMutableArray()
beaconDetails = NSMutableArray()
if(beacons.count > 0){
for i in 0 ..< beacons.count {
let beacon = beacons[i]
let beaconUUID = beacon.proximityUUID;
let minorID = beacon.minor;
let majorID = beacon.major;
let rssi = beacon.rssi;
var proximity = ""
switch (beacon.proximity) {
case CLProximity.unknown :
print("Proximity: Unknown");
proximity = "Unknown"
regionNumber.text = "0"
regionInOut.text = "outside"
break
case CLProximity.far:
print("Proximity: Far");
proximity = "Far"
regionNumber.text = "1"
regionInOut.text = "inside"
break
case CLProximity.near:
print("Proximity: Near");
proximity = "Near"
regionNumber.text = "1"
regionInOut.text = "inside"
break
case CLProximity.immediate:
print("Proximity: Immediate");
proximity = "Immediate"
regionNumber.text = "1"
regionInOut.text = "inside"
break
}
beaconUuids.add(beaconUUID.uuidString)
var myBeaconDetails = "Major: \(majorID) "
myBeaconDetails += "Minor: \(minorID) "
myBeaconDetails += "Proximity:\(proximity) "
myBeaconDetails += "RSSI:\(rssi)"
print(myBeaconDetails)
beaconDetails.add(myBeaconDetails)
distanceBeacon.text = proximity
}
}
}

func locationManager(_ manager: CLLocationManager, didEnterRegion region: CLRegion) {
//日時の記録(日本時間)
let f = DateFormatter()
f.dateStyle = .short
f.timeStyle = .medium
print(f.string(from: Date()))
//開始:regionNumberに表示(領域内=1)
regionNumber.text = "1"
dateTimeNow.text = f.string(from: Date())
regionInOut.text = "inside"
Register()
print("didEnterRegion: iBeacon found");
manager.startRangingBeacons(in: region as! CLBeaconRegion)
}

func locationManager(_ manager: CLLocationManager, didExitRegion region: CLRegion) {
//日時の記録(日本時間)
let f = DateFormatter()
f.dateStyle = .short
f.timeStyle = .medium
print(f.string(from: Date()))
//regionNumberに表示(領域外=0)
regionNumber.text = "0"
dateTimeNow.text = f.string(from: Date())
regionInOut.text = "outside"
Register()
print("didExitRegion: iBeacon lost");
manager.stopRangingBeacons(in: region as! CLBeaconRegion)
}
}


下記のように画面を作成し、紐づけます。

スクリーンショット 2019-05-15 23.30.09.png


さあ動かそう!


  • ご自身のスマホに今回作成したアプリをビルドします。

  • アプリを起動し、位置情報の使用を承認します。

  • ビーコンを発信します。

  • 領域、距離、UUIDが変更されれば成功です:sunny:

そしてMySQLはというと…

image.png

お!記録されていますね!成功です!


所感

作成したのは1年前ですが、Swiftまわりがあまりわからない状態で実施したので、下調べが大変でした。そして思い出すのも大変でした…笑

(不足、タイポあれば再記載します…)

Apple Developer Programに入られている方は通知機能入れても面白いと思います。


参照