首页 > 代码库 > 微信硬件H5面板开发(二) ---- 实现一个灯的控制

微信硬件H5面板开发(二) ---- 实现一个灯的控制

在第一节中讲解了openApi的调用,这一篇讲一下如何实现一个灯的控制。就用微信提供的lamp例子来做,将代码扒下来(实在是没办法,没有示例),整合到自己的项目中。lamp源码:http://files.cnblogs.com/files/stoneniqiu/lamp.zip。

你可以自己扒,带参数的页面在浏览器中打开会马上跳转,不带参数的会提示参数不全,需要用mobile模式观看。

呈现的界面如下:

技术分享

目录结构 

解压开lamp.js ,目录如下,这个demo是基于sea.js+zepto实现,sea.js用来加载模块,zepto提供ajax请求和tab事件等。

技术分享

common中包含了一个keyConfig.js(地址参数),一个reqData.js(请求封装)还有一个zepto,ui里是一个上面图片的中的slider一样的组件。util中是一组方法集合。最重要的就是lamp.js 。

define(function (require) {    var $ = require("common/zepto");    var keyConfig = require("common/keyConfig");    var reqData = http://www.mamicode.com/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,            one rror: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,这个不用解释了。注意到在绑定开关时间的地方,其实是先调用了一次setdata

 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-->延时读取。 加上后端的部分,全部的流程图如下。

技术分享

所以拿到前端代码只是一半,后端还需要自己实现。

实现

纯静态文件是无法请求微信服务器的,所以我们需要自己实现后台的部分,这也是第一节中要讲的目的。

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 =http://www.mamicode.com/ {        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 =http://www.mamicode.com/ {        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 =http://www.mamicode.com/ getUserWxData();            return Json(userdata.ResponseData, JsonRequestBehavior.AllowGet);        }

getdata比较简单就是返回数据,但是这个数据是在ReceiveWXMsg方法中设置的。这个上一节也讲过,这是在公众号后台我们设置的一个地址。

   public string ReceiveWXMsg()        {            //somecode            try            {                var userdata =http://www.mamicode.com/ getUserWxData();                var data =http://www.mamicode.com/ 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 = http://www.mamicode.com/JsonConvert.DeserializeObject(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=http://www.mamicode.com/new WxResponseData()); }            set { _responseData =http://www.mamicode.com/ 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交流。

 

微信硬件H5面板开发(二) ---- 实现一个灯的控制