在第一節中講解了openApi的調用,這一篇講一下如何實現一個燈的控制。就用微信提供的lamp例子來作,將代碼扒下來(實在是沒辦法,沒有示例),整合到本身的項目中。lamp源碼:http://files.cnblogs.com/files/stoneniqiu/lamp.zip。css
你能夠本身扒,帶參數的頁面在瀏覽器中打開會立刻跳轉,不帶參數的會提示參數不全,須要用mobile模式觀看。html
呈現的界面以下:前端
解壓開lamp.js ,目錄以下,這個demo是基於sea.js+zepto實現,sea.js用來加載模塊,zepto提供ajax請求和tab事件等。node
common中包含了一個keyConfig.js(地址參數),一個reqData.js(請求封裝)還有一個zepto,ui裏是一個上面圖片的中的slider同樣的組件。util中是一組方法集合。最重要的就是lamp.js 。ajax
define(function (require) { var $ = require("common/zepto"); var keyConfig = require("common/keyConfig"); var reqData = require("common/reqData"); var util = require("util/util"); var ProcessBar = require("ui/process-bar"); var pageParam = { device_id: util.getQuery("device_id"), device_type: util.getQuery("device_type"), appid: util.getQuery("appid") }; var lastModTime = 0; var powerBtn = $("#powerBtn"), // 開關按鈕 lightBar; var device_status= { services: { lightbulb: {alpha:0}, operation_status:{status:0} } }; // 數據對象 (function () { if(!pageParam.device_id || !pageParam.device_type){ alert("頁面缺乏參數"); return; } log("appid:" + pageParam.appid); log("device_id:" + pageParam.device_id); log("device_type:" + pageParam.device_type); powerBtn.on("tap", togglePower); // 開關按鈕事件 initBar(); initInterval(); // todo : for test, delete before submit // renderPage({}); })(); /** * 初始化進度條 */ function initBar() { log("初始化lightBar"); lightBar = new ProcessBar({ $id: "lightBar", min: 0, stepCount: 100, step: 1, touchEnd: function (val) { device_status.services.lightbulb.alpha = val; log("亮度值爲:"+val); setData(); } }); } /** * 請求數據 */ function getData() { reqData.ajaxReq({ //url: keyConfig.GET_LAMP_STATUS, url:'https://api.weixin.qq.com/device/getlampstatus', data: pageParam, onSuccess: renderPage, onError:function(msg) { log("獲取數據失敗:" + JSON.stringify(msg)); } }); } /** * 設置數據 */ function setData() { console.log("setUrl", keyConfig.SET_LAMP_STATUS); lastModTime = new Date().getTime(); // 更新最後一次操做時間 reqData.ajaxReq({ // url: keyConfig.SET_LAMP_STATUS, url: 'https://api.weixin.qq.com/device/setlampstatus', type: "POST", data: JSON.stringify(device_status) }); log("setData:" + JSON.stringify(device_status)); } /** * 開關按鈕事件 */ function togglePower() { $("#switchBtn").toggleClass("on").toggleClass("off"); log("燈的狀態status:"+device_status.services.operation_status.status); if(device_status.services.operation_status.status==0){ device_status.services.operation_status.status = 1; log("燈的狀態:1"); } else { device_status.services.operation_status.status = 0; log("燈的狀態:0"); } setData(); } /** * 輪詢 */ function initInterval() { getData(); setInterval(function () { if((new Date().getTime() - lastModTime) > 2000){ // 當有設置操做時,中止1s輪詢,2秒後繼續輪詢 getData(); } }, 1000); } /** * 渲染頁面 */ function renderPage(json) { // todo : for test, delete before submit // json = { // device_status: { // services: { // operation_status: { // status: 0 // }, // lightbulb: { // alpha: 0 // } // } // } // }; log("renderPage:"+json); if(!json.device_status){ return; } console.log("json", json); device_status = json.device_status; log(device_status); if(device_status.services.operation_status.status==0){ $("#switchBtn").addClass("on").removeClass("off"); } else { $("#switchBtn").addClass("off").removeClass("on"); } lightBar.setVal(device_status.services.lightbulb.alpha); } });/* |xGv00|4199711a9ade00e2807e7ea576d92f55 */
首先咱們看到pageParam對象是獲取頁面上參數的,device_id,device_type以及appid三個參數。其實有用的只有前面兩個,由於appid的話,後臺服務器已經配置了,並且在微信中的經過「進入面板」的時候只附帶了id和type兩個參數。而後device_status是一個設備狀態對象對象是燈,根據微信services的定義,燈有一個亮度值。這個在上一篇提到過。而後是一個當即執行的匿名函數,這個函數函數裏面會先檢查一下參數,而後初始化開關和亮度條。最好進入循環。initInterval中就是不斷的經過getdata獲取數據。注意到這兒有一個lastModTime的比較,而後延時2秒再觸發,這個地方主要是由於每次設置以後再從服務器撈到數據有一個延時。本來是10,你設置了20,bar也到了20的位置,可是呢,服務器還有一個10在路上發過來,你設置的20並無立刻失效,這會有一個卡頓的效果。但這個兩秒也不是那麼的有效,卡頓仍是會有;另一方面就是,不能設置太快,設置太快了會報50019的錯誤(設備正在被操做);getdata成功後,就是renderpage,這個不用解釋了。注意到在綁定開關時間的地方,實際上是先調用了一次setdatajson
powerBtn.on("tap", togglePower); function togglePower() { $("#switchBtn").toggleClass("on").toggleClass("off"); log("燈的狀態status:"+device_status.services.operation_status.status); if(device_status.services.operation_status.status==0){ device_status.services.operation_status.status = 1; log("燈的狀態:1"); } else { device_status.services.operation_status.status = 0; log("燈的狀態:0"); } setData(); }
這個做用有兩個,一個是獲取設備目前的狀態,由於設備可能沒有開啓,或者沒有聯網,二個是將參數傳遞給後臺,否則getdata無效。最後理清一下思路就是後端
獲取參數-->初始化-->setdata一次-->循環-->渲染頁面 界面操做-->setdata-->延時讀取。 加上後端的部分,所有的流程圖以下。api
因此拿到前端代碼只是一半,後端還須要本身實現。瀏覽器
純靜態文件是沒法請求微信服務器的,因此咱們須要本身實現後臺的部分,這也是第一節中要講的目的。緩存
html:
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta id="viewport" name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> <title>個人燈泡</title> <link href="/css/common.css" rel="stylesheet" /> <link href="/css/light_switch.css" rel="stylesheet" /> </head> <body> <div> <div class="body"> <div class="inner"> <div id="switchBtn" class="status_button off"> <div class="button_wrp"> <div class="button_mask"> <div class="alerter_button" id="powerBtn"> <i class="status_pot"></i> <span class="on">ON</span> <span class="off">OFF</span> </div> </div> </div> <div class="on"> <h2>燈已開</h2> </div> </div> <div id="reData"></div> </div> </div> <div class="foot"> <div class="slider_box J_slider_box"> <i class="slider_box_icon icon dark"></i> <div id="lightBar" class="slider_box_bar"> <div class="slider_box_slider J_slider" style="left:0%"> <p class="slider_box_slider_label J_value"></p> <i class="slider_box_slider_touch"></i> </div> <div class="slider_box_line"> <span class="slider_box_line_fill J_fill" style="width:0%"></span> </div> </div> <i class="slider_box_icon icon light"></i> </div> </div> </div> <script src="/js/sea.js"></script> <script> seajs.config({ base: '/js/', //map: [[/^(.*\.(?:css|js))(.*)$/i, "$1"]], charset: 'utf-8' }); seajs.use("baby"); </script> </body> </html>
本身的實現就拿掉了遮罩和config部分,將sea.js的目錄改到本身對應的目錄便可:
seajs.config({ base: '/js/', //map: [[/^(.*\.(?:css|js))(.*)$/i, "$1"]], charset: 'utf-8' }); seajs.use("baby");
這個baby(命名和產品有關~)就至關因而lamp。 另外就是,修改請求地址。也就是經過後臺調用api來實現getdate和setdata。初版我修改的js和lamp.js的差異不大 就增長了一個log爲了調試,修改調用路徑。
define(function (require) { var $ = require("common/zepto"); var util = require("util/util"); var ProcessBar = require("ui/process-bar"); var requestData = { services: { lightbulb: { alpha: 10 }, air_conditioner: {}, power_switch: {}, operation_status: { status: 0 } }, device_type: util.getQuery("device_type"), device_id: util.getQuery("device_id"), user: '', }; var lastModTime = 0; var powerBtn = $("#powerBtn"), // 開關按鈕 lightBar; function log(msg, arg) { console.log(msg, arg); msg = JSON.stringify(msg); if (arg) { msg = msg + "," + JSON.stringify(arg); } $.post('/device/log', { msg: msg }); } (function () { bindEvent(); if (!requestData.device_id || !requestData.device_type) { alert("頁面缺乏參數"); return; } powerBtn.on("tap", togglePower); // 開關按鈕事件 initBar(); queryDevice(); })(); function bindEvent() { $(".footer .nav_side li").click(function () { activePage($(this).data("index"), $(this)); }); } function activePage(index, $self) { $self.parent('li').addClass("on"); $body.find('.page:eq(' + index + ')').addClass("active").siblings().removeClass("active"); } /** * 初始化進度條 */ function initBar() { log("初始化lightBar"); lightBar = new ProcessBar({ $id: "lightBar", min: 0, stepCount: 100, step: 1, touchEnd: function (val) { requestData.services.lightbulb.alpha = val; log("亮度值爲:" + val); setData(); } }); } /** * 開關按鈕事件 */ function togglePower() { $("#switchBtn").toggleClass("on").toggleClass("off"); if (requestData.services.operation_status.status == 0) { requestData.services.operation_status.status = 1; log("燈的狀態:1"); } else { requestData.services.operation_status.status = 0; log("燈的狀態:0"); } setData(); } function queryDevice() { $.getJSON('/device/RequestDeviceStatus', { reqstr: JSON.stringify(requestData) }, function (data) { console.log(data); if (data.error_code == 0) { //請求成功; initInterval(); console.log("查詢成功"); } else { alert(data.error_msg); } }); } /** * 輪詢 */ function initInterval() { getData(); setInterval(function () { if ((new Date().getTime() - lastModTime) > 2000) { // 當有設置操做時,中止1s輪詢,2秒後繼續輪詢 getData(); } }, 1000); } function setData() { $.getJSON('/device/RequestDeviceStatus', { reqstr: JSON.stringify(requestData) }, function (data) { console.log(data); lastModTime = new Date().getTime(); if (data.error_code == 0) { console.log("設置成功"); } }); } function getData() { $.post('/device/getData', function (data) { $("#reData").html(JSON.stringify(data)); if (data && data.services) { renderPage(data); } }); }; function renderPage(json) { if (!json.services) { return; } console.log("json", json); requestData = json; if (requestData.services.operation_status.status == 0) { $("#switchBtn").addClass("off").removeClass("on"); } else { $("#switchBtn").addClass("on").removeClass("off"); } lightBar.setVal(requestData.services.lightbulb.alpha); } })
我將pageParam和device_status作成了一個對象。requestData。
var requestData = { services: { lightbulb: { alpha: 10 }, // air_conditioner: {}, power_switch: {}, operation_status: { status: 0 } }, device_type: util.getQuery("device_type"), device_id: util.getQuery("device_id"), user: '', };
後臺就是兩個主要方法,一個設置(查詢頁就是設置),一個讀取。這裏又回到上一節的內容了。我先查詢一次設備(lamp中在綁定)以後,再進入循環。
public ActionResult RequestDeviceStatus(string reqstr) { if (string.IsNullOrEmpty(reqstr)) { return Json("-1", JsonRequestBehavior.AllowGet); } var args = JsonConvert.DeserializeObject<RequestData>(reqstr); args.user = getOpenId(args.device_type, args.device_id); Session["warmwood"] = args.device_id; //args.services.air_conditioner = null; args.services.power_switch = null; args.services.lightbulb.value_range = null; try { var res = wxDeviceService.RequestDeviceStatus(getToken(), args); if (res.error_code != 0) { Logger.Debug("error_code:" + res.error_code); Logger.Debug("error_msg:" + res.error_msg); } return Json(res, JsonRequestBehavior.AllowGet); } catch (ErrorJsonResultException e) { if (e.JsonResult.errcode.ToString() == "access_token expired") { //從新獲取token } Logger.Debug("請求失敗:" + e.Message); } return Json("-1", JsonRequestBehavior.AllowGet); }
這個方法先將字符串轉成咱們的RequestData對象,RequestData以下:
public class RequestData { public string device_type { get; set; } public string device_id { get; set; } public string user { get; set; } public Service services { get; set; } public object data { get; set; } }
services就是根據微信services定義的,能夠參考上一節,而後用wxDeviceService請求。
var res = wxDeviceService.RequestDeviceStatus(getToken(), args); if (res.error_code != 0) { Logger.Debug("error_code:" + res.error_code); Logger.Debug("error_msg:" + res.error_msg); } return Json(res, JsonRequestBehavior.AllowGet);
設置以後立刻會受到是否設置成功的響應,error_code 可能爲50019(設置頻繁),50013(網絡問題)等等。真正的設備狀態是經過getdata得到的。
public JsonResult GetData() { var userdata = getUserWxData(); return Json(userdata.ResponseData, JsonRequestBehavior.AllowGet); }
getdata比較簡單就是返回數據,可是這個數據是在ReceiveWXMsg方法中設置的。這個上一節也講過,這是在公衆號後臺咱們設置的一個地址。
public string ReceiveWXMsg() { //somecode try { var userdata = getUserWxData(); var data = wxDeviceService.GetDeviceStatus(Request); userdata.ResponseData = data; Logger.Debug("ResponseData.asy_error_code:" + userdata.ResponseData.asy_error_code); Logger.Debug("ResponseData.asy_error_msg:" + userdata.ResponseData.asy_error_msg); setUserWxData(userdata); } catch (Exception e) { Logger.Debug(e.Message); } return echostr; }
wxDeviceService以下:
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Web; using Newtonsoft.Json; using Niqiu.Core.Domain.Common; using Senparc.Weixin; using Senparc.Weixin.Exceptions; using SendHelp= Senparc.Weixin.CommonAPIs.CommonJsonSend; namespace Portal.MVC.WXDevice { public class WxDeviceService:IWxDeviceService { //private readonly ICacheManager _cacheManager; //public WxDeviceService(ICacheManager cacheManager) //{ // _cacheManager = cacheManager; //} public TokenResult GetAccessToken() { var url = string.Format(WxDeviceConfig.AccessTokenUrl, WxDeviceConfig.AppId, WxDeviceConfig.APPSECRET); var res = SendHelp.Send<TokenResult>(null, url, null, CommonJsonSendType.GET); return res; } public WxResponseData GetDeviceStatus(HttpRequestBase request) { Stream postData = request.InputStream; StreamReader sRead = new StreamReader(postData); string postContent = sRead.ReadToEnd(); if (!string.IsNullOrEmpty(postContent)) { Logger.Debug("收到數據:" + postContent); } try { var data = JsonConvert.DeserializeObject<WxResponseData>(postContent); data.rawStr = postContent; Logger.Debug("轉換消息狀態:" + data.asy_error_msg); return data; } catch (Exception e) { Logger.Debug(e.Message); throw; } } public OpenApiResult RequestDeviceStatus(string accessToken, RequestData data) { var url = string.Format(WxDeviceConfig.GetDeviceStatusUrl, accessToken); return SendHelp.Send<OpenApiResult>(accessToken, url, data); } public OpenApiResult SetDevice(string accessToken, RequestData data) { var url = string.Format(WxDeviceConfig.GetDeviceStatusUrl, accessToken); return SendHelp.Send<OpenApiResult>(accessToken, url, data); } public string GetOpenId(string accessToken,string deviceType,string deviceId) { try { var url = string.Format(WxDeviceConfig.GetOpenid, accessToken, deviceType, deviceId); var res = SendHelp.Send<OpenIdResult>(accessToken, url, null, CommonJsonSendType.GET); return res.GetOpenId(); } catch (ErrorJsonResultException e) { Logger.Debug(e.Message); throw; } } } }
這方法讀到數據後就交給了userdata 緩存起來。在getdata方法中返回。
private UserWxData getUserWxData() { var target = _cacheManager.Get<UserWxData>(userKey) ?? new UserWxData(); return target; } private string userKey { get { var key = Session["warmwood"] ?? Session.SessionID; Session.Timeout = 240; return key.ToString(); } }
UserWxData是我自定義的對象,包含了下面的幾個熟悉。
public class UserWxData { private WxResponseData _responseData; public UserWxData() { CreateTime = DateTime.Now; } public DateTime CreateTime { get; set; } public TokenResult AccessToken { get; set; } public WxResponseData ResponseData { get { return _responseData??(_responseData=new WxResponseData()); } set { _responseData = value; } } public string OpenId { get; set; } }
比較重要的是token和responseData。WxResponseData 也就是最終要發給頁面上的對象。包含你須要的功能的參數。
public class WxResponseData { public int asy_error_code { get; set; } public string asy_error_msg { get; set; } public string create_time { get; set; } public string msg_id { get; set; } /// <summary> /// notify 說明是設備變動 /// set_resp 說明是設置設備 /// get_resp 說明獲取設備信息 /// </summary> public string msg_type { get; set; } public string device_type { get; set; } public string device_id { get; set; } public object data { get; set; } public Service services { get; set; } public string user { get; set; } public string rawStr { get; set; } }
severices看本身的設備定義,好比我如今包含了空調,開關,溫度溼度。
public class Service { public lightbulb lightbulb { get; set; } public air_conditioner air_conditioner { get; set; } public power_switch power_switch { get; set; } public operation_status operation_status { get; set; } public tempe_humidity tempe_humidity { get; set; } }
到這兒,整個過程就講完了,獲取token和openid上一節講過,就不贅述了。若是後端是node的話,就不須要這麼多的類型轉換了。
最後能夠看下效果:
小結:以上就這一篇的所有內容,流程圖畫可能畫的不夠好,但這都是摸索出來的結果,微信硬件雖然提供了自定義的連接設置,可是沒有提供demo。文中有不對或者不合適的地方歡迎拍磚,歡迎加Q交流。