深度剖析微信公众号配网 Airkiss 原理与过程,esp826

2017-12-01  本文已影响650人  半颗心脏

【微信小程序控制硬件①】 全网首发,借助 emq 消息服务器带你如何搭建微信小程序的mqtt服务器,轻松控制智能硬件!
【微信小程序控制硬件②】 开始微信小程序之旅,导入小程序Mqtt客户端源码,实现简单的验证和通讯于服务器!
【微信小程序控制硬件③】 从软件到硬件搭建一个微信小程序控制esp8266的项目,自定义通讯协议,为面试职位和比赛项目加分!
【微信小程序控制硬件④】 深度剖析微信公众号配网 Airkiss 原理与过程,esp8266如何自定义回调参数给微信,实现绑定设备第一步!


一、前言;


二、准备材料以及注意事项;


1.png

问:对于个人公众号可以有这个微信配网的权限功能吗?

问:对于企业公众号的微信配网的权限功能如何开启呢?

问:和微信小程序配置后台一样,这个服务器都是需要https吗?

三、如何调起微信公众号的配网界面;




3.1 微信公众号后台配置要点。


2.png
3.png
在这里插入图片描述
5.png

四、 服务器php代码编写。


6.png
<?php
/**
 * Created by PhpStorm.
 * User: XuHongYss
 * Date: 2018/12/1
 * Time: 15:57
 */

class JSSDK {

    private $appId;
    private $appSecret;

  //构造方法,传入appid和密钥
   public function __construct($appId, $appSecret) {
       $this->appId = $appId;
      $this->appSecret = $appSecret;

   }

//获取签名
 public function getSignPackage() {

        $jsapiTicket = $this->getJsApiTicket();
        // 注意 URL 一定要动态获取,不能 hardcode.
        $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://";
        $url = "$protocol$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";

        $timestamp = time();
        $nonceStr = $this->createNonceStr();

        // 这里参数的顺序要按照 key 值 ASCII 码升序排序
        $string = "jsapi_ticket=$jsapiTicket&noncestr=$nonceStr&timestamp=$timestamp&url=$url";

        $signature = sha1($string);
        //var_dump($signature);exit;

        $signPackage = array(
            "appId"     => $this->appId,
            "nonceStr"  => $nonceStr,
            "timestamp" => $timestamp,
            "url"       => $url,
            "signature" => $signature,
            "rawString" => $string
        );
        return $signPackage;
    }

    /**
     * 
     *  创建随机数
     * 
     * @param int $length 长度,默认是16
     * @return string  返回随机数
     */
    private function createNonceStr($length = 16) {
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }


    /**
     * @return mixed 获取 JsApiTicket
     */
    private function getJsApiTicket() {

        // jsapi_ticket 应该全局存储与更新,以下代码以写入到文件中做示例
        $data = json_decode($this->get_php_file("jsapi_ticket.php"));

        if ($data->expire_time < time()) {
            $accessToken = $this->getAccessToken();
            $url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=$accessToken";
            $res = json_decode($this->httpGet($url));
            $ticket = $res->ticket;
            if ($ticket) {
                $data->expire_time = time() + 7000;
                $data->jsapi_ticket = $ticket;
                $this->set_php_file("jsapi_ticket.php", json_encode($data));
            }
        } else {
            $ticket = $data->jsapi_ticket;
        }

        return $ticket;
    }

    /**
     * @return mixed 获取AccessToken
     */
    private function getAccessToken() {
        // access_token 应该全局存储与更新,以下代码以写入到文件中做示例
        $data = json_decode($this->get_php_file("access_token.php"));

        if ($data->expire_time < time()) {

            $url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$this->appId&secret=$this->appSecret";
            //var_dump($url);exit;
            $res = json_decode($this->httpGet($url));
            //var_dump($res->expires_in);exit;
            $access_token = $res->access_token;
            //var_dump($access_token);exit;
            if ($access_token) {
                $data->expire_time = time() + 7000;
                $data->access_token = $access_token;
                $this->set_php_file("access_token.php", json_encode($data));
            }
        } else {
            $access_token = $data->access_token;
        }
        return $access_token;
    }

    /**
     * @param $url https请求的url
     * @return mixed
     */
    private function httpGet($url) {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_TIMEOUT, 500);

        // 为保证第三方服务器与微信服务器之间数据传输的安全性,所有微信接口采用https方式调用,必须使用下面2行代码打开ssl安全校验。
        // 如果在部署过程中代码在此处验证失败,请到 http://curl.haxx.se/ca/cacert.pem 下载新的证书判别文件。
        curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);
        curl_setopt($curl, CURLOPT_URL, $url);

        $res = curl_exec($curl);
        curl_close($curl);

        return $res;
    }


    /**
     * @param $filename  文件名
     * @return string 内容
     */
    private function get_php_file($filename) {
        return trim(substr(file_get_contents($filename), 15));
    }

    /**
     * @param $filename 文件名字
     * @param $content 内容
     */
    private function set_php_file($filename, $content) {
        $fp = fopen($filename, "w");
        fwrite($fp, "<?php exit();?>" . $content);
        fclose($fp);
    }
}

<?php
require_once "jssdk.php";
$jssdk = new JSSDK("填入微信提供的APPID", "填入微信提供的密钥");
$signPackage = $jssdk->GetSignPackage();
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
</body>
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script>
    wx.config({
        beta:true,//开启内测接口调用,注入wx.invoke方法
        debug:false,//关闭调试模式
        appId: '<?php echo $signPackage["appId"];?>',//AppID
        timestamp: <?php echo $signPackage["timestamp"];?>,//时间戳
        nonceStr: '<?php echo $signPackage["nonceStr"];?>',//随机串
        signature: '<?php echo $signPackage["signature"];?>',//签名
        jsApiList:['openWXDeviceLib','startScanWXDevice','onScanWXDeviceResult','configWXDeviceWiFi']
    });

    // echo 'start config';
    wx.ready(function () {
        // 在这里调用 API
        wx.checkJsApi({
            jsApiList: ['configWXDeviceWiFi'],
            success: function(res) {
                wx.invoke('configWXDeviceWiFi', {}, function(res){
                    var err_msg = res.err_msg;
                    if(err_msg == 'configWXDeviceWiFi:ok') {
                        //配置成功
                        wx.invoke('openWXDeviceLib',{'connType':'lan'},function(res){
                           // alert(res.err_msg);
                        });

                        wx.invoke('startScanWXDevice',{'connType':'lan'}, function(res) {
                            console.log('startScanWXDevice',res);
                            alert(JSON.stringify(res));
                        });

                        wx.on('onScanWXDeviceResult',function(res){
                            alert("扫描到1个设备"+JSON.stringify(res));
                            //自己解析一下res,里面会有deviceid,扫描设备的目的就是为了得到这个
                            //然后就可以开始绑定了
                        });
                        
                    } else {
                        //配置失败
                        alert(err_msg);
                    }
                });

            }
        });
    });
    wx.error(function(res){
        alert("配置出错:"+res);
    });
</script>
</html>


4.1 如何调用?

7.png

五、esp8266实现airkiss原理配网;




#include <aos/aos.h>
#include <hal/wifi.h>
#include <string.h>

#include "lwip/ip_addr.h"
#include "lwip/pbuf.h"
#include "espressif/c_types.h"
#include "espressif/esp_libc.h"
#include "espressif/esp_wifi.h"

#include "airkiss.h"

// airkiss 状态回调函数
typedef void (*airkiss_cb_fn)(AIR_KISS_STATE state, void *pdata);

void start_airkiss(airkiss_cb_fn airkiss_done);
static void start_scan(void);
static void udp_send_random(uint8_t num);
static void channel_change_action(void *arg);

// 当前监听的无线信道
uint8_t cur_channel = 1;
uint8_t wifi_ssid_crc;
uint8_t airkiss_random_num;
char wifi_ssid[32 + 1];          /* SSID got form airkiss */
char wifi_pwd[64 + 1];           /* password got form airkiss */

// 信道锁定标志
uint8_t airkiss_channel_locked = 0;

// Airkiss 过程中需要的 RAM 资源,完成 Airkiss 后可释放
airkiss_context_t *akcontexprt;

// 定义 Airkiss 库需要用到的一些标准函数,由对应的硬件平台提供,前三个为必要函数
const airkiss_config_t akconf = {
    (airkiss_memset_fn)&memset,
    (airkiss_memcpy_fn)&memcpy,
    (airkiss_memcmp_fn)&memcmp,
    (airkiss_printf_fn)&printf 
};

airkiss_cb_fn airkiss_cb = NULL;
hal_wifi_init_type_t type;

extern hal_wifi_module_t aos_wifi_esp8266;

uint8_t crc8_chk_value(uint8_t *str)
{
    uint8_t crc = 0;
    uint8_t i;

    while(*str != '\0')
    {
        crc ^= *str++;
        for(i = 0; i < 8; i++)
        {
            if(crc & 0x01)
                crc = (crc >> 1) ^ 0x8c;
            else 
                crc >>= 1;
        }
    }
    
    return crc;
}
//wifi 事件回调函数
const hal_wifi_event_cb_t wifi_event_cb = {
    &wifi_connect_fail,
    &wifi_ip_got,
    &wifi_stat_chg,
    &wifi_scan_compeleted,
    &wifi_scan_adv_compeleted,
    &wifi_para_chg,
    &wifi_fatal_err
};
    
// 用于切换信道的定时任务
static void channel_change_action(void *arg)
{
    if (!airkiss_channel_locked)
    {
        // 切换信道
        if (cur_channel >= 13)
            cur_channel = 1;
        else
            cur_channel++;
        
        hal_wifi_set_channel(&aos_wifi_esp8266, cur_channel);
        airkiss_change_channel(akcontexprt);
        aos_post_delayed_action(100, channel_change_action, NULL);
    }
}
//配网完成
static void airkiss_finish(void)
{
    int8_t err;
    uint8 buffer[256];
    airkiss_result_t result;
    err = airkiss_get_result(akcontexprt, &result);
    
    if (err == 0)
    {
        stpcpy(wifi_pwd, result.pwd);
        wifi_ssid_crc = result.reserved;
        airkiss_random_num = result.random;
    }
    else
    {
        printf("AIRKISS_STATUS_GETTING_PSWD_FAILED\r\n");
    }
    
    aos_free(akcontexprt);
    start_scan();
}

static void wifi_promiscuous_rx(uint8_t *data, int len, hal_wifi_link_info_t *info)
{
    int8_t ret;
    
    ret = airkiss_recv(akcontexprt, data, len);
    
    if (ret == AIRKISS_STATUS_CHANNEL_LOCKED)
    {
        airkiss_channel_locked = 1;
        airkiss_cb(AIRKISS_STATE_FIND_CHANNEL, NULL);
        printf("T|LOCK CHANNEL : %d\r\n", cur_channel);
    }
    else if (ret == AIRKISS_STATUS_COMPLETE)
    {
        hal_wifi_stop_wifi_monitor(&aos_wifi_esp8266);
        airkiss_finish();
    }
}
//开始扫描
static void start_scan(void)
{
    wifi_set_opmode(STATION_MODE);
    hal_wifi_install_event(&aos_wifi_esp8266, &wifi_event_cb);
    hal_wifi_start_scan(&aos_wifi_esp8266);
}
//调用函数
void start_airkiss(airkiss_cb_fn airkiss_done)
{
    int8_t ret;
    
    airkiss_cb = airkiss_done;
    akcontexprt = (airkiss_context_t*)aos_malloc(sizeof(airkiss_context_t));
    
    // 初始化 Airkiss 流程,每次调用该接口,流程重新开始
    ret = airkiss_init(akcontexprt, &akconf);
    if (ret < 0)
    {
        printf("Airkiss init failed!\r\n");
        return;
    }
    
    // 开始抓包
    cur_channel = 1;
    airkiss_channel_locked = 0;
    hal_wifi_set_channel(&aos_wifi_esp8266, cur_channel);
    hal_wifi_register_monitor_cb(&aos_wifi_esp8266, wifi_promiscuous_rx);
    hal_wifi_start_wifi_monitor(&aos_wifi_esp8266);
    aos_post_delayed_action(100, channel_change_action, NULL);
    airkiss_cb(AIRKISS_STATE_WAIT, NULL);
}


#define DEVICE_TYPE         "https://blog.csdn.net/xh870189248"
#define DEVICE_ID       "https://github.com/xuhongv"

#define DEFAULT_LAN_PORT    12476 //服务器的UDP端口

LOCAL esp_udp ssdp_udp;
LOCAL struct espconn pssdpudpconn;
LOCAL os_timer_t ssdp_time_serv;

uint8  lan_buf[200];
uint16 lan_buf_len;
uint8  udp_sent_cnt = 0;

const airkiss_config_t akconf =
{
    (airkiss_memset_fn)&memset,
    (airkiss_memcpy_fn)&memcpy,
    (airkiss_memcmp_fn)&memcmp,
    0,
};

LOCAL void ICACHE_FLASH_ATTR
airkiss_wifilan_time_callback(void)
{
    uint16 i;
    airkiss_lan_ret_t ret;
    
    if ((udp_sent_cnt++) >30) {
        udp_sent_cnt = 0;
        os_timer_disarm(&ssdp_time_serv);//s
        //return;
    }

    ssdp_udp.remote_port = DEFAULT_LAN_PORT;
    ssdp_udp.remote_ip[0] = 255;
    ssdp_udp.remote_ip[1] = 255;
    ssdp_udp.remote_ip[2] = 255;
    ssdp_udp.remote_ip[3] = 255;
    lan_buf_len = sizeof(lan_buf);
    ret = airkiss_lan_pack(AIRKISS_LAN_SSDP_NOTIFY_CMD,
        DEVICE_TYPE, DEVICE_ID, 0, 0, lan_buf, &lan_buf_len, &akconf);
    if (ret != AIRKISS_LAN_PAKE_READY) {
        os_printf("Pack lan packet error!");
        return;
    }
    
    ret = espconn_sendto(&pssdpudpconn, lan_buf, lan_buf_len);
    if (ret != 0) {
        os_printf("UDP send error!");
    }
    os_printf("Finish send notify!\n");
}

void ICACHE_FLASH_ATTR
airkiss_start_discover(void)
{
    ssdp_udp.local_port = DEFAULT_LAN_PORT;
    pssdpudpconn.type = ESPCONN_UDP;
    pssdpudpconn.proto.udp = &(ssdp_udp);
    espconn_regist_recvcb(&pssdpudpconn, airkiss_wifilan_recv_callbk);
    espconn_create(&pssdpudpconn);

    os_timer_disarm(&ssdp_time_serv);
    os_timer_setfn(&ssdp_time_serv, (os_timer_func_t *)airkiss_wifilan_time_callback, NULL);
    os_timer_arm(&ssdp_time_serv, 1000, 1);//1s
}


8.png

六、后记;


另外,不要把我的博客作为学习标准,我的只是笔记,难有疏忽之处,如果有,请指出来,也欢迎留言哈!


上一篇下一篇

猜你喜欢

热点阅读