关键词:
在第一节中讲解了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 = 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> <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为了调试,修改调用路径。
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中在绑定)之后,再进入循环。
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; } } } }
这方法读到数据后就交给了userdata 缓存起来。在getdata方法中返回。
微信小程序控制硬件17分享腾讯物联平台腾讯连连小程序蓝牙设备h5自定义面板开发的那些坑,支持控制安信可tb/pb蓝牙模组esp32/c3/s3模组,让你少走弯路。(附带源码)(代码片段)
...步骤3.1准备工作3.2启动3.3启动Demo项目的实时编译3.4配置微信开发者工具3.5:微信开发者工具打开H5面板四、蓝牙设备H5开发页面介绍1.设备搜索页2.设备面板页开发流程1.搜索页2.面板页五、FAQ常见问题六、开源微信物联网控制... 查看详情
微信小程序控制硬件16安信可esp32-s开发板实现移植腾讯物联开发平台蓝牙llsync协议,实现一键蓝牙快速配网+远程控制。(附带源码)(代码片段)
...言二、源码目录说明三、编译指导四、常见问题五、开源微信物联网控制一览表另外,不要把我的博客作为学习标准,我的只是笔记,难有疏忽之处,如果有,请指出来,也欢迎留言哈! 微信物联... 查看详情
stm32实现led灯的闪烁
...号是stm32f03ZET6。 首先,我们要对LED做一个基本的硬件了解。LED,中文名称,发光二极管。它是一种发光器件。LED通常情况下只有两个引脚,一个正极,一 查看详情
homeassistant自动化实践(一)
...化:要完成如上需求,我需要在HA中接入一些设备,例如硬件准备完毕后,开始配置HA,首先抽象出一个“控制面板”,对于开关灯这件小事,我觉得三个控制开关就足够了:自动开灯,自动关灯,以及自动关灯的延时时间设置... 查看详情
#物联网征文#ffhhi3516dv300驱动开发——编写led灯控制程序(代码片段)
...发一个设备驱动,以及如何在应用层调用驱动。开发环境硬件平台:润和AI_Camera 查看详情
微信小程序控制硬件第16篇:分享一个基于微信airkiss配网的微信小程序,摆脱腾讯物联平台sdk的束缚,实现一键配网安信可wifi模组。(附带源码)(代码片段)
...airkiss简介三、开始使用四、API说明五、FAQ六、本人开源微信物联网控制一览表另外,不要把我的博客作为学习标准,我的只是笔记,难有疏忽之处,如果有,请指出来,也欢迎留言哈! 微信物联网... 查看详情
微信小程序控制硬件第16篇:分享一个基于微信airkiss配网的微信小程序,摆脱腾讯物联平台sdk的束缚,实现一键配网安信可wifi模组。(附带源码)(代码片段)
...airkiss简介三、开始使用四、API说明五、FAQ六、本人开源微信物联网控制一览表另外,不要把我的博客作为学习标准,我的只是笔记,难有疏忽之处,如果有,请指出来,也欢迎留言哈! 微信物联网... 查看详情
微信小程序控制硬件16安信可esp32-s开发板实现移植腾讯物联开发平台蓝牙llsync协议,实现一键蓝牙快速配网+远程控制。(附带源码)(代码片段)
...言二、源码目录说明三、编译指导四、常见问题五、开源微信物联网控制一览表另外,不要把我的博客作为学习标准,我的只是笔记,难有疏忽之处,如果有,请指出来,也欢迎留言哈! 微信物联... 查看详情
求教thinkpadfn键灯的打开与关闭
...电脑开始菜单,打开控制面板。2、在控制面板中打开【硬件和声音】。3、点击【设备和打印机】项下的【鼠标】。4、打开鼠标属性后,选择【UltraNav】选项卡,点击取消勾选【启用TouchPad】选项关闭触摸板。参考技术AFn+Esc是Fn... 查看详情
基于h5的微信支付开发详解
这次总结一下用户在微信内打开网页时,可以调用微信支付完成下单功能的模块开发,也就是在微信内的H5页面通过jsApi接口实现支付功能。当然了,微信官网上的微信支付开发文档也讲解的很详细,并且有实现代码可供参考,... 查看详情
微信小程序控制硬件第17篇:腾讯连连小程序通过llsync蓝牙协议控制安信可pb-02模组,无需网络实现蓝牙本地通讯。(附带源码)(代码片段)
...环境工程目录烧录步骤三、腾讯连连小程序调试本人开源微信物联网控制一览表另外,不要把我的博客作为学习标准,我的只是笔记,难有疏忽之处,如果有,请指出来,也欢迎留言哈! 微信物联... 查看详情
微信小程序控制硬件第16篇:分享一个基于微信airkiss配网的微信小程序,摆脱腾讯物联平台sdk的束缚,实现一键配网安信可wifi模组。(附带源码)(代码片段)
...airkiss简介三、开始使用四、API说明五、FAQ六、本人开源微信物联网控制一览表另外,不要把我的博客作为学习标准,我的只是笔记,难有疏忽之处,如果有,请指出来,也欢迎留言哈! 微信物联网... 查看详情
stm32怎么实现单片机控制led灯常亮10s后熄灭?
首先实现LED灯的点亮和熄灭,控制连接LED灯的管脚输出高低电平就可以实现。如果电流比较大可以增加三极管驱动电路。10秒定时可以用定时器实现,设置一个1秒的定时器。上电点亮LED灯,并开始计时,10秒时间到熄灭LED就可以... 查看详情
微信公众号怎样直接跳转h5页面?
点击公众号直接跳转目标链接的H5页面,是微信的商品信息功能(商品消息:是特殊类型的图文消息,用户打开后跳转到商户网站的商品详情页,不经过公众平台中间页 ),14年底已经取消了,之前开通这个功能的就可以实... 查看详情
微信小程序控制硬件第17篇:腾讯连连小程序通过llsync蓝牙协议控制安信可pb-02模组,无需网络实现蓝牙本地通讯。(附带源码)(代码片段)
...环境工程目录烧录步骤三、腾讯连连小程序调试本人开源微信物联网控制一览表另外,不要把我的博客作为学习标准,我的只是笔记,难有疏忽之处,如果有,请指出来,也欢迎留言哈! 查看详情
微信公众号输入关键字后返回一个h5分享链接怎么实现?
微信公众号输入关键字后返回一个H5分享链接怎么实现,就像这个图片上的效果,比如输入关键字“123”,返回一个带样式的分享链接,而不是单纯的一个URL字符串;参考技术A在微信公众号自动回复栏目的关键词回复设置相关关... 查看详情
uniapp微信小程序外壳内联h5实现支付(代码片段)
业务场景:用户有现成的微信H5应用(有微信支付)。用户想要一个一摸一样的小程序版本,但是又不想高成本去重新开发,所以可以考虑采用小程序的web-view组件内联现有的微信H5应用(哇简直不要再偷懒... 查看详情