Google Homeに話しかけてWindowsを操作してみる


# はじめに
[前回記事(Google Homeに話しかけてテレビの電源を操作してみる)](https://qiita.com/miso_develop/items/234f81a7649af08b2861)にて(Chromecastを使わずに)テレビの電源の音声操作を実現しました。
電源操作つながりで今度はメインPCとして使用しているWindowsの起動・スリープをGoogle Homeより音声操作してみようと思います。

起動には「[WOL(Wake on LAN)](https://ja.wikipedia.org/wiki/Wake-on-LAN)」を使用し、スリープはFirebaseの更新をフックにコマンドを実行します。
コマンド実行できちゃうので、当然スリープ以外にもWindows操作はなんでも出来ちゃいます。



# 処理の流れ
![image](https://qiita-image-store.s3.amazonaws.com/0/207109/d10326f4-0449-f26e-adeb-dbd9a1ac971c.png)



# 環境
- Google Home
- Raspberry Pi3 (Raspbian Lite 9.1)
    - Node.js (v8.6.0)
- Windows10 Pro
    - Node.js (v6.11.4)



# Windowsの起動(Wake on LAN)
Windowsの起動には「[WOL(Wake on LAN)](https://ja.wikipedia.org/wiki/Wake-on-LAN)」を使用します。
WOLとはネットワーク経由でPCやネットワーク機器を起動させる仕組みです。
基本的にはハードウェア側に依存する仕組みで、マザーボード(BIOS/UEFI)、ネットワークアダプタ、OSがそれぞれ対応している必要があります。
と言っても大抵のPCは対応しているはずです。
ラズパイは非対応ですが…

今回の環境ではラズパイからネットワーク経由でWindows PCを起動させます。
当然ながらラズパイとWindows PCを同じネットワーク上に置いておいて下さい。

## PCのWOL設定
まずはWindows PCにてBIOS/UEFIとネットワークアダプタのWOL設定を有効化しましょう。

### BIOS/UEFIの設定
ここはもうPCによりけりですが、例として私は以下の部分を設定しました。
ひたすらWOLっぽい項目を探して有効化していっただけですが…

[ NIC設定 ]
![201710304484.JPG](https://qiita-image-store.s3.amazonaws.com/0/207109/55426b1a-76ea-f7c3-2f64-f82162d7733b.jpeg)

[ ACPI設定 ]
![201710304486.JPG](https://qiita-image-store.s3.amazonaws.com/0/207109/405c9acc-9377-62ea-2e43-bf3a6472d1f3.jpeg)

[ 起動設定 ]
![201710304488.JPG](https://qiita-image-store.s3.amazonaws.com/0/207109/1ca8edfa-b88e-e681-684c-d15ec021626d.jpeg)

他にも設定項目名としては以下のようなのがあるみたいです。

> - Wake On Lan を含むメニューの名称の例
>     - Power / Power Management Setup / Power Management Event
>     - APM Configuration
>     - Advanced
> - Wake On Lan の設定項目の名称の例
>     - WOL / Wake On LAN
>     - Remote Wakeup
>     - PME Event Wake Up
>     - Power On by PCI/PCIE Device

参考：[Wake on Lan で Windows 10 の PC を遠隔で起動する](https://qiita.com/gam0022/items/4c2a3fdc29ac4977b51c#wake-on-lan-%E3%82%92%E6%9C%89%E5%8A%B9%E5%8C%96)

### ネットワークアダプタの設定
続いてネットワークアダプタ側です。
ここはWindowsからの操作になるので設定方法は大体統一されているかと思います。

- Win + Rのショートカットで「ファイル名を指定して実行」を開いて`devmgmt.msc`と入力しデバイスマネージャを開きます。
- そしてネットワークアダプタを右クリックしてプロパティを選択します。
- プロパティウインドウより「詳細設定」タブを選択し、「Wake on ～」といったWOLぽい項目をオンにします。
    - 私の環境ではそれっぽい項目はありませんでした。
- 続いて「電源の管理」タブを選択し、同様にWOLぽい項目をオンにし、OKで閉じます。
    - 私の環境では下図のような項目でした。

![image](https://qiita-image-store.s3.amazonaws.com/0/207109/9db546cd-a1d8-56d2-4c06-35125ef87ee0.png)

それとWindows10の場合、高速スタートアップを有効にしているとダメな場合があるみたいです。
私の環境では有効にしてても特に問題はありませんでした。
もし上手くいかない場合はここをオフってみて下さい。

はい、以上でWindows側のWOL設定は完了です。

## Node.jsモジュールの導入
それではラズパイからWindowsを起動してみましょう。
適当にディレクトリをきって「[wol](https://www.npmjs.com/package/wol)」というNode.jsモジュールをインストールします。

```
$ npm install wol
```

続いてindex.jsを作成します。

```js:index.js
const wol = require('wol')
wol.wake('xx:xx:xx:xx:xx:xx')
```

これだけです。
`'xx:xx:xx:xx:xx:xx'`にはPCのMACアドレスを入れます。
MACアドレスはコマンドプロンプトから`ipconfig /all`で調べられます。

あとはWindowsをスリープなりシャットダウンなりしてから実行してみて下さい。

```
$ node index.js
```

## Google Homeから音声操作
続いてGoogle Homeから音声操作できるようにしてみましょう。
IFTTTでGoogle Homeに話しかけたワードをFirebaseへ格納し、ラズパイ上のNode.jsでFirebaseの更新をフックにwolを実行します。

### FirebaseとIFTTTの設定
過去記事より以下の部分を参考に設定してみて下さい。
起動ワードとかはよしなにお願いします。

[Firebaseの設定](https://qiita.com/miso_develop/items/c2008235cb4a8567cfa9#firebase%E3%81%AE%E8%A8%AD%E5%AE%9A)
- Firebase設定
- IFTTT Applet作成

### Node.jsモジュールのインストール
必要なNode.jsモジュールをインストールします。
適当なプロジェクトディレクトリを作り、以下のコマンドを実行して下さい。

```
$ npm init -y
$ npm install firebase
$ npm install superagent
$ npm install wol
```

### index.jsの実装
以下のように実装を行います。
confingはFirebaseコンソールの「ウェブアプリに Firebase を追加」より取得できる情報に置き換えて下さい。
あとMACアドレスも対象PCのものにして下さい。

```js:index.js
const request = require("superagent");
const firebase = require("firebase");
const wol = require("wol");

//Windows MAC Address
const mac = "xx:xx:xx:xx:xx:xx";

//firebase config
const config = {
  apiKey: "xxxxxxxxxxxxxxxxxxxxxxx",
  authDomain: "xxxxxxxx-xxxxx.firebaseapp.com",
  databaseURL: "https://xxxxxxxx-xxxxx.firebaseio.com",
  projectId: "xxxxxxxx-xxxxx",
  storageBucket: "xxxxxxxx-xxxxx.appspot.com",
  messagingSenderId: "xxxxxxxxxxxxxxxx"
};
firebase.initializeApp(config);



//database更新時
const path = "/googlehome";
const key = "word";
const db = firebase.database();
db.ref(path).on("value", function(changedSnapshot) {
  //値取得
  const value = changedSnapshot.child(key).val();
  if (value) {
  	console.log(value);
  	
    //コマンド生成
    const command = getJsonData(value.split(" ")[0], {
      
      //Windows
      "pc": () => {
        const wake = () => wol.wake(mac);
        return getJsonData(value.split(" ")[1], {
          "起動": wake,
          "軌道": wake,
          "つけ": wake,
          "オン": wake,
          "default": false
        });
      },
      
      //default
      "default": () => false,
    })();
    console.log(command);
    
    //コマンド実行
    if (command) {
      //typeof
      if (typeof command === "string") {
        const exec = require('child_process').exec;
        exec(command);
      } else if (typeof command === "function") {
        command();
      }
        
      //firebase clear
      db.ref(path).set({[key]: ""});
    }
    
  }
});



//jsonからvalueに一致する値取得
function getJsonData(value, json) {
  for (let word in json)  if (value == word) return json[word]
  return json["default"]
}
```

できたら実行してみます。

```
$ node index.js
```

Google Homeに話しかけてみてWindowsを起動させてみて下さい。
正常に動いているなら後はサービス化なりforeverなりしちゃいましょう。

参考：
[Systemdを使ってさくっと自作コマンドをサービス化してみる](https://qiita.com/DQNEO/items/0b5d0bc5d3cf407cb7ff)
[foreverコマンドでNodeJSアプリを起動したままにする](https://qiita.com/h1y0r1n/items/a2cf8f2e8d3d31ccba49)



# Windowsのスリープ・シャットダウン
次にWindowsをスリープ・シャットダウンさせてみましょう。
こっちはWOLでは制御できないので、WindowsにNode.jsをインストールしFirebase
の更新をフックにコマンドを実行させます。
ラズパイ側と同じ実装ですね。
そして最後にNode.jsを常時起動するようにして完成です。

## WindowsへのNode.jsのインストール
WindowsへのNode.jsのインストールは以下の記事を参考にさせて頂きました。
[Windowsでnode.jsをバージョン管理する](https://qiita.com/t_kojima/items/780b3b3133a17cceb175)

## Firebaseによるフック
「Google Homeから音声操作」と同様になります。
FirebaseとIFTTTは既に設定済みですので、Node.jsモジュールのインストールとWindows用index.jsの作成になります。

### Node.jsモジュールのインストール
先ほどラズパイ側でやったのと同じです。
適当にプロジェクトフォルダを作成して以下のコマンドを実行します。

```
npm init -y
npm install firebase
npm install superagent
```

### Windows用index.jsの作成
index.jsはラズパイのをコピって修正しちゃって下さい。
コマンド実行部を以下のように書き換えます。
あとこっちではWOLは不要なのでrequireをコメントアウトしちゃいましょう。

```js:index.js
      ...
      
      // const wol = require("wol");  //Windows側では不要
      
      ...
      
      //pc
      "pc": function() {
        //Windowsコマンド
        const sleep = "start C:/Windows/System32/rundll32.exe powrprof.dll,SetSuspendState";
        const shutdown = "shutdown -s -t 0";
        const dispoff = "powershell C:/PowerShell/dispoff.ps1";
        const excel = '"C:/Program Files (x86)/Microsoft Office/Office14/EXCEL.EXE"';
        return getJsonData(value.split(" ")[1], {
          "スリープ": sleep,
          "スタンバイ": sleep,
          "止め": sleep,
          "とめ": sleep,
          "消し": sleep,
          "けし": sleep,
          "落とし": sleep,
          "おとし": sleep,
          "シャットダウン": shutdown,
          "モニター": dispoff,
          "画面": dispoff,
          "ディスプレイ": dispoff,
          "Excel": excel,
          "エクセル": excel,
          "default": false
        });
      },
      
      ...
```

スリープ・シャットダウンの他にディスプレイオフやExcelの起動なんかも実装しています。
こんな感じでコマンド実行できるものはなんでもいけちゃいます。

ちなみにディスプレイオフに使用しているのはPowerShellスクリプトです。
以下の記事のPowerShell版を参考にさせて頂きました。
[この夏、ディスプレイを強制的にスリープにする!](https://qiita.com/sharow/items/ef78f2f5a8053f6a7a41)

なおPowerShellスクリプト(.ps1)はデフォルトでは実行を制限されています。
管理者権限でPowerShellを実行し、以下のコマンドを実行することで使えるようになります。

```
Set-ExecutionPolicy RemoteSigned
```

## Node.jsの常時起動化
最後にこのindex.jsを常時起動化させてWindowsが立ち上がったら勝手に実行するようにしちゃいましょう。
方法としては2種類あります。
ひとつはWindowsサービス化、もうひとつはスタートアップでのforever実行です。

### Windowsサービス化
常時起動であればサービス化が一番自然です。
ただしひとつ問題があります。
ディスプレイオフやExcelといったGUIアプリケーションの起動ができない事です。
「起動できない」という表現は語弊があり、正しくは「見えない」のです。

サービス実行されるプロセスはユーザがログインして見えている環境とは別の環境になります。
サービス化して音声コマンドからExcelの起動はできますが、プロセスだけ生成されて画面上には出てきません。
なのでサービス化するなら電源操作や目に見えなくていいバッチ処理とかの場合が良いでしょう。

### スタートアップでのforever実行
まず「forever」についてですが、これはNode.jsをバックグラウンドで実行させるNode.jsモジュールです。
これをスタートアップ時に実行させればユーザログイン後の実行となるため、GUIアプリケーション等も同じ画面上に表示されます。

ただこちらもちょっと微妙な点があって、GUIアプリケーションと一緒にコマンドプロンプトが表示されてしまいます。
しかもGUIアプリケーションが終了されるまで消えません。
これは恐らくforeverの仕様かなと思います。
惜しい…

どちらも一長一短かとは思いますが、私はディスプレイオフはなんとしても実現したかったのでスタートアップでのforever実行を採用しています。
音声コマンドの内容によってはindex.jsを分割し、サービス化、スタートアップそれぞれ一緒に動かしちゃってもいいかもしれません。

それでは、それぞれの方法について説明していきます。

### Windowsサービス化の方法
Node.jsのWindowsサービス化自体にも「winser」というNode.jsモジュールを使用します。
Node.jsでなんでもできちゃいますね。

以下の記事を参考にサービス化してみて下さい。
[winserを使用してnode.jsアプリをWindowsサービスに登録・実行する](https://qiita.com/Kazunori-Kimura/items/c40ce82e7c044869fd27)
- winserのインストール
- package.jsonの修正
- サービス登録コマンドの実行

### スタートアップでのforever実行の方法
まずは以下のコマンドでforeverをインストールして下さい。

```
npm install -g forever
```

次にindex.jsのフォルダにfirebase.cmdというファイルを作成します。
名前はなんでもいいですが、拡張子は`.cmd`もしくは`.bat`にしといて下さい。

中身は以下のように記述します。

```bat
cd /d %~dp0
forever start index.js
```

最後にこのファイルのショートカットを作成し、Windowsのスタートアップフォルダ内に設置して下さい。

以上でindex.jsの常時起動化も完了です。

これでWindowsをGoogle Homeからスリープ・シャットダウンや、色んな操作をできるようになりました。
コマンド実行でできる事はなんでできるので夢が広がりますね。



# おわりに
これでシーリングライトから始まり、PS4、テレビ、Windowsと家にあるリモート操作可能な家電は大体Google Homeで音声操作できるようになりました。
残るはエアコンですが、これは赤外線モジュールのirMagicianのメモリ不足で現状操作不可なので、ブレッドボードで赤外線回路組んでリベンジしたいなと思ってます。

あとはIFTTTを使わず[Actions on Google](https://developers.google.com/actions/)からの操作も試してみたので、情報を整理してまた投稿したいと思います。

※2017/11/02追記　Actions on Googleやってみました↓
[IFTTTを使わずActions on Google(Dialogflow)でGoogle Homeから家電を音声操作してみる](https://qiita.com/miso_develop/items/28cbfcdfb61d455ce346)

※2017/11/24追記　エアコンも操作できるようにしました
[Google Homeに話しかけてエアコンを操作してみる](https://qiita.com/miso_develop/items/204b2e16b1e58e52dc07)