ThinkPHP5中使用workman框架與硬件設備藍牙鎖通訊

wKioL1m3YkniCtxTAAC8pLc-3p4304.jpg


通篇分爲三大塊:服務器、藍牙鎖、APP

先說服務器:

使用的是TP5、workman框架使用composer安裝的

安裝wm可直接參考TP5的官方手冊,講解的很細緻https://www.kancloud.cn/manual/thinkphp5/235128

wKioL1nIabGzY4xcAADwFs3xy54876.jpg

Server.php文件

這裏我對Server類進行了一些改動

  1. 爲了加入定時器的功能

  2. 新增加了一個$inner_text_worker = new Worker('Text://0.0.0.0:5678');服務協議,用作APP端發送開鎖指令&告知藍牙鎖進行開關鎖;

  3. 構造函數裏面重寫了$this->worker->onWorkerStart函數,這樣的話Worker控制器裏面的onWorkerStart函數將失去作用,如果不在這裏重寫,去Worker控制器裏面的onWorkerStart函數加定時器將不起作用,因爲Server構造函數裏面已經運行了(Worker::runAll();)所有協議。

  4. 由於這裏進行了AES的加、解密,參考的時候可以忽略加解密容易瀏覽。

<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <[email protected]>
// +----------------------------------------------------------------------

namespace think\worker;

use Workerman\Worker;
use Workerman\Lib\Timer;
use Workerman\MySQL\Connection;

/**
 * Worker控制器擴展類
 */
abstract class Server
{
    protected $worker;
    protected $worker2;
    protected $socket    = '';
    protected $protocol  = 'http';
    protected $host      = '0.0.0.0';
    protected $port      = '2346';
    protected $processes = 1;

    /**
     * 架構函數
     * @access public
     */
    public function __construct()
    {
        // 實例化 Websocket 服務
        $this->worker = new Worker($this->socket ?: $this->protocol . '://' . $this->host . ':' . $this->port);
        // 設置進程數
        $this->worker->count = $this->processes;
        // 設置進程名稱
        $this->worker->name = "bluetooth";
        
        // 初始化
        $this->init();

	        // 自定義開始
        // worker進程中開啓一個Text協議進程
        $this->worker->onWorkerStart = function ($worker) {
            require_once "/data/var/www/html/zmartec_bluetooth/vendor/workerman/workerman/Lib/Connection.php";
            // 將db實例存儲在全局變量中(也可以存儲在某類的靜態成員中)
            global $db;
            $db = new Connection("mysql主機IP地址", "mysql端口", "mysql用戶", "密碼", "數據庫名稱");
            
            // 心跳 start
            // 進程啓動後設置一個每秒運行一次的定時器
            Timer::add(1, function ()use($worker){
                $time_now = time();
                foreach ($worker->connections as $connection) {
                    // 有可能該connection還沒收到過消息,則lastMessageTime設置爲當前時間
                    if (empty($connection->lastMessageTime)) {
                        $connection->lastMessageTime = $time_now;
                        continue;
                    }
                    // 上次通訊時間間隔大於心跳間隔(300秒),則認爲客戶端已經下線,關閉連接
                    if ($time_now - $connection->lastMessageTime > 300) {
                        if ($connection->uid) {
                            $connection->close();
                            echo "\r\n" . "客戶端:" . $connection->uid . "超過心跳時間,被斷開" .  "\r\n"; // $connection->uid
                        } else {
                            $connection->close();
                            echo "\r\n" . "客戶端:" . xxx . "超過心跳時間,被斷開" .  "\r\n"; // $connection->uid
                        }
                    }
                }
            });
            // 心跳end
            
            // Text協議,處理APP的開鎖、關鎖指令
            $inner_text_worker = new Worker('Text://0.0.0.0:5678');
            $inner_text_worker->onMessage = function ($connection, $buffer) {
                global $worker;
                // $data數組格式,裏面有uid,表示向哪個uid的用戶推送數據
                $data = json_decode($buffer, true);
				var_dump($data);
                $uid = $data['serial'];
                $send_data = $data['data'];
                var_dump("開鎖明文串:" . $data['plaintext']);
                
                // 獲取校驗值
                $xor = get_xor_value(preg_replace('/(0x)/', '', substr_replace($send_data, '', -1)));
                echo "獲取校驗值:" . dechex($xor) . "\r\n";
		// echo "轉換之後的獲取校驗值:" . hexToStr(dechex($xor)) . "\r\n";
                // 通過workerman,向uid的頁面推送數據
                $ret = $this->sendMessageByUid($uid, "aacc22" . $send_data . (strlen(dechex($xor)) == 1 ? "0" . dechex($xor) : dechex($xor)));
		// var_dump("Text協議發送結果:" . $ret);
                // 返回推送結果
                $connection->send($ret ? 'ok' : 'fail');
            };
            $inner_text_worker->listen();
        };
        // 自定義結束

        // 設置回調'onWorkerStart', 
        foreach (['onConnect', 'onMessage', 'onClose', 'onError', 'onBufferFull', 'onBufferDrain', 'onWorkerStop', 'onWorkerReload'] as $event) {
            if (method_exists($this, $event)) {
                $this->worker->$event = [$this, $event];
            }
        }
        // Run worker
        Worker::runAll();
    }

    protected function init()
    {
    }

    
    // 針對uid推送數據
    public function sendMessageByUid($uid, $message)
    {
        global $worker;
		// echo "uid-only:";
		// var_dump($worker);// $worker->uidConnections[$connection->uid]
        if(isset($worker->uidConnections[$uid]))
        {
            $connection = $worker->uidConnections[$uid];
			// var_dump("開鎖完整串:" . toHexString(hexToStr(preg_replace('/(,)/', '', preg_replace('/(0x)/', '', $message)))) . "("  . strlen(toHexString(hexToStr(preg_replace('/(,)/', '', preg_replace('/(0x)/', '', $message))))) . ")". "開鎖完整串");
            $connection->send(hexToStr(preg_replace('/(,)/', '', preg_replace('/(0x)/', '', $message))));
            return true;
        } else {
            echo "no-2\r\n";
        }
        return false;
    }
}


Worker.php控制器類:

<?php
namespace app\index\controller;

use think\worker\Server;
use Workerman\Lib\Timer;
use think\Model;
use Workerman\MySQL\Connection;
use app\index\controller\User;
use think\Controller;

/**
* 類
* 處理服務器與藍牙鎖設備
* 之間通訊
*/
class Worker extends Server
{
//     protected $socket = 'http://0.0.0.0:2348';
    protected $socket = 'tcp://0.0.0.0:2349';

	// 加解密串
    protected $key_init = "0x23,0x4f,0xe6,0x27,0x45,0x69,0x73,0x5b,0x0,0x18,0xc3,0xd1,0xa5,0xc5,0x28,0xc1";
    
    protected $host = "mysql主機IP地址";
    protected $port = "mysql端口";
    private $user = "mysql用戶";
    private $password = "密碼";
    private $db_name = "數據庫名稱";
    
    
    
    /**
     * 收到信息
	 *
     * @param $connection
     * @param $data
     */
    public function onMessage($connection, $data)
    {
        /**
         * start-註釋區這塊用作給蘇工測試,後續刪除
         */
        file_put_contents('/tmp/zmartec_bluetooth.log', date("Y-m-d H:i:s")."\r\n" . toHexString($data) . "\r\n\r\n", FILE_APPEND|LOCK_EX);
        /**
         * end-註釋區這塊用作給蘇工測試,後續刪除
         */
        
        $data = toHexString($data);
        
		// 這裏是爲了處理粘包問題,只做了粘包3次的處理
		// 如果你有更好的處理方式,可換成你自己的
        if ($this->loop($connection, $data)) {
            $data_new = original_data_process($data);
            $length_byte = hexdec($data_new[2]);
            $total_length = count($data_new);
            
            if ($total_length - $length_byte - 4 > 0) {
                $data = substr($data, ($length_byte + 4) * 2);
                var_dump("處理過後的子串1:" . $data);
                if ($this->loop($connection, $data)) {
                    $length_byte_new = hexdec($data_new[($length_byte + 4 + 2)]);
                    var_dump("xxx" . $length_byte_new);
                    if ($total_length - $length_byte - 4 - $length_byte_new -4 > 0) {
                        $data = substr($data, (($length_byte_new + 4) * 2));
						// var_dump("字符串總長度:" . strlen($data));
                        var_dump("處理過後的子串2:" . $data);
						// die;
                        $this->loop($connection, $data);
                    }
                }
            }
        }
        
    }
    
    /**
     * 當連接建立時觸發的回調函數
     *
     * @param $connection
     */
    public function onConnect($connection)
    {
        // 通過全局變量獲得db實例
        global $db;
        $time = time();
        echo "已連接Client的IP:" . $connection->getRemoteIp() . "\r\n";
    }
    
    /**
     * 當連接斷開時觸發的回調函數
     *
     * @param $connection
     */
    public function onClose($connection)
    {
        global $worker;
        echo "\r\n斷開連接Client的IP:" . $connection->getRemoteIp() . "\r\n";
        /**
         * 這裏如果設備異常斷開,
         * 會導致同一臺設備再此連接時候也以序列號
         * 爲uid標識的設備也被unset掉
         */
		// if (isset($connection->uid)) {
		// // 連接斷開時刪除映射
		// unset($worker->uidConnections[$connection->uid]);
		// }
        echo "\r\n連接id:" . $connection->id . "disconnect \r\n";
    }
    
    /**
     * 當客戶端的連接上發生錯誤時觸發
     *
     * @param $connection
     * @param $code
     * @param $msg
     */
    public function onError($connection, $code, $msg)
    {
        echo "error $code $msg\n";
    }
    
    /**
     * 每個進程啓動
     *
     * @param $worker
     */
    public function onWorkerStart($worker)
    {
        require_once __DIR__ . '/../../../vendor/workerman/workerman/Lib/Connection.php';
        
        // 將db實例存儲在全局變量中(也可以存儲在某類的靜態成員中)
        global $db;
        $db = new Connection($this->host, $this->port, $this->user, $this->password, $this->db_name);
        
        // 進程啓動後設置一個每秒運行一次的定時器
        Timer::add(1, function ()use($worker){
            $time_now = time();
            foreach ($worker->connections as $connection) {
                // 有可能該connection還沒收到過消息,則lastMessageTime設置爲當前時間
                if (empty($connection->lastMessageTime)) {
                    $connection->lastMessageTime = $time_now;
                    continue;
                }
                // 上次通訊時間間隔大於心跳間隔,則認爲客戶端已經下線,關閉連接
                if ($time_now - $connection->lastMessageTime > 1000) {
                    $connection->close();
                }
            }
        });
        
        echo $worker->id . "\r\n";
    }


    public function loop($connection, $data)
    {
        // 給connection臨時設置一個lastMessageTime屬性,用來記錄上次收到消息的時間
        $connection->lastMessageTime = time();
        // 通過全局變量獲得db實例
        global $db,$worker;
        // 轉爲數組
        $data = original_data_process($data);
        // 獲取長度位
        $length_byte = hexdec($data[2]);
        // 獲取開頭標識位
        $head_byte = array_slice($data, 0, 2);
        $head_byte = implode('', $head_byte);
        
        if ($head_byte != 'aacc') {
            echo "數據異常\r\n";
            return true;
        }
        
        if ($length_byte == 34) {
            // 校驗數據
            if (true !== verify_xor_value($data)) {
                //$connection->send("xor is not match.");
                echo "xor is not match.\r\n";
                //                 return ;
            }
            // 解密
            $data_slice = array_slice($data, 3, 35 - 1);
            print_r("發送的被解密串:" . json_encode($data_slice) . '\r\n');
            $random = array_slice($data_slice, 0, 18);
            print_r("發送的隨機串:" . json_encode($random) . '\r\n');
            $ciphertext = array_slice($data_slice, 18, 33);
            print_r("發送的密文串:" . json_encode($ciphertext) . '\r\n');
            $decrypt = zm_decrypt($ciphertext, $this->key_init, $random);
            var_dump("解密的完整串:" . $decrypt);
            // 序列號
            $serial = substr($decrypt, -10);
            // 設備號
            $device_num = substr($decrypt, 6, 16);
            $start = substr($decrypt, 0, 6);
            $start_4_byte = substr($decrypt, 0, 4);
            //         $connection->send("result." . json_encode($decrypt));//return '';
        
            // 設備連接
            if ("010203" == $start) {
                // 判斷當前客戶端是否已經驗證,即是否設置了uid
                if (!isset($connection->uid)) {
                    $time = time();
                    // 拿到序列號作爲uid
                    $connection->uid = $serial;
                    echo "\r\n" . date('Y-m-d H:i:s') . "\r\n";
                    echo "設備連接開始:" . "\r\n";
                    var_dump("連接設備的序列號:" . $serial);
                    var_dump("連接設備的設備號:" . $device_num);
                    /* 保存uid到connection的映射,這樣可以方便的通過uid查找connection,
                     * 實現針對特定uid推送數據
                    */
                    $worker->uidConnections[$connection->uid] = $connection;
                    //                 Array
                    //                 (
                    //                     [device_num] => 330b41c003300310
                    //                 )
                    $device_id = $db->select('device_num')
                    ->from('zm_device')
                    ->where('serial= :serial AND device_num = :device_num')
                    ->bindValues(array('serial'=>"$serial", 'device_num' => "$device_num"))
                    ->row();
                    if (!$device_id) {
                        $result_device = $db->insert('zm_device')->cols(array(
                            'serial' => "$serial",
                            'device_num' => "$device_num"
                        ))->query();
                    }
                    // 記錄設備在線狀態
                    $online_device_id = $db->select('id')
                    ->from('zm_online_device')
                    ->where('device_id= :device_id')
                    ->bindValues(array('device_id'=>"$device_id[device_num]"))
                    ->row();
                    $result = $db->insert('zm_online_device')->cols(array(
                        'device_id' => '1',
                        'host' => $connection->getRemoteIP(),
                        'created_time' => $time
                    ))->query();
        
                    echo "設備連接處理結束:" . "\r\n";
                    // 設備連接確認
                    $connection->send(hexToStr("AACC060000000000FFFF"));
                    return true;
                } else {
                    $connection->send(hexToStr("AACC060000000000FFFF"));
                }
                // 上鎖
            } else if ("9999" == $start_4_byte) {
                $serial_4_byte = substr($decrypt, -8);
                echo "\r\n" . date('Y-m-d H:i:s') . "\r\n";
                var_dump("上鎖序列號:" . $serial_4_byte);
                // 故障、狀態字節
                $fault_st_2_byte = substr($decrypt, -10, 2);
                $device_num = $db->select('device_num')
                ->from('zm_device')
                ->where('serial= :serial')
                ->bindValues(array('serial' => "00$serial_4_byte"))
                ->row();
                //                 var_dump("上鎖序列號查詢結果" . $device_num);
                if ($device_num) {
                    $lock = $db->update('zm_device')
                    ->cols(array('is_lock_status' => '00'))
                    ->where("serial = '00$serial_4_byte'")
                    ->query();
                    echo "上鎖故障、狀態字節值:" . $fault_st_2_byte . "\r\n";
                    echo "上鎖狀態字節值:" . substr($fault_st_2_byte, -2) . "\r\n";
                    if ($lock || substr($fault_st_2_byte, -2) == '00') {
                        echo "\r\n上鎖成功:" . $serial_4_byte . "\r\n";
                        $connection->send(hexToStr("AACC060000000000FFFF"));
                    } else if (substr($fault_st_2_byte, -2) == '01') {
                        echo "\r\n上鎖失敗:" . $serial_4_byte . "\r\n";
                        $connection->send(hexToStr("AACC060000000000FFFF"));
                        return true;
                    } else {
                        echo "\r\n上鎖失敗,序列號:" . $serial_4_byte . "\r\n";
                        $connection->send(hexToStr("AACC060000000000FFFF"));
                        return true;
                    }
                } else {
                    echo "\r\n設備不存在,沒有設備記錄\r\n";
                    $connection->send(hexToStr("AACC060000000000FFFF"));
                    return true;
                }
            }
        } else if ($length_byte == 16){ // 定位數據
            echo "\r\n" . date('Y-m-d H:i:s') . "\r\n";
            echo "這是定位數據信息\r\n";
            echo implode(',', $data) . "\r\n";
            echo "發送定位設備的IP:" . $connection->getRemoteIp() . "\r\n";
            echo "發送定位設備的端口:" . $connection->getRemotePort() . "\r\n";
            echo "當前設備經度:" . hexdec(implode('', array_slice($data, 8, 1))) . "." . ((strlen(hexdec(implode('', array_slice($data, 9, 3)))) != 4) ? hexdec(implode('', array_slice($data, 9, 3))) : '0'.hexdec(implode('', array_slice($data, 9, 3)))) . "\r\n";
            echo "當前設備緯度:" . hexdec(implode('', array_slice($data, 4, 1))) . "." . ((strlen(hexdec(implode('', array_slice($data, 5, 3)))) != 4) ? hexdec(implode('', array_slice($data, 5, 3))) : '0'.hexdec(implode('', array_slice($data, 5, 3))));
            echo "\r\n";
            $location_data = array_slice($data, 3, $length_byte);
            $verify_value = $data[$length_byte+3];
            // 校驗數據
            if (true !== common_verify_xor_value($location_data, $verify_value)) {
                //$connection->send("xor is not match.");
                echo "The location xor is not match.\r\n";
            }
            return true;
        } else if ($length_byte == 4) {
            echo "\r\n" . date('Y-m-d H:i:s') . "\r\n";
            echo "這是心跳包數據信息\r\n";
            echo implode(',', $data) . "\r\n";
            echo "發送心跳包設備的IP:" . $connection->getRemoteIp() . "\r\n";
            echo "發送心跳包設備的端口:" . $connection->getRemotePort() . "\r\n";
            echo "\r\n";
            return true;
        } else if ($length_byte == 10) {
            echo "這是所有應答包數據信息\r\n";
            echo implode(',', $data);
            echo "\r\n";
            // 校驗數據
            if (true !== response_verify_xor_value($data)) {
                //$connection->send("xor is not match.");
                echo "The response xor is not match.\r\n";
            }
            // 獲取序列號
            $serial = array_slice($data, -5, 4);
            $serial = implode('', $serial);
            // 獲取電壓
            $electric = array_slice($data, 3, 2);
            $electric = implode('', $electric);
            // 獲取標識符
            $flag = array_slice($data, -6, 1);
            $flag = implode('', $flag);
            if ('ff' == $flag) {
                echo "默認應答包:ff\r\n";
                echo "設備電壓值:" . substr(hexdec(implode('', array_slice($data, 3, 2))), 0, 1) . "." . substr(hexdec(implode('', array_slice($data, 3, 2))), 1, 2) . "V" . "\r\n";
                $db->update('zm_device')
                ->cols(array('electric' => "$electric"))
                ->where("serial = '00$serial'")
                ->query();
            } else if ('01' == $flag) {
                echo "開鎖成功\r\n";
                $db->update('zm_device')
                ->cols(array('electric' => "$electric", 'is_lock_status' => '01'))
                ->where("serial = '00$serial'")
                ->query();
                $connection->send(hexToStr("AACC060000000000FFFF"));
            } else if ('00' == $flag) {
                echo "開鎖失敗\r\n";
                $db->update('zm_device')
                ->cols(array('electric' => "$electric", 'is_lock_status' => '00'))
                ->where("serial = '00$serial'")
                ->query();
                $connection->send(hexToStr("AACC060000000000FFFF"));
            } else {
                echo "不知情況data:" . implode(',', $data) . "\r\n";
            }
            return true;
        } else {
            echo 'xxx';
            return false;
        }
    }
    
    
}



未完待續。。。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章