仁和歌ブログ

情報のインプットしかしてこなかった自分が、情報のアウトプットに挑戦していくブログ。

「Samsung DeX」使用中に端末側にぼかしが入る

Galaxy DeX

Samsung DeXを使用中に、端末側の画面にぼかしが発生する問題に悩まされました。
解決策を見つけたので、その手順を備忘録として共有します。

環境

項目 バージョン
端末 Galaxy S20 5G
OS Android 12
Samsung DeX 4.2.15.2

問題

スマホに外部ディスプレイを繋げて、物理キーボードとマウスを接続して、外部ディスプレイ側で作業をしていました。
端末側はYouTubeを再生し、視聴しながらの作業です。

しばらく作業して、ふとYouTubeに目をやると画面にぼかしが入っていました。
端末側の画面をタップしたり、DeX側で設定画面やアプリ一覧を開くとぼかしが消えるという状況でした。

原因

色々調べてみたところ、ぼかしが発生するのはソフトウェアキーボードが原因のようでした。

キーボードの後ろにぼかしを入れる仕様になっているIMEを使っていると、この事象が発生するようです。

ソフトウェアキーボードが非表示の設定になっていても、内部的にはキーボードが表示されており、ぼかしだけが適用されるという状況のようです。

解決策

問題の解決には以下の2つの方法があります。

  1. IME(Input Method Editor)をGalaxyキーボードに変更する。
  2. DeX中のソフトウェアキーボードの表示設定を「テレビ/モニター」に変更する。

Galaxyキーボードを使用していれば、この事象は発生しないようです。 ぼかしを入れる仕様になっていないのでしょう。 ですが、この方法だと毎回IMEを変える必要があるため面倒です。

なので個人的には2の方法がおすすめです。
非表示状態のIMEが外部ディスプレイ側に表示されることになるため、端末側にぼかしが入らなくなります。
もちろん、DeX側は通常通りぼかしのない状態で利用できます。

最後に

2の方法は物理キーボードを繋いでいる限りは問題なく使えています。
仮に物理キーボード無しで使用する場合は、設定を変える必要があるかもしれません。

以上、備忘録でした。

2023/11/27 追記

よく確認したら、DeX中のソフトウェアキーボードの表示設定を「テレビ/モニター」に変更したら、自動的に「Galaxyキーボード」に切り替わるようでした。

DeX中のソフトウェアキーボードの表示設定を「テレビ/モニター」に変更 Galaxyキーボードに切り替わっており、変更不可になっている。

しかも、Galaxyキーボードから変更不可になっていました。

これはDeXの仕様で、普通には変えられないようです。
コマンドで強制的に値を変更すればキーボードを変更できるようで、詳しくはこちらの方がNoteにまとめてくださっていました。

Samsung DeX で Gboard を使う|chiisama log

今回、私の方では試していませんが、必要になれば試してみたいと思います。

Samsung DeX で Gboard を使う

今回、私は最初から「オンスクリーンキーボードの位置」を「端末」にしていました。
そのため「端末側にぼかしが入る症状」としか認識していませんでした。

しかし、この設定にしていればDeX側でGboardが使えていました。

なので「Samsung DeX」使用中に他のキーボードに変えたい場合は「オンスクリーンキーボードの位置」を「端末」にするといいかもしれません。

DeX中のソフトウェアキーボードの表示設定を「端末」に変更

「端末側はマウスパッドにするから画面は見ない」という場合にはこちらの対応の方がハードルが低いかもしれませんね。

以上。

WSL(WSL2)でバージョン確認が出来ないとき【`wsl --version`が使えない】

前々から気になっていたのですが、当方の環境ではwsl --versionでバージョン確認が出来ませんでした。
wsl --versionを実行すると、WSLコマンドの使い方が表示されるのです。

ひょんなことから原因が判明したので、簡単にまとめたいと思います。

1. WSL2である必要がある。

そもそも--version オプションは、WSL(WSL1)では使用できません。

これについては多くの情報が出ているので、既にご存知の方も多いと思います。 Windowsバージョンが相当古いバージョンでない限り、wsl --updateでWSL2にすることが出来ます。

2. Store版WSLである必要がある。

WSL2にはwsl --installでインストール可能な「Built-in版」とMicrosoft Storeから入手してインストール可能な「Store版」の2種類があります。
正確には「あった」と言った方が正しいですが。

今までwsl --installでインストールされるWSLはOSに内蔵された「Built-in版」でしたが、特定のOSバージョンからwsl --installで「Store版」がインストールされるようになったそうです。

3. アップデート「KB5020030」が適用されている必要がある。

Store版のWSLを使用するには、Windows10のアップデート「KB5020030」が適用されている必要があります。
「KB5020030」はオプションの更新で、Windows Updateから「Windows 10、バージョン 22H2 の機能更新プログラム」をダウンロードしてインストールすることで適用できます。(注意:この更新を適用するにはPCの再起動が必要)

この更新を適用した後であれば、wsl --installwsl --updateでStore版のWSLがインストールされるようになります。

WSLの更新後は、WSLの再起動が必要になるので注意してください。
Store版のWSLに更新されていれば、wsl --versionが使えるようになります。

まとめ

WSL2(Store版)でなければwsl --versionを使用することができませんでした。
Store版のWSLを使用するには、Windows10のアップデート「KB5020030」が適用されている必要があります。

アップデート「KB5020030」が適用されていない場合、以下の手順でOSとWSLの更新を行う必要があります。

  1. Windows Updateからオプション更新「Windows 10、バージョン 22H2 の機能更新プログラム」(KB5020030)をダウンロードしてインストール
  2. PCを再起動
  3. wsl --updateでWSLを更新
  4. WSLを更新後、wsl --shutdownで停止し、WSLを再起動

【当方の環境】

バージョン
エディション Windows 10 Home
バージョン 21H1 → 22H2
OS ビルド 19044.2311 → 19045.2486

document.createElementの初期化をワンライナーで書きたい

TL;DR

let textInput;
document.body.append(
  ((elm=document.createElement('div'))=>(e=>e)(elm
    ,elm.id='box'
    ,elm.class...

let textInput;
document.body.append(
  ((elm=document.createElement('div'))=>(e=>e)(elm
    ,elm.id='box'
    ,elm.classList.add('box')
    ,elm.append(
      //左ボタン
      ((lBtn=document.createElement('button'))=>(e=>e)(lBtn
        ,lBtn.id="lBtn"
        ,lBtn.classList.add('item')
        ,lBtn.classList.add('btn')
        ,lBtn.innerText="左"
        ,lBtn.addEventListener('click',(ev)=>{
          textInput&&(textInput.value="←")
        })
      ))()
    )
    ,elm.append(
      //値表示
      textInput=((elm=document.createElement('input'))=>(e=>e)(elm
        ,elm.id="textInput"
        ,elm.classList.add('item')
        ,elm.value=""
      ))()
    )
    ,elm.append(
      //右ボタン
      ((rBtn=document.createElement('button'))=>(e=>e)(rBtn
        ,rBtn.id="rBtn"
        ,rBtn.classList.add('item')
        ,rBtn.classList.add('btn')
        ,rBtn.innerText="右"
        ,rBtn.addEventListener('click',(ev)=>{
          textInput&&(textInput.value="→")
        })
      ))()
    )
    ,elm.append(
      //style
      ((style=document.createElement('style'))=>(e=>e)(style
        ,style.textContent=`/*injection style*/
.box {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: stretch;
  justify-content: space-between;
}
.item {
  flex-grow: 1;
}
#textInput {
  text-align: center;
}
`
      ))()
    )
  ))()
);

背景

私はよく、既存サイトを使いやすくするために、プレーンなJS(ES6)でサッと改造したりします。(例えば、 Gmailのツールチップを消したり )

その時、document.createElementで新規要素を作成して、画面に追加することがしばしばあります。

//div要素の作成
let elm = document.createElement('div');
//各種プロパティの設定
elm.id='elm1';
elm.classList.add('elm');
elm.style.color="red";
elm.innerText=1;
//bodyに追加
document.body.append(elm);

上記のように要素の作成とプロパティの初期化を行い、適当にbodyなどに追加します。

要素が1つだけなら良いのですが、大体は複数の要素をネストして目的のパーツを作成します。

let box = document.createElement('div');
box.id='box';
box.classList.add('flexbox');
let item1 = document.createElement('div');
item1.id='item1';
item1.classList.add('flexitem');
box.append(item1);
let item2 = document.createElement('div');
item2.id='item2';
item2.classList.add('flexitem');
box.append(item2);
document.body.append(box);

「要素の生成」と「プロパティの初期化」と「要素の追加」がグッチャグチャに入り混じってますね。

サッと改造するだけなので、記述順は毎回マチマチです。 先に要素の生成と追加を済ませて、後からプロパティを設定することもあります。

let box = document.createElement('div');
let item1 = document.createElement('div');
let item2 = document.createElement('div');
document.body.append(box);
box.append(item1);
box.append(item2);
box.id='box';
box.classList.add('flexbox');
item1.id='item1';
item1.classList.add('flexitem');
item2.id='item2';
item2.classList.add('flexitem');

そして毎回途中で「なんか見づらい…」となり、そして毎回「本当はメソッドチェインで書きたいなぁ」と思います。

例えばこんな風に。

document.body.append(
  document.createElement('div')
  .id='box';
  .append(
    document.createElement('div')
    .id='item1'
    .classList.add('flexitem');
  )
  .append(
    document.createElement('div');
    .id='item2'
    .classList.add('flexitem');
  )
)

jQueryとかではメソッドチェインで実装可能そうですね。

Dartとかではカスケード記法(ダブルドット記法)というのが作られているらしく、似たような書き方ができるようです。

他にも同じような悩みを持っている人がいたのでしょうか。

ただ、私はES6で書きたい…

F12で既存サイトを改造するのに、わざわざライブラリ入れたりトランスパイラ噛ませたりしたくない…

というわけで、ES6だけで出来る方法を編み出しました。

本題

まず、ワンライナーでdocument.createElement&初期化してみる。

let elm = ((elm=document.createElement("div"))=>(e=>e)(elm/*,elm.id='hoge',elm.classList.add('hoge'),elm.innerText='hoge'*/))();

コピペで使いやすいように、プロパティの初期化部分はコメントアウトしておきました。

改行や中括弧を足して、構造を見ていきましょう。

let elm = (
  (elm=document.createElement("div")) => {//引数elmの初期値で要素の作成
    return (//「第一引数をそのまま返すアロー関数」の戻り値をそのまま返す
      e=>e//第一引数をそのまま返すアロー関数
    )(elm//「第一引数をそのまま返すアロー関数」の第一引数に要素を渡して実行
/*第二引数以降はプロパティの初期化
     ,elm.id='hoge'
     ,elm.classList.add('hoge')
     ,elm.innerText='hoge'
*/)
  }
)();//これらを即実行して、作成した要素を変数に挿入

アロー関数無しで書いた方が伝わる部分もあると思うので、functionでの書き方でも書いておきます。

let elm = (
  function(elm=document.createElement("div")) {
    return (
      function(e){return e}
    )(elm/*,elm.id='hoge',elm.classList.add('hoge'),elm.innerText='hoge'*/)
  }
)();

2つの無名関数を即実行し、内側の関数実行の余剰引数でプロパティ設定を済ませてしまっているわけです。

ポイントは3点。

  • 第一引数をそのまま返すアロー関数
  • 「第一引数をそのまま返すアロー関数」を実行する際の第二引数以降でプロパティを設定
  • 引数elmの初期値で要素の作成

順に説明していきます。

第一引数をそのまま返すアロー関数

まずは第一引数をそのまま返すアロー関数について。

ワンライナーで書くと、何処がそれに該当するのかわかりづらいので、該当の関数だけ抜き出します。

//アロー関数
e=>e
//function
function(e){return e}

これを実行すると、第一引数をそのまま返します。そのまんまです。

この関数の役割は、「document.createElementした要素を呼び出し元へ返す」と「第二引数以降を無視する」の2点になります。

「第二引数以降を無視する」については、次の節で解説します。

「第一引数をそのまま返すアロー関数」を実行する際の第二引数以降でプロパティを設定

「第一引数をそのまま返すアロー関数」は、引数を一つしか取りません。

そのため、第二引数以降(余剰引数)は「実行はされるが無視される」ということになります。(参照することも可能ですが今回は使用しません)

(e=>e)(
  elm//第一引数は呼び出し元に返される
  ,elm.id='hoge'//余剰引数
  ,elm.classList.add('hoge')//余剰引数
  ,elm.innerText='hoge'//余剰引数
)

余剰引数に指定した式は実行されるので、プロパティの設定は実行され、第一引数に指定した要素のみが呼び出し元に返されることになります。

これによりメソッドチェインのように「処理実行後に自身を返却する」という動きになります。

引数elmの初期値で要素の作成

無名関数が2つあるのでわかりづらいですが、外側の関数だけを抜き出せば何をしているのかわかりやすいと思います。

//アロー関数
((elm=document.createElement("div"))=>elm)()
//function
(function(elm=document.createElement("div")) {
  return elm
})()

内側の無名関数をelmに置き換えると、「引数elmの初期値で要素を作成し、それをそのまま返却しているだけ」になります。

なので、これと先程までの関数を組み合わせて「要素の生成」と「プロパティの初期化」を実現しているわけです。

他の方法

余談ですが、要素の作成は引数elmの初期値で作成しなくても実現可能です。

let elm = (
  function(elm) {
    return (
      function(e){return e}
    )(elm/*,elm.id='hoge',elm.classList.add('hoge'),elm.innerText='hoge'*/)
  }
)(document.createElement("div"));//これでも可能だが、div要素を生成したことが後ろを見ないとわからないのは使い勝手が悪い

これでも問題ないのですが、document.createElement("div")が最後にくると読みづらいため、引数elmの初期化で要素の生成をおこなっています。

使用例

このワンライナーを使えば、前章の例は以下のように書けます。

let box = ((elm=document.createElement('div'))=>(e=>e)(elm,elm.id='box',elm.classList.add('flexbox')))();
let item1 = ((elm=document.createElement('div'))=>(e=>e)(elm,elm.id='item1',elm.classList.add('flexitem')))();
let item2 = ((elm=document.createElement('div'))=>(e=>e)(elm,elm.id='item2',elm.classList.add('flexitem')));
document.body.append(box);
box.append(item1);
box.append(item2);

これで綺麗に「要素の生成と初期化」と「要素の追加」に分けることができました。

ただ、こままだと初期化するプロパティが増えたとき、追加する場所が探しずらいです。 実用の際には以下のように改行して可読性を上げるのが良いでしょう。

let box = ((elm=document.createElement('div'))=>(e=>e)(
  elm
  ,elm.id='box'
  ,elm.classList.add('flexbox')
))();
let item1 = ((elm=document.createElement('div'))=>(e=>e)(
  elm
  ,elm.id='item1'
  ,elm.classList.add('flexitem')
))();
let item2 = ((elm=document.createElement('div'))=>(e=>e)(
  elm
  ,elm.id='item2'
  ,elm.classList.add('flexitem')
))();
document.body.append(box);
box.append(item1);
box.append(item2);

要素の追加もワンライナーで行う

ここまできたら全てワンライナーにしてしまいましょう。

document.body.append(
  ((elm=document.createElement('div'))=>(e=>e)(
    elm
    ,elm.id='box'
    ,elm.classList.add('flexbox')
    ,elm.append(
      ((elm=document.createElement('div'))=>(e=>e)(
        elm
        ,elm.id='item1'
        ,elm.classList.add('flexitem')
      ))()
    )
    ,elm.append(
      ((elm=document.createElement('div'))=>(e=>e)(
        elm
        ,elm.id='item2'
        ,elm.classList.add('flexitem')
      ))()
    )
  ))()
);

ほぼ「メソッドチェイン的に書きたい」と思っていたのに近い書き方になりました。

まとめ

ワンライナーで要素の生成と初期化ができるようになりました。

let elm = ((elm=document.createElement("div"))=>(e=>e)(elm/*,elm.id='hoge',elm.classList.add('hoge'),elm.innerText='hoge'*/))();

要素の追加も行うなら、改行とインデントはしっかりした方が良いでしょう。

document.body.append(
  ((elm=document.createElement('div'))=>(e=>e)(
    elm
    /*,elm.id='hoge',elm.classList.add('hoge'),elm.innerText='hoge'*/
    ,elm.append(
      ((elm=document.createElement('div'))=>(e=>e)(
        elm
        /*,elm.id='hoge',elm.classList.add('hoge'),elm.innerText='hoge'*/
      ))()
    )
    ,elm.append(
      ((elm=document.createElement('div'))=>(e=>e)(
        elm
        /*,elm.id='hoge',elm.classList.add('hoge'),elm.innerText='hoge'*/
      ))()
    )
  ))()
);

以上。

謹賀新年

あけましておめでとうございます。

新年1発目ということで、今年の抱負を記事にしようと思います。

今年の目標はズバリ…












『ブログを軌道に乗せたい』






超曖昧(笑)

というわけで、具体的に掘り下げようと思います。

主目標 : ブログを軌道に乗せたい。

ブログが軌道に乗った状態というのは以下の2要素だと思います。

  • テーマが決まっている。
  • 定期的に更新されている。

テーマについて

このブログは、基本的には技術ブログです。
日々の業務で学んだことや、趣味で調べたことを体系的にまとめることを主目的としています。

ただ、去年一年間ブログを書いてみた感じですと、技術記事だけだと投稿頻度が厳しくなるということがわかりました。
それなりにしっかりした技術記事を書くには、結構な時間が必要になるためです。

時間が無い時に記事を書こうとすると、気軽に書ける雑記が増える傾向にありました。
雑記を書くこと自体は問題ないのですが、雑記が多くなると「技術ブロク」というテーマが揺らいでしまいます。

そこで第二のテーマを決め、雑記を書くとしても可能な限りそのテーマに寄せるようにしたいと思います。
しかし、まだ第二のテーマを何にするか決まっていません。
今年は資格勉強をしようと思っているので、それを第二のテーマにしようかと考えています。

定期的な更新について

2020年の振り返り記事でも書きましたが、去年は週一本を目標に記事を書いていました。
しかし、その達成率は50%ほどで、決して十分と言える状況ではありません。
その為、毎週一本の記事を投稿するにはどうすればいいか、掘り下げて考える必要がありそうです。

定期的な更新にネックとなっている部分

先にも書きましたが、しっかりした技術記事を書くためには、結構な時間が必要になります。

何に時間がかかっているのか?
そこを考えてみたところ、以下の2つが思い当たりました。

  • 情報を仕入れる時間。または、情報の裏を取る時間。
  • 記事としてまとめる時間。

情報を仕入れる時間。または、情報の裏を取る時間。

記事として書く以上、間違ったことは出来るだけ書かないようにしています。
そのため、知識が曖昧な部分については都度調べているため、そこで時間がかかってしまいます。

曖昧な情報について調べれば、新たに情報が出てきて更にその情報について調べる。
そうしてどんどん記事を書き上げるための時間が嵩んでいきました。

記事としてまとめる時間。

記事としてまとめる際に文章構成に迷ってしまい、時間がかかる事が多くありました。
書きたい内容は決まっていても、そこを書くまでの流れや全体の構成がなかなか定まらないことがあります。
そうなると、一つの記事に時間がかかってしまい、投稿が遅くなってしまうのです。

ネックとなっている部分を解消するには

情報を調べる時間を減らすには

調べる時間を減らすため、曖昧な知識を減らす必要があります。
曖昧な知識をしっかりした知識にするには、恒常的な学習が不可欠です。
ですので、これから毎日10分でも勉強の時間を作ることにします。
前述した資格勉強でも良いですし、業務を進める上で曖昧にして次に進む必要があって保留にしていた内容を深堀りするでもいいです。
何でも良いので、毎日学習するようにします。

記事をまとめる時間を減らすには

記事の雛形を用意しようと思います。
雛形があれば、全体の構成や話の流れに迷う時間を減らす事ができます。
もちろん雛形で対応出来ない内容のこともあると思いますが、 少なくとも資格勉強であれば定形的な文章構成で記事にできそうです。

まとめ

今年の抱負は『ブログを軌道に乗せる』

そのために ブログテーマを安定させる。 (第二テーマを決める)
そして、 毎週1本の記事を定期的に上げる。

記事を定期的に上げ続けるために、一つの記事を書くのにかける時間を減らす。
その為に、 記事の雛形を作成する。
また、曖昧な知識を減らすため、毎日10分以上勉強する。

以上、抱負という名の決意表明でした。

今年の振り返り

年末ということで、今年一年を振り返ってみようと思います。

ブログに関して

全てはここから始まりました。

niwaka1535.hateblo.jp

今のままではいけないと一念発起し、「えいや!」と勢いのままブログを開設しました。
それが今年の1月です。

しばらくは"只々書き溜める、自己満足ブログ"になるかもしれません

Hello World(自己紹介) - 仁和歌ブログ

この時、「しばらくは」と言っていましたが、今も自己満足ブログのまんまな気がするので、なんとかしないといけないですね(汗)

さて、ここで今年の記事投稿数を月単位で見てみましょう。

1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
1 2 4 4 5 4 1 4 4 3 2 3

実は、週一本を目標に記事を書いてきたのですが、達成率は50%といったところです。
しかも、これは一月4週で見た場合の話で、実際のカレンダーで見たら酷いものです…

今月なんて酷いものです。
今日、この記事を上げれば4本目になるのですが…

f:id:niwaka1535:20201231202009p:plain

頭2週間に何も投稿できておらず、3周目で2本まとめて書き上げて投稿。
その後、1週間空いて年末に2本を上げるという体たらく(笑)

計画性の無さが露呈していますね(汗)

とはいえ、ずっとこんな調子だったわけではありませんでした。
8月9月ごろは、ちゃんと毎週記事を上げれていたのです。

f:id:niwaka1535:20201231201804p:plain

悪いところばかり見ていては、改善に繋がりません。
このちゃんとできていた夏頃と、ガタガタになっている冬頃の違いをしっかり分析し、来年に活かしたいと思います。

おわりに

世の中の情勢的には、2020年はコロナ一色といった感じでした。
2021年どうなるのでしょうかね。

コロナは去年の12月には武漢で猛威を振るっており、1月に日本上陸を許してしまった結果、今に至ったと思っています。
今年の12月に変異種がイギリスや南アフリカで確認されており、既に国内に持ち込まれつつあるようです(今はまだ空港で発見できているようですが)。

このままでは2020年の二の舞になりそうです。
とはいえ、来年の事を言えば鬼が笑うと言いますし、今はただ今できることに専念した方が良さそうですね。

では、良いお年を。

『Alexa、寝落ちする』 その1 〜Alexa購入編〜

先日、Amazonの「ブラックフライデー&サイバーマンデー」で、Alexa(Echo Dot)とSwitchBotを購入しました。

5日間のBig Sale: Amazon ブラックフライデー&サイバーマンデー 2020

それぞれの設定方法や試してみて良かった点・良くなかった点などを記事にしていこうと思います。

発端

私はいつも寝る前にYoutubeを見ているのですが、よく「そのまま寝落ちして朝まで部屋の電気が点きっぱなし」ということをしてしまいます。
一晩中点きっぱなしというのは電気代もかかるし睡眠の妨げにもなるので、自動で消せるようにしようというのが購入のきっかけでした。

SwithcBotとは?

SwitchBot(スイッチボット)は、すべてのスイッチとボタンを機械的に制御するスマートなIoTロボットです。

SwitchBot(スイッチボット) | スマートホームにらくらくスイッチ| Alexa | Google Home

簡単に言えば、後付けでスマートホーム化(IoT化)をする為の製品です。

スマートホーム対応の製品は、その製品自体が家庭内ネットワークに接続し、直接Alexaと連携します。
SwitchBotを使用すれば、スマートホーム未対応の照明をAlexaと連携させることができるようになるのです。

今回の目的である「自動で照明を消せるようにする」ための方法は2つありました。
1つは壁にある照明のスイッチに「SwitchBotボット」という製品を取り付ける方法。
もう1つはリモコン付き照明を「SwitchBotハブミニ」でコントロールする方法です。

我が家は元からリモコン付き照明だったので、SwitchBotハブミニを購入しました。

余談ですが、以下の製品を使えば、非リモコンの照明を赤外線リモコンに対応させることが出来るようです。

もう少し安い製品もあるみたいですが、そういった製品は壁のスイッチで照明をON/OFFできなくなるなどの欠点があるようでした。

Alexaとは?

言わずと知れた、Amazon製のAIアシスタントです。
音声コントロールで色々な事ができるようになる優れ物です。

今回購入した「Echo Dot」は、AIスピーカーに分類されます。
クラウド上のAIアシスタントであるAlexaと通信することで、音声認識による音楽再生や家電のコントロールができるようになります。

これらAIアシスタントAIスピーカーの関係は、AppleGoogleの製品と並べると伝わりやすいと思います。

Amazon Apple Google
AIアシスタント Alexa Siri Googleアシスタント
AIスピーカー AmazonEcho HomePad GoogleHome

上記表に、AmazonAIスピーカーとして「AmazonEcho」と書きましたが、AmazonAIスピーカーは「AmazonEchoシリーズ」として結構な種類が出ているためです。
「Echo Dot」はその中の1つで「小型&ディスプレイ無し」の機種です。

現在、Amazonで購入できるEchoシリーズ製品は以下の通りです。

機種 特徴
f:id:niwaka1535:20201230202740j:plain Echo Dot (第3世代) 最初の一台に
f:id:niwaka1535:20201230202823j:plain New Echo Dot 球体フォルムが生み出すクリアなサウンド
f:id:niwaka1535:20201230202846j:plain New Echo Dot with clock ベットサイドにぴったり
f:id:niwaka1535:20201230202901j:plain New Echo さらにリッチで細やかなプレミアムサウンド
f:id:niwaka1535:20201230202921j:plain Echo Studio スタジオ品質のHi-Fiスマートスピーカー
f:id:niwaka1535:20201230202936j:plain Echo Show 5 スクリーン付きで便利に
f:id:niwaka1535:20201230202936j:plain Echo Show 8 8インチHDスクリーン付き
f:id:niwaka1535:20201230203009j:plain New Echo Show 10 声と動きにあわせてディスプレイが350°回転

正直、何処まで便利に使えるかわからなかったので、私は一番安いEchoDot(第三世代)を最初の1台として選びました。

購入した物

Echo Dot(第3世代)とSwitchBotハブミニを買いました。

セールのおかげで、Echo Dot(第3世代)は定価¥4,980の商品が¥1,802(税込)、 SwitchBotは定価¥3,980の商品が¥2,178(税込)で購入できました。

おわりに

購入編は以上です。
2つ合わせて¥4,000以内で買えたのは本当に良かったです。(使ってみた感想を踏まえても)

ただ、少し後悔が有るとすれば、SwitchBotの関連商品はもっと沢山買っておいてもよかったということですね。
SwitchBotボット(通称、指ボット)とか、色々なところに貼り付けて何でも操作できそうなので。(とはいえ、思いつく中で実用的に使えそうなのは、PCの電源を外出先からONにできることくらいしか思いつかないですが…)

次回

  • 『Alexa、寝落ちする』 その2 〜SwitchBot設定・感想編〜
  • 『Alexa、寝落ちする』 その3 〜Alexa設定・感想編〜

【PHP】PODでプリペアドステートメントなSQLを動的に作成していたときのtips

f:id:niwaka1535:20201219183247p:plain

はじめに

まず、こちらを見ていただきたい。

<?php
// クエリ生成
$sql = "SELECT * FROM goods WHERE del_flg = 0 ";
if (isset($_POST['name']) && $_POST['name'] != '') {
    $sql .= "AND name LIKE ? ";
}
if (isset($_POST['type']) && $_POST['type'] != '') {
    $sql .= "AND type = ? ";
}
if (isset($_POST['price']) && $_POST['price'] != '') {
    $sql .= "AND price >= ? AND price < ? ";
}
// PDO処理
$stmt = $pod->prepare($sql);
$count = 1;
if (isset($_POST['name']) && $_POST['name'] != '') {
    $stmt->bindValue($count++, '%'.$_POST['name'].'%');
}
if (isset($_POST['type']) && $_POST['type'] != '') {
    $stmt->bindValue($count++, $_POST['type']);
}
if (isset($_POST['price']) && $_POST['price'] != '') {
    $stmt->bindValue($count++, $_POST['price']);
    $stmt->bindValue($count++, $_POST['price'] + PRICE_RANGE);
}
$result = $stmt->execute();

これは商品(goods)を商品名(name)や種類(type)や価格(price)で検索する処理です。
画面としてはだいたいこんな感じです。

f:id:niwaka1535:20201211092034p:plain

要するに「部分一致検索」「完全一致検索」「範囲検索」のサンプルです。
- 商品名(name)はフリーテキスト検索の部分一致検索。
- 種類(type)はラジオボタンやプルダウンで選択されたの値の完全一致検索。
- 価格(price)はfromとtoを指定する範囲検索。
($_POST['price']にはfromの値が入っており、toはfromにPRICE_RANGEを加算して求めている)

ポイント

  • 検索条件を追加するかの判定(if文)が、文字結合の箇所とbindValueの2箇所で行われている。(しかも全く同じ判定式)
  • 検索の種類に応じて、bindする値の数や扱いが異なる。(完全一致検索はそのままbind。部分一致検索は値に'%'を結合してからbind。範囲検索は一つのクエリ結合に対して複数の値をbind。)

本題

関数作った。

<?php
/**
 * @param PDOStatement Object
 * @param array of `array('value'=>$v[,'type'=>$t][,'name'=>$n])`.
 * @return result
 */
function pdo_stmt_bind_values($stmt, $stmt_params) {
    foreach ($stmt_params as $index => $param) {
        if (is_array($param)) {
            // 配列であれば、値とdata_typeがセットされていたとして扱う
            if (!array_key_exists('value', $param)) {
                // valueが設定されていなければ処理中断
                return false;
            }
            if (array_key_exists('name', $param)) {
                // nameが指定されていれば「:name」形式
                if (is_null($param['value'])) {
                    // 値がNULLなら、data_typeに明示的にNULLを指定
                    $stmt->bindValue($param['name'], $param['value'], PDO::PARAM_NULL);
                } elseif (array_key_exists('type', $param)) {
                    // data_typeが指定されていたなら指定
                    $stmt->bindValue($param['name'], $param['value'], $param['type']);
                } else {
                    // シンプルに値をバインド
                    $stmt->bindValue($param['name'], $param['value']);
                }
            } else {
                // nameが指定されていなれば1からのindex形式
                if (is_null($param['value'])) {
                    // 値がNULLなら、data_typeに明示的にNULLを指定
                    $stmt->bindValue($index+1, $param['value'], PDO::PARAM_NULL);
                } elseif (array_key_exists('type', $param)) {
                    // data_typeが指定されていたなら指定
                    $stmt->bindValue($index+1, $param['value'], $param['type']);
                } else {
                    // シンプルに値をバインド
                    $stmt->bindValue($index+1, $param['value']);
                }
            }
        } else {
            // 配列でなければ、値がセットされていたとして扱う
            if (is_null($param)) {
                // 値がNULLなら、data_typeに明示的にNULLを指定
                $stmt->bindValue($index+1, $param, PDO::PARAM_NULL);
            } else {
                // シンプルに値をバインド
                $stmt->bindValue($index+1, $param);
            }
        }
    }
    return true;
}

こうやって使う。

<?php
// クエリ生成
$stmt_params = array();
$sql = "SELECT * FROM goods WHERE del_flg = 0 ";
if (isset($_POST['name']) && $_POST['name'] != '') {
    $sql .= "AND name LIKE ? ";
    array_push($stmt_params, '%'.$_POST['name'].'%');
}
if (isset($_POST['type']) && $_POST['type'] != '') {
    $sql .= "AND type = ? ";
    array_push($stmt_params, $_POST['type']);
}
if (isset($_POST['price']) && $_POST['price'] != '') {
    $sql .= "AND price >= ? AND price < ? ";
    array_push($stmt_params, $_POST['price']);
    array_push($stmt_params, $_POST['price'] + PRICE_RANGE);
}
// PDO処理
$stmt = $pod->prepare($sql);
pdo_stmt_bind_values($stmt, $stmt_params);
$result = $stmt->execute();

SQL文の生成とbind値の設定が1箇所にまとまってスッキリ。

まとめ

ORM(ORマッパー)導入しよ?(笑)

冗談はさておき、古いプロジェクトでは往々にして文字結合でSQL文を作っていることがあります。
そういったプロジェクトを改修する場合に、既存のSQL文の生成部分を全てORM記述に置き換えるというわけにはいきません。
なので、この方法が役立つ場面があるんじゃないかと思い、Tipsとして残しておきます。

作成したbind用の関数は、名前付きプリペアードステートメントにも対応しているので、必要に応じて改造してもいいかもしれません。