微信公众号硬件开发杂谈(一)

时间:2022-08-31 22:45:16

最近帮朋友研究一个单片机的项目,简单接触了一下微信公众号的硬件平台,遇到很多问题,简单记录一下

该怎么连接

准备工作

首先不管用什么接口,做硬件和软件的交互一般还是先想着怎么去做一个基础的连接操作,最开始查到一些教程并参考微信官方的说法就是先申请设备接入的权限,以测试号来说,申请非常简单,如图所示

微信公众号硬件开发杂谈(一)

申请完成后,点击【设置】进入设备管理页面,按照提示一步步操作就可以添加设备,我这边是使用的是第三方的硬件设备,通过wifi进行连接,根据相关文档进行如下设置,具体选项内容应该要根据硬件设备类型来定。

微信公众号硬件开发杂谈(一)

经过简单的设置就可以生成一个设备,可以在设备列表看到,另外提一下,似乎一直都存在一个意义不明的undefined设备,应该是平台开发人员保留的,不影响使用

微信公众号硬件开发杂谈(一)

常规方法

通过这种方式新建的设备默认拥有100个配额,代表能授权100个可以控制的设备,我这里的截图中是已经授权了一个设备,授权需要通过接口发送请求,具体接口内容在后面详述。之后开始说第一次连接这个重要的问题,但是我这边并不方便验证结果,在这里说下最后的实践结果。一些教程会提到通过扫描产品详情里的二维码就可以执行连接操作,但对于普通测试情况并不符合。以下是通常情况的操作流程:

  • 在产品配置中连接方式选择wifi
  • 进行设备授权(不确定是否必要)
  • 扫描产品详情中的二维码会调起Airkiss页面(现在先简单理解成一个输入你当前连接wifi的密码的页面)
  • 打开硬件设备的AirLink模式,并在手机上输入wifi密码进行连接
  • 连接成功后,会跳转至一个搜索设备的页面

微信公众号硬件开发杂谈(一)

微信公众号硬件开发杂谈(一)1hcjud.jpg)

微信公众号硬件开发杂谈(一)

问题就出在最后一步,我在这里进行了反复的测试,前置的配置操作重复了很多遍,还换过开发板,在这里都是搜索不到设备,经过大量的查找资料发现如果想通过这种方式扫描到设备必须在设备芯片的固件中写入公众号的ID,并且我后来也在设备详情中发现了相关依据

微信公众号硬件开发杂谈(一)

虽然找到了问题,但是固件的编写是个很大的问题,简单的烧录可以做到,但修改编译固件的代码却是不懂,所幸后面发现了另一种方法,但是由于最终使用的固件版本是第三方平台机智云的,所以无法验证其他的情况是否可以,这里主要是记录一下开发的过程。

JS-SDK

上面介绍的方法在扫码后就会自动跳出一个页面,这个是微信官方提供的,如果不想使用这个页面的话就可以通过JS-SDK的方式。

在进行下一步操作之前我们还要了解一下这个跳出的页面是什么,官方的描述如下:

Airkiss是微信硬件平台为Wi-Fi设备提供的微信配网、局域网发现和局域网通讯的技术。开发者若要实现通过微信客户端对Wi-Fi设备配网、通过微信客户端在局域网发现Wi-Fi设备,或者把微信客户端内的音乐、图片、文件等消息通过局域网发送至Wi-Fi设备,需要在硬件设备中集成相应的AirKiss静态库。

通过这句话我们可以知道要想在局域网内发现设备就可以通过这一方法,更详细的说明可以看这个页面
AirKiss概述及应用场景

根据Airkiss2.0的文档我们可以知道通过JSAPI可以进行配网,以达到我们的目的。首先在Web层引用微信JSAPI,JSAPI技术是微信JS-SDK的一部分,,主要是通过 wx.readywx.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配置会返回提示消息。