最近帮朋友研究一个单片机的项目,简单接触了一下微信公众号的硬件平台,遇到不少问题,简单记录一下javascript
首先无论用什么接口,作硬件和软件的交互通常仍是先想着怎么去作一个基础的链接操做,最开始查到一些教程并参考微信官方的说法就是先申请设备接入的权限,以测试号来讲,申请很是简单,如图所示html
申请完成后,点击【设置】进入设备管理页面,按照提示一步步操做就能够添加设备,我这边是使用的是第三方的硬件设备,经过wifi进行链接,根据相关文档进行以下设置,具体选项内容应该要根据硬件设备类型来定。java
通过简单的设置就能够生成一个设备,能够在设备列表看到,另外提一下,彷佛一直都存在一个意义不明的undefined设备,应该是平台开发人员保留的,不影响使用算法
经过这种方式新建的设备默认拥有100个配额,表明能受权100个能够控制的设备,我这里的截图中是已经受权了一个设备,受权须要经过接口发送请求,具体接口内容在后面详述。以后开始说第一次链接这个重要的问题,可是我这边并不方便验证结果,在这里说下最后的实践结果。一些教程会提到经过扫描产品详情里的二维码就能够执行链接操做,但对于普通测试状况并不符合。如下是一般状况的操做流程:json
问题就出在最后一步,我在这里进行了反复的测试,前置的配置操做重复了不少遍,还换过开发板,在这里都是搜索不到设备,通过大量的查找资料发现若是想经过这种方式扫描到设备必须在设备芯片的固件中写入公众号的ID,而且我后来也在设备详情中发现了相关依据api
虽然找到了问题,可是固件的编写是个很大的问题,简单的烧录能够作到,但修改编译固件的代码倒是不懂,所幸后面发现了另外一种方法,可是因为最终使用的固件版本是第三方平台机智云的,因此没法验证其余的状况是否能够,这里主要是记录一下开发的过程。浏览器
上面介绍的方法在扫码后就会自动跳出一个页面,这个是微信官方提供的,若是不想使用这个页面的话就能够经过JS-SDK的方式。服务器
在进行下一步操做以前咱们还要了解一下这个跳出的页面是什么,官方的描述以下:微信
Airkiss是微信硬件平台为Wi-Fi设备提供的微信配网、局域网发现和局域网通信的技术。开发者若要实现经过微信客户端对Wi-Fi设备配网、经过微信客户端在局域网发现Wi-Fi设备,或者把微信客户端内的音乐、图片、文件等消息经过局域网发送至Wi-Fi设备,须要在硬件设备中集成相应的AirKiss静态库。
经过这句话咱们能够知道要想在局域网内发现设备就能够经过这一方法,更详细的说明能够看这个页面
AirKiss概述及应用场景微信开发
根据Airkiss2.0的文档咱们能够知道经过JSAPI能够进行配网,以达到咱们的目的。首先在Web层引用微信JSAPI,JSAPI技术是微信JS-SDK的一部分,,主要是经过 wx.ready 和 wx.config 这两个函数调用官方的接口,具体说明参考
JSAPI介绍
使用Airkiss文档里的configWXDeviceWiFi这个方法就能够进行自定义的AirKiss链接,这里我使用的框架是ASP.NET MVC,因此能够看到AppID、timestamp、nonceStr、signature这几个参数是从后台传入的,具体的后台生成代码放在下面参考,有些参数须要自行传入,注意修改。
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script> <script type="text/javascript"> $(function () { }); $('#btnLink').click(function () { wx.config({ beta: true, debug: true, // 开启调试模式,调用的全部api的返回值会在客户端alert出来,若要查看传入的参数,能够在pc端打开,参数信息会经过log打出,仅在pc端时才会打印。 appId: '@ViewBag.appId', // 必填,公众号的惟一标识 timestamp: '@ViewBag.timeStamp', // 必填,生成签名的时间戳 nonceStr: '@ViewBag.randomStr', // 必填,生成签名的随机串 signature: '@ViewBag.signatur',// 必填,签名 jsApiList: ["configWXDeviceWiFi"] // 必填,须要使用的JS接口列表 }); wx.invoke('configWXDeviceWiFi'); }); </script>
C#后台代码参考
public class AirkissHelper { public static string appID = BaseConfig.appID; public static string appsecret = BaseConfig.appsecret; class TokenResultMessage // 封装调用access_token接口返回的数据 { public string access_token; public string expires_in; } class JsapiResultMessage // 封装调用jsapi_ticket接口返回的数据 { public string errcode; public string errmsg; public string ticket; public string expires_in; } /// <summary> /// Get请求封装 /// </summary> /// <param name="url"></param> /// <returns></returns> private static string GetWebUrl(string url) { WebClient client = new WebClient(); // 建立浏览器 Stream stream = client.OpenRead(url); // 传入url地址 return new StreamReader(stream).ReadToEnd(); // 获得响应字符串 } /// <summary> /// sha1签名算法 /// </summary> /// <param name="str"></param> /// <returns></returns> private static string Sha1(string str) { var sha1 = System.Security.Cryptography.SHA1.Create(); byte[] bytes = Encoding.UTF8.GetBytes(str); byte[] bytesArr = sha1.ComputeHash(bytes); StringBuilder sb = new StringBuilder(); foreach (var item in bytesArr) { sb.AppendFormat("{0:x2}", item); } return sb.ToString(); } /// <summary> /// 生成时间戳 /// </summary> public static string GetTimeStamp() { TimeSpan ts = DateTime.UtcNow - new DateTime(1970, 1, 1); return Convert.ToInt64(ts.TotalSeconds).ToString(); } /// <summary> /// 生成32位随机字符串 /// </summary> public static string GetRandomStr() { string strArr = "0123456789QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"; Random rand = new Random(); string randStr = ""; for (int i = 0; i < 32; i++) { int index = rand.Next(strArr.Length); randStr += strArr.Substring(index, 1); } return randStr; } /// <summary> /// 生成签名 /// </summary> public static string GetSignatur(string timestamp,string nonceStr) { // 经过API获取access_token string tokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appID + "&secret=" + appsecret; var tokenInfo = new JavaScriptSerializer().Deserialize<TokenResultMessage>(GetWebUrl(tokenUrl)); // 经过API获取jsapi_ticket string jsapiUrl = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + tokenInfo.access_token + "&type=jsapi"; var jsapiInfo = new JavaScriptSerializer().Deserialize<JsapiResultMessage>(GetWebUrl(jsapiUrl)); // 当前网页的URL,不包含#及其后面部分 string nowUrl = BaseConfig.nowUrl; //string nowUrl = Request.Url.AbsoluteUri; // 部署服务器后应动态获取页面url // 用Dictionary集合存储各变量 Dictionary<string, string> dic = new Dictionary<string, string> { ["timestamp"] = timestamp, ["noncestr"] = nonceStr, ["url"] = nowUrl, ["jsapi_ticket"] = jsapiInfo.ticket }; // 对变量名字典排序 string[] arrKey = new string[] { "timestamp", "noncestr", "url", "jsapi_ticket" }; arrKey = arrKey.OrderBy(n => n).ToArray(); // 拼接字符串 string signatureStr = ""; foreach (var item in arrKey) { signatureStr += item + "=" + dic[item] + "&"; } signatureStr = signatureStr.Substring(0, signatureStr.Length - 1); // 对拼接串sh1签名,获得最终签名 return Sha1(signatureStr); } }
作好配置部分的代码后,在页面上使用 wx.invoke('configWXDeviceWiFi'); 就能够手动调起Airkiss页面,输入wifi密码后手动链接,根据wx.config里面的debug配置会返回提示消息。
在上一篇中咱们说到要使用设备首先要进行受权操做,设备完成受权以后就能够经过接口来作一些操做,受权接口的具体参数官方说明看这个地址
设备受权
入参中的AccessToken若是不明白的要看下微信开发的基础部分,参数body中id为deviceid,调试的时候mac地址和id都要向硬件厂商索要,authkey这些参数先能够自行填写,等有了再填入正确的。“connect_protocol”这个参数填4,由于我们进行的是wifi设备开发,close_strategy这个是断开策略,请注意。下面auth_ver选0,不加密,与上面对应,op_type这个参数注意,为0时设备受权,为1时设备更新,完成后检查问题,返回正确参数deviceid和device_type,除了返回正确的入参,在公众号的产品列表中也能够看到受权配额-1,这个过程是不可逆的,已经受权的配额就不能再取消,可是能够经过“设备状态查询”接口查询到三种状态(未受权、已受权、已绑定)。
若是没有设备想模拟在线,可使用设备受权新接口,还会返回了qrticket,把这个字符串放到二维码生成器(随便搜索一下就有)中,会返回一个二维码,扫码便可绑定设备,而后再经过 第三方主动发送设备状态消息给微信终端 模拟状态
{ "device_type": "公众号原始ID", "device_id": "XXX", "open_id": "XXX", "msg_type": " 2", "device_status": " 1" }
测试公众号能够在页面右上角看到原始ID,device_id和open_id都是基础参数,状态这里填“1”就是在线状态,接口请求成功就会在公众号的名称下面看到一个“已链接”的状态。
在调试过程当中咱们可使用微信的接口调试工具或者Postman进行接口测试,但在产品实际应用中仍是要实现HTTP请求的代码,这里贴一下帮助类的示例
/// <summary> /// 获取html /// </summary> /// <param name="url">url</param> /// <param name="encoding">encoding</param> /// <returns>HttpResult</returns> HttpResult _Get(string url, Encoding encoding) { HttpResult result = new HttpResult(); try { var response = HttpClient.GetAsync(url); result.result = true; result.html = response.Result.Content.ReadAsStringAsync().Result; // .Content.ReadAsStringAsync(); } catch (Exception ex) { result.result = false; result.html = "转发接口失败," + ex.Message; } return result; } /// <summary> /// 获取html /// </summary> /// <param name="url"></param> /// <param name="paramss"></param> /// <param name="encoding"></param> /// <param name="jsonstring"></param> /// <returns></returns> private HttpResult _Post(string url, Dictionary<string, string> paramss, Encoding encoding, string jsonstring = "") { HttpResult r = new HttpResult(); HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url); req.Proxy = null; try { string param = string.Empty; if (string.IsNullOrWhiteSpace(jsonstring)) { foreach (string p in paramss.Keys) { param += p + "=" + paramss[p] + "&"; } req.ContentType = "application/x-www-form-urlencoded"; } else { param = jsonstring; req.ContentType = "application/json"; } foreach (var headerParam in paramss) { req.Headers.Add(headerParam.Key, headerParam.Value); } byte[] bs = Encoding.UTF8.GetBytes(param); string responseData = string.Empty; req.Method = "POST"; req.ContentLength = bs.Length; using (Stream reqStream = req.GetRequestStream()) { reqStream.Write(bs, 0, bs.Length); reqStream.Close(); } using (HttpWebResponse response = (HttpWebResponse)req.GetResponse()) { using (StreamReader reader = new StreamReader(response.GetResponseStream(), encoding)) { responseData = reader.ReadToEnd().ToString(); } r.result = true; r.html = responseData; } } catch (Exception e) { r.html = e.ToString(); if (req != null) { req.Abort(); } return r; } return r; } public class HttpResult { public bool result; public Task<string> htmlasync; public string html; public int stateCode; }
Get请求结构较为简单,须要注意的是Post请求中,根据接口的不一样,参数会出现不一样的位置,在url、header和body中均有可能须要传参。
之前知道有这么个东西,可是具体有什么做用,特别在哪是不太清楚,或者为何要用这个东西,但经过此次接触微信硬件的开发大概理解了一点,首先要明确WebSocket是一种与HTTP不一样的协议,引用维基百科的说明以下:
服务器能够经过标准化的方式来实现,而无需客户端首先请求内容,并容许消息在保持链接打开的同时来回传递。经过这种方式,能够在客户端和服务器之间进行双向持续对话。
简单来讲他可让客户端和服务器之间保持一个低耗的长链接,那么这个和硬件开发有什么关系呢?
最初我只是知道智能家居经过wifi远程控制,因此想单片机应该也能够经过一样的原理实现,利用一个业务服务器接受用户的操做,而后服务器发送请求给家里的路由器,再由路由器转发给家里的设备,既然是这样,就转化成了我说属性的HTTP请求,中间的传输无非就是POST请求发送json格式的数据。
想来是挺简单的,可是虽然开发的深刻,发现硬件设备是一个发信器,它只向外请求并接收返回的结果,可是当路由器想发送接收自客户端的设备状态数据时殊不知道发送给谁,这样引发的一个状况是若是要想实现实时的设备控制,须要设备不停地向服务器发送GET请求来获取当前的状态,这样就造成的极大的浪费,这样的轮询过程当中大部分的数据可能都是无效的,好比请求头中的一些必要条件,但实际要传输的状态数据每每就只是一个字符串。
在WebSocket出现以前,也确实都是这样作的,但自从有了WebSocket,硬件设备就能够和服务器之间创建一个长链接,这也就是咱们在前面所实现“上线”操做,实际上就是打开这一长链接。
此次作硬件的研究断断续续持续了好久,其中真的是遇到了至关多的问题,尤为是在不了解的领域中,可是随着问题一个个被克服,有让我感觉到了最初的那种解决问题的快乐,由于问题比较零碎,并且本身的理解还不到位,因此其中不少问题不能造成段落记录下来比较惋惜,可是整个开发的过程和结果都是至关使人难忘的。