yamamoWorks

.NET技術を中心に気まぐれに更新していきます

Google HomeとAzure IoT HubとRaspberry PiでTVとPS4をコントロール【概要編】

Google Home miniを購入したので様々な家電のコントロールを実現したいと思い赤外線リモコンの信号を制御するeRemote miniを買いましたが、Amazon EchoやGoogle Homeでスマート家電として認識できるのは現状では照明のみでテレビやエアコンのコントロールはできません。 *1

出来ないなら自前で作ろう、という事でテレビ周りのコントロールを出来るようにしてみます。様々な実現方法がありますが、今回は以下の条件で進めます。

実現したいこと

  • 「OK Google、プレステを起動して」で、PS4を起動する
  • 「OK Google、プレステを消して」で、PS4をスタンバイモードにする
  • 「OK Google、torneを起動して」で、PS4を起動してtorneを開く
  • 「OK Google、Netflixを起動して」で、PS4を起動してNetflixを開く
  • 「OK Google、テレビBRAVIAをつけて」で、テレビの電源をONにする *2
  • 「OK Google、テレビBRAVIAを消して」で、テレビの電源をOFFにする
  • 「OK Google、NHKに変えて」で、テレビのチャンネルを変える

持ってるもの

  • Sony BRAVIA HX850
  • PlayStation 4
  • Raspberry Pi 2 Model B
  • eRemote (今回は使わない)

Chromecastがあればテレビのコントロールは簡単ですが買わない方向で。

アーキテクチャ

f:id:yamamoWorks:20180301013529p:plain

Google Home(というかAssistant)はIFTTTと連携しているので、Google Assistantをトリガーとした処理を定義できます。

機器のコントロールについては、BRAVIAもPS4もLAN経由でコントロールが可能である事が分かったので、眠っていたRaspberry Piを活用し信号を送るプログラムを作成することにします。 但しBRAVIAの電源ONについてはLAN経由では出来ないのでHDMI対応機器間での制御信号をやりとりする規格であるHDMI-CECを用いることにします。なのでRaspberry PiとテレビをHDMIケーブルで繋げます。

次にGoogle HomeでトリガーされたコマンドをRaspberry Piまで伝達する方法ですが、ググるとGoogleのMBaaSであるFirebaseを使う例が殆どなので、同じことをやっても面白くないのでAzure IoT Hubを使うことにします。

Raspberry Pi上には、IoT Hubに接続し受信したイベントに従ってPS4やBRAVIAに信号を送るプログラムをNode.jsで作成します。※自分のNode.jsの勉強を兼ねて

今回はここまで。次回から各部分を実際に作っていきます。

 


  1. テレビを照明として登録する裏ワザ的な使い方はありますが…

  2. 「テレビをつけて」はChromecastデバイスの制御として認識されるのでテレビのブランド名で呼ぶ事にします

認証済みアプリ/サービスの削除ページリンク集

すぐ分からなくなるのでメモ。

「Googleアカウントでログイン」みたいなやつで承認した外部サービスやアプリの認証を削除するページへのリンク集。

MediaKey Timer

皆さんSpotifyという音楽配信サービスはご存知ですか? ジャンル&気分というプレイリストがあり作業時や就寝時に音楽を流すのに最適です。 www.spotify.com f:id:yamamoWorks:20170103232900p:plain:w400

ただ少し使い勝手が悪く感じる事があります。 例えばSleepジャンルにあるこのプレイリストは全部で7時間30分の音楽が登録されており、就寝時に再生したら起きるまで音が鳴りっぱなしになってしまいます。 Spotiryのアプリに再生停止タイマーなる機能はありません。 f:id:yamamoWorks:20170103233001p:plain:w400

SpotifyをiPhoneで再生する場合はiPhone付属のタイマーアプリでタイマー終了時の動作を「再生停止」にセットすれば任意の時間で再生を停止する事ができます。 f:id:yamamoWorks:20170103235347p:plain:w300

これと同じ事をWindowsでも実現するアプリを作ってみました。

MediaKey Timer

昨今のキーボードにはメディア再生停止ボタンがついているものが多いかと思います。 f:id:yamamoWorks:20170103235946j:plain

SpotifyのWindowsアプリはこのメディア再生停止ボタンに対応しているので、これを指定した時間後に擬似的に押す処理を実行するだけのアプリです。 なおiTunesなどメディア再生停止ボタンに対応しているアプリであればコントロール可能です。

f:id:yamamoWorks:20170104000122p:plain

ダウンロードはこちら(ZIPを解凍して適当な場所に置いてください) github.com

Visual Studioスタートページの「最近使用したファイル」を自動で整理

昔から気になっていたのです、Visual Studioのスタートページにある「最近使用したファイル」にゴミが残ることを。

コードの動作確認などで一時的なプロジェクトを作っては消すなんてことは皆さん多々あると思うのですが、「最近使用したファイル」は既に存在しないプロジェクトも表示されてしまうんですよね。

そこで、勉強も兼ねて「最近使用したファイル」から存在しないエントリーを削除するVS拡張を作って公開してみました。

visualstudiogallery.msdn.microsoft.com

Visual Studio 2015の拡張機能と更新プログラムからもインストール可能です。 f:id:yamamoWorks:20161229220403p:plain

これをインストールしておくと「最近使用したファイル」から存在しないエントリーを自動で削除してくれます。 f:id:yamamoWorks:20161229220645p:plain

解説

折角ですので仕組みも解説しておきます。 まず「最近使用したファイル」はレジストリに記録されています。

HKEY_CURRENT_USER\SOFTWARE\Microsoft\VisualStudio\14.0\MRUItems\{a9c4a31f-f9cb-47a9-abc0-49ce82d0b3ac}\Items

VS拡張でここに記録されているソリューションファイル/プロジェクトファイルの存在確認をしてレジストリを書き換えればいいので朝飯前ですね。

まずExtensibilityにあるVSIX Projectプロジェクトを作成します。 f:id:yamamoWorks:20161229222143p:plain

次にExtensibilityにあるVisual Studio Packageをプロジェクトに追加します。 f:id:yamamoWorks:20161229222557p:plain

するとPackageクラスを継承したクラスが自動生成されているのでInitializeメソッドをオーバライドで実装します。

Registry

PackageクラスにはUserRegistryRootプロパティがあり「HKEY_CURRENT_USER\SOFTWARE\Microsoft\VisualStudio\14.0」を表すRegistryKeyを返してくれるので便利です。後は目的のサブキーを開いて書き換えるだけです。

protected override void Initialize()
{
    base.Initialize();

    var regKey = UserRegistryRoot.OpenSubKey("MRUItems")?
        .OpenSubKey("{a9c4a31f-f9cb-47a9-abc0-49ce82d0b3ac}")?
        .OpenSubKey("Items", true);

    if (regKey == null)
        return;

    var mruItems = regKey.GetValueNames()
        .Select(name => (string)regKey.GetValue(name))
        .Where(value => File.Exists(value.Split('|').First()))
        .ToArray();

    foreach (var name in regKey.GetValueNames())
    {
        regKey.DeleteValue(name);
    }

    for (var i = 0; i < mruItems.Length; i++)
    {
        regKey.SetValue(i.ToString(), mruItems[i]);
    }

    regKey.Close();
}

デバック実行すると別のVisual Studioが起動して動作確認が可能です。 ちなみに、ここで起動してくるVisual Studioが参照するレジストリは「... \VisualStudio\14.0Exp\ ...」となっており開発環境と混在しないようになっています。

で、上記コードで動かしてみると・・・少し残念な挙動となります。

実は「最近使用したファイル」に表示する内容はVS拡張のInitializeメソッドが呼ばれる前に既に読み込まれており 、Initializeメソッドでレジストリを変更してもその時の起動では反映されないのです。 もちろん、次に起動した際には反映された状態になります。

この挙動では納得がいきません (・へ・)

そこで「最近使用したファイル」が表示される仕組みをILSpyで探り、何とか起動時に反映されるように実現できました。

ProjectMruList

「最近使用したファイル」の処理で中心となるのが Microsoft.VisualStudio.Shell.UI.Internal.dll にある Microsoft.VisualStudio.PlatformUI.ProjectMruList というInternalのクラスです。
Internalです・・・黒魔術決定。

ProjectMruListのインスタンスはDataSourceFactoryから{9099ad98-3136-4aca-a9ac-7eeeaee51dca}のIDで取り出せる事が解ったので、下記のコードで実現できました。

protected override void Initialize()
{
    base.Initialize();

    var dataSourceFactory = GetService(typeof(SVsDataSourceFactory)) as IVsDataSourceFactory;
    if (dataSourceFactory == null)
        return;

    IVsUIDataSource projectMruList;
    dataSourceFactory.GetDataSource(Guid.Parse("9099ad98-3136-4aca-a9ac-7eeeaee51dca"), 1, out projectMruList);

    var projectMruListType = Type.GetType("Microsoft.VisualStudio.PlatformUI.ProjectMruList, Microsoft.VisualStudio.Shell.UI.Internal", true);
    var removeAtItemMethod = projectMruListType.GetMethod("RemoveItemAt");
    var itemsProperty = projectMruListType.GetProperty("Items");

    var fileSystemMruItemType = Type.GetType("Microsoft.VisualStudio.PlatformUI.FileSystemMruItem, Microsoft.VisualStudio.Shell.UI.Internal", true);
    var pathProperty = fileSystemMruItemType.GetProperty("Path");

    var items = (IList)itemsProperty.GetValue(projectMruList, null);
    for (var i = items.Count - 1; i > -1; i--)
    {
        var path = (string)pathProperty.GetValue(items[i], null);
        if (!File.Exists(path))
        {
            removeAtItemMethod.Invoke(projectMruList, new object[] { i });
        }
    }
}

朝飯前が晩飯前になってしまいました (;・∀・)

ソースコードはこちらに公開してあります。 github.com