Androidのメモとか

ポキオの日記です。今日も遅延してない。

続・SonosのNode-RED向けノードを更新して様々な情報を取得できるようにしました

つけめんたべたい。

ポキオ Node-RED Sonos

ちょっとずつ機能を増やしてます

前回もちょっと機能を増やしましたが・・・。

relativelayout.hatenablog.com

IssueでSonosのスピーカー名を取りたいという話したあったので、今回はそれに対応しました。

github.com

flows.nodered.org

v0.1.9から、msg.nameにPlayerの名前が入るようになりました。こんな感じです。

{
    "name": "Living Room",   // a name of Sonos player.
    "event": "CurrentTrack",   // event kind. if `More Events` is checked, this may vary.
    "address": "192.168.1.35",   // an ip address of Sonos player.
    "payload": {   // current track info is included in `payload`.
        "id": null,
        "parentID": null,
        "title": "Scream & Shout",
        "artist": "will.i.am",
        "album": "#willpower",
        "albumArtURI": "https://i.scdn.co/image/ab67616d0000b2738165b764264fb3705d7367d6",
        "uri": "x-sonos-spotify:spotify:track:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "duration": 282,
        "queuePosition": 1
    },
    "_msgid": "xxxxxxxx.xxxxxx"
}

Node-RED Dashboardにぶちこむ

さて、色々情報が取れる様になってきたので、これをNode-RED Dashboardを使って可視化もできちゃいます。

ポキオ Node-RED Sonos

Artworkなどの情報は、上記のCurrentTrackのイベントから取得可能ですし、PlayStateやVolumeなども通知するような設定にしておけば取得できますので、あとは情報をNode-RED Dashboardに送るだけです。こんな感じで可視化できると楽しいですねぇ。

ポキオ Node-RED Sonos

Dashboardから入力を受け取って、それをトリガーにSonosをコントロールもしてみたいですね・・・。おっと、今回はこのへんで。

SonosのNode-RED向けノードを更新して様々な情報を取得できるようにしました

やきにくだべたい。

ポキオ Node-RED Sonos

以前作ったSonosノードをアップデート

ちょっと前に世界一簡単なSonosノードを作って公開したわけですが。

relativelayout.hatenablog.com

簡単さにフォーカスを当てているため、現在の曲をイベントとして飛ばすだけのノードになっていますが、「もっと情報を取りたい」という要望があったため、今回はそこんところを拡張しました。

様々な状態変化に対応

v0.1.7から、現在の曲情報に加えて、音量や再生状態の情報に対応しました。

flows.nodered.org

プロパティからMore Events (Experimental)にチェックを入れると、この機能が有効になります。

ポキオ Node-RED Sonos

追加で取れる情報は以下の通り。

  • NextTrack
  • PlayState
  • AVTransport
  • Volume
  • Muted
  • RenderingControl

とりあえず使ってみて、どんな感じかお試しくださいませ。

More EventsとNode-RED Dashboardを組み合わせる

たとえば、Node-RED Dashboardと組み合わせるとこんなことができます。

ポキオ Node-RED Sonos

そう、現在聞いている曲情報と音量を表示するような簡単なDashboardが作れちゃいます。

ポキオ Node-RED Sonos

よしなーにノードから流れる情報をフィルタして、Dashboardに流し込んでるだけです。今回は情報表示だけですが、Dashboard上から音楽の再生制御をできると、ちょっとおもしろそうですよね。

Node-REDのDashboardを使ったら秒で京急運行情報ページができた

良き。

ポキオ Node-RED Dashboard 京急

Node-RED Dashboard

そんなものもあったなぁと思いながら、今まで一回も使ったことなかったんですが、いざ使ってみると最高ですね、控えめに言って。

flows.nodered.org

Node-REDのFlowで扱っているデータの可視化を簡単にできるのがDashboardで、Webページを作ることができます。

やってみよう

とりあえずDashboardを試してみようと思います。まずは、先程のnode-red-dashboardノードをインストールします。

ポキオ Node-RED Dashboard 京急

すると、こんな感じでノードがモワッと増えます。これらはそれぞれDashboard上のUIの部品に対応していて、いろいろな表示が可能になっています。今回はシンプルに京急運行情報を表示するだけのDashboardを作ってみます。

ポキオ Node-RED Dashboard 京急

使うノードは3つで、

  • インジェクションノード:京急ノード発火用
  • 京急ノード:以前作った、京急運行情報を取得するだけのノード
  • UI Textノード:京急運行情報を表示するUIパーツ

これらをただつないで、UI Textノードの設定で、表示設定をしてあげると・・・。

ポキオ Node-RED Dashboard 京急

こんな感じで、チョッパヤでWebページが作れました。(URLは気にしないでw)

Dashboard、UIパーツのバリエーション的に、センサーデータの可視化とかに向いてそうな雰囲気があるので、Node-REDだけでセンサー監視画面とか簡単にできちゃいそう・・・。これは事件ですね。

ノイキャンイヤホンを買ったらWFH with kidsでも生き残れそうな気がした

すっごい静か。

ポキオ WFH with kids WF-1000XM3

厳しい戦い

真面目な話、テレワークをしながら子供の面倒を見るのは非常に厳しいわけです。弊家では、日中は2人の子供を1人で面倒を見ながら仕事をしているわけですが、やたら喧嘩したり、テレビ会議中に限って騒ぎ出したり…。まぁ、子どもたちもストレス溜まってるんだと思うんですけどね。

で、今まではDVD見せたりお菓子を食べさせたりして紛らせていたんですが、とはいえちょっと限界で、どうしても集中して進めたい仕事が続くと、あまりパフォーマンスが出せずに困っていました。

思い切ってノイキャンイヤホンを買う

これ以上ストレスを溜めるのも良くないし、手軽に静かな環境を作るにはノイズキャンセリングイヤホンしかないと思い、思い切ってイヤホンを買ってみました。

この投稿をInstagramで見る

静寂を買った。

pokio(@pokiiiiio)がシェアした投稿 -

耳からうどんを垂らすことも考えましたが、みんなうどんを垂らしがちだったので、WF-1000XM3にしてみました。久しぶりにちゃんとしたガジェットを購入したので、非常に体調が良いです(笑)

とにかく静か

WFH with kidsで使ってみたんですがとにかく静か。(語彙力)

ポキオ WFH with kids WF-1000XM3

数年前に購入したノイズキャンセリングヘッドホンとは比べ物にならないほどのノイキャン効果に感じます。ちょっと子供が騒いでても全く気にならないし、食洗機や換気扇の音が完全にキャンセルされているので、集中したい作業のときは本当に役に立ちます。今日はひたすらRIP SLYMEを聞きながらコーディングしてました(笑)

しかも、それが左右独立の完全ワイヤレスで実現できているので、耳周りも軽いし、耳に汗もかきません。これから暑くなるシーズンでもガンガン使えそうな気がします。

ポキオ WFH with kids WF-1000XM3

自分がたてている音さえもキャンセルされるすごいイヤホンなんですが、一方でボタン一つで外の音も聞くことができるクイックアテンションモードも搭載されているので、急に子供に話しかけられても瞬時に対応できるスグレモノ。嗚呼、なんで早く買わなかったんだろう。

イヤホンを外すと音楽を一時停止してくれたり、無駄にいろいろやってくれてすごい。これならコロナ収束後の通勤も楽しくなりそうですね(出勤したいとは言ってない)。

久しぶりに訪れた静寂

マイクの性能も口からマイクが遠い割には良くて、テレビ会議でも使えて最高でした。ただ、周りで子供が騒いでると、それも忠実に集音してくれるので、そこまでキャンセルはしてくれないので注意が必要そうです(笑)

全集中で仕事したいときに本当に便利なアイテムなので、同じ境遇で頑張ってる人たちには本当におすすめですー。ノイキャンでストレスを少しでも軽減して、厳しい状況を乗り越えていきたいですね。

GooglePhotosの写真をMarkdown形式に変換するツールをGASを使ってWebApp化した

まぐろたべたい。

ポキオ GooglePhotoMarkdowner Online

もうWebAppでよくね?

以前作成したElectronアプリがありまして。

relativelayout.hatenablog.com

これは、GooglePhotosにアップした写真への直リンクを取得し、Markdownなドキュメントに貼り付けられるように変換するツールです。

このアプリは、GooglePhotos上の画像をリンクで共有した際に、そのリンク先のページのOpen Graph Protocolで記載された画像パスを取得し、Markdown形式に変換するアプリです。要はHTTP-GETして、パースして、適当な文字列を付加するだけの単純なアプリです。

もともとElectronで実装していたものの、デスクトップアプリでなくてもいいようなレベルの実装内容なのと、最近地味に使っているChrome OSでも使いたくなった(Electronが動かない)ため、今回はWebApp化を行ってみました。

WebAppをGASで超絶簡単に作成する

GASは光速でWebAPIが無料で作れることに定評がありました。

relativelayout.hatenablog.com

WebAPIができるので、サーバーサイドの処理をWebAPIとして切り出してしまったり、WebAPIの返り値に静的なHTMLを返せばWebページのホスティングもできてしまうすごい子なんです。

今回はこんな感じで処理を書いていました。

  • GAS側で実装したのはmarkdowner.gsindex.html
  • UIや基本的な処理はindex.html記載(めんどくさかった)
  • ただしindex.html上で他のドメインにHTTP-GETを行うとCORSに引っかかってしまう
  • そこでmarkdowner.gs側にHTTP-GETをする機能を実装
  • index.htmlからgoogle.script.runを使ってmarkdowner.gsの処理を呼び出す

コードは例によって最後に記載しておきます。

これで念願のWebApp化完了

出来上がったものがこちら

ポキオ GooglePhotoMarkdowner Online

https://bit.ly/3dYB5Yd

これでブラウザさえあればブログ執筆の効率化ができそうです。一応WebAppのURLを公開しておきます、動作の保証はありませんがきっと動きます。

クソコードはこちら

markdowner.gs

function doGet() {
  return HtmlService.createHtmlOutputFromFile('index');
}

function convertUrl(url){
  var response = UrlFetchApp.fetch(url);
  return response.getContentText().match(/<meta property="og:image".*?>/g)[0].match(/http.*?=/g)[0].replace("=", "");
}

index.html

<!DOCTYPE html>
<html>

<head>
    <base target="_top">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
        integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
</head>

<body role="document">
    <nav class="navbar navbar-dark bg-primary">
        <span class="navbar-brand mb-0 h1" id="result">GooglePhotoMarkdowner Online</span>
    </nav>

    <form class="mx-2">
        <div class="form-group">
            <div class="row mt-2 align-items-end">
                <div class="col">
                    <label for="photoLink">URL</label>
                    <input class="form-control" type="url" id="photoLink" placeholder="Google Photoへの共有リンクを貼り付けてください">
                </div>
            </div>

            <div class="row mt-2">
                <div class="col-3">
                    <label for="photoSize">サイズ</label>
                    <input class="form-control" type="number" id="photoSize" placeholder="(オプション)">
                </div>

                <div class="col">
                    <label for="photoAlt">代替テキスト</label>
                    <input class="form-control" type="text" id="photoAlt" placeholder="(オプション)">
                </div>

                <div class="col">
                    <label for="photoTitle">画像タイトル</label>
                    <input class="form-control" type="text" id="photoTitle" placeholder="(オプション)">
                </div>
            </div>

            <div class="row justify-content-center mt-2">
                <div class="col-auto">
                    <button type="button" class="btn btn-primary btn-lg" id="start" onclick="convert()">変換</button>
                </div>
                <div class="col-auto">
                    <button type="button" class="btn btn-secondary btn-lg" id="clear" onclick="clearAll()">クリア</button>
                </div>
            </div>



            <div class="row mt-4 align-items-end">
                <div class="col">
                    <label for="photoLink">変換されたURL</label>
                    <input class="form-control" type="text" id="resultUrl">
                </div>
            </div>

            <div class="row mt-2 align-items-end">
                <div class="col">
                    <label for="photoLink">Markdown記法</label>
                    <input class="form-control" type="text" id="resultMarkdown">
                </div>
            </div>

            <div class="row mt-2 align-items-end">
                <div class="col">
                    <label for="photoLink">HTML記法</label>
                    <input class="form-control" type="text" id="resultHtml">
                </div>
            </div>
        </div>
    </form>

    <!-- Modal -->
    <div class="modal fade" id="processingModal" tabindex="-1" role="dialog" aria-labelledby="processingModalTitle"
        aria-hidden="true">
        <div class="modal-dialog modal-dialog-centered modal-sm" role="document">
            <div class="modal-content">
                <div class="modal-body">
                    変換中…
                </div>
            </div>
        </div>
    </div>

    <div class="modal fade" id="errorModal" tabindex="-1" role="dialog" aria-labelledby="errorTitle" aria-hidden="true">
        <div class="modal-dialog modal-dialog-centered modal-sm" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title modal-title-danger">エラー</h5>
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                </div>
                <div class="modal-body">
                    変換できませんでした。
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-danger" onclick="$('#errorModal').modal('hide');">OK</button>
                </div>
            </div>
        </div>
    </div>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
        integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous">
        </script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js"
        integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous">
        </script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js"
        integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous">
        </script>

    <script>
        function pasteFromClipboard() {
            if (navigator.clipboard) {
                navigator.clipboard.readText()
                    .then(function (text) {
                        $("#photoLink")[0].value = text;
                    });
            }
        }

        function convert() {
            if (!$("#photoLink")[0].value) {
                $('#errorModal').modal('show');
                return;
            }
            $('#processingModal').modal('show');
            getOgImageUrl();
        }

        function getOgImageUrl() {
            google.script.run.withSuccessHandler(function (result) {
                var generatedUrl = generateResizedUrl(result);
                setAddressResult(generatedUrl);
                setMarkdownResult(generatedUrl);
                setHtmlResult(generatedUrl);
                $('#processingModal').modal('hide');
            }).convertUrl($("#photoLink")[0].value);
        }

        function generateResizedUrl(url) {
            var result = url;

            if ($("#photoSize")[0].value) {
                result += "=s" + $("#photoSize")[0].value;
            } else {
                result += "=s0";
            }

            return result;
        }

        function setAddressResult(result) {
            $("#resultUrl")[0].value = result;
        }

        function setMarkdownResult(result) {
            var markdownResult = "![" + $("#photoAlt")[0].value + "](" + result + " \"" + $("#photoTitle")[0].value + "\")";
            $("#resultMarkdown")[0].value = markdownResult;
        }

        function setHtmlResult(result) {
            var htmlResult = "<img src=\"" + result + "\" alt=\"" + $("#photoAlt")[0].value + "\" title=\"" + $("#photoTitle")[0].value + "\">";
            $("#resultHtml")[0].value = htmlResult;
        }

        function clearAll() {
            $("#photoLink")[0].value = "";
            $("#photoSize")[0].value = "";
            $("#photoAlt")[0].value = "";
            $("#photoTitle")[0].value = "";
        }

        function copyUrl() {
            if (navigator.clipboard) {
                navigator.clipboard.writeText($("#resultUrl")[0].value);
            }
        }

        function copyMarkdown() {
            if (navigator.clipboard) {
                navigator.clipboard.writeText($("#resultMarkdown")[0].value);
            }
        }

        function copyHtml() {
            if (navigator.clipboard) {
                navigator.clipboard.writeText($("#resultHtml")[0].value);
            }
        }
    </script>
</body>

</html>

他意はないけどArduinoでPCがスリープしないようにしてみたよ

やましいことなんてなにもない(震)

ポキオ Arduno Freaduino

マウスとして振る舞うArduino

以前、Arduinoをキーボードデバイスとしてエミュレーションすることで、世にはびこる無駄な定型文をボタン一つで入力できるようにしました。 今回は、その応用とも言うべき使い方をしてみようと思います。

relativelayout.hatenablog.com

というのも、(細かいことは言いませんが)PCが勝手にスリープしてしまって、スリープ解除しようとしたらパスワードを求められて、めんどくさいなって思って、スリープに落ちる時間を調整しようと思ったら、なんか権限がなくて変えられなかったりすることってあるじゃないですか?あるんですよ。そういうときに、スリープに落ちないようにPCを起きたままにしておいてくれるデバイスがあればなぁと思い、作ってみました。

ATmega32U4搭載のArduinoでできる技

マウスやキーボードのエミュレーションは、どのArduinoでもできるわけではなくて、ATmega32U4が搭載されているものに限られます。が、互換ボードでも安くてATmega32U4を搭載しているものもあるので最高です。

こんな感じで「定期的にちょっと動かす」っていうルーティンを実装できちゃいます。

#include <Mouse.h>
#define INTERVAL 1 * 60 * 1000
#define DELTA 10


void setup() {
  Mouse.begin();
}

void loop() {
  Mouse.move(DELTA, 0, 0);
  delay(100);
  Mouse.move(0, DELTA, 0);
  delay(100);
  Mouse.move(DELTA * -1, 0, 0);
  delay(100);
  Mouse.move(0, DELTA * -1, 0);
  delay(100);
  delay(INTERVAL);
}

これで、画面がロックされなくなるぞーーー!

Sonosのホームオーディオを無理やりクルマに積んでみた

よせばいいのに。

ポキオ Sonos IKEA Symfonisk

tl;dr

これです。


ポキオ・カープール 第5回 「Sonos x IKEAのSymfoniskをクルマに載せて走ってみた」

ホームオーディオをカーオーディオに?

ただやってみたかっただけなんです。

www.ikea.com

ちょっと前に発表された、SonosとIKEAのコラボスピーカー(Symfonisk)が有るんですが、こやつをクルマに載せたらどうなるかやってみたくて、ちょっとトライしてみました。

ポキオ Sonos IKEA Symfonisk

もともとSymfoniskをはじめ、Sonosのスピーカーは、

  • AC電源で駆動
  • Wi-Fiネットワークが必要

なんですが、これらをなんとか車内にこしらえるべく、ポータブル電源とモバイルルーターを準備。

ポキオ Sonos IKEA Symfonisk

まずはポータブル電源。Symfoniskは100-240V・50-60Hz対応なので何も気にせずにつなぐだけで使えました。消費電力もおそらく大した事がないので、ある程度長時間使えそうです。

この投稿をInstagramで見る

楽天モバイルun-limit、結局w04で運用する流れ。

pokio(@pokiiiiio)がシェアした投稿 -

Wi-Fi楽天モバイルUn-limitのSIMカードを、WiMAX用のモバイルルーター(Speed Wi-Fi NEXT W04)に挿して準備しました。音楽ストリーミングサービスを使うくらいなら、4G回線で問題ありませんでした。

で、実際に載せてみてどうだったの?

こんな感じでドライブしてきました。

ポキオ Sonos IKEA Symfonisk

  • 電源、Wi-Fiは問題なかった
  • カーブでスピーカーが安定しなかった
  • 音が広がる感じではないので、包まれているような感覚が薄かった

なので、Sonos Beamのようなサウンドバーをおいたほうがいいかもしれません。Beamを貸してくれる人がいたら貸してください・・・(笑)あとはAlexaが使えると、もしかしたら便利かもしれないですねぇ。(SymfoniskはAlexa非対応)

「Androidのメモとか」は、Amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、Amazonアソシエイト・プログラムの参加者です。

このブログは個人的なメモ書きであったり、考えを書く場所であります。執筆者の所属する団体や企業のコメントや意向とは無関係であります。また、このブログは必ずしも正しいことが書かれているとは限らず、誤字脱字や意図せず誤った情報を載せる場合がありえます。それが原因で読者が不利益を被ったとしても、執筆者はいかなる責任も負いません。ありがとうございます。