一蝗肪、前言蝴乔、微信Airkiss官方文檔:
http://iot.weixin.qq.com/wiki/new/index.html?page=4-1-1
簡介:AirKiss是微信硬件平臺為Wi-Fi設(shè)備提供的微信配網(wǎng)、局域網(wǎng)發(fā)現(xiàn)和局域網(wǎng)通訊的技術(shù)产弹。開發(fā)者若要實現(xiàn)通過微信客戶端對Wi-Fi設(shè)備配網(wǎng)氏身、通過微信客戶端在局域網(wǎng)發(fā)現(xiàn)Wi-Fi設(shè)備,或者把微信客戶端內(nèi)的音樂遗契、圖片、文件等消息通過局域網(wǎng)發(fā)送至Wi-Fi設(shè)備病曾,需要在硬件設(shè)備中集成相應(yīng)的AirKiss靜態(tài)庫牍蜂。
二、開發(fā)模塊介紹
微信的Airkiss開發(fā)包含以下4部分泰涂。
1 廠商提供的硬件設(shè)備鲫竞,底層對Airkiss協(xié)議的support。
2 微信配網(wǎng)前端開發(fā)[小程序或者微信公眾帳號]逼蒙。
3 AirKiss協(xié)議層開發(fā)从绘。
4 Android層聯(lián)網(wǎng)處理。
ps:如果需要上報配網(wǎng)等信息給服務(wù)器是牢,在聯(lián)網(wǎng)完成后處理僵井。
簡圖如下:
三、硬件對AirKiss的協(xié)議support
- 硬件能力要求:
- 能夠切換信道妖泄;
- 具備定時器功能驹沿,能夠提供100ms的定時中斷;
- 能夠設(shè)置為混雜模式蹈胡,接收802.11網(wǎng)絡(luò)幀;
- 提供一種進入AirKiss模式的控制方式朋蔫,例如一個按鍵罚渐;
- 軟件能力要求:
- 能夠提供類似標準memset函數(shù)的功能函數(shù);
- 能夠提供類似標準memcpy函數(shù)的功能函數(shù)驯妄;
- 能夠提供類似標準memcmp函數(shù)的功能函數(shù)荷并;
- 能夠提供至少232字節(jié)的全局緩沖空間(完成AirKiss后用戶可用于自己的應(yīng)用程序或進行釋放);
四青扔、微信端開發(fā)
參考這位同學的詳細介紹:http://blog.csdn.net/jrainbow/article/details/50509162
效果如圖:
五源织、AirKiss協(xié)議開發(fā)
很多WiFI廠商都和微信有了合作,實現(xiàn)了Airkiss微猖、AirSync等功能谈息。每個廠商的具體實現(xiàn)不竟相同,我們以正基科技為例凛剥,它提供了(AMPAK)AP6212的EasySetupTarget.zip侠仇,實現(xiàn)了微信的Airkiss功能。略注意,這份代碼在使用的時候你需要調(diào)整一些細節(jié)逻炊,使得在android平臺下順暢運行
1. 代碼結(jié)構(gòu)如下:
2. 代碼入口分析:
命令參數(shù)k可以設(shè)置16位key互亮,-p即為設(shè)置開啟協(xié)議,其中4為airkiss余素。
void usage() {
printf("-h: show help message\n");
printf("-d: show debug message\n");
printf("-k <v>: set 16-char key for all protocols\n");
printf("-p <v>: bitmask of protocols to enable\n");
printf(" 0x%04x - bcast\n", 1<<EASY_SETUP_PROTO_BCAST);
printf(" 0x%04x - neeze\n", 1<<EASY_SETUP_PROTO_NEEZE);
printf(" 0x%04x - Air Kiss\n", 1<<EASY_SETUP_PROTO_AKISS);
printf(" 0x%04x - Xiaoyi\n", 1<<EASY_SETUP_PROTO_XIAOYI);
printf(" 0x%04x - changhong\n", 1<<EASY_SETUP_PROTO_CHANGHONG);
printf(" 0x%04x - jingdong\n", 1<<EASY_SETUP_PROTO_JINGDONG);
printf(" 0x%04x - jd JoyLink\n", 1<<EASY_SETUP_PROTO_JD);
}
static void signal_handler(void) {
printf("aborted\n");
killed = 1;
}
int main(int argc, char* argv[])
{
printf("enter exec main\n");
int ret;
int len;
uint16 val;
int flag = 0;
for (;flag < 1;) {
printf("enter the mainloop\n");
int c = getopt(argc, argv, "dhek:p:");
printf("args parse %c\n",c);
printf("optarg %s\n",optarg);
if (c < 0) {
break;
}
switch (c) {
case 'd':
debug_enable = 1;
break;
case 'k':
bcast_set_key(optarg);
bcast_set_key_qqcon(optarg);
neeze_set_key(optarg);
neeze_set_key_qqcon(optarg);
akiss_set_key(optarg);
jingdong_set_key(optarg);
jd_set_key(optarg);
printf("finish set key:%s\n",optarg);
break;
case 'p':
sscanf(optarg, "%04x", (uint32*)&val);
easy_setup_enable_protocols(val);
printf("finish set protocol:%s\n",optarg);
break;
case 'h':
usage();
return 0;
case 'e':
printf("finish parse");
flag = 1;
break;
default:
usage();
return 0;
}
}
....
}
3豹休、編譯方式:
一、編譯可執(zhí)行文件
adb push到開發(fā)版桨吊。執(zhí)行./easysetup -p4(airkiss協(xié)議為4)慕爬,即可以監(jiān)聽airkiss配網(wǎng)的發(fā)生。
二屏积、編譯靜態(tài)庫医窿,修改源代碼為jni接口調(diào)用。這個時候程序在執(zhí)行到easy_setup_ioctl()接口的時候炊林,會報easy setup ioctl(cmd=263) failed: 1(Operation not permitted)
錯誤.
easy_setup_start()接口如下:
int easy_setup_ioctl(int cmd, int set, void* param, int size) {
struct ifreq ifr;
wl_ioctl_t ioc;
int ret = 0;
if (g_ioc_fd < 0) {
LOGE("easy setup ioctl: control socket not initialized.\n");
printf("easy setup ioctl: control socket not initialized.\n");
return -1;
}
ioc.cmd = cmd;
ioc.buf = param;
ioc.len = size;
ioc.set = set;
strncpy(ifr.ifr_name, WLAN_IFACE, IFNAMSIZ);
ifr.ifr_name[IFNAMSIZ-1] = 0;
ifr.ifr_data = (caddr_t) &ioc;
if ((ret = ioctl(g_ioc_fd, SIOCDEVPRIVATE, &ifr)) < 0) {
/* log if not WLC_SCAN_RESULTS(51) */
if (cmd != 51) {
LOGE("easy setup ioctl(cmd=%d) failed: %d(%s)\n", cmd, errno, strerror(errno));
printf("easy setup ioctl(cmd=%d) failed: %d(%s)\n", cmd, errno, strerror(errno));
}
return -1;
}
return 0;
}
分析:權(quán)限問題導(dǎo)致姥卢,做出了如下的應(yīng)對:
把apk放在/system/app中、
設(shè)置platform簽名憑證渣聚、以及設(shè)置shareUid独榴,
相應(yīng)的so權(quán)限及用戶組設(shè)置也沒有問題
糾結(jié)了很久,依然沒有解決
直到發(fā)現(xiàn)了和這位仁兄的雷同的細節(jié):http://leave001.blog.163.com/blog/static/1626912932012566429951/
解決辦法如下:
原來在函數(shù)dev_ioctl中奕枝,會檢查CAP_NET_ADMIN權(quán)限棺榔,進入這個函數(shù),發(fā)現(xiàn)檢查的是進程是否在group
AID_NET_ADMIN中:
if (cap == CAP_NET_ADMIN && in_egroup_p(AID_NET_ADMIN)) return 0;
在app中申請系統(tǒng)app才有的權(quán)限:<uses-permission android:name="android.permission.NET_ADMIN"/>
即可以解決權(quán)限問題隘道。
六症歇、Android聯(lián)網(wǎng)處理。
在收到j(luò)ni或者可執(zhí)行文件里面?zhèn)鬟f過來的ssid和password,即可以利用wifimanager來配網(wǎng)
代碼舉例如下:
private fun startConnectThread(ssid: String, password: String, type: WifiCipherType) {
val result = openWifi()
if (!result) {
RLog.e(TAG, "open wifi failed")
return
}
RLog.e(TAG, "wifi config --- ssid:" + ssid + ",password:" + password)
val wifiConfig: WifiConfiguration = createWifiInfo(ssid, password, type)
if (wifiConfig == null) {
RLog.e(TAG, "wifiConfig is null!")
return
}
val tempConfig: WifiConfiguration? = isExistSSID(ssid)
tempConfig?.apply {
wifiManager.removeNetwork(tempConfig.networkId)
}
val netId = wifiManager.addNetwork(wifiConfig)
wifiManager.enableNetwork(netId, true)
val connected = wifiManager.reconnect()
if (connected) {
RLog.e(TAG, "connect success")
} else {
RLog.e(TAG, "connect error")
}
}
private fun createWifiInfo(ssid: String, password: String, type: WifiCipherType): WifiConfiguration {
val config = WifiConfiguration()
config.allowedAuthAlgorithms.clear()
config.allowedGroupCiphers.clear()
config.allowedKeyManagement.clear()
config.allowedPairwiseCiphers.clear()
config.allowedProtocols.clear()
config.SSID = "\"" + ssid + "\""
when (type) {
WifiCipherType.WIFICIPHER_INVALID -> {
config.wepKeys[0] = ""
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
config.wepTxKeyIndex = 0
}
WifiCipherType.WIFICIPHER_WEP -> {
if (!TextUtils.isEmpty(password)) {
if (isHexWepKey(password)) {
config.wepKeys[0] = password
} else {
config.wepKeys[0] = "\"" + password + "\""
}
}
config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN)
config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED)
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE)
config.wepTxKeyIndex = 0
}
WifiCipherType.WIFICIPHER_WPA -> {
config.preSharedKey = "\"" + password + "\""
config.hiddenSSID = true
config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN)
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP)
config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK)
config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP)
// 此處需要修改否則不能自動重聯(lián)
// config.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP)
config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP)
config.status = WifiConfiguration.Status.ENABLED
}
}
return config
}
七谭梗、智能硬件聯(lián)網(wǎng)后返回random值給微信
此處是有巨坑的忘晤,樓主被坑的不要不要的
A. 微信是這么說的:
模塊拿到上面的數(shù)據(jù)以后還需要進行AirKiss微信配網(wǎng)流程的最后一步,利用接收到的SSID和密碼以后連上對應(yīng)的路由器激捏,立即發(fā)送以上面打印出來的random數(shù)為內(nèi)容的UDP廣播包(只有1個數(shù)據(jù))设塔,目的端口號為10000,建議廣播包的個數(shù)至少為20個远舅,發(fā)送方收到該廣播包后就能確認接收方已經(jīng)準確接收到所有數(shù)據(jù)了闰蛔,由于各平臺連接路由器、發(fā)送UDP廣播包的實現(xiàn)差異較大图柏,并且該功能為模塊自帶功能序六,與AirKiss2.0庫無關(guān),這里不進行舉例說明爆办。
那是不是我對著10000端口在連網(wǎng)成功后發(fā)大于20個random值就ok难咕???
然而測試后發(fā)現(xiàn)微信web端只會提示連接超時余佃,而不會顯示聯(lián)網(wǎng)成功暮刃。
B. 廠商會這么說: 俺不知道!1痢椭懊! 或者這么說 你都連上網(wǎng)了,你讓微信端配網(wǎng)一段時間后自動取消那個頁面步势。 果然一些公司是真心做完就不管用戶體驗的氧猬。
-
在我郁悶的時候,我的給力同事告訴我有一個市場上的設(shè)備正確的回復(fù)了random坏瘩,我們可以來抓包看看盅抚。admire。
抓包如下圖:
分析:原來別人回復(fù)的是:random(2位)+mac地址(12位){ps:而這個body才7字節(jié)倔矾,我一開始沒注意}于是我依樣畫葫蘆這么做了妄均,發(fā)送代碼如下:結(jié)果還是不行,乖乖抓了個包哪自。發(fā)現(xiàn)了異常丰包,我的是14字節(jié)。如下圖:
又在我司某同學給力支持下壤巷,判斷發(fā)送代碼沒問題邑彪,問題是需要把內(nèi)容變成跟正確的一樣,從14個字節(jié)到7個字節(jié)胧华。比如16進制的cd寄症,就需要來個小trick,char a = 205
撑柔,這樣原始的14字節(jié)就變成了7字節(jié)瘸爽。微信端終于顯示配網(wǎng)完成。
SO,發(fā)送的random要么就一個1個字節(jié)的random值铅忿, 要么就是7個字節(jié)的random+mac地址值,根據(jù)硬件平臺來定
跨過大坑灵汪,前面有別的新坑等著檀训,至少,解決問題這一天心情還是很愉悅的享言,咩哈哈