ライブコーディングに挑戦してみた

先日LINE Developer Communityのイベント「LIVE CODING SHOW」でライブコーディングに挑戦しました。

linedevelopercommunity.connpass.com

今までハンズオンやライブデモをやることはありましたが、ライブコーディングは未知の領域でした。

何が起こるか分からないこの感覚を久々に味わってみたいと思い、挑戦してみました。

リハーサル

ライブコーディングの生配信はあまりイメージが無かったので、じっくりリハーサルをするところからはじめました。

用意した機材

基本構成

ライブコーディングの配信に用意した機材として以下のものです。

  • 手元のキーボードが映すカメラ
  • 斜めからの顔のアップを映すカメラ
  • 声とキーボードのタイピング音が拾えるマイク

つまりカメラ2台と外付けマイクを用意しました。

カメラは2台ともLogicoolのC920nを使いました(1台しかなかったので1台は貸してもらいました)

そして、マイクはマランツのUSBコンデンサーマイクを用意しました。スタンドとセット買えるのでそれっぽい雰囲気になりますw。

もちろん音声は申し分ありません。

これらを合わせたセッティングがこんな感じです。

f:id:KMiura:20210401000034p:plain

それっぽいですねw。顔を写すカメラは三脚を使っていて、手元を写すカメラはモニターの上に乗せて最大限下向きになるように調節しました。

スマホのキャプチャ

あとは作ったLINE botスマホ上で動かすデモをするためにスマホの画面をキャプチャするために、Chrome Castを使いました。

これはGoogle Homeが最初に発売された時におまけでついてもので、つい最近まで封を開けていませんでした。

しかしオンラインイベントとかでスマホの画面を使ってデモを見せる機会が増えたのをきっかけに出番が増えるようになりました。

画面を映したいスマホGoogle Homeアプリをインストールしておき、Chrome Castと接続をしてミラーリングを行うとHDMIで画面を出力できるようになります。

あとはUSB接続できるHDMIキャプチャ(中国の通販で手に入るやすいやつ)にChrome Castを接続してパソコン上でスマホ画面を出力するための機材のセッティングは完了です。

パソコンの画面上に投影するときにはWindowsにデフォルトで入ってるカメラアプリでChrome Castを接続したキャプチャを選べば簡単に共有できます。

おそらく入力ソースはUSB VIDEOだと思います。

配信ソフト

配信はGoogle meetからのカメラ画像、画面共有をOBSで合成してYoutubeライブでやってました。

OBSの配信設定はLINE Developer Community事務局の方にやっていただいたので特にここで話すことはありませんw。

Google Meetの接続は当然ライブコーディングをしているパソコンで顔を映していますが、もう1台パソコンを用意して手元を写すようにしました。

なので先程のセッティングに追加でサブPCを用意して2台のPCでGoogle Meetに参加しました。

台座は以前イベントで手に入れたブラウンくんのぬいぐるみの箱がちょうど良かったので活用しましたw。

f:id:KMiura:20210402022945j:plain

また、OBSで顔だけ切り出せるようにするために、顔を映している映像にはグリーンバックの仮想背景をいれています。

これらを合成させた配信イメージがこうなりました。

f:id:KMiura:20210402024854p:plain

モノクロだとなんかかっこいいですよねw。

リハを終えて出てきた課題

さて、リハを終えていくつか課題が出てきました。

モニターの上のカメラからキーボード映しても角度的にキーボードと手元がきれいに映らない

モニターの上にカメラを載せているのは普段そこから顔を映すのにはちょうどいいのですが、手元を映すのには微妙なアングルです。

というわけでキーボードの真上から移せるスタンドを追加でいくつか用意してもらいました。

ブラウザの動作が重い

Google Meetはブラウザ上で動作しているせいかブラウザで検索するだけでも地味に動作が重かったです。

IoTLTで登壇するときもGoogle Meet使っていますが、そのときは仮想背景を使ってなくてブラウザの動作が重くならないのでおそらく背景の切り取り部分でかなり処理が持っていかれているのでしょう。

その割にはZoomより背景の切り取り精度が悪いですねw。

メインPCが配線地獄

これはもうしょうがないですねw。

普段キーボードとトラックボールが有線でそこにカメラ、キャプチャー、マイクを接続してメインPCのUSBが配線地獄でしたw。

パソコンには2つしかUSB端子が無いので、USBハブを2つ接続しました。

f:id:KMiura:20210402232409p:plainf:id:KMiura:20210402232455p:plainf:id:KMiura:20210402232536p:plain

本番

いざ本番、思ってたよりも視聴者数が少ない中でしたが逆に気は楽でしたw。

そして前回の課題を多少解消する形で機材配置を変えました。

f:id:KMiura:20210418020935p:plain

画面が見づらくなりましたが、顔は斜めから映してますし手元は前よりもキーボードが見やすくなったので、絵面的には良くなりました。

配線&機材地獄で世の中のYouTuberの人たちの配信の苦労を味わいましたw。

作業に関しては序盤はいつもの調子でできたのでよかったのですが、だんだん処理が重くなって待つ時間が長くなって結構焦りました。

ネットもそうだしCPUもかなり消費していて辛かったですね…

途中でWifiの接続を確認したらよりによって2.4GHz帯で接続していたことに気づきました。

いつも5GHz帯で接続してたのでどうりで遅かったわけです。

で、接続を変更したらネットワークの問題は解決しました。

でも結局全体の処理の重さの解決には繋がりませんでした。

3年以上使っていたPCなのでそろそろオンライン配信とかで耐えれるPCに買い替えた方がいいのかな…

とはいえ初の試みにしては結構楽しめましたし、見ていただいた方にも「トラブルがあっても動じなくてすごい!」と好評いただき一安心しましたw。

内心はトラブルで焦りましたが、いざ動画を見返すと割と普段の僕をそのまま映しているので変に気張ってないのは良かったかなと思いました(もちろん普段はここまでべらべら喋りながら仕事してるわけじゃないですよw)。

ぜひ次回も挑戦したいです!

編集された完全版

後日、ライブコーディングの様子をきれいに編集してもらい、いい感じにかっこいい動画に仕上がりましたw。

いいところで画面をアップしていたり当日にあった長く待たされたところは切ってもらえたので、かなり見やすいと思います。

(この動画がいいなと思ったら高評価お願いしますw)

www.youtube.com

おまけ

今回のライブコーディングではAzure Functionsを使って実装しましたが、こんな感じのbotを実装(したかった)という記事を以下で公開しているのこちらもよかったらぜひチェックお願いします。

zenn.dev

秋葉原のArduinoガチャで手に入れたArduino nano互換機を動かして詰まりまくった話

先日久々に秋葉原にでかけました。

そこの東京ラジオデパートというところがありまして、そこにはArduino nano互換機やArduinoで使えるセンサー、モジュールなどが出てくるArduinoガチャというのがあります。(ラジオデパートの2階に設置されていますがたまに設置されていないこともあります)

今まで何度かやっているのですが、今回は1発でArduino nano互換機を出してしまいましたw。

互換機を出せたのはいいのですが、今後何かのネタに使えそうなセンサーを求めてさらに2回ガチャをやって期待通り(?)見たこともないセンサーたちを引きました。(このセンサーについてはまた今度書こうと思います)

ガチャで引いた割にはあたりかなと思いましたが、おそらく中国から大量に仕入れたパーツを手に入れたことを考えると1回300円でもと取れているかどうか微妙ですね。

Amazonで調べてみたら画像と一致したものが見つかりました。価格は500円近くしてるので、もとは十分取れてますね。(ちゃんと動けば)

前置きはこのぐらいにして、今回はこのArduinoガチャで手に入れたArduino nano互換機を動かしてみた話です。

ここまで書くと「どうせLチカのプログラム書いて終わりなんだろ?」って思った人もいるかもしれません。

いやいや、動かすまで地味に大変だったんですよw

というわけで開封から見ていきましょう。

開封の儀

早速カプセルあけて中身を確認しました。

中身はガチャらしく商品名の書かれた簡素な紙とArduino nano互換機ボードにピンヘッダだけです。

当然ピンヘッダは実装して無いですがついているだけ良心的ですね。

Arduino nano generic

はんだ付けしてみた

とりあえずピンヘッダをはんだ付けしてみました。

その時に気づいたのですがなんとUSB端子が見事に潰れてました…

これが1つ目の詰まりポイントです。

f:id:KMiura:20210412005048p:plain

ボードはカプセルに入れたまま持って帰ってきたので、これはシンプルに初期不良ですね。

検品ちゃんとやったのかな…?

動かす前から不安ですが、格安の互換機なので気にしてはいけません!

とりあえずUSB接続できないと何も始まらないのでマイナスドライバを使って潰れてるところを無理やりこじ開けてなんとかUSB接続できました。

ブートローダを書き込む

とりあえずLチカのプログラムを書き込もうとしたのですが、シリアルポートでボードを認識しませんでした…

f:id:KMiura:20210412013258p:plain

これだとファームウェアの書き込みができないので、ブートローダーを書き込むことにしました。

やり方は以下の公式ドキュメントにある手順にそってやると簡単にできます。

https://www.arduino.cc/en/Tutorial/BuiltInExamples/ArduinoISP

まずは、ブートローダを書き込むために別のArduinoボードにファイル→スケッチ例→11.Arduino ISP→Arduino ISPファームウェアを書き込みます。

今回は手元にあった純正品のArduino UNOにファームウェアを書き込みました。

f:id:KMiura:20210412014612p:plain

ブートローダが書き込めれば互換機でも大丈夫だと思います。

続いて配線です。Arduino UNOと互換機とは図の通りに配線すれば問題ありません。(図は公式チュートリアルのやつから拝借)

f:id:KMiura:20210412015216p:plain

実際に配線するとこんな感じです。ブレットボードとセットなので撮影がしやすかったです。

f:id:KMiura:20210412015658p:plain

あとは、ツール→書き込み装置→Arduino as ISPを選択してブートローダを書き込むをクリックして書き込みます。

f:id:KMiura:20210412020254p:plain

ドライバのインストール

さてブートローダを書き込んだのでこれでファームウェアを書き込めばいいと思ったのですが、さっきと同じようにシリアルポートを認識せず全然解決しませんでした…

f:id:KMiura:20210412024216p:plain

調べてみたらどうやらこの互換機に使われているシリアル変換チップにはCH340が使われていて別途ドライバのインストールが必要みたいです。

ドライバは以下の南京沁恒微电子股份有限公司のサイトからダウンロードできます。

www.wch-ic.com

サイトにアクセスしたら最新のドライバのインストーラをダウンロードします。

f:id:KMiura:20210412030555p:plain

ダウンロードしたらインストーラを起動して、Installをクリックします。

f:id:KMiura:20210412031132p:plain

インストールを終えたらデバイスマネージャー上でシリアルポートが認識されていれば、ドライバのインストールができています。

f:id:KMiura:20210412031525p:plain

ここまでやって無事にArduino nano互換機でLチカができました。

まとめ

今回は秋葉原のガチャで手に入れたArduino nano互換機を動かしてみました。

とても安いですが、調べないとわからないことだらけで地味に苦戦しました。

Amazonのレビューを見てると今回の作業を試してもうまくいかなかったというコメントもあるようなので、USB端子が潰れててもちゃんと動いたのでまだマシだったのかもしれませんねw。

これで次にArduinoガチャで互換機ゲットした時にはスムーズに作業が進むと思います。(いくつ手に入れれば気が済むのやらw)

MIT App Inventorとmicro:bitでBLE通信をやってみた

普段はサーバーエンジニアとして仕事をしていますが、プログラミングを勉強し始めたときにはサーバーサイドをやりたいという考えはなくて、スマホアプリをやってみたいと思って開発を始めていました。

ただ当時の僕にとってはやはり開発が難しくてあっという間に挫折しましたw。

あとは単純に作りたいものがなくて、モチベーションもなかったんですよね…

あれから4年経った今、職業柄ネイティブアプリのサーバーサイドを開発という形でスマホアプリの開発に携わるようになりました。

そこで改めて今回はネイティブアプリの開発の再入門をするべく、MIT App Inventorをやってみたいと思います。

とりあえずチュートリアルやってみた

公式のチュートリアルでは、ボタンを押したら予め用意した音源を再生するアプリや、入力した文字を読み上げるアプリが用意されており、どちらもチュートリアルどおりに簡単に試せました!

これらのチュートリアルは、以下のリンクで試すことができます。(上記ツイートのアプリはどちらもチュートリアルの内容からアレンジを入れたものです)

appinventor.mit.edu

BLEやってみたい!

とりあえずチュートリアルをいくつかやってみて思ったこととしては、めちゃめちゃ簡単にアプリを作ることができました!

何よりNoCodeでスマホのセンサー、アクチュエータも使えるので作れるアプリのバリエーションも豊富なのがいいですね。

その中にはどうやらBluetoothも使えるようになっており、さらに拡張機能を用意すればBLE通信もできるようです。

ということは、今までネイティブアプリでやってみたかったBLE通信ができるようになるってことですね。

それもノンコーディングで!

というわけで今回の本題である、App Inventorを使ったBLE通信を試してみたいと思います。

そしてせっかくなのでマイコン側もノンコーディングでBLE通信のプログラムをやってみたいと思ったので、micro:bitを使ってみたいと思います。(使用するmicro:bitはV2ではない旧モデルです)

拡張機能のインストール

App InventorでのIoTにまつわるドキュメントは以下のものが見つかりました。

iot.appinventor.mit.edu

そして、運良くmicro:bitを使ったドキュメントも見つかったのでこれを参考にやってみます。

http://iot.appinventor.mit.edu/assets/howtos/MIT_App_Inventor_Microbit_LED.pdf

まずは以下のページの上部にあるBLEの拡張機能のダウンロードします。

iot.appinventor.mit.edu

続いて以下のページの上部にあるmicro:bit拡張機能をダウンロードします。

iot.appinventor.mit.edu

App Inventorで新規のプロジェクトを作成し、Extensionをクリックし、Import extensionから先程インストールした拡張機能を読み込みます。

f:id:KMiura:20210308232421p:plain

アプリのレイアウト

必要な拡張機能を読み込んだところでアプリを作っていきましょう。

今回はBLEで接続したmicro:bitのLEDマトリックスをボタン操作で表示を切り替えるアプリを作っていきます。

f:id:KMiura:20210309213828p:plain

導入したコンポーネントとしてBLEデバイスの認識、デバイスの接続をするためのボタンを上部に配置しています。

ボタンを押したときのステータスをテキストで表示するようにしています。

また、認識したデバイス情報を一覧で出力できるようにリストビューを入れています。

また、ペアリングしたデバイスの操作をするためのボタンも用意しました。

あとは、今回導入した拡張機能の中のBluetoot LEMicrobit_Ledをそれぞれアプリビューに追加します。

この2つはアプリ上には表示されませんがアプリに必要な機能としてスマホ画面の下に表示されます。

また、Microbit_LedについてBLEデバイスとの紐付けを行います。

これによりmicro:bitをBLEで操作できるようになります。

アプリのプログラム

続いてアプリのプログラムを作成します。

プログラムの大枠は以下の画像のようになります。

f:id:KMiura:20210309221134p:plain

上記のプログラムの中にあるcall SmileFacecheckについては以下のブロックを組んだ上で呼び出すブロックになります。

このブロックはmicro:bitマトリックスLEDで描画する図を文字列で指定してそれをマイクロビットに送信するための配列に変換します。

f:id:KMiura:20210309221453p:plain

micro:bitのプログラム

micro:bitは以下のプログラムになります。

今回のアプリであれば最低限LEDサービスが定義出ていればOKです。

ついでにデバイス側でペアリングしたことがわかるようにしてみました。

動作確認

実際に動作確認です。

BLEは機種依存があるので実機で確認する必要があります。

App InventorのコンパニオンアプリをAndroid端末にダウンロードしたらQRとかで読み込んで接続すれば確認できます。

アプリの動作は以下の動画のようになります。

Scanボタンで接続するデバイスを選択して接続できたら、ボタンが2種類表示されてLEDの表示が切り替わります。

まとめ

今回はApp Inventorでmicro:bitとBLE通信をやってみました。

ネイティブアプリを挫折してた人間でも簡単にアプリを作ることができて、しかもBLEまであっという間にできてしまうのは時代の進歩を感じますね(App Inventorはだいぶ前からあるっぽいですがw)。

何よりmicro:bit拡張機能でUUIDとかを考えずにBLE通信ができるのが便利で、アプリもハードもノンコーディングでシステム構築できてしまいました。

Grove Barometer SensorをRaspberry Piで動かしてみた

前回に引き続き積みモジュールネタです。

supernove.hatenadiary.jp

前回はCO2センサーを使いましたが、今回は気圧センサーを使ってみます。

このセンサーはi2cなので、Grove base hatであればi2cの端子に接続すれば大丈夫です。(写真の手前には別のモジュールをつなげています。)

f:id:KMiura:20210214024000p:plain

ところが、このモジュールのPythonサンプルはgrove.pyには無いので代わりに使えそうなサンプルコード探しから始まりました。

探してみるとスイッチサイエンスが公開しているbme280のサンプルがありました。

github.com

bme280とbmp280の違いとしては湿度を取れるか取れないかの違いで、GroveのBMP280のi2cアドレス(0x77)に修正すればこのサンプルはそのまま使えます。

ちなみにbmp280では湿度の測定ができないので、このサンプルを実行すると湿度だけ0になります。

サンプルコード

先程のBME280のサンプルをもとにGroveのBMP280向けにアレンジしたのが以下のコードになります。

smbus2のインストールが必要になりますが、Grove.pyをインストールしていれば別途インストールする必要はないと思います。                                                                                                                                        

# coding: utf-8

from smbus2 import SMBus
import time

bus_number  = 1
i2c_address = 0x77

bus = SMBus(bus_number)

digT = []
digP = []

t_fine = 0.0


def writeReg(reg_address, data):
    bus.write_byte_data(i2c_address,reg_address,data)


def get_calib_param():
    calib = []
    
    for i in range (0x88,0x88+24):
        calib.append(bus.read_byte_data(i2c_address,i))
    calib.append(bus.read_byte_data(i2c_address,0xA1))
    for i in range (0xE1,0xE1+7):
        calib.append(bus.read_byte_data(i2c_address,i))

    digT.append((calib[1] << 8) | calib[0])
    digT.append((calib[3] << 8) | calib[2])
    digT.append((calib[5] << 8) | calib[4])
    digP.append((calib[7] << 8) | calib[6])
    digP.append((calib[9] << 8) | calib[8])
    digP.append((calib[11]<< 8) | calib[10])
    digP.append((calib[13]<< 8) | calib[12])
    digP.append((calib[15]<< 8) | calib[14])
    digP.append((calib[17]<< 8) | calib[16])
    digP.append((calib[19]<< 8) | calib[18])
    digP.append((calib[21]<< 8) | calib[20])
    digP.append((calib[23]<< 8) | calib[22])

    for i in range(1,2):
        if digT[i] & 0x8000:
            digT[i] = (-digT[i] ^ 0xFFFF) + 1

    for i in range(1,8):
        if digP[i] & 0x8000:
            digP[i] = (-digP[i] ^ 0xFFFF) + 1


def readData():
    data = []
    for i in range (0xF7, 0xF7+8):
        data.append(bus.read_byte_data(i2c_address,i))
    pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
    temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
    hum_raw  = (data[6] << 8)  |  data[7]
    
    temp = compensate_T(temp_raw)
    pres = compensate_P(pres_raw)
        
    return temp, pres


def compensate_P(adc_P):
    global  t_fine
    pressure = 0.0
    
    v1 = (t_fine / 2.0) - 64000.0
    v2 = (((v1 / 4.0) * (v1 / 4.0)) / 2048) * digP[5]
    v2 = v2 + ((v1 * digP[4]) * 2.0)
    v2 = (v2 / 4.0) + (digP[3] * 65536.0)
    v1 = (((digP[2] * (((v1 / 4.0) * (v1 / 4.0)) / 8192)) / 8)  + ((digP[1] * v1) / 2.0)) / 262144
    v1 = ((32768 + v1) * digP[0]) / 32768
    
    if v1 == 0:
        return 0
    pressure = ((1048576 - adc_P) - (v2 / 4096)) * 3125
    if pressure < 0x80000000:
        pressure = (pressure * 2.0) / v1
    else:
        pressure = (pressure / v1) * 2
    v1 = (digP[8] * (((pressure / 8.0) * (pressure / 8.0)) / 8192.0)) / 4096
    v2 = ((pressure / 4.0) * digP[7]) / 8192.0
    pressure = pressure + ((v1 + v2 + digP[6]) / 16.0)  

    return pressure/100


def compensate_T(adc_T):
    global t_fine
    v1 = (adc_T / 16384.0 - digT[0] / 1024.0) * digT[1]
    v2 = (adc_T / 131072.0 - digT[0] / 8192.0) * (adc_T / 131072.0 - digT[0] / 8192.0) * digT[2]
    t_fine = v1 + v2
    temperature = t_fine / 5120.0
    return temperature


def setup():
    osrs_t = 1         #Temperature oversampling x 1
    osrs_p = 1         #Pressure oversampling x 1
    mode   = 3         #Normal mode
    t_sb   = 5         #Tstandby 1000ms
    filter = 0          #Filter off
    spi3w_en = 0           #3-wire SPI Disable

    ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode
    config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en

    writeReg(0xF4,ctrl_meas_reg)
    writeReg(0xF5,config_reg)


if __name__ == '__main__':
    setup()
    get_calib_param()
    while True:
        try:
            temp, pres = readData()
            print("Temp:{0:.1f}, Pressure:{1:.1f}hPa".format(temp, pres))
            time.sleep(1)
        except KeyboardInterrupt:
            break

実行すると、以下の用に温度、気圧が出力されます。

いい感じですね👍

Temp:27.2, Pressure:1021.6hPa
Temp:27.2, Pressure:1021.7hPa
Temp:27.2, Pressure:1021.6hPa
Temp:27.2, Pressure:1021.7hPa
Temp:27.2, Pressure:1021.6hPa
Temp:27.2, Pressure:1021.7hPa

これで台風来たときにはすぐにRaspberry Piを起動して気圧取れますね。

おまけ

今回はsmbus2だけを使って動かせるサンプルコードを紹介しましたが、実はbmp280にはPythonライブラリがあり、それを使えばさっきのサンプルコードもほんの数行書くだけで終わりますw。

ライブラリは例によって以下のpipコマンドでインストールします。

sudo pip3 install bmp280

あとは以下のサンプルコードでセンサーの値が取れます。

簡単ですねw。

import time
from bmp280 import BMP280
from smbus2 import SMBus

bus = SMBus(1)
bmp280 = BMP280(i2c_addr=0x77, i2c_dev=bus)

while True:
    temperature = bmp280.get_temperature()
    pressure = bmp280.get_pressure()
    print('{:05.2f}*C {:05.2f}hPa'.format(temperature, pressure))
    time.sleep(1)

ちなみに、前回はライブラリ側のバグがあり修正が必要でしたがこちらは普通に動かせますのでご安心をw

Grove VOC and eCO2 Gas SensorをRaspberry Piで使ってみた

GroveはSeeedが開発したハードウェアのコネクターのシステムです。

最近は多くのセンサーがGroveに対応するようになってGroveで大抵のセンサー測定は事足ります。

その昔、秋葉原で初めてGroveのきっとを見たときは「こんなの買ってもやれること限られるだろ?」と思って買おうと思わなかったんです。(当時はRaspberry PiでGroveが使えることを知りませんでした)

しかし、とあるハッカソンでGroveの心拍センサーを使ったときに「なんか使いやすいなこれ」と思ったんです。

心拍センサーなんてあんまり他で見たことありませんし、なんか扱いが面倒くさそうでしたけどGroveで使えるように設計されていてよくできてるなと感心しました。

それ以来、いろんなGroveのモジュールを買い集めては試すようになり、すっかりGroveの虜になりました。

特にRaspberry Piで使うときにはピンの番号をいちいち調べるのが面倒なのでGrove Base Hat を使ってGroveと連携すればRaspberry PiでGroveモジュールを使うのが簡単になります。

前置きが長くなりましたが、今じゃ気になったGroveモジュールを見つけては買っているとあんまり使う機会がなくそのまま積みモジュールが増えていくわけですw。

で、今回はその積みモジュールの一つである「Grove VOC and eCO2 Gas Sensor」を触ってみたいと思います。

ドキュメント

Seeedから出している大抵のGroveモジュールにはドキュメントが存在します。

もちろん、このモジュールにもドキュメントがあります。

それがこちら↓

wiki.seeedstudio.com

きほんはこれをベースにすすめていきます。

インストール

grove.py

今回は「Grove Base HAT for Raspberry Pi」を使うので、以下のコマンドでgrove.pyをインストールします。

curl -sL https://github.com/Seeed-Studio/grove.py/raw/master/install.sh | sudo bash -s -

ライブラリのインストール

このセンサーにはSGP-30が使われています。そしてこのセンサーを使うときには別途ライブラリーをインストールする必要があります。 ライブラリは以下のコマンドでライブラリをクローンします。

git clone https://github.com/Seeed-Studio/Seeed_Python_SGP30.git
cd Seeed_Python_SGP30
sudo python3 setup.py install

サンプルコードの実行

サンプルコードは公式ドキュメントにある以下のコードを動かしてします。

import seeed_sgp30
from grove.i2c import Bus
 
sgp30 = seeed_sgp30.grove_sgp30(Bus())
while True:
  data = sgp30.read_measurements()
  co2_eq_ppm, tvoc_ppb = data.data
  print("\r  tVOC = {} ppb CO2eq = {}  ".format(
                               tvoc_ppb, co2_eq_ppm))

実行すると以下のエラーが出ました。

ImportError: cannot import name 'SMBusWrapper' from 'smbus2' (/usr/local/lib/python3.7/dist-packages/smbus2/__init__.py)

ドキュメント通りに実行してこんなことになることあるのか?と思うのですが、オープンソースってたまにそういうことありますねw。

で、さすがにこのまま終わるわけにはいかないので、なんとかすることにしました。

なんとかしてみた

このセンサーにはI2Cが使われており、seeed_sgp30の大元のライブラリであるsgp30にはsmbus2が使用されています。

そのときにSMBusWrapperを呼び出そうとしているようです。

とりあえずsmbus2でSMBusWrapperがメソッドとしてあるのか調べてみましたが、それらしき記述は見当たりませんでした。 (それはエラーになるな…)

github.com

そして、sgp30のライブラリのソースを確認してSMBusWrapperがどこで使われるのか確認したら以下のmain関数で使われていました。

def main():
    with SMBusWrapper(1) as bus:
        sgp=Sgp30(bus,baseline_filename=BASELINE_FILENAME+".TESTING")
        print("resetting all i2c devices")
        sgp.i2c_geral_call()
        print(sgp.read_features())
        print(sgp.read_serial())
        sgp.init_sgp()
        print(sgp.read_measurements())
    bus.close()

1箇所だけしか使われていなかったのでこれは修正はそこまで大変ではありませんね。 でもどう修正すればいいのか悩みつつもう一回smbus2のレポジトリのREADMEにあるサンプルコードを眺めてたら以下の記述を見つけました。

from smbus2 import SMBus

with SMBus(1) as bus:
    bus.pec = 1  # Enable PEC
    b = bus.read_byte_data(80, 0)
    print(b)

さっきのmain関数によく似ています。

なんだ、簡単に修正できるじゃないかw。

ライブラリの修正

というわけでRaspberry Piにインストールしたライブラリのソースを直に修正しちゃいます!

修正するのは、/usr/local/lib/python3.7/dist-packages/sgp30/sgp30.pyです。

まずは2行目のライブラリのインポートの記述を以下のように修正します。

from smbus2 import SMBus, i2c_msg

そして、118行目からのmain関数にあるwith構文を以下のように修正します。

修正点としては、SMBusWrapper(1)SMBus(1)にしました。

ついでにwith構文を使っているので関数の一番下にあるbus.close()を削除します。

def main():
    with SMBus(1) as bus:
        sgp=Sgp30(bus,baseline_filename=BASELINE_FILENAME+".TESTING")
        print("resetting all i2c devices")
        sgp.i2c_geral_call()
        print(sgp.read_features())
        print(sgp.read_serial())
        sgp.init_sgp()
        print(sgp.read_measurements())

もう一回動かしてみた

ライブラリを修正できたところでもう一回サンプルコードを動かしてみます。

するとセンサーの値がスムーズに取れました。

いい感じですね👍

  tVOC = 13 ppb CO2eq = 400
  tVOC = 8 ppb CO2eq = 401
  tVOC = 18 ppb CO2eq = 413
  tVOC = 11 ppb CO2eq = 408
  tVOC = 14 ppb CO2eq = 409
  tVOC = 8 ppb CO2eq = 413
  tVOC = 14 ppb CO2eq = 407
  tVOC = 9 ppb CO2eq = 407
  tVOC = 8 ppb CO2eq = 401
  tVOC = 6 ppb CO2eq = 403

センサーの値が変わらないときにはセンサーに軽く息を吹きかけるとセンサーの値が変化すると思います。

まとめ

今回はGrove VOC and eCO2 Gas SensorをRaspberry Piで動かしてみました。

ライブラリの問題でうまく動きませんでしたが、原因が単純で修正も簡単で拍子抜けしました。

ただ動かしてみたネタを書きたかったのに、図らずもエラー対処記録になってしまいましたがネタとしては程よい感じでしたw。

さて、修正をPRしないと…

Amazon Lightsailで格安のVPNサーバーを構築してみた

最近Clubhouse流行ってますよね。 そんなClubhouseですが、最近利用者が増えて日本だと夜に接続するとサーバー落ちが頻発するようになりました。

そこでエンジニアがサーバー落ちを防ぐために何をすべきか議論をしている様子を見ていると、

VPNで海外に接続してみたらどうじゃろ」

という話が出てきました。

なるほど、みんながつないでアクセスを集中しているネットワークに接続しないようにすればいいんですね。

とはいえわざわざVPNのサービスをこのためだけに契約するのもアホらしいですし、中にはEC2で構築してみたという話も出てきました。

エンジニアならAWS使ってVPNサーバー立てたいなとは思いますが、EC2はネットワークのセットアップとか面倒くさそうです。

というわけで今回はAmazon Lightsailを使って簡単に格安でVPNサーバーを立ててみます。

Amazon Lightsailとは?

AWSが提供するVPS(Virtual Private Server)サービスです。

一番安いとなんとたった月額3.5ドルで使うことができるらしいです。

そしてEC2と違いストレージやネットワークなどをパッケージされていることが特徴です。

aws.amazon.com

構築してみた

インスタンスを立てる

まずはAWSにログインします。(アカウント登録してない方はそこからやりましょう)

aws.amazon.com

コンソール画面の検索欄で「lightsail」と打ち込んでlightsailをさがして起動します。

f:id:KMiura:20210205230551p:plain

インスタンスの作成」をクリックします。

f:id:KMiura:20210205230738p:plain

インスタンスロケーションをアメリカかイギリスあたりで設定しておきます。

そうなってない場合は「AWSリージョンとアベイラビリティゾーンの変更」をクリックします。

f:id:KMiura:20210205231158p:plain

リージョンの選択画面を開いたらリージョンを選択します。 今回はオレゴンを選択します。

f:id:KMiura:20210205231455p:plain

インスタンスイメージの選択します。 プラットフォームは「Linux / Unix」、設計図は「OSのみ→Debian9系」を選択します。

f:id:KMiura:20210205231849p:plain

インスタンスの初回起動時にVPN実行するスクリプトを設定します。

「起動スクリプトの追加」をクリックします。 f:id:KMiura:20210205232241p:plain

コードを入力する欄が表示されるので、以下のスクリプトをコピペします。

インスタンスプランはおそらく3.5ドルのプランが選択されていると思います。

f:id:KMiura:20210205233532p:plain

インスタンス名は任意のもので設定します。

その後、「インスタンスの作成」をクリックします。

f:id:KMiura:20210205234412p:plain

用意したインスタンスがしばらくして「実行中」と表示されれば無事にインスタンスが立ち上がりました。 f:id:KMiura:20210205234753p:plain

静的IPを割り当てる

ネットワーキングタブを選択し、「静的IPの作成」をクリックします。

インスタンスにアタッチされていれば無料で使うことができます。

f:id:KMiura:20210205235154p:plain

静的IPを先程作成したインスタンスへアタッチします。

アタッチを完了し、パブリックIPアドレスが表示されたら割当は完了です。

f:id:KMiura:20210205235818p:plain

ポートを公開する

インスタンスの管理画面を開き、「ネットワーキング」タブを選択します。

下の方にスクロールしてファイヤーウォールのルールとしてUDP:514TCP:114を設定します。

f:id:KMiura:20210206001037p:plain

デフォルトのパスワードの確認

Lightsailのトップページに戻り、インスタンスのターミナルアイコンをクリックしてブラウザのターミナルを立ち上げます。

f:id:KMiura:20210206001454p:plain

sudo /vpn/pritunl default-passwordVPN管理画面のパスワードを確認します。

f:id:KMiura:20210206001615p:plain

pritunlにログイン

https://[設定した静的パブリックIPアドレス]:114/を開き、先程コマンドで確認したユーザー名、パスワードを入れます。

開くときに警告が出ると思いますが無視してアクセスしましょう。

f:id:KMiura:20210206002148p:plain

ログインしたらパスワードを変更します。

f:id:KMiura:20210206002837p:plain

ユーザーの設定

Usersタブを選択して、Organizationを設定します。

f:id:KMiura:20210206003128p:plain

任意のユーザー名、PINを設定します。

f:id:KMiura:20210206003608p:plain

サーバーの作成

Serversタブを選択して、Add Serverをクリックします。

f:id:KMiura:20210206003820p:plain

任意のサーバー名を設定します。

ポート番号は514プロトコルudpとします。

f:id:KMiura:20210206004232p:plain

先ほど作成したOrganizationとアタッチしてサーバーを起動させます。

f:id:KMiura:20210206004801p:plain

コンソールにログが表示されたらサーバーは起動できています。

f:id:KMiura:20210206004849p:plain

設定ファイルをダウンロード

Usersタブを選択して、「Get temporary profile link」をクリックします。

f:id:KMiura:20210206010526p:plain

ZIPファイルのダウンロードリンクにあるダウンロードアイコンをクリックします。

f:id:KMiura:20210206011117p:plain

ダウンロードしたら展開して、設定ファイルをiPhoneで参照できるようにストレージサービスにアップロードします。

Google Driveにアップロードします。

iPhoneに接続する

それではiPhoneVPNに接続します。

Googleドライブアプリを開いて先程保存したファイルの詳細を開いて、「アプリで開く」をタップします。

f:id:KMiura:20210206015917p:plain

メニューからOpenVPNを選択します。

f:id:KMiura:20210206020018p:plain

OpenVPNが開いたらインポートしたファイルが表示されるので「Add」を選択します。

f:id:KMiura:20210206020813p:plain

Usersで設定したユーザー名を入力し、ADDをタップしてVPNの設定を許可します。

f:id:KMiura:20210206020908p:plain

Profileを有効にしたらpritunlの初回ログインで設定したパスワードを入力します。

f:id:KMiura:20210206021341p:plain

ユーザー設定のときに設定したPINを入力します。

f:id:KMiura:20210206021619p:plain

無事に接続できました。 いい感じですね👍

f:id:KMiura:20210206022012p:plain

検証

といいたいところですが、このブログを書きながらやっていたらそんなにサーバーが落ちず、普通に音声が聞こえてました。

せっかく、用意したんですけどねw。

でも一応ネットワークのテストをやってみた結果、速度的にはそんなに悪くなかったです。

まぁ1台しかつなげてませんからねw。

f:id:KMiura:20210206023710p:plain

PyTorchで学習したモデルをOpenCVで使ってみる

前回はPyTorchでマスクを付けている人を分類するモデルを作りました。

supernove.hatenadiary.jp

学習させたモデルをOpen CVで使ってみたいと思うのですが、毎回PyTorchを使うのは面倒なのでOpen CVだけでモデルを使う方法を試してみました。

モデルの変換

学習したモデルはPTHで保存できますが、これだと毎回PyTorchを呼び出す必要があります。

そこでモデルをOpen CVで使うためにOpen CVのDNNモジュールに対応しているONNX形式のモデルに変換する必要があります。

PyTorchではtorch.onnx.exportを使えば学習したモデルを簡単にONNX形式で保存できます。

また、Open CVのDNNモジュールを使う場合は、CPUを使うことが多いのでGPUで学習していた場合は念の為モデルをCPU向けに変換します。

これを踏まえて前回のモデルをONNXで変換する場合は以下の通りになります。

torch.onnx.export(model.to('cpu'), torch.randn(1, 3, 224, 224) , "face_mask_detector.onnx")

これで汎用性のあるモデルができました。

モデルを使う

あとはDNNモジュールを使って以下のようにモデルを呼び出せば使えます。

faceMaskNet = cv2.dnn.readNetFromONNX("face_mask_detector.onnx")