Quantcast
Browsing Latest Articles All 14 Live
Mark channel Not-Safe-For-Work? (0 votes)
Are you the publisher? or about this channel.
No ratings yet.
Articles:

ラズパイと電子工作の活用事例です

External article

RaspberryPi + OBD2 で取得した車両情報を解析してみる

オフィスや自宅を快適にするIoT byゆめみ③ Advent Calendar 2018

さて、このAdvent Calendarの趣旨に合っているのか?と思っている皆さん。「タイトル」的にはアウトですが、「テーマ」的にはセーフという曲折を含めつつ、書いていきます:)

Advent Calendar の趣旨は?

このアドカレのタイトルは「オフィスや自宅を快適にするIoT」ですが、テーマは「続々と登場するIoTガジェットやサービス。果たして実際に生活は豊かになるのか?皆さんが試してみた、生活や身の回りを改善するIoTプログラミングを、教えてください。」
とあったので、「自分の好きな車 = 身の回り」ってことにしました(すいません🙇‍♂️)

TL;DR

ODB2 + Raspi で車の走行性能を数値化できる(ようになるための準備ができる)
車種にもよりますが、自分の車では「エンジン回転数」「スピード」を取得してグラフ化しました。下のグラフは回転数のグラフです。

キャプチャ.PNG

ODB2/ODBⅡとは

OBD = On-Board Diagnostics です。ざっくり言えば、元は「自動車の自己診断システム」で、最近では「車両情報」の意味も含まれていると思います。車速、回転数、水温、湯音、CANデータなどなどです。

歴史

1991年、US カリフォルニア州にて、州内で販売される新車に搭載が義務付けられた。
1996年、US 全土でも新車に搭載が義務付け。
2001年、EU でも排出ガス規制の一環で新車に搭載義務付け。
2006年、日本でも新車に搭載義務付け。

2008年時点で、アメリカで販売される全ての自動車にCANを信号のプロトコルとして埋め込む事が義務づけられている

本来の目的のデータ以外にも、車速、エンジン回転数などが取得できる。
規定されているデータの種類(PID)は100種類 + 各自動車メーカ独自拡張。なので、一概にすべての車に適用はできないかもしれません。

ODB2 ≠ CAN

CAN (Controller Area Network) = プロトコル
OBD2 = CAN 上でやり取りされるデータ

CANの一例

プロトコル 備考
SAE J1850 PWM フォードが使用
SAE J1850 VPW ドイツの自動車メーカーが使用
ISO 9141-2 クライスラーや、ヨーロッパ、アジアの車で使われる
ISO 14230 KWP2000 Keyword Protocol 2000
ISO 15765 CAN ボッシュによって開発された。他のOBDプロトコルと違って、変種が自動車業界の外でも使用されている。

システム構成

今回OBD2からデータ取得するために、以下のようなシステム構成になりました。

スペック等 備考
検証車 TOYOTA カローラフィールダー エアロツアラー
OBD2コネクタ ELM327 Bluetooth対応版 iPhone未対応
RaspberryPi3 Model B+ Python3, obd2ライブラリ使用
macbook pro

OBD2コネクタ

image.png

結構いろんな種類あるんですが自分はこれを使ってます。はずれが多いらしいんですが、これは問題なく使えてるので大丈夫かと

超小型モデル OBDII 診断 ELM327 Bluetooth ブルートゥース スキャンツール テスター OBD2 - Amazon.co.jp

システム説明

主にBluetoothを制御するstart.shと、OBD2からデータを取得しExcelに落とし込むlogging.pyの2つのファイルで構成されています。
実行するときにはstart.shsudoで実行することで、Bluetooth設定が終わったら自動的にlogging.pyが起動します。

start.sh

start.sh
sudo hciconfig hci0 up
sudo rmmod rfcomm
sudo modprobe rfcomm
sudo rfcomm bind 0 AA:BB:CC:11:22:33
ls /dev | grep rfcomm
sudo rfcomm listen 0 1 &
sudo python logging.py

この各行の意味を追っていきたいと思います。

hciconfig

hciconfigとはBluetooth(BT)をコマンドラインユーティリティから使うためのコマンドです。Bluezデーモンを使うのでインストールされてない場合は以下の通りインストールします

# bluezを動かすために必要なライブラリ群
$ sudo apt-get install -y libglib2.0-dev libdbus-1-dev libudev-dev libical-dev bluetooth bluez-utils blueman

# bluez本体
$ sudo mkdir car_tmp && cd car_tmp
$ wget http://www.kernel.org/pub/linux/bluetooth/bluez-5.45.tar.xz
$ xz -dv bluez-5.45.tar.xz && tar -xf bluez-5.45.tar 
$ cd bluez-5.45/ && ./configure --enable-experimental
$ make
$ sudo make install

無事インストールできれば、BTが認識できているか確認してみます。以下のようにBT機器のMACアドレスが表示されてればOKです。

pi@raspberrypi:~ $ sudo hcitool lescan
LE Scan ...
74:DE:1A:E6:4E:4F (unknown)
34:36:3B:C7:FB:E9 (unknown)
34:36:3B:C7:FB:E9 (unknown)
74:DE:1A:E6:4E:4F (unknown)

pi@raspberrypi:~ $ 
pi@raspberrypi:~ $ sudo hcitool scan                                                                                                                                                                                                          
Scanning ...
        AA:BB:CC:DD:EE:FF       xxxxxxxxxxxxxxxxxxx
pi@raspberrypi:~ $ 

確認ができたらhcitool -aでBTモジュールを確認します

hcitool -a

hci0:   Type: USB
        BD Address: 00:1B:DC:XX:XX:XX ACL MTU: 310:10 SCO MTU: 64:8
        UP RUNNING PSCAN
        RX bytes:384 acl:2323 sco:0 events:255 errors:0
        TX bytes:512 acl:4949 sco:0 commands:512 errors:0
        Features: 0xff 0xff 0x8f 0xfe 0x9b 0xff 0x59 0x83
        Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3
        Link policy: RSWITCH HOLD SNIFF PARK
        Link mode: SLAVE ACCEPT
        Name: 'BlueZ at localhost.localdomain-0'
        Class: 0x120104
        Service Classes: Networking, Object Transfer
        Device Class: Computer, Desktop workstation
        HCI Ver:  (0x4) HCI Rev: 0x12e7 LMP Ver:  (0x4) LMP Subver: 0x12e7
        Manufacturer: Cambridge Silicon Radio (10)

RaspiにあるBTモジュールが認識されていれば、このような感じの情報が出力されます。このhci0というのがデバイスごとに割り振られるデバイスIDです。このデバイスIDを使って通信するBTモジュールを指定します。なので、メモしておきましょう。

sudo hciconfig hci0 up

このコマンドでデバイスIDがhci0のデバイスの電源をUPにします。このコマンドを実行してsudo hciconfigを確認したときにUP RUNNINGという表記がされていればOKです。

$ sudo hciconfig
hci0:   Type: BR/EDR  Bus: UART
        BD Address: B8:27:EB:2E:E0:10  ACL MTU: 1021:8  SCO MTU: 64:1
        UP RUNNING
        RX bytes:717 acl:0 sco:0 events:42 errors:0
        TX bytes:1532 acl:0 sco:0 commands:42 errors:0

rfcomm

rfcommはBluetoothの通信プロファイルに1つです。このプロファイルでは、通信上はBluetoothで接続されているデバイスと通信を行うのですが、見かけ上、シリアル通信としてプログラムできるという利点があります。

image.png

rmmod

Loadable Kernel Moduleのアンロードを行います。後で説明するmodprodeで読み込んだ情報を削除します。OBD2の接続するとき毎回行う必要はないかもしれないんですが、念のためやってます。rfcomm自体は再起動すると設定が失われます。逆に言えば電源が入っていればずっと保持されるので、rmmodしておいたほうが無難かと...。

modprobe

rmmodの反対でLoadable Kernel Moduleのロードを行います。modprobe rfcommrfcommプロトコルが利用可能になります。

rfcomm bind

rfcommセクションでも説明したようにrfcommプロトコルを利用すれば、シリアル通信としてプログラムできるようになります。しかし、そのままではシリアル通信として利用できないので、シリアルポートにrfcommbindする必要があります。バインドする先は/dev/rfcommXです。Xは0~9などの任意の数値を指定できますが、今回は0を指定しておきます。

sudo rfcomm bind 0 AA:BB:CC:11:22:33

ここのAA:BB:CC:11:22:33とは接続先のBTのMACアドレスです。sudo hcitool lescansudo hcitool scanで見つけたMACアドレスをメモしておきましょう。ただ、今回利用するELM327モジュールは全てAA:BB:CC:11:22:33に統一されているようでした。

ls /dev | grep rfcomm

無事にバインドが成功していれば、ls /dev | grep rfcommを実行すれば先ほど指定した番号のシリアルポートが確認できるかと思います。

rfcomm listen

先ほどバインドした番号のシリアルポートに来るデータ読む(listenする)コマンドを実行します。これを実行することで/dev/rfcomm0に流れてくるデータを他のプログラムでも確認することができます。1はチャンネルを表しています。

sudo rfcomm listen 0 1 &

&とは、このrfcomm listenというコマンドはプロセスとして動作します。このlistenの動作自体はバックグラウンドで動作してもらいたいので&を付けます。つまり、この&を付けて実行したコマンドはバックグラウンドで動作するようになります。

logging.py

使用しているOBD2ライブラリの公式サイトはこちら

logging.py
import obd
from obd import OBDStatus
import time, csv
import os

f = open("data.csv", "w")
writer = csv.writer(f, lineterminator="\n")

connection = obd.OBD()
print (connection.status())

if connection.status() == OBDStatus.CAR_CONNECTED:
    writer.writerow(["rpm", "speed"])
    while(True):
        try:
            rpm = connection.query(obd.commands.RPM)
            speed = connection.query(obd.commands.SPEED)
            writer.writerow([rpm.value.magnitude, speed.value.magnitude])
            print (rpm.value.magnitude, speed.value.magnitude)
            # time.sleep(.2)
        except KeyboardInterrupt:
            pass
else:
    connection.close()

obd.OBD()で先ほどbindしたポート等と確認して自動的にOBD2スキャンツール(ELM327)にアクセスしてくれます。その接続先(データ取得先)をconnectionという変数で保持し、今後この変数に対して、値の要求などを行います。
接続に使用するポートを変更したいときには、以下のように使用するポートを指定します。

import obd

# 直接シリアルポートを指定
connection = obd.OBD("/dev/ttyUSB0")

# 利用できるシリアルポートから選択する
ports = obd.scan_serial()      # ['/dev/ttyUSB0', '/dev/ttyUSB1']
connection = obd.OBD(ports[0]) 

接続できているかは以下で確認できます。

connection.is_connected()

#or
from obd import OBDStatuus
if connection.status() == OBDStatus.CAR_CONNECTED:

OBDStatusには以下が定義されています

# no connection is made
OBDStatus.NOT_CONNECTED # "Not Connected"

# successful communication with the ELM327 adapter
OBDStatus.ELM_CONNECTED # "ELM Connected"

# successful communication with the ELM327 and the vehicle
OBDStatus.CAR_CONNECTED # "Car Connected"

Basic Usage

import obd

connection = obd.OBD() # auto-connects to USB or RF port

cmd = obd.commands.SPEED # select an OBD command (sensor)

response = connection.query(cmd) # send the command, and parse the response

print(response.value) # returns unit-bearing values thanks to Pint

基本的な使い方としては上記の通りで、connectionに対して値を要求するクエリを実行します。そのクエリの返り値が要求した結果となっています。別にcmdを使わなくてもconnection.query(obd.commands.SPEED)でも動作します。お好みで

Module Layout

このライブラリのレイアウトは以下の通りです。すべてを使いこなせる気はしませんが...。

import obd

obd.OBD            # main OBD connection class
obd.Async          # asynchronous OBD connection class
obd.commands       # command tables
obd.Unit           # unit tables (a Pint UnitRegistry)
obd.OBDStatus      # enum for connection status
obd.scan_serial    # util function for manually scanning for OBD adapters
obd.OBDCommand     # class for making your own OBD Commands
obd.ECU            # enum for marking which ECU a command should listen to
obd.logger         # the OBD module's root logger (for debug)

csv出力

f = open("data.csv", "w")
writer = csv.writer(f, lineterminator="\n")

データ出力する先と改行を指定します。ファイルの出力先はlogging.pyと同じ位置になりますが、パス指定すれば任意の場所に出力することも可能です。データをファイルに書き込むときには以下のように記述します。

# カラム名の書き込み
writer.writerow(["rpm", "speed"])

# 取得データの書き込み
writer.writerow([rpm.value.magnitude, speed.value.magnitude])

取得したデータ分析

この取得したデータをOctaveを使って、解析的なことをしてみたいなと思います。今回利用するのはGNU Octaveです。

GNU Octave は、主に数値解析を目的とした高レベルプログラミング言語である。Octaveは線形ならびに非線形問題を数値的に解くためのコマンドライン·インタフェースを提供する。また、 MATLABとほぼ互換性のある、数値実験を行うためのプログラミング言語として使用することができる。 Octaveは、GNUプロジェクトの一つでGNU General Public Licenseの条件の下のフリーソフトウェアである。 GNU OctaveとScilabは、MATLABのオープンソース代替品の一つである。 ただし、Octaveは、ScilabよりもMATLABとの互換性維持に重点を置いている
Wikipedia - GNU Octave

ダウンロード先はこちら
解析は普通のPCを使います。今回はWindowsを使いますがMacでも同じです。インストール等の開設は省略します。インストーラーで終わってしまうので...。Macはportsbrewでインストールになります。

今回は「エンジン回転数」のデータを使って、回転数のピーク部を検出して印付けしたいと思います。100%検出できるわけではないです。以下のような画像のグラフを作成します。
キャプチャ.PNG

解析データ作成

生成したcsvのデータから、解析したデータのみを取り出します。今回はエンジン回転数のファイルを作成します。ファイルの中身は数値のみになります。

car_rpm.txt
636.5
644.5
642
641.25
638.25
639
639
640.5
:

Octaveでファイル読み込み

Octaveでそれぞれのファイルを読み込みます。この読み込まれたデータはOctave上では行列として認識されます。上記2つのデータの場合「n行1列」のデータ配列となります。しかし、rpm[1] speed[10]のようにアクセスしたい場合は「転置」を行う必要があります。

% ファイルからデータ読み込み
rpm   = load('car_rpm.txt'  , ' ') ;
% 行列の転置
rpm   = rpm.' ;

グラフ設定

そして、横軸(時間軸)を決めていきたいのですが、今回は時間を使用した解析は行わないので「1」から連番で振っていきます。

%
% bからcまでのm個を用いた「m X 1」の行列を生成
% a = b : c ;
%
x = 1 : length(rpm) ; 

これでグラフに表示する準備ができました。グラフを表示するには以下のように記述します。

% データをグラフにプロット
plot(x, rpm) ;
hold on ;

% ラベルの表示設定
xlabel('TIME') ;
ylabel('rpm') ;
%
% 走行データ解析プログラム
% コマンドウィンドウで pkg load signal 必須
%

% 走行データの読み込み
rpm   = load('car_rpm.txt', ' ') ;

% 走行データ配列の転置
rpm = rpm.' ;

% 時間軸の生成
x = 1 : length(rpm) ;

% グラフのプロット
plot(x, rpm) ;
hold on ;

% ラベルの表示設定
xlabel('TIME') ;
ylabel('RPM') ;

グラフ結果

car_blog1.jpg

エンジン回転数のピーク値を見つける

Octaveには様々な計算ライブラリが搭載されています。今回単純にピークを検出するfindpeaksを用います。この関数はsignalパッケージに含まれているため、Octaveのコマンドウィンドウで

pkg load signal

と打ってもらう必要があります。

findpeaks

実際に先ほどのプログラムにfindpeaksを追加してグラフに表示させてみます。

%
% 走行データ解析プログラム
% コマンドウィンドウで pkg load signal 必須
%

% 走行データの読み込み
rpm   = load('car_rpm.txt', ' ') ;

% 走行データ配列の転置
rpm = rpm.';

% 時間軸の生成
x = 1 : length(rpm);

[pks1 idx1] = findpeaks(rpm, 'DoubleSided', 'MinPeakDistance', 1);

% グラフのプロット
plot(x, rpm);
hold on;
plot(idx1, pks1, 'o') ;

% ラベルの表示設定
xlabel('TIME');
ylabel('RPM');

car_blog2.jpg

しかし、このfindpeaksを用いただけでは正常にピーク値を導き出すことができません。今求めたいpeakとは「明らかに突出していて、回転数が前回の測定値よりも格段に大きいとき」という意味を持ちますので、以下のようなpeak値は不要なのです。

car_blog3.jpg

なので、プログラムにひと手間加え、次のようなプログラムが完成します。

ピーク値検出

%
% 走行データ解析プログラム
% コマンドウィンドウで pkg load signal 必須
%

% 走行データの読み込み
rpm   = load('car_rpm.txt', ' ') ;

% 走行データ配列の転置
rpm = rpm.';

% 時間軸の生成
x = 1 : length(rpm);

[pks1 idx1] = findpeaks(rpm, 'DoubleSided', 'MinPeakDistance', 1);

new_pks1 = [];
new_idx1 = [];
th_rpm = 20; % 認識する閾値を指定

for i = 3 : length(rpm) - 2

  prev    = rpm(i - 2);  % currentの一つ前のデータ
  current = rpm(i);      % peakかどうか確認する点
  next    = rpm(i + 2);  % currentの一つ後のデータ

  b_slope = current - prev; % 2つのデータの差を計算
  a_slope = current - next; % 2つのデータの差を計算

  %
  % b_slope > 0 と a_slope > 0 より current が前後よりも突出している(つまりpeakの可能性)
  % b_slope, a_slope ともに閾値以上の差(つまりpeakの可能性)
  %
  if (b_slope > 0) && (b_slope > th_rpm)
    if (a_slope > 0) && (a_slope > th_rpm)
      if (rpm(i) > 1500)
        % 可能性のあるものだけ、新規変数に追加
        new_pks1 = [new_pks1, rpm(i)];
        new_idx1 = [new_idx1, i];
      endif
    endif
  endif
endfor

% グラフのプロット
plot(x, rpm);
hold on;
plot(new_idx1, new_pks1, 'o');

% ラベルの表示設定
xlabel('TIME');
ylabel('RPM');

car_blog4.jpg
car_blog5.jpg

修正を加える前よりかは、きれいに取得できていることが分かると思います。

まとめ

世の中には「家具、家電、その他いろいろ」なものに対するIoT製品や記事はよく見かけるのですが、「車」という分野ではあまり見かけないような気がしました。最近では「自動運転」が騒がれていますが、こんな感じの簡単で面白い「情報 X 車」コラボをしたくなったので、あえてレベルを下げてみました。
まだまだ、できることはあります。これをうまく応用して作りこむと
image.png

こんな感じの「自作の」ヘッドマウントディスプレイすら作れるようになると思います。ってかできます。なので、この記事で興味を持った方は調べてみてください。もっと面白いことたくさんあります!!

ではでは~。

ソースコード

https://gist.github.com/nomunomu0504/dc3dc538bbc7738ecdff2a771a0c6c29

RaspberryPi 3 Model B+ でlircを使ってリモコン化する(その1)

ブログはこちらです

RaspberryPi 3 Model B+ でlircを使ってリモコン化する(その1)

はじめに

RaspberryPi3とIRレシーバーとIR送信機を使って、家電類を制御するためのメモ。
ラズパイのGPIOからIR送信機を使って制御する。

目指すは、スマホやPCからラズパイにリクエストを送信して、機器を制御することです。

今回使ったもの

・Raspberry Pi 3 Model B+

・赤外線センサ(VS1838B)

・赤外線LED(型番不明)

・抵抗330Ω

IR解析

LIRCというものを用いてリモコンキーの解析、送信を行います。
LIRCとはIR信号の出コードや送信などを行うことのできるLinuxのパッケージです

LIRCのインストールとセットアップ

インストール

$ sudo apt-get install -y lirc

セットアップ

新しいラズパイと古いラズパイでは少し異なるようなので注意してください。また、gpio_in_pingpio_out_pinは各自置き換えてください。

lircの有効化設定

・古いラズパイ(/etc/modules)

lirc-rpi,gpio_in_pin=24,gpio_out_pin=25

・新しいラズパイ(/boot/config.txt)

# IR-Remote controller
dtoverlay=lirc-rpi
dtparam=gpio_out_pin=25
dtparam=gpio_in_pin=24
dtparam=gpio_in_pull=up

ハードウェア設定

/etc/lirc/hardware.confにdeamon起動時の引数・デバイス・モジュールを指定します。以下をコピペで実行してください。

cp /etc/lirc/hardware.conf ~/hardware.conf
sed -i -e "s/LIRCD_ARGS=\"\"/LIRCD_ARGS=\"--uinput\"/g" ~/hardware.conf
sed -i -e "s/DRIVER=\"UNCONFIGURED\"/DRIVER=\"default\"/g" ~/hardware.conf
sed -i -e "s/DEVICE=\"\"/DEVICE=\"\/dev\/lirc0\"/g" ~/hardware.conf
sed -i -e "s/MODULES=\"\"/MODULES=\"lirc_rpi\"/g" ~/hardware.conf
cat ~/hardware.conf
sudo mv /etc/lirc/hardware.conf /etc/lirc/hardware.conf.bak
sudo cp ~/hardware.conf /etc/lirc/hardware.conf

ここまで来たら一旦再起動を行います。sudo reboot

デバイスチェック

/dev/lirc0が存在するかを確認します

$ ls -l /dev/lirc*
crw-rw---- 1 root video 244, 0 Sep 1 15:41 /dev/lirc0

lsmodで lircの存在を確認します

$ lsmod | grep lirc
lirc_rpi 9032 0
lirc_dev 10583 1 lirc_rpi
rc_core 24377 1 lirc_dev

それぞれ確認出来たら、一旦サービスを停止します。

$ sudo /etc/init.d/lirc stop

IRレシーバーのチェック

$ mode2 -d /dev/lirc0を実行し、IRレシーバーに向かってリモコンキーを押して、以下のような表示がされれば動作確認OKです。表示されない場合は、もう一度最初から設定を行うか、GPIOピンなどを確認してください。

pulse 426
space 415
pulse 390
space 450
pulse 389
space 450
pulse 391
space 449
pulse 416
space 424
pulse 416
space 423
pulse 423
:
:

リモコンを学習させる

これから実際にリモコンの学習を行っていきます。ここでは、半自動的に学習を行ってくれるコマンドを用いて行っていきます。

# "dvd_player_ir"の部分は各自変更してください
$ irrecord -n -d /dev/lirc0 ~/dvd_player_ir.conf

"""
irrecord: could not open /dev/lirc0
irrecord: default_init(): Device or resource busy
irrecord: could not init hardware (lircd running ? --> close it, check permissions)
"""
のようなエラーが表示されたら、LIRCサービスを再起動してください
$ sudo /etc/init.d/lirc restart
irrecord -  application for recording IR-codes for usage with lirc
Copyright (C) 1998,1999 Christoph Bartelmus(lirc@bartelmus.de)

Press RETURN to continue.
(リターンキーを押す)

Now start pressing buttons on your remote control.

It is very important that you press many different buttons and hold them
down for approximately one second. Each button should generate at least one
dot but in no case more than ten dots of output.
Don't stop pressing buttons until two lines of dots (2x80) have been
generated.

Press RETURN now to start recording.
(リターンキーを押す)
(リモコンのボタンを押したままにする)
................................................................................
................................................................................
Found const length: 107893

Please keep on pressing buttons like described above.
(リモコンのボタンを連打する。押したままにしない)

Please enter the name for the next button (press <ENTER> to finish recording)
(リモコンのボタンの名前を入力する。例:play)
play

Now hold down button "play".
(リモコンの Playボタンを押す)
Got it.
Signal length is 67

Please enter the name for the next button (press <ENTER> to finish recording)
(リモコンのボタンの名前を入力する。例:stop)
stop

Now hold down button "stop".
(リモコンの Stopボタンを押す)
Got it.
Signal length is 67

Please enter the name for the next button (press <ENTER> to finish recording)
(以下同様)
pause

Now hold down button "pause".
(以下同様)
Got it.
Signal length is 67

Please enter the name for the next button (press <ENTER> to finish recording)
(以下同様)
eject

Now hold down button "eject".
(以下同様)
Got it.
Signal length is 67

Please enter the name for the next button (press <ENTER> to finish recording)
(リターンを押すと学習モードを終了する)
リモコンのボタン学習の時に下記エラーが出てリモコン学習が出来ない場合は -fオプションを付ける。
Something went wrong. Please try again. (9 retries left)
Something went wrong. Please try again. (8 retries left)
Something went wrong. Please try again. (7 retries left)

例: -fを付けてリモコン学習コマンド irrecordを実行する。
$ irrecord -n -f -d /dev/lirc0 ~/dvd_player_ir.conf
Checking for toggle bit mask.
Please press an arbitrary button repeatedly as fast as possible.
Make sure you keep pressing the SAME button and that you DON'T HOLD
the button down!.
If you can't see any dots appear, then wait a bit between button presses.

Press RETURN to continue.
(リターンを押す)
(リモコンのボタンを連打する。押したままにしない)
.......................
No toggle bit mask found.
Successfully written config file.

上記で設定ファイルの生成を行ったら、lircのコンフィグファイルに設定ファイルを適用させていきます。

生成したファイルを編集する

$ sudo nano dvd_player_ir.conf

# 変更前 name /home/pi/dvd_player_ir.conf
# 変更後 name dvd_player
# 注意点 name dvd playerのように途中に空白が含まれるとエラーになります。

ファイルを適用する

/etc/lirc/lircd.confに生成したファイルの中身を書き込むのですが、初めての場合、ファイルの中身は以下のようになっていると思います。

#UNCONFIGURED
#
# To find out how to get a proper configuration file please read:
#
#       /usr/share/doc/lirc/README.Debian

なので、今回生成したファイルをコンフィグファイルとしてコピーします。

$ sudo rm -rf /etc/lirc/lircd.conf
$ sudo cp dvd_player_ir.conf /etc/lirc/lircd.conf

それ以外の場合は、コンフィグファイルの一番下に生成したファイルの内容をすべてコピーしてください。

LIRCサービスの再起動

$ sudo /etc/init.d/lirc restart

実際に制御してみる

学習させたリモコンの内容を確認

$ irsend list "" ""
irsend: DVD_PLAYER

$ irsend list DVD_PLAYER ""
irsend: 0000000000000001 play
irsend: 0000000000000002 stop
irsend: 0000000000000003 pause
irsend: 0000000000000004 eject

リモコンキーの送信

$ irsend SEND_ONCE DVD_PLAYER play

リモコンキーを複数回送信(Ex. 100回)

$ irsend -#100 SEND_ONCE DVD_PLAYER play

リモコンキーをコマンドを用いて複数回送信(Ex. 100回)

$ for i in {0..100}; do irsend SEND_ONCE DVD_PLAYER play; echo $i; done;

ここまでで、リモコンキーを学習させ、コマンドを打つことで実際に制御することができるようになりました。
次回は、PCやスマホから制御できるようにしていきたいと思います。

つづき(2018/07/31)

RaspberryPi 3 Model B+ でlircを使ってリモコン化する(その2)

RaspberryPi 3 Model B+ でlircを使ってリモコン化する(その2)

前回からのつづき

RaspberryPi 3 Model B+ でlircを使ってリモコン化する(その1)

さて、前回のつづきで、ラズパイをスマホやPCから制御したいと思います。

今回やること

今回は「Slack」とラズパイを連携させて、Slackを介して制御させます

下準備

SlackでIncomingWebHookの有効化

以下のアドレスから、IncomingWebHooksを有効化します
[https://my.slack.com/services/new/incoming-webhook/]

ここで、投稿するチャンネル(制御するためのチャンネル)を選択し、Webhook URLを取得します。
image.png

ここで取得したWebhook URLをコピペします。また、ここで取得したアドレスを用いて制御を行っていきます。
image.png

ここでは、Slackへ投稿するIncomingWebHookの表示名、アイコンなどはカスタマイズ可能です。これは各個人で変更することができます。

apache2, php5のインストール

$ sudo apt-get install apache2 php5
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following extra packages will be installed:
  apache2-bin apache2-data apache2-utils libapache2-mod-php5 libapr1 libaprutil1 libaprutil1-dbd-sqlite3
  libaprutil1-ldap liblua5.1-0 libonig2 libperl4-corelibs-perl libqdbm14 lsof php5-cli php5-common php5-json
  php5-readline
Suggested packages:
  apache2-doc apache2-suexec-pristine apache2-suexec-custom php-pear php5-user-cache
The following NEW packages will be installed:
  apache2 apache2-bin apache2-data apache2-utils libapache2-mod-php5 libapr1 libaprutil1 libaprutil1-dbd-sqlite3
  libaprutil1-ldap liblua5.1-0 libonig2 libperl4-corelibs-perl libqdbm14 lsof php5 php5-cli php5-common php5-json
  php5-readline
0 upgraded, 19 newly installed, 0 to remove and 81 not upgraded.
Need to get 6,829 kB of archives.
After this operation, 24.3 MB of additional disk space will be used.
Do you want to continue? [Y/n] Y

Slackからラズパイにアクセスされる際に、apacheがないとhttp経由でアクセスすることができません。

また、ラズパイと異なるネットワークからアクセスるる場合は、Internet側のIPアドレスに80ポートでアクセスしたとき、ラズパイのIPアドレスにポートフォワードしてあげるように設定してください。

さらには、www-dataではrootの権限がないので、www-dataにroot権限を与えます。

今回はすべてローカルネットワークでの完結です。

$ nano /etc/sudoers

# 以下をファイルの一番下に追記してください
www-data ALL=(root) NOPASSWD: ALL

Slackwebのセットアップ

Slackwebのインストール

$ sudo pip install slackweb
Downloading/unpacking slackweb
  Downloading slackweb-1.0.5.tar.gz
  Running setup.py (path:/tmp/pip-build-ZtOu3c/slackweb/setup.py) egg_info for package slackweb

Installing collected packages: slackweb
  Running setup.py install for slackweb

Successfully installed slackweb
Cleaning up...

投稿テスト

とりあえずパッケージが入ったので投稿テストしてみます。ここではiPythonを使います。

$ ipython
Python 2.7.9 (default, Sep 17 2016, 20:26:04) 
Type "copyright", "credits" or "license" for more information.

IPython 2.3.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: import slackweb

In [2]: slack_url = "https://hooks.slack.com/services/xxx/xxx/xxxxx"

In [3]: slack = slackweb.Slack(url=slack_url)

In [4]: slack.notify(text="Hello World!! From iPython")
Out[4]: u'ok'

image.png

このようにSlackに投稿されていればOKです。

実際に動かす

webページを作る

ここでは簡単なボタンが設置されてるだけのwebページを作ります。ボタンが押されたらphpからirsendコマンドを実行し、その後pythonを呼び出しSlackにメッセージを投げます。

/var/www/html/ajax.php
<?php
if (isset($_POST['action'])) {
        exe($_POST['action']);
}
function exe($selector) {
        exec('irsend SEND_ONCE fctv_controller ' . $selector);
        exec('python /var/www/slack.py '. $selector . "を実行したよ");
        exit;
}
?>
/var/www/html/index.php
<html>
<head>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
  <script>
    $(document).ready(function(){
      $('.button').click(function(){
          var clickBtnValue = $(this).val();
          var ajaxurl = 'ajax.php',
          data =  {'action': clickBtnValue};
          $.post(ajaxurl, data)
                .done( function (response) {
                        // Response div goes here.
                        // alert(response);
                });
      });

    });
  </script>
</head>
<body>
  <input type="submit" class="button" name="tv_power" value="tv_power" />
</body>
</html>
/var/www/slack.py
import sys, slackweb
argvs = sys.argv

SLACK_URL = "https://hooks.slack.com/services/xx/xx/xxxx"

slack = slackweb.Slack(url=SLACK_URL)
slack.notify(text=argvs[1])
/var/www/ir.sh
irsend SEND_ONCE XXXXXXXX xxxxxxxx

それぞれをそれぞれのファイルに書き込み、作成します。そして、ラズパイのIPアドレスにアクセスし、ボタンを押すことにより、制御することができると思います。

サークルの部室をスマートロック化したお話

はじめに

はじめまして。私は現在大学生であり、プログラミングをしたりゲームを制作したりするサークルに所属しています。
部室はPCが置かれていて冷暖房もあるためかなり快適な環境になっているのですが少し問題がありました。

さて何でしょう
...
...
...
鍵が使いにくすぎる!!!!!!!!:rage::rage:

説明しますと、鍵収納BOXというダイヤル式で開けることのできる箱の中に部屋に入るための鍵が保管されているのですが、この鍵収納BOXがなかなか開け辛いのです。。。
これではユーザーエクスペリエンスが低すぎ!ってことなので自分でUX高めなシステムを構築しました。

制作したもの

  • 全体像 IMG_20181211_150345.jpg
  • モーター周辺 IMG_20181211_150350.jpg
  • メイン装置中身 IMG_20181211_150740.jpg

PaSoRiに大学の学生証をかざすとサーボモーターがサムターンを回すことで開錠できるようになっています。
また、扉が閉まると施錠されるオートロックとなっており、出るときには青いボタンで解錠することが可能になっています。

使用部品

使用ライブラリ

製作にあたって

私は回路もRaspberryPiもPythonも触るのが初めてだったため、インターネットで先人たちの多大なる努力の結果実現されたライブラリやその使用方法を書き記したブログなどを大変利用させていただきました。おかげで完成させることができ大変助かりました。ありがとうございました。

製作

RaspberryPiにはGPIOというピンが生えており、センサーの読み取りやサーボモーターの制御に使用することができます。
初RaspberryPiのためGPIO操作の方法を調べまくった結果pigpioといのが良さそうということでpigpioを採用することに決定しました。
割り込みと高精度PWMをどちらも行えるのが制作していてとても役に立ちました。
参考サイト:
- pigpioホームページ
- https://karaage.hatenadiary.jp/entry/2017/02/10/073000

pigpioはとても便利なのですが、pythonでライブラリを読み込むだけでは実行できず、raspberryPiの起動ごとにpidpiodというデーモンを起動させないと動作しません。流石に面倒なので自動化します。下記の参考サイトに載っているものを使わせていただき、service化することで自動起動を実現しました。
参考サイト:
- https://tomosoft.jp/design/?p=8768

ここまでできると、あとはひたすらPythonでプログラムを組んでいくだけです。

サーボモータ

まずはサーボモーターを動かしてみましょう。
サーボモーターはPWMという信号で制御することができます。このサーボモーターだと、20msのうちに電圧がHighになっていた割合で回転角度を決定することができます。この(Hiの時間/20ms)*100をデューティー比と呼び、例えば10msだけHighになっていた場合はデューティー比50%となります。
データシートが公開されてなく正確な値を割り出すことはできませんが、ほかのサーボモータ(SG90)の場合だと2.5%~12%の間で操作します。
また、サーボモータのPWMはデータシートを見ると5VがHighとなっていますが、raspberryPiでは3.3Vしか出力できません。しかし、3.3Vでも操作可能でしたので昇圧せずこのまま使用することにしました。

/home/pi/smartlock/servo.py
from time import sleep

class Servo:
    # 変えてね
    servo_pin = 18

    def __init__(self, pi):
        # pigpioのインスタンスを渡してね
        self.pi = pi

    # 任意の角度に回転0-180
    def servo_rotate(self, degree):
        motor_pulse = ((degree * 100 / 9) + 500)
        self.pi.set_servo_pulsewidth(Servo.servo_pin, motor_pulse)
        sleep(0.5)
        self.pi.set_servo_pulsewidth(Servo.servo_pin, 0)

    # 鍵を開ける方向に回転
    def open_lock(self):
        # duty:11.25%
        self.pi.set_servo_pulsewidth(Servo.servo_pin, 2250)
        sleep(0.2)
        # サーボ開放
        self.pi.set_servo_pulsewidth(Servo.servo_pin, 0)

    # 鍵を締める方向に回転
    def close_lock(self):
        # duty:6.5%
        self.pi.set_servo_pulsewidth(Servo.servo_pin, 1300)
        sleep(0.2)
        # サーボ開放
        self.pi.set_servo_pulsewidth(Servo.servo_pin, 0)

set_servo_pulsewidthの2つ目の引数の値を変えていろいろ試してみてください。

PaSoRi

次はPaSoRiを触ってみましょう。raspberryPiにUSBで接続し、nfcpyを使用して操作します。
nycpyをインストールするときは公式のInstallationを参考にしましょう。libusb入れる必要があるのを忘れがち。
https://nfcpy.readthedocs.io/en/latest/topics/get-started.html#installation

次に公式のexampleであるtagtool.pyを動かしてみましょう
https://github.com/nfcpy/nfcpy

ここまでできるとnfcpyを利用する準備はばっちり!
使用したいカードを解析していきましょう!
なんですが、ここだけ大学の先輩がコードを書いてくれたため方法があまりわかりません:bow_tone1:

大学の先輩が書いてくれたコードでは学籍番号とカード固有のIDmをハッシュ化して取り出しています。

扉の開閉検知

はじめはリードスイッチ(磁気に感知するセンサ)を使用していたのですが、むき出して使用していたことによる破損に加えてマグネットの位置がいつのまにかずれている事件が発生したので圧力センサを使用することに途中で変更しました。扉の軸がある方に圧力センサを設置し、ある閾値を超えると扉がしまったと検知します。
また、今回使用した圧力センサはアナログ出力なためraspberryPiで使用するにはA/D変換が必要でした。なのでA/DコンバーターのMCP3002-I/Pを使用しました。
参考サイト:
- https://qiita.com/f_nishio/items/4b9723c4e622a51aaeb5
- http://akizukidenshi.com/download/ds/microchip/mcp3002.pdf

/home/pi/smartlock/door.py
class Door:
    MINIMUM_VALUE = 250
    CLOSE = 0
    OPEN = 1

    def __init__(self, pi):
        self.pi = pi
        self.h = pi.spi_open(0, 200000, 0) # 0ch 200kHz(5V) mode0

    def door_status(self):
        (count, rx_data) = self.pi.spi_xfer(self.h, [0x68, 0x00])
        value = ((rx_data[0] & 3) << 8) + rx_data[1]
        if value >= Door.MINIMUM_VALUE:
            return Door.CLOSE
        else:
            return Door.OPEN

データベース

同時アクセスが不要なためPythonに標準で付属するライブラリSQLiteを採用

  • User Database
Student_id Name IDm

こんな感じで保持してあります。一応IDmはSHA256によりHash化してあります。

  • Logs database
num Student_id Name enter_time leave_time

ログはこんな感じで保持してあります。numはauto incrementになっており、自動で数字が入るようになっております。
timeを分けた理由は聞かないでください:innocent:

スイッチ

nfcpyではカードを読み取るまで無限ループが発生しボタンの検知を行うことができません。そんなときにもpigpioが大活躍してくれます!
pigpioには入力割り込みという機能があり、電圧レベルの変化を検知してプログラムを実行する機能が備わっているのです。

/home/pi/smartlock/switch.py
import time
import door
import pigpio


class Switch:
    switch_pin = 22

    def __init__(self, pi, servo):
        self.pi = pi
        self.door = Door(pi)
        self.time_old = time.time()
        self.servo = servo
        # switchのピンを入力に設定
        pi.set_mode(Switch.switch_pin, pigpio.INPUT)
        # プルダウン抵抗を有効化
        pi.set_pull_up_down(Switch.switch_pin, pigpio.PUD_DOWN)
        # 割り込みの関数を設定 RISING_EDGE→LOWからHIGHになると実行
        self._cb = pi.callback(Switch.switch_pin, pigpio.RISING_EDGE, self._cbf)

    def _cbf(self, gpio, level, tick):
        # チャタリング防止
        if time.time() - self.time_old < 1:
            return
        else:
            # 鍵開ける
            self.servo.open_lock()
            time.sleep(5)
            # 扉しまってなかったら鍵閉めるまで待つ
            while self.door.door_status() == self.door.OPEN:
                time.sleep(0.5)
            self.servo.close_lock()
        # チャタリング防止用の時間更新
        self.time_old = time.time()

Slackへ送信

部内での連絡にSlackを使用しているので入退室情報をSlackに送信するように設定しました。
SlackにIncoming webhookを利用できるように設定し、requestsライブラリを使って送信しようと思ったのですRaspberryPi Zeroには重すぎたのかrequestsライブラリを全然読み込まない事件が発生したので標準のurllib2を使用するようにしました。

/home/pi/smartlock/slack.py(一部)
import urllib2
import json

url = 'https://hooks.slack.com/services/XXXXXXXX/XXXXXXX/XXXXXXXXXXXXXXXXXXXX'


def send_slack(Name):
    body = "%sが入室しました" % Name
    data = json.dumps({"text": body})
    req = urllib2.Request(url, data, {'Content-Type': 'application/json'})
    try:
        f = urllib2.urlopen(req)
    except urllib2.HTTPError:
        print "slack_error"
    f.close()

自動起動

ここまででほとんど機能的なものは完成したのですが、再起動すると自動ではpythonプログラムは実行されないためmainのプログラムをサービス化して自動起動するようにしましょう。

/home/pi/smartlock.sh
cd ~/smartlock
python __main__.py

↑このファイルを/home/pi/において

/etc/systemd/system/smartlock.service
[Unit]
Description = smart lock

[Service]
ExecStart=/bin/sh /home/pi/smartlock.sh
ExecStop=/bin/kill -INT ${MAINPID}
Restart=always
Type=simple

[Install]
WantedBy=multi-user.target

こんな感じでサービスを定義して、

# 自動起動有効化
$ sudo systemctl enable smartlock.service
# 起動
$ sudo systemctl start smartlock.service
# 状態確認
$ sudo systemctl status smartlock.service

できた:relaxed:

登録画面

さて、ここまででスマートロックの機能の99%を制作できたはずなのですが、肝心のユーザー登録画面がありませんね。いちいちssh接続してユーザー登録用のpythonを実行するのもかなり面倒なのでPCからクリック1回で登録・削除・確認ができるシステムを作っていきましょう。
PCはWindowsがほとんどだと思うのでPowershellを使っていきます。
事前準備としてRaspberryPiとPCとの間で公開鍵を交換します。linuxにはssh-copy-idという便利コマンドがあるのですが、Windowsには当然ながらありません。しかし同等機能を実現するスクリプトを公開してくださってる方がいたのでありがたく利用させていただきました。

上記の手順に従ってもらえば鍵交換が簡単に終わります。
更に事前準備としてユーザーを登録するpythonコードとシェルスクリプトを用意しましょう

/home/pi/smartlock/add_user.py
# -*- coding: utf-8 -*-
import hashlib
import time
from logging import DEBUG, Formatter, StreamHandler, getLogger

# from reader import Reader
import database
import slack

logger = getLogger(__name__)
handler = StreamHandler()
handler.setFormatter(Formatter("[%(levelname)s] %(message)s"))
logger.setLevel(DEBUG)
logger.addHandler(handler)
logger.propagate = False


def main():
    name = raw_input("追加するユーザー名を入力してください(英数字のみ):")

    # ここでカード読み込み

    database.make_info("ここに番号", name, "ここにIDm")
    print ("追加完了しました。")
    slack.add_user("ここに番号", name)
    time.sleep(1)


if __name__ == "__main__":
    main()
/home/pi/add_user.sh
cd ~/smartlock
# 一旦止めないと読み取りができない
sudo systemctl stop smartlock.service
# 追加用pythonコード実行
python add_user.py
# 再開
sudo systemctl start smartlock.service

やっと事前準備が完了しましたね。
次にPowershellで実行するスクリプトを用意していきましょう

manager.ps1
Write-Output "入退室管理システム管理スクリプト"

$input = Read-Host "何を実行しますか?(1:ユーザー追加 2:名前変更 3:ユーザー削除 その他:終了)"

switch($input){

  "1"{
  Write-Output "しばらくお待ちください。"
  ssh pi@192.168.xxx.xxx -t 'stty erase ^H
  sh add_user.sh'
  }

  "2"{
  Write-Output "しばらくお待ちください。"
  ssh pi@192.168.xxx.xxx -t 'stty erase ^H
  sh rename_user.sh'
  }

  "3"{
  Write-Output "しばらくお待ちください。"
  ssh pi@192.168.xxx.xxx -t 'stty erase ^H
  sh delete_user.sh'
  }

default{
Write-Output "終了します。" 
}

}

Read-Host "終了しました。Enterを押してください。"

xxxのところは環境に応じて変更お願いします。
リネームと削除は同じようにpythonとシェルスクリプトを用意してください。
上記のファイルを好きなところに保存し右クリック→送る→デスクトップにショートカットを作成
の手順でショートカットを作成します。それだけだとpowershellが自動で立ち上がらないので、
デスクトップのショートカットを右クリック→プロパティ→リンク先の前に

powershell -ExecutionPolicy RemoteSigned -File

を追加する。

例) powershell -ExecutionPolicy RemoteSigned -File C:\Users\XXX\manager.ps1

はいこれでダブルクリックで実行できるようになりました:relaxed:

仕上げ

はんだ付けしましょう! 回路はこんな感じで接続しました
smartlock_circuit.png
少し余計な部品がついていますが、こんな感じで接続しました!
ハンダきたねえとかの苦情は受け付けません。ハンダ付け2回目なんで許してください。
ピンヘッダがなくて頑張って線でつないだのですが、皆さんはピンヘッダ使いましょう。 地獄を見たければ真似してください。
IMG_20180604_013144.jpg

ケースづくり

何を思ったのか3Dプリンタを購入してしまったので3Dプリンタで制作していきます。
Fusion360を使用し3回ぐらい試行錯誤したらいい感じのが出来上がりました
2018-12-12.png
穴が一個多いのはサーボモータの電源用に準備していたのですが、不要とわかったので使いません。
あと、蓋のデータが行方不明になっちゃいました:sob:

サーボマウンタ作り

こちらも3Dプリンタで制作していきます。
Fusion360を使ってこんな感じのものを作ってみました
2018-12-12 (1).png
2018-12-12 (2).png
IMG_20181211_150350.jpg
説明が難しいのでこの3枚で察してください:stuck_out_tongue_winking_eye:
扉には強力両面テープで接着してあります。
ちなみに、このマウンタだけで印刷に3時間近くかかりました。

これにて完成!!

smartlock.gif

超ベンリ:smile::smile:

Github

何もかもが初心者の書いたコードでも良いなら、まとめておいておきます。
カード読み込み部分だけ自分が書いたわけではないので省略させていただきました。
https://github.com/konikoni428/smartlock

最後に

自分で制作したものが日常で活躍できてるってたのしー。
部員にも喜んでいただけたようで、大変よい経験となりました。
今後もどんどん制作していこうと思うので、そのときはまたシェアしたいと思います。
読んでいただきありがとうございました。

リクガメのおウチの温度/湿度をモニターしてawsに投げるのを1000円くらいでやる(予定)

すいません。いきなりタイトルに偽りありです。
センサーはこの465円で買ったやつを使います。

Rasbee DHT22 デジタル温度センサーモジュール AM2302 接続線付き Arduino DIY用 1個 [並行輸入品]

あとはESP32。これを使います。

これが500円かっていうとたぶん違うんだけど、ESP32なら500円あれば用意できるよねって事で許して下さい。1000円くらいでやるできる、ですね。。で、どこで買ったかというとたぶんaliexpress系のとこで買ったはず。なんか電池ボックスついてて便利そうだなって買ってたけどそれっきり眠ってました。せっかくなのでこれ使おうかなと。

これっぽいかな。
https://www.banggood.com/ja/WeMos-WiFi-Bluetooth-Battery-ESP32-Development-Tool-p-1164436.html?cur_warehouse=CN

で、何やるかっていうと、この記事の続編みたいなもんです。

コンビニで買った育てるサラダをrasPiで撮ってKinesisに流してゴニョゴニョする 前編

すでにリクガメさんは我が家にいらっしゃってて外からストリーミング動画は観れてるんだけど、温度とか湿度とかハッキリ見えてないし、そもそもモニターもしといたほうがいいだろうと。

そんなわけでまずは値を拾うのと外に飛ばしてみるところまで。
たぶん3部作くらいでこれが前編になる。

環境

  • wemos esp32
  • Rasbee DHT22 デジタル温度センサーモジュール AM2302
  • MicroPython

ESP32はarduino IDEでしか触った事ないんだけど、なんかちっちゃいPythonが使えるらしいので使ってみようと思います。ちっちゃいrubyも使えるらしいですね。

MicroPythonセットアップ

micropython esp32でググって一番上にHitした、こちらを参考にさせていただきましょう。

http://ken5owata.hatenablog.com/entry/2017/10/21/003706

  1. esptoolsとやらを入れる
pip install esptool
  1. http://micropython.org/downloadよりesp32-20181215-v1.9.4-754-g5146e7949.bin をDLします
  2. ESP32にファームウェアを焼きます。comポートはデバイスマネージャーで見るとわかります。自分はcom8だった。
C:\a>esptool --port COM8 flash_id
esptool.py v2.5.1
Serial port COM8
Connecting........_
Detecting chip type... ESP32
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core
MAC: 30:ae:a4:43:a7:58
Uploading stub...
Running stub...
Stub running...
Manufacturer: c8
Device: 4016
Detected flash size: 4MB
Hard resetting via RTS pin...

成功したっぽいです。

C:\a>esptool --port COM8 erase_flash
esptool.py v2.5.1
Serial port COM8
Connecting........_
Detecting chip type... ESP32
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core
MAC: 30:ae:a4:43:a7:58
Uploading stub...
Running stub...
Stub running...
Erasing flash (this may take a while)...
Chip erase completed successfully in 3.9s
Hard resetting via RTS pin...

C:\a>esptool --chip esp32 --port COM8 write_flash -z 0x1000 C:\a\esp32-20181215-v1.9.4-754-g5146e7949.bin
esptool.py v2.5.1
Serial port COM8
Connecting........_____....._
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core
MAC: 30:ae:a4:43:a7:58
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Auto-detected Flash size: 4MB
Compressed 1084016 bytes to 685385...
Wrote 1084016 bytes (685385 compressed) at 0x00001000 in 61.4 seconds (effective 141.3 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

こちらもうまくいきました。

  1. teratermでつないでhelloworld
MicroPython v1.9.4-754-g5146e7949 on 2018-12-15; ESP32 module with ESP32
Type "help()" for more information.
>>> print ("hello Esp32")
hello Esp32
>>>

おー!pythonだ!
ちなみにボーレートを115200にしたらうまく表示されました。

なお起動時にOSError: [Errno 2] ENOENT ってエラーが出てたりしますが、main.pyを作ってやると消えるらしいです。main.pyがあると最初に実行するようになってるんですね。

f = open("main.py", "w")
f.write("print(\"hello Esp32\")\n")
f.close()

起動時のメッセージがこんな感じになりました。

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0018,len:4
load:0x3fff001c,len:4732
load:0x40078000,len:7496
load:0x40080400,len:5512
entry 0x4008114c
I (399) cpu_start: Pro cpu up.
I (400) cpu_start: Single core mode
I (400) heap_init: Initializing. RAM available for dynamic allocation:
I (403) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (409) heap_init: At 3FFC0E00 len 0001F200 (124 KiB): DRAM
I (416) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
I (422) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (428) heap_init: At 400915E0 len 0000EA20 (58 KiB): IRAM
I (435) cpu_start: Pro cpu start user code
I (5) cpu_start: Starting scheduler on PRO CPU.
hello Esp32
MicroPython v1.9.4-754-g5146e7949 on 2018-12-15; ESP32 module with ESP32
Type "help()" for more information.
>>>

なお、upyshとかいうちっちゃいシェルがあるみたい。

>>> from upysh import *

upysh is intended to be imported using:
from upysh import *

To see this help text again, type "man".

upysh commands:
pwd, cd("new_dir"), ls, ls(...), head(...), cat(...)
newfile(...), mv("old", "new"), rm(...), mkdir(...), rmdir(...),
clear

>>> ls
     139 boot.py
      21 main.py

>>> cat ("boot.py")
# This file is executed on every boot (including wake-boot from deepsleep)
#import esp
#esp.osdebug(None)
#import webrepl
#webrepl.start()

viとかあるとすごい便利なんだけど。。

そんなわけでuPyCraft (IDE)

なんかそういうステキなやつがあるらしいです。

Wemos Lolin ESP32 OLED で、MicroPython から OLED 出力をする

サイトに沿ってやったらサックリ入りました。ちなみにV1.1です。

22.png

センサーを読んでみる

main.py
import dht
import machine
import time

print("Starting DHT22.")
d = dht.DHT22(machine.Pin(4))

while True:
  print("Measuring.")

  ng = 0
  try:
    d.measure()
  except:
    ng = ng + 1

  print("Temperature: %3.1f °C" % d.temperature())
  print("   Humidity: %3.1f %%" % d.humidity())

  time.sleep(5)

温度と湿度取れたー

>>> exec(open('./main.py').read(),globals())
Starting DHT22.
Measuring.
Temperature: 23.2 C
   Humidity: 36.6 %
Measuring.
Temperature: 23.2 C
   Humidity: 36.5 %

Ambient

さて、なんかAmbientというのが便利らしいです。IoT向けのクラウドサービスなのですが、ここにデータを送ると記録されてグラフで確認できたりもするみたい。
ひとまず、ここにデータ送るプログラム書いてみようかな。

  1. まず登録 -> https://ambidata.io/usr/signup.html
  2. ライブラリをGET -> https://github.com/AmbientDataInc/ambient-Python-lib 使うのはambient.py
  3. esp32にambient.pyをコピー
  4. コードをこんな感じにしました
main.py
import dht
import machine
import time
import ambient

ssid = '000000000'
password = 'XXXXX'
channel_id = '0000'
write_key = '0000000000000000'

def do_connect(ssid, password, timeout=10):
  import network
  sta_if = network.WLAN(network.STA_IF)
  if not sta_if.isconnected():
    print ('connecting ...')
    sta_if.active(True)
    sta_if.connect(ssid, password)
    while not sta_if.isconnected() and timeout > 0:
      print('.')
      time.sleep(3)
      timeout -= 1
  print(sta_if.ifconfig())


print("Starting DHT22.")
d = dht.DHT22(machine.Pin(4))
am = ambient.Ambient(channel_id, write_key)

do_connect(ssid, password)

while True:
  print("Measuring.")

  ng = 0
  try:
    d.measure()
  except:
    ng = ng + 1

  print("Temperature: %3.1f °C" % d.temperature())
  print("   Humidity: %3.1f %%" % d.humidity())

  res = am.send({
    "d1": d.temperature(),
    "d2": d.humidity()
  })

  print(res.status_code)
  res.close()
  time.sleep(5)

すると・・

Starting DHT22.
state: 3 -> 5 (10)
add 0
aid 6

connected with 00000000, channel 9
dhcp client start...
cnt 


connecting ...
.


scandone


ip:192.168.xxx.xxx,mask:255.255.255.0,gw:192.168.xxx.xxx


('192.168.xxx.xxx', '255.255.255.0', '192.168.xxx.xxx', '192.168.xxx.xxx')
Measuring.


Temperature: 23.3 °C
   Humidity: 37.3 %


200

そしてambientの画面では・・

23.png

サンプル少なくてグラフは出てないですけどね。。
10分平均値にしたから10分経ったらでるかな。
データのcsvダウンロードしたらちゃんと記録されてたのでOK.

data8500.csv
created,d1,d2,d3,d4,d5,d6,d7,d8
2018-12-20T13:27:06.241Z,23.3,37.3,,,,,,
2018-12-20T13:27:11.641Z,24.3,34.8,,,,,,
2018-12-20T13:27:17.065Z,24.3,34.7,,,,,,
2018-12-20T13:27:22.490Z,24.3,34.7,,,,,,
2018-12-20T13:27:27.916Z,24.3,35,,,,,,
2018-12-20T13:27:33.314Z,24.4,35.4,,,,,,
2018-12-20T13:27:38.714Z,24.4,35.3,,,,,,
2018-12-20T13:27:44.139Z,24.4,35.5,,,,,,
2018-12-20T13:27:49.538Z,24.5,35.5,,,,,,
2018-12-20T13:27:54.964Z,24.5,35.2,,,,,,
2018-12-20T13:28:00.388Z,24.6,35.2,,,,,,
2018-12-20T13:28:05.813Z,24.6,35,,,,,,

お次はこれをaws iotに飛ばしてみたい。
最後にiotからのjsでviewかなぁ。

自分家の不満を解消するためにつくったIoTデバイスで GUGEN2018に出た話

はじめに

現職の枠組みの外で、趣味で モノづくりをする有志活動に参加していて、
今年の最大成果としてGUGEN2018へ出品した成果報告の内容が、
AdventCalendarにとても合いそうだと、後輩からそそのかされたので投稿してみます。

GUGENとは

GUGEN公式HP より、

GUGENは日本最大級のオリジナルハードウェアコンテストです
2013年からはじまりました「GUGENコンテスト」も今年で6年目、前身となる「電子工作コンテスト(2009~2012年開催)」から数えて10年目となり、実用性や商品性の高いアイデアを表彰するMakersの登竜門となっております。

というコンテストです。

作ったモノ

チームとして、3作品エントリーし一次審査通過は2作品。
その中でも、ソフトエンジニアな自分がもっとも貢献できたConecO-massを紹介します。

ConecO-massコンセプト

非IoTデバイスにくっつける重さ計測デバイス!
家庭にある消耗品にくっつけられる重さセンサー。
重さを計測して、管理でき、残量が無くなりかけたらLINE通知する
IoTデバイスを日曜ハックしてみました!
ConecO-massコンセプト.png

ハードウェア構成

ハードウェアはシンプルに。
もしかしたらコンテスト優勝して量産するかも?をモチベーションに、
初めから全力で小型化・簡素化に取り組みました。
電気的な部分は、ボタン電池、BLEマイコン、感圧センサーのみ!
そして、ポイントの何にでもくっつく部分をかなり模索・考えました。
靴下半分に切り出して磁石と鉄板でくっつける!
HW構成.png

あと本番はピタッとマグという商品を使ってかなり収まりのイイ感じになりました。
gugen本番.png
ハードウェアカバーの丸い筒は、ビニール配管を切断・塗装して作ったり。
ほんとうに身近なモノをフル活用して製作にあたりました。

ソフトウェア構成

自分が一番貢献できた、ソフト実装部。
Viewの実装は、bluejellyというOSSを利用し、JavaScriptのみで、Bluetooth通信と表示を実装しました。
↓こんな感じ。(ソース全体は非公開プロジェクトです、すみません!)
ConecO-serverCap.png

LINE通知はPythonで行ったのですが、データのやりとりには、
以前作ったStack-String-Serviceを利用。
※Qiitaにもまとめています。
「マイクロサービスアーキテクチャを自分なりに捉えて、Stackというマイクロサービスを作ってみた。」

JavaScriptで取得した値はたった4行で外のサーバーへ通知できます。

        var xhr = new XMLHttpRequest();
        var url = "https://stack-string.herokuapp.com/PushStack?string="
          + encodeURIComponent(value + "," + notificationValue + "," + objectNameValue);
        xhr.open("POST", url, true);
        xhr.send("");

https://stack-string.herokuapp.com にて、
実際にサービスをデプロイしているので、本番も上記のコードで動かしてました。

そして、クライアント側はPythonにて、

import requests
r = requests.get("https://stack-string.herokuapp.com/PopStack")

こんだけ。ね、簡単でしょ?
あとは、GETしてきたrでとれた値に応じて、LINE通知をするのを実装して、

coneco-lineNotify.png

これで日用使いにことたりる、IoTハックができ、
コンテストにも出せるクオリティとなりました!

作成秘話

自分は、よく牛乳を飲むのですが。
牛乳って冷蔵庫に入っているとき、どれくらい入ってるかわからないじゃないですか。
出先で買い物してる間に家族が飲んだりしてたら買ってくればよかったと後悔したり。
なので、冷蔵庫から出して使ったときに計測してなくなりかけたらLINE(外に)通知する。
構成にこだわって作り始めました。

消耗品だからこそ、IoT化も進まないだろうし、
Conecoという名前は、Conect Object(なんにでも接続する)という意味をこめてます。

また、名前にもしたからには、

上のアタッチメントは何にでもくっつく。
下のセンサーはアタッチメントより上の形に依存しない。

と、ハードもソフトもモジュール的に疎結合になるよう徹底しました。
副産物的に詰め替えや洗い物対応できたという感じ。

おわりに。この記事を通じて

私は、日ごろ某メーカーにて、組み込みソフトの開発を担当しています。
このGUGENには、会社のメンバーと、有志団体(趣味)として出展しました。
決して私一人の手柄ではありません。

日ごろ、仕事としてメカ、電気、ソフトを専門とする人があつまり、
こぞって同じ目的に向かうとサクッと綺麗にモノができあがる。
ということが分かった、とても良い体験であり、
日々の業務は、こういった非日常のアウトプットの質を向上させると確認できました。
みなさんも、とっさのアウトプットが上質であるよう日々の業務に取り組みましょう!

Raspberry PiとImgurを使って無料で自宅監視カメラLINEボットを構築する

External article

IIJ Machinistで室温をモニタリングする。

Machinist

IIJから、Machinistというサービスが発表されました。数値データ(メトリクス)を記録し、グラフ化、監視、比較できるサービスです。
現在ベータ版で、10メトリクス、保存期間は直近の1ヶ月までなら無料で利用できるので、Raspberry Piに繋いだ温度センサーで取得した室温を記録してみることにします。

データ保存

基本的にJSON形式のデータをhttps/putするだけ。チュートリアルに従ってjsonを書きます。

{
  "agent": "BME280",
  "metrics": [
    {
      "namespace": "bme280",
      "name": "temperature",
      "tags": {
        "location": "living"
      },
      "data_point": {
        "value": 22.37
      }
    }
  ]
}

温度計測

Raspberry pi3B にBME280基盤を、ブレッドボードなしでジャンパ線4本で接続。
BME280のRubyによるデータの読み出しは、ruby-i2c-devicesを使用。

qiita.rb
#!/usr/bin/env ruby

$LOAD_PATH << "/home/pi/BME280/ruby/i2c/lib"
require "/home/pi/BME280/ruby/i2c/lib/i2c"
require "/home/pi/BME280/ruby/i2c/lib/i2c/driver/i2c-dev"
require "/home/pi/BME280/ruby/i2c/lib/i2c/device/bme280"

@device = I2CDevice::Bme280.new(driver: I2CDevice::Driver::I2CDev.new("/dev/i2c-1"))
#p @device.read_id
#p @device
@device.write_config(I2CDevice::Bme280::T_STANDBY_0_5MS, I2CDevice::Bme280::FILTER_16)
@device.write_ctrl_hum(I2CDevice::Bme280::OVERSAMPLE_1)
@device.write_ctrl_meas(I2CDevice::Bme280::OVERSAMPLE_16, I2CDevice::Bme280::OVERSAMPLE_2, I2CDevice::Bme280::MODE_NORMAL)
#p @device.calc_sensor_data
temp = @device.calc_sensor_data[:temp]

ひとまずこれで室温を取得。

JSON組み立て

データをハッシュで整理してJSONに変換するのがとっつきやすそう。

require 'json'
metrics = [ {"namespace" => "bme280","name"=>"temperature","tags"=> {"location"=> "living"},"data_point"=>{"value"=> temp}}]
data = {"agent" => "BME280","metrics"=> metrics}

input = JSON.pretty_generate( data )

これで先のJSONが組み上がります。

https put

できたJSONをput

require 'net/http'
require 'openssl'
require 'uri'

url = URI.parse("https://gw.machinist.iij.jp/endpoint")
http = Net::HTTP.new(url.host,url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER

api_key = "your_api_key"
req = Net::HTTP::Post.new(url.request_uri)
req["Content-Type"] = "application/json"
req['Authorization'] = 'Bearer ' + access_token
#BearerのBが大文字でないと認証通りません。
req.body = input
res = http.request(req)

puts res.code, res.msg,res.body

これでデータをPUTできました。
cronに登録して2分おきで回しておきます。

データ

Machinist.png
室温データがこんな感じで保存、確認できます。(21時まではデータに欠落があります)
22:30に暖房をオフにしたのがよくわかりますねw。

監視

監視設定で、保存した値が設定値を上回る、下回るなどした時にメールで通知することができます。
メール以外は今の所実装されていないので、メールをトリガーにして他のサービスや製品と連動を実装すれば色々使い道がありそうです。

まとめ

IIJの新サービス Machinistで室温の記録、監視を設定してみました。

Raspberry Pi 向けのイメージを Packer で作成する

Raspberry Pi 向けのイメージを Packer で作成する

概要

Raspberry Pi でミドルウェアの設定などをいじって動かしていると試行錯誤の最中はともかく、構成が固まった後に何をしたかわからなくなりがちです。

そこで、 Packer を使って最初から設定されているイメージを作成し、そのイメージで Raspberry Pi を動かすことを試みます。

本編

Packer とは

https://www.packer.io/

Builder と Provisioner を組み合わせてマシンのイメージを作成するツールです。詳しくは説明しないので気になる方は各自で調べましょう。

  • Builder: ターゲットとするプラットフォームに向けたイメージを生成する。例えば EC2 や VirtualBox など。
  • Provisioner: マシンの設定をする。例えば Shell や Ansible など。

ARM 向けの Packer

Packer 公式で様々な Builder を提供していますが、その中の一つに QEMU を使ったものがあります。こちらを使っても Raspberry Pi が採用している ARM 向けのイメージは作成可能で実際にリポジトリ1も存在していますが、環境依存のものがあったり、設定が少し難しかったりするのでここでは次のリポジトリの方法をとります。

https://github.com/solo-io/packer-builder-arm-image

このリポジトリでは Packer の Builder として arm-image を作っています。 Raspberry Pi に特化しているので Packer の設定そのものはシンプルになります。

ただし、 Linux 上でしか動かないいくつかのコマンドに依存しているのでここでは Vagrant 2経由で使用することにします。

実際に動かしてみる

packer-builder-arm-image のリポジトリをクローンして、試しに Vagrant を起動するとそれだけでデフォルトの設定でイメージが生成されます。
依存しているイメージの取得が遅かったりするので心にゆとりを持って作業しましょう。

$ git clone https://github.com/solo-io/packer-builder-arm-image
$ cd packer-builder-arm-image
$ vagrant up
$ ls output-arm-image.img

初回の立ち上げでは VM のプロビジョニング作業にしばらく時間がかかると思いますが、 2 回目以降は各種のキャッシュが効いてくるのでそこまで時間はかかりません。

カスタマイズ

ベースイメージの変更

さて、実際にイメージが作成できるのは確認できたと思いますが、ベースのイメージが古くなっているので新しいものをベースにして試してみましょう。

https://downloads.raspberrypi.org/raspbian_lite/images/

ここでは 2018-11-15 のイメージを選びました。設定する際にイメージのチェックサムも必要なのでついでに確認しておきましょう。

$ curl https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2018-11-15/2018-11-13-raspbian-stretch-lite.zip.sha256
47ef1b2501d0e5002675a50b6868074e693f78829822eef64f3878487953234d  2018-11-13-raspbian-stretch-lite.zip

下記のファイルをリポジトリ直下に作成します。

my-image.json
{
  "variables": {
  },
  "builders": [{
    "type": "arm-image",
    "iso_url" : "https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2018-11-15/2018-11-13-raspbian-stretch-lite.zip",
    "iso_checksum_type":"sha256",
    "iso_checksum":"47ef1b2501d0e5002675a50b6868074e693f78829822eef64f3878487953234d",
    "last_partition_extra_size" : 1073741824
  }],  
  "provisioners": [
    {
      "type": "shell",
      "inline": ["apt-get update && apt-get install -y golang"]
    }
  ]
}

基本的には example.json をコピーして編集しただけです。このファイルをもとにイメージの生成をしてみます。

$ PACKERFILE=my-image.json vagrant provision --provision-with build-image

これでベースイメージを変更することができました。

そのほかのカスタマイズ

my-image.json を作った際に provisioners という項目があったと思います。 Packer ではこの部分を好きに変えることで最終的に生成されるイメージをカスタマイズすることができます。

自分の用途に合わせて必要なミドルウェアやその設定を入れたイメージを作成してみましょう。 packer-builder-arm-image の README にもいくつか参考になる設定が記載されています。

エミュレート

作成したイメージの動作を確認したかったらどうすればいいでしょうか。環境にもよりますがもし QEMU が導入できる環境であれば Raspberry Pi のエミュレーションをすることが可能です。
イメージにあったカーネルを取得したりする必要はあるもののそのまま確認できるのはなかなか便利です。

$ wget https://github.com/dhruvvyas90/qemu-rpi-kernel/raw/master/kernel-qemu-4.14.50-stretch
$ wget https://github.com/dhruvvyas90/qemu-rpi-kernel/raw/master/versatile-pb.dtb
$ qemu-system-arm \
  -kernel kernel-qemu-4.14.50-stretch \
  -hda ./output-arm-image.img \
  -dtb ./versatile-pb.dtb \
  -cpu arm1176 -m 256 \
  -M versatilepb -no-reboot -serial stdio \
  -append "root=/dev/sda2 panic=1 rootfstype=ext4 rw" \
  -net nic -net user,hostfwd=tcp::5022-:22 

/boot に関してはおそらく dtd がマウントされてしまい確認することはできません。

まとめ

イメージを物理的に MicroSD カードなんかに焼く作業はどうしても発生するのでその度にクラウドって便利だなということを実感する日々です。


  1. https://github.com/Demonsthere/raspberry-packer_scripts 

  2. https://www.vagrantup.com/ Packer と同じく HashiCorp 製の仮想環境を管理するツール。 

Google Homeで給湯ポットの気持ちを代弁してみる

はじめに

社内のコミュニケーションスペースには給湯ポットが置いてあります。コーヒーを飲もうと思い、ドリップバッグをカップにかけ、給湯ボタンを押すとすぐにゴボッ、ゴボゴボッ...(ほぼ空だった)。しょんぼりしながら給水する悲壮感。あぁ、これはなんとかしたい。

水を補充するモチベーションって何でしょう...。感謝かな。やっぱり、感謝されたら嬉しいよね。ついでに「お湯を入れに来たら湯沸かし中だった」問題も回避できるようにしよう。

やりたいこと

  • 水を入れて湯沸かしがかかったら「給水していただき、ありがとうございます」などと発話する。
  • 発話内容は複数候補からランダムに選択する。
  • 湯沸かしに合わせて「湯沸かしを開始/終了しました」とSlackへ通知する。

どうなったか

発話はこんな感じになりました。(途中で、椅子に足を引っかけた音が...)

movie.png
※動画が始まらない場合はこちらから... :confounded:

設置してまだ数日しか経っていませんが、心なしか、満水近くの給湯ポットに出会うことが多くなった気がしています。(親バカフィルター越し)

ただ、すぐに飽きられてしまうのは目に見えているので、少しずつでも改良していかないと...。1

どうやったか

給湯ポットのあるコミュニケーションスペースに、なんとMESH一式が転がっていたので、GPIOタグを使ってみることにしました。

処理フロー

給湯ポットに交流電流センサを取り付け、その出力電圧をGPIOタグで監視しました。MESHレシピを挟み、発話はIFTTT→Firebase→Ubuntu→Google Home、Slack通知はMESH SDK→Slackとしました。

flow.png

途中のMESHレシピは以下のようになりました。

recipe.png

給湯ポットの湯沸かし監視

平均値整流型電流変換回路を組み、クランプ式交流電流センサ(CTL-10-CLS)の出力を変換してGPIOタグへ繋げました。GPIOタグの入力上限である3Vに調整できるよう、半固定抵抗を挟みました。

circuit.png

MESHレシピでは「しきい値2Vを上回る」「0.5Vを下回る」をトリガーとしました。

gpio.png

変換回路の作成にあたっては、多機能電力計の製作mbedによる自宅の電力消費量測定を参考にさせていただきました。何から手を付けたらよいか分からず、暗くなると自動点滅するLEDキャンドルを購入してアワアワするレベルの電子回路:beginner:でも達成できました。ありがとうございます。

Google Homeでの発話

発話内容は、FirebaseのDatabaseを経由して、Google Homeで読み上げさせました。処理フローのIFTTT→Firebase→Ubuntu→Google Homeのうち、Firebase~Google Homeは以前の記事紹介した処理とほぼ同じ(差異はOSだけ)ですので、ここではIFTTT→Firebaseのみを記載します。

IFTTT

読み上げ要求がかかったことを、IFTTTを利用して、Firebaseへ通知します。

this

thisとしてMESHの「Event from MESH app received」を指定します。

mesh.png

that

thatではWebhooksを利用します。

FirebaseのDatabaseは以下のような構成にしています。wordの値として「announce 発話内容」と書き込むと、その更新イベントが下位フローでフックされ、Google Homeが読み上げます。

プロジェクト名-xxxxx
 └ googlehome
   └ word: ""

そのためWebhooksは以下のように指定しました。

Webhooks.png

MESHレシピでは、GPIOタグからの出力を「ランダムに切替える」スイッチを経由して複数のIFTTTへ繋げ、それぞれで読み上げさせたい内容を指定しました。例えばこんな感じ。

ifttt.png

イベントIDは前項の「Event from MESH app received」で指定したものとそろえます。

湯沸かし状況のSlack通知

MESH SDKを利用して「給湯ポット」タグを作成し、Slackへの通知を行いました。

MESH SDK

tag.png

開始/終了の入力コネクタを用意し、受け取ったコネクタに応じたメッセージをSlackへ通知します。通知するメッセージは、MESHレシピの設定から変更できるよう、プロパティとしました。

function.png

ファンクションの各メソッドは以下のように実装しました。Slackへの通知ではSlack APIを利用しました。

Initialize
return {
  runtimeValues : { messageText : '' }
};
Receive
var messageText = '';
switch ( index ) {
  case 0:
    messageText = properties.startMessage;
    break;
  case 1:
    messageText = properties.endMessage;
    break;
}
runtimeValues.messageText = messageText;

return {
  runtimeValues : runtimeValues,
  resultType : 'continue'
};
Execute
var url = 'https://slack.com/api/chat.postMessage';
var data = {
  token : 'xxxx-xxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx', //HubotのAPI Token
  channel : '#thermo-pot', //チャンネル名
  text : runtimeValues.messageText,
  as_user : true
};

ajax( {
  url : url,
  data : data,
  type : 'post',
  timeout : 5000,
  success : function ( contents ) {
    log( 'Thermo-Pot: Success' );
    callbackSuccess( {
      resultType : 'continue'
    } );
  },
  error : function ( request, errorMessage ) {
    log( 'Thermo-Pot: Network error' );
    callbackSuccess( {
      resultType : 'continue'
    } );
  }
} );

return {
  resultType : 'pause'
};

Resultメソッドは利用しませんでした。

おわりに

Slackを確認すると...、

slack.png

うぁぁぁー、めっちゃ感謝されてたー。どうやら湯沸かしがかかった後は、数分ごとに「停止」→「すぐに再開」が繰り返されるようで...。まずはここから改良しないとー。

(追記)
圧倒的感謝っ...!問題は、電圧が下がってから、一定時間のうちに電圧が上がった場合を「何もなかった」(湯沸かしの終了/開始は発生していない)と判断することで回避できました。

あとがき

作っていて思ったのですが、電流センサの出力を監視すると「利用された」ことが分かる、つまり「そこに人がいる」ことが把握できます。これって、人とつながりを持てるきっかけになりそうな気がしています。回路を組む必要のない製品(変換器一体型の交流電流センサ(CTT-CLS-CV))もあるようですので、いろいろなところで活用できたらなと思っています。


  1. 「今日は気分が良いので記念撮影をしましょう。それではいきますよ~。3・2・1(カシャ)」イベントを不定期に発動させて、給水してくれた方の写真を撮り、画像認識して「xxさんが給水してくれました」とSlackへ投稿するとか。Pepperくん、入社してください。 

研究室にUX最高な無人販売機を設置する話

External article

オーディオプレイヤーを再デザインした話。 またはIoT時代のOOUXについて。

External article

Ai Lockerを作ったお話

この記事はゆめみとQiitaのコラボ企画である「オフィスや自宅を快適にするIoT byゆめみ③ Advent Calendar 2018」 24日目のエントリです。

はじめに :golf:

はじめまして。:boy_tone1: 私は大阪で学生をしている者です。

最近、学部内でコンテストが行われていたので、そこに出展する作品を作りました。

この記事では、IoT,AIの知識0の状態から、1週間で友達と一緒に作り上げたプロダクト"Ai Locker"の完成までの道のりについて書きます。

【コンテストの概要】
テーマ: 未来のデスクトップ
詳細: 机の周りにあったらいいなと思うアイテムをMonoLab(大学内の工房)でつくる

Ai Lockerとは:question:

皆さんはをいくつもっていますか?

家の鍵、会社の鍵、倉庫の鍵、引き出しの鍵、ロッカーの鍵 :key: :key: :key:

少なくとも3個以上を毎日持ち歩いている人が多いのではないでしょうか?

そしていざ使う時、似た鍵がありすぎで、ドアの前でアタフタ:confounded:、、、なんてことも。。。

そこで僕たちが開発したのはリアルタイムピクチャパスワードを実装したAi Locker:robot:です。

お手持ちのスマホをかざすだけでロックが解除できます。

このロッカーを使えば、鍵の心配とはおさらば。:wave:

きっと、オフィスや自宅が快適になります。:thumbsup:

ペーパーレス、キャッシュレスの次はキーレスの時代が来ることでしょう!!!

(大風呂敷を広げました。ごめんなさい:bow_tone1:)
※ここからプロダクトの制作過程の話になります

Day1. アイデアソン :bulb:

まず、何を作るかすら、決まってなかったので、Yahoo! Japan OsakaさんのMix Leap Open Dayで、友達とアイデアソンをしました。

電源、Wi-Fi、ドリンク、お菓子だけでなく、アイデア出しの方法やUI/UXの話などをしてくださいました。本当にありがとうございます。:laughing:

またその時の話は別の記事に書いていますので、ご覧いただけると幸いです。 :smile:

アイデアソンでは
ブレインストーミングをして、

構想を練って、

アーキテクチャを考えた結果、

指パッチンをしたら、おかしが出てくる引き出しをつくることにしました!!

Day2. Lピカ & Lチカ(Arduino) :tools:

次の日、MonoLabにある電子工作キットで、Lピカ、Lチカをしました。

中学校の理科の授業のとき、乾電池と豆球をつないで光らせたことぐらいしか、電子工作をしたことがなく、LittleBitsやArduino、ブレッドボードなんて触ったこともなかったのですが、ググりながら試行錯誤し、指パッチン(マイクセンサー)をトリガーにライトを光らせるところまで、出来ました。
ただ、指パッチン以外(手を叩いても)でも反応してしまうことに気づきました。

そこで、指パッチンを判別するAIの分類器、finger-snapを探し出し、クローンしてみました。
:arrow_down:

成功。
マイク部分は電子工作ではなく、USBマイクを使うことにしました。

Day3. 計画 :arrow_upper_right:

アイデアソンはしたものの、具体的な計画にまで落とし込めてなかったので、必要な部品、技術を洗い出し、締め切りの20日までになんとか落とし込みました。:sweat_drops:

改めてみてみると、無茶苦茶な計画:scream:
バイトや授業がある中で、ハード過ぎるし、レーザーカッター使ったこともないし、DCモーターの制御なんかしたことないのによく、こんな短期間で出来ると思ったな、自分。
何より、一緒にやってくれてる友達にはマジで感謝:sob:

Day4. 買い出し :moneybag:

とりあえずモーターとベルコンが必要だと分かったし、買い出しに行きました。

いいサイズ感のものがあれば、引き出しは買おうと思ってたけど、無かったので結局作ることに。

Day5. レーザープリンタ & LINE bot

とりあえず、レーザーカッターを使ってみることに。illustratorのデータが必要だということが分かりました。illustratorは何回か使ったことがある程度でしたが、なんとかなりました。💦

LINE Beaconをラズパイにセットアップすることにも成功!このままトントン拍子で行くと思っていました。この時は、、、

Day6. 挫折 :sob: & 方向転換 :leftwards_arrow_with_hook: & TensorFlow Lite :tools:

サーボモーターの制御は上手くいったんですが、DCモーターはうまく制御ができず、よく調べてみると、モータードライバーが必要だと気づきました、あと残り1日、やってもたー。:sob: 内心、これは終わったなと思いました。

でも、幸運にもチーム開発だったので、僕がテンパってても、友達がちゃんと冷静に判断してフォローしてくれました。「作るもの変えよう!!」と。

これがきっかけとなって、もう一度アイデアを練り直しました。
そして、「引き出しのアイデアはそのまま使って、DCモータをでロックの部分を実装。LINEで解除できるIoT Locker」を作ることに。

完成の目処が立ったのですが、なんか、未来感薄いなと思い、AIというか、機械学習を使ってみることにしました。AIの知識はほとんど無かったんですが、必死にググって、TensorFlowLiteのデモアプリを見つけ、

Objective-C++の知識は0でしたが、ググりまくって、if文と文字列操作、アラートの出し方HTTPのGETリクエストをする方法を習得し、AI技術を使ったリアルタイムピクチャパスワードを実装しました。

Ai Lockerの最終的なアーキテクチャ図です

実物はこんな感じ

需要はないと思いますが、一応、GitHubにも公開してるのでよかったら見てください。
iOS APP
Raspberry pi

Day7. 動画撮影 :video_camera: & 資料作成 :bookmark:

提出資料と動画は前日に作っておくつもりだったんですが、疲れすぎて、寝落ちしてしまい、早朝5時ぐらいから家で撮影、編集しました。(笑)
撮影風景

提出資料

提出した動画はこんな感じになりました。

Vimoというアプリで動画編集を行いました。
Vimo iOS
Vimo Android

12. まとめと感想

Mix Leapのスタッフの方やMonoLabのスタッフの方はもちろんですが、チームのメンバーである友達に常にめちゃくちゃ助けてもらってもらってるな。ありがたいなー。と感じる一週間でした。:bow_tone1:

次回、何かチャンスが有れば、もっと余裕をもって計画して、面白いものをまたチームで作れたらなと思います。:muscle:

結果発表が楽しみです。:relaxed:

Browsing Latest Articles All 14 Live