WeChat hardware H5 development to control lights

php中世界最好的语言
Release: 2018-03-16 14:27:00
Original
3689 people have browsed it

这次给大家带来微信硬件H5开发之控制灯光,微信硬件H5开发控制灯光的注意事项有哪些,下面就是实战案例,一起来看一下。

你可以自己扒,带参数的页面在浏览器中打开会马上跳转,不带参数的会提示参数不全,需要用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 = 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 */
Copy after login

 首先我们看到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();
    }
Copy after login

 这个作用有两个,一个是获取设备目前的状态,因为设备可能没有开启,或者没有联网,二个是将参数传递给后台,不然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>
    <p>
        <p class="body">
            <p class="inner">
                <p id="switchBtn" class="status_button off">
                    <p class="button_wrp">
                        <p class="button_mask">
                            <p class="alerter_button" id="powerBtn">
                                <i class="status_pot"></i>
                                <span class="on">ON</span>
                                <span class="off">OFF</span>
                            </p>
                        </p>
                    </p>
                    <p class="on">
                        <h2>灯已开</h2>
                    </p>
                </p>
                <p id="reData"></p>
            </p>
        </p>
        <p class="foot">
            <p class="slider_box J_slider_box">
                <i class="slider_box_icon icon dark"></i>
                <p id="lightBar" class="slider_box_bar">
                    <p 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>
                    </p>
                    <p class="slider_box_line">
                        <span class="slider_box_line_fill J_fill" style="width:0%"></span>
                    </p>
                </p>
                <i class="slider_box_icon icon light"></i>
            </p>
        </p>
    </p>
    <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>
Copy after login

View Code

自己的实现就拿掉了遮罩和config部分,将sea.js的目录改到自己对应的目录即可:

   seajs.config({
            base: '/js/',            //map: [[/^(.*\.(?:css|js))(.*)$/i, "$1"]],
            charset: 'utf-8'
        });
        seajs.use("baby");
Copy after login

这个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);
    }
})
Copy after login

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: '',
    };
Copy after login

后台就是两个主要方法,一个设置(查询页就是设置),一个读取。这里又回到上一节的内容了。我先查询一次设备(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);
        }
Copy after login

这个方法先将字符串转成我们的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; }
    }
Copy after login

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);
Copy after login

设置之后马上会受到是否设置成功的响应,error_code 可能为50019(设置频繁),50013(网络问题)等等。真正的设备状态是通过getdata获得的。

getdata

        public JsonResult GetData()
        {            var userdata = getUserWxData();            return Json(userdata.ResponseData, JsonRequestBehavior.AllowGet);
        }
Copy after login

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;
        }
Copy after login

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;
            }
        }
    }
}
Copy after login

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();
            }
        }
Copy after login

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; }
    }
Copy after login

比较重要的是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; }
    }
Copy after login

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; }
    }
Copy after login

到这儿,整个过程就讲完了,获取token和openid上一节讲过,就不赘述了。如果后端是node的话,就不需要这么多的类型转换了。

最后可以看下效果:

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

JS里特别好用的轻量级日期插件

JavaScript关于IE8兼容问题的处理

The above is the detailed content of WeChat hardware H5 development to control lights. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template