조명 제어를 위한 WeChat 하드웨어 H5 개발
这次给大家带来微信硬件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 */
首先我们看到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> <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>
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的话,就不需要这么多的类型转换了。
最后可以看下效果:
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
위 내용은 조명 제어를 위한 WeChat 하드웨어 H5 개발의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











HTML의 테이블 테두리 안내. 여기에서는 HTML의 테이블 테두리 예제를 사용하여 테이블 테두리를 정의하는 여러 가지 방법을 논의합니다.

HTML 여백-왼쪽 안내. 여기에서는 HTML margin-left에 대한 간략한 개요와 코드 구현과 함께 예제를 논의합니다.

HTML의 Nested Table에 대한 안내입니다. 여기에서는 각 예와 함께 테이블 내에 테이블을 만드는 방법을 설명합니다.

HTML 테이블 레이아웃 안내. 여기에서는 HTML 테이블 레이아웃의 값에 대해 예제 및 출력 n 세부 사항과 함께 논의합니다.

HTML 입력 자리 표시자 안내. 여기서는 코드 및 출력과 함께 HTML 입력 자리 표시자의 예를 논의합니다.

HTML 순서 목록에 대한 안내입니다. 여기서는 HTML Ordered 목록 및 유형에 대한 소개와 각각의 예에 대해서도 설명합니다.

HTML에서 텍스트 이동 안내. 여기서는 Marquee 태그가 구문과 함께 작동하는 방식과 구현할 예제에 대해 소개합니다.

HTML onclick 버튼에 대한 안내입니다. 여기에서는 각각의 소개, 작업, 예제 및 다양한 이벤트의 onclick 이벤트에 대해 설명합니다.
