在中讲解了 openApi 的调用,这一篇讲一下如何实现一个灯的控制。就用微信提供的 lamp 例子来做,将代码扒下来 (实在是没办法,没有示例),整合到自己的项目中。lamp 源码:。
你可以自己扒,在浏览器中打开会马上跳转,的会提示参数不全,需要用 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 */
首先我们看到 pageParam 对象是获取页面上参数的,device_id,device_type 以及 appid 三个参数。其实有用的只有前面两个,因为 appid 的话,后台服务器已经配置了,而且在微信中的通过 "进入面板" 的时候只附带了 id 和 type 两个参数。然后 device_status 是一个设备状态对象对象是灯,根据微信的定义,灯有一个亮度值。这个在上一篇提到过。然后是一个立即执行的匿名函数,这个函数函数里面会先检查一下参数,然后初始化开关和亮度条。最好进入循环。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:
View Code
- @ {
- 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 为了调试,修改调用路径。
View Code
- 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 如下:
View Code
- 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(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(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(accessToken, url, data);
- }
- public OpenApiResult SetDevice(string accessToken, RequestData data) {
- var url = string.Format(WxDeviceConfig.GetDeviceStatusUrl, accessToken);
- return SendHelp.Send(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(accessToken, url, null, CommonJsonSendType.GET);
- return res.GetOpenId();
- } catch(ErrorJsonResultException e) {
- Logger.Debug(e.Message);
- throw;
- }
- }
- }
- }
这方法读到数据后就交给了 userdata 缓存起来。在 getdata 方法中返回。
View Code
- private UserWxData getUserWxData() {
- var target = _cacheManager.Get(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 交流。
来源: http://www.cnblogs.com/stoneniqiu/p/5840820.html