微信公衆號硬件開發雜談

最近幫朋友研究一個單片機的項目,簡單接觸了一下微信公衆號的硬件平臺,遇到不少問題,簡單記錄一下javascript

該怎麼鏈接

準備工做

首先無論用什麼接口,作硬件和軟件的交互通常仍是先想着怎麼去作一個基礎的鏈接操做,最開始查到一些教程並參考微信官方的說法就是先申請設備接入的權限,以測試號來講,申請很是簡單,如圖所示html

申請完成後,點擊【設置】進入設備管理頁面,按照提示一步步操做就能夠添加設備,我這邊是使用的是第三方的硬件設備,經過wifi進行鏈接,根據相關文檔進行以下設置,具體選項內容應該要根據硬件設備類型來定。java

通過簡單的設置就能夠生成一個設備,能夠在設備列表看到,另外提一下,彷佛一直都存在一個意義不明的undefined設備,應該是平臺開發人員保留的,不影響使用算法

常規方法

經過這種方式新建的設備默認擁有100個配額,表明能受權100個能夠控制的設備,我這裏的截圖中是已經受權了一個設備,受權須要經過接口發送請求,具體接口內容在後面詳述。以後開始說第一次鏈接這個重要的問題,可是我這邊並不方便驗證結果,在這裏說下最後的實踐結果。一些教程會提到經過掃描產品詳情裏的二維碼就能夠執行鏈接操做,但對於普通測試狀況並不符合。如下是一般狀況的操做流程:json

  • 在產品配置中鏈接方式選擇wifi
  • 進行設備受權(不肯定是否必要)
  • 掃描產品詳情中的二維碼會調起Airkiss頁面(如今先簡單理解成一個輸入你當前鏈接wifi的密碼的頁面)
  • 打開硬件設備的AirLink模式,並在手機上輸入wifi密碼進行鏈接
  • 鏈接成功後,會跳轉至一個搜索設備的頁面

問題就出在最後一步,我在這裏進行了反覆的測試,前置的配置操做重複了不少遍,還換過開發板,在這裏都是搜索不到設備,通過大量的查找資料發現若是想經過這種方式掃描到設備必須在設備芯片的固件中寫入公衆號的ID,而且我後來也在設備詳情中發現了相關依據api

雖然找到了問題,可是固件的編寫是個很大的問題,簡單的燒錄能夠作到,但修改編譯固件的代碼倒是不懂,所幸後面發現了另外一種方法,可是因爲最終使用的固件版本是第三方平臺機智雲的,因此沒法驗證其餘的狀況是否能夠,這裏主要是記錄一下開發的過程。瀏覽器

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配置會返回提示消息。

設備受權與狀態查詢

在上一篇中咱們說到要使用設備首先要進行受權操做,設備完成受權以後就能夠經過接口來作一些操做,受權接口的具體參數官方說明看這個地址
設備受權

入參中的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」就是在線狀態,接口請求成功就會在公衆號的名稱下面看到一個「已鏈接」的狀態。

HTTP請求幫助類

在調試過程當中咱們可使用微信的接口調試工具或者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

之前知道有這麼個東西,可是具體有什麼做用,特別在哪是不太清楚,或者爲何要用這個東西,但經過此次接觸微信硬件的開發大概理解了一點,首先要明確WebSocket是一種與HTTP不一樣的協議,引用維基百科的說明以下:

服務器能夠經過標準化的方式來實現,而無需客戶端首先請求內容,並容許消息在保持鏈接打開的同時來回傳遞。經過這種方式,能夠在客戶端和服務器之間進行雙向持續對話。

簡單來講他可讓客戶端和服務器之間保持一個低耗的長鏈接,那麼這個和硬件開發有什麼關係呢?

最初我只是知道智能家居經過wifi遠程控制,因此想單片機應該也能夠經過一樣的原理實現,利用一個業務服務器接受用戶的操做,而後服務器發送請求給家裏的路由器,再由路由器轉發給家裏的設備,既然是這樣,就轉化成了我說屬性的HTTP請求,中間的傳輸無非就是POST請求發送json格式的數據。

想來是挺簡單的,可是雖然開發的深刻,發現硬件設備是一個發信器,它只向外請求並接收返回的結果,可是當路由器想發送接收自客戶端的設備狀態數據時殊不知道發送給誰,這樣引發的一個狀況是若是要想實現實時的設備控制,須要設備不停地向服務器發送GET請求來獲取當前的狀態,這樣就造成的極大的浪費,這樣的輪詢過程當中大部分的數據可能都是無效的,好比請求頭中的一些必要條件,但實際要傳輸的狀態數據每每就只是一個字符串。

在WebSocket出現以前,也確實都是這樣作的,但自從有了WebSocket,硬件設備就能夠和服務器之間創建一個長鏈接,這也就是咱們在前面所實現「上線」操做,實際上就是打開這一長鏈接。

後記

此次作硬件的研究斷斷續續持續了好久,其中真的是遇到了至關多的問題,尤爲是在不瞭解的領域中,可是隨着問題一個個被克服,有讓我感覺到了最初的那種解決問題的快樂,由於問題比較零碎,並且本身的理解還不到位,因此其中不少問題不能造成段落記錄下來比較惋惜,可是整個開發的過程和結果都是至關使人難忘的。

相關文章
相關標籤/搜索