米家智慧裝置自動化控制

weixin_34146805發表於2018-12-15

最近搞了一個青米插線板,可以繫結到米家app,然後進行遠端控制。
但是,本猿需求可不止於此。這樣,雖然可以通過米家app遠端控制,但是沒能實現自動化哇。

需求

我們意淫一下這樣的場景,比較簡單,哥們兒就不畫圖了:

  1. 有一個小黑屋,屋裡有個青米插線板插在插座上,該青米插線板通過小黑屋的路由器連線網路;
  2. 還有部手機,手機通過充電器連在青米插線板上,同時手機通過 4G 連線網路;
  3. 當手機電量低於 20% 的時候,我要告訴插線板開啟電源,當手機電量高於 80% 的時候,我要告訴插線板斷開電源。

這樣,其實我們需求就拆分為二:

  1. 自動控制,即手機根據電量自己去告訴插線板開啟還是關閉;
  2. 遠端控制,目前已知的介面來看,我們無法通過 藍芽、紅外等進行通訊。另外,現在手機和插線板一個連 4G,一個連小黑屋的路由器,二者不在同一個網段,二者要想通訊需要藉助一臺公網的伺服器,即實現一個遠端的端到端通訊。

思路

定了以上的需求,這個時候我想到了這樣幾個辦法,

  1. 寫個自動化測試的程式,這裡只針對 Android 手機,可以使用 appium,macaca,或是直接 adb 命令等等吧,去米家app裡面模擬人操作。此法有些笨重,而且只能在本地跑。因為你需要一部真機或是模擬器(米家 app可以在模擬器登陸),所以沒辦法搬到雲端跑,所以還需要做一些額外的工作實現遠端控制。
  2. 逆向米家app,直接呼叫米家介面。這個有一點好處,就是逆向後你可以將專案部署到雲端,實現遠端自動化控制。因為我查了下,小米貌似沒有開放這些介面,哪位仁兄看到公開介面還請指教。逆向的話,成本有些高,我抓了下相關的介面,有個加密的簽名,然後,我逆向看了下 Android app, 對,米家 app 沒有加固,所有原始碼一覽無餘,只是這個 app 有些大,實在懶得去破解了,哪位兄臺破解了,記得賜教。
  3. 米家的智慧裝置都遵循了 miio 的協議,協議詳情可以參見 這裡。這個時候,如果小黑屋裡有另一臺電腦連線了路由器,那這個時候,同一網段的這臺電腦就可以去操作插線板了,但這個時候,需要其它輔助辦法,去實現遠端控制了。

實現

以上思路,我選擇 3 來實現。
首先,要想操作米家智慧硬體,先需要獲取該裝置的 token這裡 提供了一些辦法。但是不一定靈奧,本猿因為不太清楚米家智慧硬體連線及通訊的流程,裡面的方式都試了,發現 token 是空的,可以說非常的小白鼠了,其實最簡單靠譜的方式在 這裡,簡要的說,就是青米插線板連上電源後,在配置前,會首先釋放出一個 WIFI, 我們用一臺電腦去連線這個 WIFI(沒有密碼),連線成功後,我們安裝 miio, 通過 miio discover 命令,就可以得到 token了。
下面看先控制器的程式碼, 裡面有註釋,多囉嗦一句,依賴的庫裡有 buggggggg,娘希皮。沒時間研究原因,留給仁兄去解決好了。

    const miio = require('miio');
    const EventEmitter = require('events');
   
    // chingmi info
    // Device ID: 88525***
    // Model info: qmi.powerstrip.v1
    // Address: 192.168.1.1
    // Token: 61071aflajfkjhahhahahahha via auto-token
    // Support: At least basic
    const chingmi = {
        did: '88525***',
        address: '192.168.1.113',
        token: '61071aflajfkjhahhahahahha'
    };
    
    class Controller extends EventEmitter {
    
        constructor() {
            super();
        }
    
        async run() {
            try {
                this.device = await miio.device({address: chingmi.address, token: chingmi.token});
                console.log('run --> device = ' + JSON.stringify(this.device.properties));
                // here, the original turnOff()/turnOn() setPower(boolean) togglePower() have bug,
                // the return value is always false. We turn off the device and init it from close state
                await this.device.turnOff();
                this.status = 'off';
                // this.initListeners();
                await this.onCreate();
            } catch (error) {
                console.log('run --> Error = ' + error);
                throw new Error('there is no valid device');
            }
        }
    
        initListeners() {
            // the following is shit, useless
            // const handler = (action, device) => console.log('Action', action, 'performed on', device);
            // this.device.on('action', handler);
            // device2.on('action', handler);
            // this.device.on('power', power => console.log('Power changed to', power));
        }
    
        async onCreate() {
        }
    
        /**
         * toggle power status
         *
         * @param status on/off
         * @returns {Promise<void>}
         */
        async switch(status) {
            if (this.status === status) return;
    
            await this.device.setPower(status);
            this.status = status;
    
            console.log(`the chingmi smart power strip is turned ${status}`);
            // alarm(`[info] the chingmi smart power strip is turned ${this.isOn ? 'on' : 'off'}`)
        }
    }
    
    // test
    // (async function f() {
    //     let ctl = new Controller();
    //     await ctl.run();
    //     await ctl.switch('on');
    // })();

上面的 deviceIdtoken 當然是假的,記得改成你自己的。當然我使用 node.js 實現的,我見 github 上也有 python、go 等相關庫,依你習慣了。

以上,插線板的控制器基本上是實現了,這段程式碼將部署在與插線板在同一區域網的一臺電腦上。
接著實現遠端控制,這個我們可以藉助 WebSocket,小黑屋的電腦作為一個 WebSocketClient,連線公網上的 WebSocketServer(部署在公網伺服器上),連線建立之後,WebSocketServer 就可以向 WebSocketClient 發命令控制插線板了。如果,你用 node.js, 可以藉助 wssocket.io 實現。

好了,問題基本解決了,我們意淫下結果好了:

  1. 手機電量低於 20% 了,那發個 turnOn 命令給 WebSocketServer,當然,這個時候為了方便,WebSocketServer 中也可以包含一個 HttpServer,手機此時只發個 http request 就好了;
  2. WebSocketServer 收到 turnOn 命令,將該命令傳送給 WebSocketClient,然後就可以通過上面的 controller 開啟插線板的開關,手機開始充電;
  3. 當電量高於 80%,那手機再發一個 turnOff 的命令給 WebSocketServer,WebSocketServer 將該命令發給 WebSocketClient,通過 controller 關閉插線板電源。

以上基本實現了我們的需求。

最後,有兩個問題,

  1. 能不能做到插線板直接與公網 server 通訊?
  2. 我通過 米家app 連線插線板的時候,到底會用什麼資料傳輸方式(藍芽、WIFI、GPS)?連線的過程還是不太清楚,有朋友門兒清的話幫忙解釋下。

相關文章