微信硬件H5面板開發(二) ---- 實現一個燈的控制

第一節中講解了openApi的調用,這一篇講一下如何實現一個燈的控制。就用微信提供的lamp例子來作,將代碼扒下來(實在是沒辦法,沒有示例),整合到本身的項目中。lamp源碼:http://files.cnblogs.com/files/stoneniqiu/lamp.zipcss

你能夠本身扒,帶參數的頁面在瀏覽器中打開會立刻跳轉,不帶參數的會提示參數不全,須要用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>
View Code

本身的實現就拿掉了遮罩和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);
    }
})
View Code

 我將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中在綁定)以後,再進入循環。

setdata

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得到的。

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;
            }
        }
    }
}
View Code

這方法讀到數據後就交給了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();
            }
        }
View Code

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交流。

相關文章
相關標籤/搜索