0.前言
去年(2013年)2月第一次接觸yeelink平臺(tái),當(dāng)時(shí)該平臺(tái)已經(jīng)運(yùn)行了一些時(shí)間也吸引了不少極客。試想自己也將投身IoT(物聯(lián)網(wǎng))行業(yè),就花了些時(shí)間研究了它。陸陸續(xù)續(xù)使用和研究了一年,大致圍繞兩個(gè)問題展開——1.yeelink平臺(tái)如何使用,2.如何構(gòu)造一個(gè)功能簡(jiǎn)單些的yeelink平臺(tái)。
【PHP學(xué)習(xí)筆記——索引博文】 本文將討論如何構(gòu)造一個(gè)簡(jiǎn)單restful架構(gòu)平臺(tái)(該平臺(tái)有點(diǎn)像yeelink,不過功能比yeelink少的多),并結(jié)合樹莓派實(shí)現(xiàn)LED的遠(yuǎn)程控制(網(wǎng)絡(luò)控制)。構(gòu)建一個(gè)RESTFul平臺(tái)涉及到很多知識(shí),通過以下鏈接提供一些學(xué)習(xí)資料。
【1】
Slim——簡(jiǎn)單的 PHP5 框架可用來創(chuàng)建 RESTful 的 Web 應(yīng)用
【2】MySQL——關(guān)系型數(shù)據(jù)庫(kù)管理系統(tǒng)
【4】
JSON——輕量級(jí)的數(shù)據(jù)交換格式
【5】
cURL——利用URL語法在命令行方式下工作的開源文件傳輸工具
如果親愛的讀者想快速入門也可以看看我的博客文章。
【2014年3月補(bǔ)充】
【2】整理完本篇博文之后,修改了部分API函數(shù)并在GitHub建立了代碼倉(cāng)庫(kù)(數(shù)據(jù)庫(kù)操作并沒有使用RedBean),如果本博文對(duì)您有用請(qǐng)點(diǎn)擊這里【GitHub Clone】。 1.REST風(fēng)格API
在HTTP協(xié)議中定義了多種動(dòng)作或者方法,這些方法具有不同的含義。
【GET 獲取】【POST 創(chuàng)建】【PUT 更新】【DELTE 刪除】
為了更好的理解以上的方法,下面結(jié)合LED遠(yuǎn)程控制舉個(gè)例子。假設(shè)在數(shù)據(jù)庫(kù)中已經(jīng)保存了家庭中的LED設(shè)備信息,這些設(shè)備信息包括LED編號(hào),LED設(shè)備描述和當(dāng)前狀態(tài)(打開或關(guān)閉)等,例如位于客廳的LED處于打開狀態(tài)。
可通過GET方法獲得某個(gè)LED設(shè)備的信息或者全部LED的信息。這些LED燈具有一個(gè)唯一的編號(hào),例如客廳的LED燈編號(hào)為1,那么 /leds/1就是編號(hào)為1的LED設(shè)備的唯一URI(可理解為網(wǎng)址)。通過這樣類似的方法使每個(gè)LED設(shè)備具有網(wǎng)址,可通過該網(wǎng)址訪問LED。通過GET方法可獲得LED設(shè)備的所有信息,這些信息可通過JSON格式描述,例如:
[{'id':1,'description':'raspberry pi IO1','status':'off'},{'id':2,'description':'raspberry pi IO2','status':'on'}]
可通過POST方法創(chuàng)建一個(gè)新LED,新增加的LED具體信息可使用JSON格式描述,例如:
{'description':'add a new led','status':'off'}
可通過PUT方法更新LED信息,而具體內(nèi)容用JSON格式描述,例如:
{“status”:'on'}
對(duì)于LED網(wǎng)絡(luò)控制,REST API設(shè)計(jì)如下:
GET /leds 返回所有的LED信息
POST /leds 增加一個(gè)LED設(shè)備
GET /leds/id 返回編號(hào)為id的LED設(shè)備信息
PUT /leds/id 更新編號(hào)為id的LED設(shè)備信息
2.數(shù)據(jù)庫(kù)準(zhǔn)備
2.1 修改mysql密碼
(2.1或2.2操作也可使用phpMyAdmin)
2.1 新建LED設(shè)備表
使用mysql控制臺(tái),進(jìn)入mysql數(shù)據(jù)庫(kù)(輸入use mysql,mysql為數(shù)據(jù)庫(kù)的名稱——安裝wampserver后的一個(gè)默認(rèn)數(shù)據(jù)庫(kù))。建立一個(gè)LED設(shè)備表,該表具有編號(hào)ID、描述description、狀態(tài)status 字段,主鍵為id且自動(dòng)增長(zhǎng)(插入該數(shù)據(jù)表時(shí) id寫寫入0或者不寫,id編號(hào)會(huì)自動(dòng)增長(zhǎng))。
CREATE TABLE IF NOT EXISTS `leds` ( id int(11) NOT NULL AUTO_INCREMENT, description text NOT NULL, status text NOT NULL, PRIMARY KEY (id)) DEFAULT CHARSET=utf8;
【小提示】
【1】選擇數(shù)據(jù)庫(kù) use <databs_name>;
【2】查看表的結(jié)構(gòu) desc <table_name>;
【3】刪除表 drop table <table_name>;
【4】id的數(shù)據(jù)類型為int(11),千萬別以為int的長(zhǎng)度為11位,int(11)只是一種int的表達(dá)方式。
2.2 插入設(shè)備內(nèi)容
可在MySQL控制臺(tái)輸入以下內(nèi)容,插入兩條數(shù)據(jù):
INSERT INTO leds (id, description , status) VALUES (1, 'raspberry pi pcf8574-IO1','on');INSERT INTO leds (id, description , status) VALUES (2, 'raspberry pi pcf8574-IO2','off');
3.GET方法獲得所有LED信息
使用GET方法或的所有LED狀態(tài)——GET /leds。
返回LED狀態(tài),使用JSON數(shù)據(jù)包描述。
【代碼片段】
<?phprequire 'rb.php';require 'Slim/Slim.php';\Slim\Slim::registerAutoloader();// 初始化數(shù)據(jù)庫(kù)連接R::setup('mysql:host=localhost;dbname=mysql','root','<your password>');R::freeze(true);$app = new \Slim\Slim();// GET /leds$app->get('/leds', function () use ($app) { // 查找所有設(shè)備 $led_array = R::getAll('select * from leds'); $app->response()->header('Content-Type', 'application/json'); // 按照J(rèn)SON格式輸出 echo json_encode( $led_array , JSON_NUMERIC_CHECK);});$app->run();?>
【代碼解釋】
【1】 require 'rb.php'; 載入redbean,請(qǐng)把rb.php放在www根目錄。
【2】R::setup('mysql:host=localhost;dbname=mysql','root','<your password>');R::freeze(true);載入數(shù)據(jù)庫(kù),填入數(shù)據(jù)庫(kù)的名稱和密碼。
【3】$led_array = R::getAll('select * from leds'); 查詢數(shù)據(jù)庫(kù)獲得LED數(shù)據(jù)包的所有內(nèi)容,getAll返回一個(gè)索引數(shù)組。
【4】 echo json_encode( $led_array , JSON_NUMERIC_CHECK); 請(qǐng)注意mysql的整形轉(zhuǎn)到PHP時(shí)將變?yōu)閟tring,如果沒有JSON_NUMERIC_CHECK選項(xiàng),那可能會(huì)獲得{“id”:'1',....},這肯定不是你所愿意看到的。
【簡(jiǎn)單測(cè)試】
可通過瀏覽器,cURL工具,瀏覽器HTTP插件進(jìn)行測(cè)試。
圖1 使用瀏覽器獲得所有LED信息
4. GET方法獲得單個(gè)LED信息
【代碼片段】
// GET /leds/:id$app->get('/leds/:id', function ($id) use ($app) { try { // 查詢數(shù)據(jù)庫(kù),只返回status狀態(tài) $led_single = R::getRow('select status from leds where id = :id',array(':id'=>$id)); if ($led_single) { $app->response()->header('Content-Type', 'application/json'); // 按照J(rèn)SON格式輸出 echo json_encode( $led_single, JSON_NUMERIC_CHECK); } else { $app->response()->status(404); } } catch (ResourceNotFoundException $e) { $app->response()->status(404); } catch (Exception $e) { $app->response()->status(400); $app->response()->header('X-Status-Reason', $e->getMessage()); }});
【代碼解釋】
【1】$app->get('/leds/:id', function ($id) use ($app) id作為參數(shù),可以輸入數(shù)字1或2等。
【2】$led_single = R::getRow('select status from leds where id = :id',array(':id'=>$id));
select status from leds where id = :id 為SQL查詢語句,和一般的SQL語句不同的是出現(xiàn):id,array(':id'=>$id)該語句實(shí)現(xiàn)了SQL語句中的:id和輸入?yún)?shù)id的綁定關(guān)系。在這里只查詢status內(nèi)容,其他內(nèi)容忽略。
【3】echo json_encode( $led_single, JSON_NUMERIC_CHECK); JSON格式輸出,請(qǐng)主意使用JSON_NUMERIC_CHECK選項(xiàng)。
【簡(jiǎn)單測(cè)試】
使用curl工具測(cè)試,在windows 控制臺(tái)中輸入以下命令:
圖2 使用cURL工具獲得單個(gè)LED信息
5. PUT更新單個(gè)LED信息
【代碼片段】
$app->put('/leds/:id', function ($id) use ($app) { try { // 獲得HTTP請(qǐng)求中的JSON數(shù)據(jù)包 $request = $app->request(); $body = $request->getBody(); $input = json_decode($body); // 查找編號(hào)為ID的記錄 $led = R::findOne('leds', 'id=?', array($id)); // 重新修改status狀態(tài),并保存 if ($led) { $led->status = (string)$input->status; R::store($led); } else { throw new ResourceNotFoundException(); } } catch (ResourceNotFoundException $e) { $app->response()->status(404); } catch (Exception $e) { $app->response()->status(400); $app->response()->header('X-Status-Reason', $e->getMessage()); }});
【代碼分析】
【1】獲得HTTP請(qǐng)求中的內(nèi)容并進(jìn)行解碼,json_decode總是返回一個(gè)PHP對(duì)象而不是數(shù)組,所有后面對(duì)于input的操作需要使用->符號(hào)。
$request = $app->request();
$body = $request->getBody();
$input = json_decode($body);
【2】$led = R::findOne('leds', 'id=?', array($id)); R::findOne總是返回一個(gè)對(duì)象,后面的操作需要使用->符號(hào)。
【3】$led->status = (string)$input->status; 修改status。
【4】R::store($led); 重新存儲(chǔ)led信息。
【簡(jiǎn)單測(cè)試】
使用cURL工具測(cè)試,請(qǐng)求的內(nèi)容為{'status':'off'},請(qǐng)求的方法為PUT。在windows控制臺(tái)下輸入以下命令:
注意:1)由于該HTTP負(fù)載并沒有返回值,所有curl指令中加入-i選項(xiàng),意為顯示HTTP響應(yīng)首部。
2)PUT方法必須大寫。
圖3 使用cURL更新單個(gè)LED狀態(tài)
圖4 編號(hào)為1的LED狀態(tài)發(fā)生改變
6.樹莓派 實(shí)現(xiàn)LED網(wǎng)絡(luò)控制
親愛的朋友,如果您還不熟悉樹莓派的話,可以參考:
通過樹莓派實(shí)現(xiàn)網(wǎng)絡(luò)控制的方法也非常簡(jiǎn)單,樹莓派不停的向服務(wù)器(在局域網(wǎng)中,IP為192.168.1.100)發(fā)送GET請(qǐng)求,服務(wù)器查詢數(shù)據(jù)庫(kù)以JSON格式返回LED信息,樹莓派根據(jù)JSON數(shù)據(jù)包的內(nèi)容控制LED燈,on為點(diǎn)亮,off為熄滅。
【1】樹莓派發(fā)送HTTP請(qǐng)求 GET /leds/1
【2】服務(wù)器返回HTTP響應(yīng) {“status”:'off'}或{“status”:'on'}
【3】樹莓派根據(jù)status控制LED設(shè)備
【代碼片段】
#!/usr/bin/env python# -*- coding: utf-8 -*-import requestsimport smbusimport RPi.GPIO as GPIOimport time# 打開 /dev/i2c-1bus = smbus.SMBus(1)# 設(shè)備URIapiurl = 'http://192.168.1.100/leds/1'while True: #發(fā)送請(qǐng)求 r = requests.get(apiurl) # 打印內(nèi)容 print(r.text) # 響應(yīng)轉(zhuǎn)換內(nèi)容為字典形式 # 轉(zhuǎn)換為字典類型 請(qǐng)注意 2.7.4版本使用r.json() led = r.json # {'value':'xx'} on打開狀態(tài),off關(guān)閉狀態(tài) if led['status'] == 'on': print('led on') bus.write_byte( 0x20 , 1 ) else: print('led off') bus.write_byte( 0x20 , 0 ) # 延時(shí)5S time.sleep(5)
【代碼測(cè)試】
由于沒有做前端,所有只能通過cURL指令改變LED的status。改變數(shù)據(jù)庫(kù)中LED的status之后,樹莓派上擴(kuò)展板的真實(shí)LED狀態(tài)便會(huì)發(fā)生變化。前端控制頁面請(qǐng)期待后續(xù)博文。
圖5 測(cè)試結(jié)果LED狀態(tài)發(fā)生改變
7.其他遐想
本文只是想闡述REST框架的創(chuàng)建和使用,樹莓派的使用并不是本文的重點(diǎn)(樹莓派讓我擴(kuò)展了知識(shí)面)。除了樹莓派之外還可以使用其他設(shè)備“享用”這個(gè)REST服務(wù),例如
arduino平臺(tái)——入門簡(jiǎn)單,加上ENC28J60可替代本文樹莓派的功能。
8.參考資料