Raspberry pi zero wとhomebridgeとHomeKitの連携その1

だいぶ以前にnodeMcuで4chリレーモジュールのうち、3chを動かし、かつ、物理スイッチからも動かせるようにしました。

gentoolinux.hatenablog.com

しかし、1ヶ月後、WiFiアクセスポイントを増やしたことからなにやら不安定に。

再起動を繰り返す始末。

別なモジュールを買っても同じ。

ESP8266は電源周りがだいぶシビアだそうでして、その影響なのか、exception(29)を吐いてstuckします。

3.3Vのピンに470μFの電解コンデンサをつけたけど、解消されず。

 

そこで、Raspberry Pi zero wに入れ替えた顛末を。

f:id:naoyukinagano:20171121223443j:plain

 

Raspberry piにRaspbian Strechを入れてSSHするところまではいろいろなところに乗っているので割愛。

rootでRaspbian Strechが動かせることを前提に話を進めます。

 

まずは、RaspbianにWebiopiをインストールします。

参考にしたのはこのページ

nw-electric.way-nifty.com

あ、ただ、Python3の有効化をし忘れました(^^ゞ

root@raspberrypi: ~/# mkdir webiopi

root@raspberrypi: ~/# cd webiopi

root@raspberrypi: ~/webiopi# wget https://sourceforge.net/projects/webiopi/files/WebIOPi-0.7.1.tar.gz

root@raspberrypi: ~/webiopi# tar xvzf WebIOPi-0.7.1.tar.gz

root@raspberrypi: ~/webiopi# cd WebIOPi-0.7.1

root@raspberrypi: ~/webiopi/WebIOPi-0.7.1# wget https://raw.githubusercontent.com/doublebind/raspi/master/webiopi-pi2bplus.patch

root@raspberrypi: ~/webiopi/WebIOPi-0.7.1#./setup.sh

んで、Basic認証をハズしました。

root@raspberrypi: ~/# nano /etc/webiopi/config

 /etc/webiopi/configファイルの以下の2箇所をコメントアウト

#passwd-file = /etc/webiopi/passwd

#prompt = "WebIOPi"

で、サービス有効化とスタートです。

root@raspberrypi: ~/# update-rc.d webiopi defaults

root@raspberrypi: ~/#/etc/init.d/webiopi start

 

続いて、PythonでGPIOがGNDに落ちるとトグルスイッチになる(消えてたら付く、付いていたら消える)ようなスクリプトを書きます。

本当ならadd_event_detectを使いたいところなのですが、電位差の検出がシビアすぎて、隣のスイッチまで検出されてコールバックが実行されてしまう始末。

ここはCPUリソースを喰ってでも安定した動作を目指しました。

追記:add_event_detectでは確かに微妙なエッジも検出してしまいますが、検出後のコールバックに"本当に該当スイッチのGPIOが0なのか? を判定するif文を入れれば誤動作を抑止できることがわかりました。詳しくは「その2」へ

 

webiopiからGPIOのピン状況を取得したり、書き込んだりするために、Pythonのrequestsライブラリを利用します。

 root@raspberrypi: ~/# apt-get install python-requests

そして、Rpi.GPIOライブラリ(プリインストール)を活用してスイッチのGPIOがGNDに落ちるのを読み取って、リレーのGPIOの状況を取得し、その状況に応じてGPIOのValueを変化させます。

何度も誤動作しないよう、1度動作したら1秒スリープさせます。

スイッチの読み取りも0.1秒ごとにしました。

プッシュスイッチがGPIO16、リレーがGPIO19で、サインスマートのリレーなので0でON、1でOFFです。

 

追記:これはとても迅速に検出可能ですが、CPUリソースを喰いますのでおすすめしません。

/root/switch/switch1.py

#! /usr/bin/python

import RPi.GPIO as GPIO
import time
import requests
switch = 16
relay = 19
url_front = 'http://localhost:8000/GPIO/'
url_back = '/value'
url_function_out = '/function/out'
url = url_front + str(relay) + url_back
url_function = url_front + str(relay) + url_function_out
url0 = url + '/0'
url1 = url + '/1'

GPIO.setmode(GPIO.BCM)
GPIO.setup(switch, GPIO.IN, pull_up_down=GPIO.PUD_UP)
gpio_function = requests.post(url_function)

try:
   while True:
   if GPIO.input(switch)==0:
     status = requests.get(url)
     if status.text=='1':
       requests.post(url0)
       time.sleep(1)
     else:
       requests.post(url1)
       time.sleep(1)
   time.sleep(0.1)

except KeyboardInterrupt:
   print '\nswitch.py Stopped'
   GPIO.cleanup()

これらのswitch変数とrelay変数の違いで3ch分をswitch1.py,switch2.py,switch3.pyとして作成しました。

起動時に実行するように、/etc/rc.localに記述します。

# switch read

/root/switch/switch1.py &
/root/switch/switch2.py &
/root/switch/switch3.py &

exit 0

これでスイッチとリレーが連動するようになり、リビングのライトが通常通り使えるようになりました。

次は、homebrigeのconfigを書き換えます。

 

Node.jsのバージョンコンフリクト

Node.jsのバージョンアップなどを行ったあっと、homebridgeを起動すると、次のようなエラーが出て起動できなくなった。

 

 /usr/lib64/node_modules/homebridge/node_modules/mdns/lib/dns_sd.js:35
throw ex;
^

Error: The module '/usr/lib64/node_modules/homebridge/node_modules/mdns/build/Release/dns_sd_bindings.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 48. This version of Node.js requires
NODE_MODULE_VERSION 57. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).
at Object.Module._extensions..node (module.js:664:18)
at Module.load (module.js:554:32)
at tryModuleLoad (module.js:497:12)
at Function.Module._load (module.js:489:3)
at Module.require (module.js:579:17)
at require (internal/module.js:11:18)
at Object.<anonymous> (/usr/lib64/node_modules/homebridge/node_modules/mdns/lib/dns_sd.js:24:20)
at Module._compile (module.js:635:30)
at Object.Module._extensions..js (module.js:646:10)
at Module.load (module.js:554:32)

 

どうも、Node.jsのバージョンが、現環境と違うものでコンパイルされているよ。リビルドし直すかインストールし直してね。

と、言っているようです。

インストールし直してもダメだったので、リビルドするのですが、どうやらリビルドコマンドを実行するディレクトリがちゃんとした場所でないと動いてくれないようです。

上記のようにhomebridgeをリビルドするなら、

 

gentoo # cd  /usr/lib64/node_modules/homebridge/

gentoo /usr/lib64/node_modules/homebridge # npm rebuild

 

というように、該当のNode.jsのモジュールがインストールされているディレクトリに移動してからnpm rebuildを実行しましょう。

Let's EncryptでメールサーバーをSSL/TLS化するときの注意

SSL証明書はいままで3~4年更新の安いものを導入していました。

私が管理する義父の会社のサーバーもそうでした。

が、今は無料でSSL化できる良い時代がやってきました。

letsencrypt.jp

未だに80や443以外のポートを使っているときにWebrootでACMEによる実在確認が出来ないという不可解があるのですが(何が悪さしているのか、ログが出てこないので切り分けが出来ない・・・)、普通に80や443のWebrootで実在確認できれば、その後もちゃんと3ヶ月の有効期限が到来する前に、cronでrenewすれば更新されます。

 

で、メールも暗号化していると、Let's Encryptはrenewで更新されているし、Apache+ブラウザもなんともないのに、メールソフトだけは「証明書の有効性が確認できませんでした」という警告が出て、メールの送受信が出来なくなります。(あ、受信しか試してなかったかも)

 

私はPostfix + Dovecotで運用しているのですが、どうもPostfixやDovectは起動時だけ証明書を読むようで(Dovecotだけだったらゴメン)、起動後は証明書の読み込みは一切行わず、起動時に読み込んだ証明書を粛々とクライアントに渡しているようで、Let's Encryptが証明書ファイルを更新しても、反映されないようです。

 

そしてローレベルな解決方法。

更新は週に1度、/etc/cron.weekly配下にリニューアルコマンドのBASHスクリプトを置いていますので、そこにPostfix + Dovecotの再起動を指示しました。

 

/etc/cron.weekly

#!/bin/sh

/usr/bin/certbot renew
/etc/init.d/dovecot restart
/etc/init.d/postfix restart 

 

これで、強制的に反映されるでしょう。

再起動しなくて良い、もっとスマートな解決方法ないかしら?(証明書を買うという方法はナシで)

一眼レフで撮った写真をGoogle Photosに自動アップロード その2 サーバースクリプト設置

<2019年1月にPicasa Photos APIの廃止準備(2019年3月に完全停止)に伴い、ここに記載の方法では写真をアップロード出来なくなりました。Google Photos APIに変更する必要があります。が、本ブログではまだbashスクリプトによるアップロード方法を模索中(勉強中)です。>

 

その1では、PQI AirCardを自宅のWiFiで使うための設定を行いました。

f:id:naoyukinagano:20171031211815j:plain

 

今回は、サーバー側の設定をしていきます。

サーバーで実現することは、

PQI AirCardをIPアドレスで死活監視。

・活性後、PQI AirCard内の画像のうち、JPGだけをFTPで取得。その後に重複取得しないようにJPEGファイル名称をとあるファイルにアペンド(追加)

Google APIの認証情報(CLIENT ID,CLIENT SECRET,REFRESH TOKEN)に基づき、ACCESSS TOKENを取得します。

Google PhotosのアルバムIDに対してACCESS TOKENで認証し、JPEGをアップロードします。

・アップロードが終わったら、撮影日ごとにフォルダを作成してファイルを移動。

これをずーっとループでやっていきます。

 

まずは、Google APIの認証情報を取得します。Googleアカウント(Gmailアドレス)があることが前提です。

こちらに書いてあることをそのままやることで、Google Photos用の認証情報が全て得られます。

そう、パクりです。

qiita.com

デベロッパーコンソールにアクセスします。

https://console.developers.google.com

私は既に一度アクセスしているので、こんな画面になります。

f:id:naoyukinagano:20171031233327p:plain

おそらく、先に適当なプロジェクト名を指定しなければならないと思います。

私の場合はGooglePhotosAPIとつけていまして、上部に表示されています。

左側の「認証情報」をクリックします。

f:id:naoyukinagano:20171031233917p:plain

大きく青く「認証情報を作成」と出ていますね。ここから「クライアントIDの作成」を選びます。

f:id:naoyukinagano:20171031234335p:plain

アプリケーションの種類は「その他」を選び、適当な名前をつけます。

私の場合は「GooglePhotosUploader」とつけてみました。

「作成」を押します。

f:id:naoyukinagano:20171031235224p:plain

このように、クライアントIDとクライアントシークレットが取得できました。これをどこかにコピーしておきます。

(Googleデベロッパーコンソールサイト上でももう一度確認できます。)

 

続いては、Webサーバー上のでの作業です。

これこそ、先のtamanobiさんの記事の通りに実行します。

まずは、クライアントIDとクライアントシークレットを使ってオーソリゼーションコードを取得します。

私はGentoo Linuxをサーバに使っている変な人なので、Gentoo上で作業します。(といってもほとんどのLinuxディストリビューションで変わらない作業だと思いますが)

作業ディレクトリとして、/home/homephotoとしました。

ここにget_authorization_code.shという名前でtamanobiさんのスクリプトを作成します。

# mkdir /home/homephoto

# cd /home/homephoto

# nano get_authorization_code.sh

すみません。なんの芸もなく、tamanobiさんのスクリプトを引用させていただきます。

下記のYOUR_CLIENT_IDは、上記で取得したクライアントIDに置き換えてください。

CLIENT_ID="YOUR_CLIENT_ID"
REDIRECT_URI="urn:ietf:wg:oauth:2.0:oob"
SCOPE="https://picasaweb.google.com/data/"
echo "https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=$CLIENT_ID redirect_uri=$REDIRECT_URI&scope=$SCOPE&access_type=offline"

これは、認証ページにアクセスするためのURLを生成するスクリプトです。

実行権限を与えて、実行します。

# chmod +x get_authorization_code.sh

# ./get_authorization_code.sh

こんなURLが出てきます。

はい、このYOUR_CLIENT_IDの部分を書き換えてブラウザでアクセスしても同じことです。はい。

https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=$CLIENT_ID redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=https://picasaweb.google.com/data/&access_type=offline

 

こんな画面が出てきますので、許可しましょう

f:id:naoyukinagano:20171104215550p:plain

 許可を押すと、味気なくオーソリゼーションコードが表示されますので、どこかにコピペしましょう。

f:id:naoyukinagano:20171104215918p:plain

 

続いて、リフレッシュトークンとアクセストークンを取得します。

この2つのトークンのうち、リフレッシュトークンは明示的に変更をしない限り変わりませんが、アクセストークンの有効期限が3600秒です。

またまたtamanobiさんの記事をパクります。

スクリプトを作りましょう。

# nano get_access_token_and_refresh_token.sh

スクリプトの中身は次の通りですが、YOUR_CLIENT_ID、YOUR_CLIENT_SECRET、先ほど取得したオーソリゼーションコードをYOUR_CODEに入れ替えましょう。

CLIENT_ID="YOUR_CLIENT_ID" # クライアントID
CLIENT_SECRET="YOUR_CLIENT_SECRET" # クライアントシークレット
AUTHORIZATION_CODE="YOUR_CODE" # 認可コード
REDIRECT_URI="urn:ietf:wg:oauth:2.0:oob" # HTTPサーバを起動しなくてもAuthorization_Codeを取得できる

curl --data "code=${AUTHORIZATION_CODE}" --data "client_id=${CLIENT_ID}" --data "client_secret=${CLIENT_SECRET}" --data "redirect_uri=${REDIRECT_URI}" --data "grant_type=authorization_code" --data "access_type=offline" https://www.googleapis.com/oauth2/v4/token

で、実行権限を付与して実行します。

# chmod +x get_access_token_and_refresh_token.sh

# ./get_access_token_and_refresh_token.sh

そうするとJSON形式でリフレッシュトークンとアクセストークンが取得できます。

このリフレッシュトークンが大事になります。

 

そして、Google Photosの特定のアルバムにアップロードしたいので、アルバムIDを調べましょう。

ブラウザで次のURLにアクセスします。

USER_IDの部分はGoogleのユーザーIDに書き換えてください。ユーザーIDはGmailアドレスの@より前ですね。

https://picasaweb.google.com/data/feed/api/user/USER_ID

そうすると、アルバムのリストが現れます。

f:id:naoyukinagano:20171104222659p:plain

アルバムのリンクURLを見ると、次のような法則になっています。

https://picasaweb.google.com/"Picasaのユーザー識別"/"アルバムID"?locked=true

このアルバムIDを控えておきます。

 

さて、ここからはオリジナルだったりtamanobiさんのスクリプトの改変だったりです。

 

まず、PQI AirCardの死活監視を行い、アクセスできたらFTPでJPGだけをGETします。

GETし終わったら、同じファイルをGETしないよう、GETしたファイルリストを作ります。

アクセストークンを取得し、JPEGファイルをGoogle Photosにアップロードします。

その後、exiftoolというアプリケーションを使って、年月日(YYYYMMDD)のディレクトリに分けて保存します。

というのを繰り返します。

お気づきの方もいらっしゃると思いますが、デジカメでファイルを連番で記録するようにしていると、XXX_9999.JPGの次は新しくディレクトリを作成し、たとえばDCIM/200CANONの次はDCIM/201CANONに保存されます。

もし10000枚目だとしたらあきらめてください((^^ゞ)

ちなみに、DCIM/200CANONディレクトリの中身を全て消去し、DCIM/201CANONのディレクトリ自体を消去すると、次からデジカメはDCIM/200CANONに撮影写真を保存していきます。

あと、なぜDCIM200CANONなのかというと、PQI AirCardにはDCIM/199WIFIという制御用ディレクトリがあるため、200CANONにしないと199WIFIに保存してしまいます。制御用のJPEGもダウンロードしてしまうことになるためです。

 

getophoto.shという名前のスクリプトにしました。

#/bin/bash

PQI_AIR_IP="192.168.xxx.xxx"

PQI_DIR="sd/DCIM/200CANON"

USER_ID="USER_ID"

CLIENT_ID="YOUR_CLIENT_ID"

CLIENT_SECRET="YOUR_CLIENT_SECRET"
REFRESH_TOKEN="YOUR_REFRESH_TOKEN"

ALBUM_ID="YOUR_ALBUM_ID"

 

#自身のディレクトリに移動
cd `dirname $0`

#プロセス番号をpid.txtに保存

echo $$ > pid.txt

 

#ここからループ

while :

do

 

#PQI AirCarにhttp出来るかどうか試す
ALIVE=$(/usr/bin/wget -nv --spider --timeout 60 -t 1 http://${PQI_AIR_IP}/ 2>&1 | grep -c '200 OK')

 

#PQI AirCadが生きていた場合の処理

if [ $ALIVE -eq 1 ]; then

 

#過去にGETしたJPGファイルのリストdonelist.txtを読み込む。念のため改行を削る

DONELIST=`cat donelist.txt | sed -e ':loop; N; $!b loop; s/\n//g'`

 

#FTPでJPGをGET
/usr/bin/wget -m -q -N --reject=${DONELIST} --no-host-directories --no-directories -A .JPG ftp://${PQI_AIR_IP}/${PQI_DIR}

 

#GETしたJPGを改行などを削除してdonelistに保存

JPGLIST=`/bin/ls | grep .JPG`
if [ -n "$JPGLIST" ]; then
/bin/echo "," >> donelist.txt
/bin/ls | grep .JPG | sed -e ':loop; N; $!b loop; s/\n/,/g' >> donelist.txt
sed -i -e ':loop; N; $!b loop; s/\n//g' donelist.txt
fi

 

#アクセストークン取得
curl -s --data "refresh_token=${REFRESH_TOKEN}" --data "client_id=${CLIENT_ID}" --data "client_secret=${CLIENT_SECRET }" --data "grant_type=refresh_token" https://www.googleapis.com/oauth2/v4/token > token.JSON
ACCESS_TOKEN=`cat token.JSON | jq -r '.access_token'`


ENDPOINT="https://picasaweb.google.com/data/feed/api/user/${USER_ID}/albumid/${ALBUM_ID}?access_token=${ACCESS_TOKEN} "

 

DIR="./" # FTPでGETしたJPGファイルが保存してあるディレクトリを指定

IFS_BAK=${IFS}
IFS="
"

 

#ファイル数だけループ
FILES=`ls -1 ${DIR}`
for FILE in ${FILES}
do

 

#MIMEタイプの指定
if [ "${FILE}" != "" ]; then
TYPE="UNKNOWN"
case "${FILE}" in
*\.bmp) TYPE="image/bmp";;
*\.JPG | *\.jpg) TYPE="image/jpeg";;
*\.png) TYPE="image/png";;
*) TYPE="UNKNOWN";;
esac
# echo "$TYPE $FILE $LENGTH"
if [ "${TYPE}" = "UNKNOWN" ]; then
continue;
fi
LENGTH=`ls -l "${DIR}/${FILE}" | tail -n1 | sed -E 's/ +/ /g' | cut -d' ' -f5`

 

#ファイルをアップロード

curl -s -XPOST "${ENDPOINT}" \
-H "Content-Type:${TYPE}" \
-H "Content-Length:${LENGTH}" \
-H "Slug:${FILE}" \
--data-binary "@${DIR}/${FILE}" > /dev/null
sleep 1
fi
done
IFS=$IFS_BAK

 

#ファイルのアップロードが全て終わったら、撮影日でYYYYMMDDというディレクトリを作ってファイルを移動
/usr/bin/exiftool -q '-Directory < CreateDate' -d %Y%m%d *.JPG
fi

sleep 10
done

 

これも実行権限をつけて実行しましょう。

chmod +x getophoto.sh

./getophoto.sh &

 

これで、サーバーと同じネットワークセグメントにいる限り、つまり、室内では、撮影する度にGoogle Photosの指定したアルバムにアップロードされます。

たしかGoogle Photosの仕様で、1アルバム1000枚までしか保存できないはずですので、上限に達したら新しいアルバムに変えましょう。

一眼レフで撮った写真をGoogle Photosに自動アップロード その1 PQI AirCardの固定IP化

私は10年前に購入したCanon EOS 40Dを今も現役で使い続けています。

f:id:naoyukinagano:20171031211751j:plain

 

しかし、このEOS 40Dは、当然Wi-Fiには対応しておらず、さらに記録媒体はコンパクトフラッシュという有様。

Wi-Fiに公式対応するためには、WFT-E3が必要になりますが、これがお高い!

 

スタパ齋藤の「週刊スタパトロニクスmobile」 サクッと無線接続!!EOS 40D用ワイヤレストランスミッター「WFT-E3」

 

で、かなり前からPQI AirCardにmicroSDを入れ、SDカードーコンパクトフラッシュアダプターというものを噛まして使っていました。

f:id:naoyukinagano:20171031211815j:plain

 

室内では家庭内LANのWiFiに、屋外では主にiPadテザリングを掛け、ShutterSnitchというアプリでJPEGだけ吸い出して大画面プレビューとしていました。

これはこれで便利でしたが、この変換、書き込みが遅いため、連射を多用する野鳥撮影には向いていなかったため、最近は通常のコンパクトフラッシュに戻し、どうしてもiPadに吸い出す場合はLightning USBカメラアダプタ+モバイルバッテリー、そしてコンパクトフラッシュリーダーライターを使って読み出していました。

ちなみに、iPad Pro 9.7 32GB + Lightning USB 3カメラアダプタ + エレコムMRS-MB07 + ANKER PowerCore Speed 10000 QC で稼働しております。 モバイルバッテリが5V 2.4Aを給電できないと、iPadはリーダーライタを認識できません。これはリーダーライタのせいではありません。

 

 

 

 

 

 

さて、話がそれましたが、そうなるとPQI AirCardが外出先では使われなくなります。

そこで、家庭内利用かつイエネコ撮影用に限定し、撮影されたそばからGoogle Photosにアップロードし、Google Photosではアルバム単位や一枚ごとに共有できるようにしました。

Googleドライブにアップロードして、Photosと共有するパターンはよく見かけますが、2048px以内なら容量にカウントされないPhotosに直接アップロードします。

 

前提として、私はプログラムが書けないサーバー管理者ですので、Linuxサーバー上で様々なソフトウェアをシェルスクリプトで組み合わせるという方法で達成します。

 

まずは、PQI AirCardのIPアドレスを固定化します。

PQI AirCaedに挿入するmicroSDカード直下にautorun.shというファイルを置くと、AirCardの起動時に自動実行してくれるのですが、これを利用して、宅内のWi-FiWi-Fi子機として接続しつつ、IPアドレスを固定化します。

参考にしたのはこのサイトです。

oichinote.com

Perlを書けるのがうらやましい・・・。

リンク先のパターンでは、PQI AirCard内のLinuxコマンド、/usr/bin/w2でWiFi接続とDHCPによるIPアドレス取得までやってしまい、DHCPで取得したIPアドレスの第4ブロックだけを91に書き換えるという動作を行っています。

私の家ではクラスCプライベートアドレスの192.168.0.0/24の中からDHCPを割り当てます。なので、192.168.0.91となります。

しかし、これをそのまま使おうとしたのですが、私のAirCardは最後にWebサーバー上のCGIに自身のIPアドレスをPOSTし、メールでスマホに報告させるスクリプトがautorun.shの最後に記述されています。

これが動かないのです。

しかし、192.168.0.91にpingもhttpもftpも通る。なぜ?

突き当たったのは、/usr/bin/w2だとデフォルトゲートウェイDHCPで設定されるのですが、その後にifconfigを実行すると、デフォルトゲートウェイが消去されることがわかりました。

PQI SAirCardにはrouteコマンドも用意されているので、route add default gw 192.168.0.xxxで設定されますが、なんせプログラムが書けないので、ifconfigする前にゲートウェイIPアドレスを取得して変数に代入するやり方がわからないのです。

だれか教えてプリーズ!

で、苦肉の策、デフォルトゲートウェイIPアドレスの最終ブロックが1であると決め打ちしてroute add default gwコマンド実行させます。

 

こんな感じになりました。私の場合は91から160に変更しています。

 #!/bin/sh
sleep 5
/usr/bin/w2

# sleep disable
kcard_cmd -s 0

sleep 1

script=`cat <<'EOF'
my $ifname;
my @ipadr;
my $pqi='160';

open (my $fh, '-|', qw/ifconfig/) or die $!;
while (my $line = <$fh>) {
$ifname = $1 if ($line =~ /^(\w+)/);
@ipadr = ($1, $2, $3, $4) if ($line =~ /inet\s+.*?(\d+)\.(\d+)\.(\d+)\.(\d+)/);
if ($ifname and @ipadr and ($ipadr[0] == 192) and ($ipadr[1] == 168)) {
system "ifconfig $ifname $ipadr[0].$ipadr[1].$ipadr[2].$pqi";
system "route add default gw $ipadr[0].$ipadr[1].$ipadr[2].1";
last;
}
}
close ($fh);

EOF
`

perl -e "$script"

 

この後、ifconfigの内容をWebサーバーのCGIにPOSTするスクリプトが続いています。

これはPQI AirCardをいじり倒しているひとりブログさんが、ツイッターにメンションしているものを、メールで飛ばすように変えたものです。

hitoriblog.com

autorun.shの最後のほうに次のように記述しました。

sleep 1


ip=`ifconfig mlan0 | grep inet`

wget -O - --post-data="${ip}" http://foo/bar/mail.cgi

 

CGIはPOSTされたIPアドレスをメールで報告するだけです。

サーバーに設置してあるmail.cgiはこんな感じ。どこから持ってきたのかしら?

#!/usr/bin/env perl

use URI::Escape;
read (STDIN, $inputescaped, $ENV{'CONTENT_LENGTH'});
$input = uri_unescape($inputescaped);

$sendmail = '/usr/sbin/sendmail'; # sendmailコマンドパス
$from = 'foo@bar.com'; # 送信元メールアドレス
$to = '1hoge@docomo.ne.jp'; # あて先メールアドレス
#$cc = 'hage@example.com'; # Ccのあて先メールアドレス
$subject = 'PQIAirCard'; # メールの件名
#$msg = 'body'; # メールの本文(ヒアドキュメントで変数に代入)

# robot対策

if ($input =~ m/inet/ ) {

$input =~ s/ //g; # 空白2文字分を削除

# sendmail コマンド起動
open(SDML,"| $sendmail -t -i") || die 'sendmail error';

# メールヘッダ出力
print SDML "From: $from\n";
print SDML "To: $to\n";
#print SDML "Cc: $cc\n";
print SDML "Subject: $subject\n";
print SDML "Content-Transfer-Encoding: 7bit\n";
print SDML "Content-Type: text/plain;\n\n";

# メール本文出力
print SDML "$input";

# sendmail コマンド閉じる
close(SDML);
}

print "OK";
exit; 

 

これで、カメラの電源投入後にスマホにメールが届けば成功です。

Raspberry Piのメディアレンダー化作戦 DLNA編

Raspberry Pi 2 Model Bのメディアレンダー化作戦。次はDLNAのレンダラーです。

Raspbian Stretch Liteですので、動画はレンダーしません。

LinuxはシンプルにCLI派です。

f:id:naoyukinagano:20171030225612j:plain

 

さて、RaspbianのDLNAレンダーですが、Gstreamerを利用したGmediarenderで構築します。

よくGmediarendererと記載されているサイトがありますが、Raspbian Stretchにはgmediarenderというパッケージがありますので、それを使います。

パッケージが用意されているなんて、さすが最新OS

なので、こちらも依存関係含めてパッケージを入れるだけ。

 root@raspberrypi:~# apt-get install libupnp-dev libgstreamer1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-alsa gmediarender

 

さて、ハマったのが起動。

普通なら

 root@raspberrypi:~# systemctl enable gmediarender

で、自動起動の登録、

root@raspberrypi:~# systemctl start gmediarender

で起動するはずですが、ps -eでもgmediarenderというプロセスは見つからず。

 

起動スクリプトが/etc/init.d/gmediarenderに入っているので見てみると、

[ "x$ENABLED" = "x1" ] || exit 0

こんな一行が。

$ENABLEDってどこに定義してあるんだろうとスクリプトを見ても、見当たりません。

ようやく探し当てたのが、/etc/default/gmediarenderというファイル。

というやらこいつが設定ファイルだったようです。

 

設定ファイル/etc/default/gmediarenderの中の

ENABLED=1

UPNP_DEVICE_NAME="$(hostname)"

INITIAL_VOLUME_DB=0.0

ALSA_DEVICE="hw:1,0"

DAEMON_EXTRA_ARGS="--logfile /var/log/gmediarender.log"

という5項目を設定しました。

ENABLEDがコメントアウトしてあったので起動しなかったんですね。

UPNP_DEVICE_NAMEは、DLNAのコントローラに通知する名前なので、よさげなものをつけて下さい。私はホスト名で。

INITIAL_VOLUME_DBは音量をdbで指定します。0は音量最高。こうしないと出力が小さくなりますので。

ALSA_DEVICEはサウンドデバイスの設定です。aplay -lで出てくるデバイスを指定できます。shairport-syncで指定したoutput_device=と同じ、USB Audioを指定しています。

DAEMON_EXTRA_ARGSは他に定義したい起動オプションを記載するようです。とりあえずログ指定を。

 

ではようやく起動です。

root@raspberrypi:~# systemctl start gmediarender

 

Windows10から見えるようになりました。

f:id:naoyukinagano:20171031003814p:plain

Raspberry Piのメディアレンダー化作戦 AirPlay編

今年で67になる母は、つい先日までイナカの実家で一人暮らしをしていました。

そんな67の母は私のすすめで2015年9月にDENONのネットワークオーディオRCD-N9を購入。

CDとラジオ、そして海外のネットラジオを楽しんでいました。

(購入当初はらくスマのdヒッツをBluetooth経由で聞いていましたが、Bluetoothが面倒なのか、dヒッツが高かったのか、聞くのをやめてしまいました。)

 

そこで、よかれと思いRaspberry Pi 2 Model Bを調達し、OpenMediaValueをインストールして、手持ちのCDを全てリッピングして保存、リッピングのやり方も解説して置いていきましたが、こちらも使われずにいたようです。

 

CDで聞けるなら、CDを入れ替えた方がいいようです。

 

そこで、弟夫婦との同居を機に、Raspberry Piを引き上げてきました。

そのRasoberry Piに第二の人生を送ってもらおうと、寝室のメディアレンダーとして活躍してもらうこととしました。

忘れないうちにインストール記録です。

 

OSはRaspbian Stretch Liteを、NOOBSを使わずに、イメージを直接SDに焼いて起動し、有線イーサを使ってdhcpcd.confで固定IPにすること、自分の持っている秘密鍵SSH接続できるようにすること、デフォルトのpiユーザーをsudo出来ないようにすること、sudoにパスワードが必要になるようにすること、自分がrootにsu(Switch User)できるように設定しました。

このあたりは難しくないので他に譲ります。

そして、sudoは面倒なので、rootで作業してしまいます。(よい子はまねしちゃだめ)

 

ケースから出しているので、こんな感じです。

f:id:naoyukinagano:20171030225137j:plain

 

Raspberry Piのデフォルトアナログ音声出力は音の悪さに定評がありますので(笑)、USB Audioに変えます。

 

たまたま以前まで使っていたBEHRINGER UCA202 U-CONTROLが手元にあったので、こちらを使います。

中身はTI PCM 2902のようです。たいへん素直なデバイスです。

せっかくデジタルOPT出力があるので、MARANTZのNA7004に入力して音出しを試みます。

f:id:naoyukinagano:20171030225612j:plain

NA7004って既にレンダラーじゃん!っていうツッコミはなしでお願いします。

完成したら別の部屋で使いますので・・・。

 

まずはAirPlayのレンダラーになっていただきます。

shairportの後継、shairport-syncを使います。(後継と言っても派生のようですが)

 

さて、githubからshairport-syncをgitしてautoconfしてmake・・・とおもったら、stretchはもうパッケージが準備されています。

さすが最新OS!

root@raspberrypi:# apt-get install shairport-sync

さて、設定しましょう。

たぶん、shairport-syncを動かすためのユーザーが必要でしょうから、作ります。

 root@raspberrypi:# groupadd -r shairport-sync

 root@raspberrypi:# useradd -r -M -g shairport-sync -s /usr/bin/nologin -G audio shairport-sync

デフォルトのアナログやHDMI出力から音を出すわけではないので、出力先を確認しておきましょう。

再生ソフトaplayの機能を借りて、再生デバイスの一覧を出してもらいます。

root@raspberrypi:~# aplay -l
**** ハードウェアデバイス PLAYBACK のリスト ****
カード 0: ALSA [bcm2835 ALSA], デバイス 0: bcm2835 ALSA [bcm2835 ALSA]
サブデバイス: 8/8
サブデバイス #0: subdevice #0
サブデバイス #1: subdevice #1
サブデバイス #2: subdevice #2
サブデバイス #3: subdevice #3
サブデバイス #4: subdevice #4
サブデバイス #5: subdevice #5
サブデバイス #6: subdevice #6
サブデバイス #7: subdevice #7
カード 0: ALSA [bcm2835 ALSA], デバイス 1: bcm2835 ALSA [bcm2835 IEC958/HDMI]
サブデバイス: 1/1
サブデバイス #0: subdevice #0
カード 1: CODEC [USB Audio CODEC], デバイス 0: USB Audio [USB Audio]
サブデバイス: 0/1
サブデバイス #0: subdevice #0

USBはカード1のサブデバイス0なので、hw:1,0かplughw:1,0のどちらかを指定することとなります。

 

shairport-syncのコンフィグはお行儀悪く、/etcの直下にshairport-sync.confがあります。

 root@raspberrypi:~# nano /etc/shairport-sync.conf

 

 general =

{

で始まる項目を設定していきます。

コメント多過ぎですね(笑)

 

まずは、AirPlayデバイス名を決めるので、name =の前のコメントアウト//を消して、ホスト名をデバイス名にします。

name = "%H"; // This means "Hostname" -- see below. This is the name the service will advertise to iTunes.

soxrを使うと音が良くなると聞いたので、// interpolation = "basic"を下記のように変えます。

interpolation = "soxr"; // aka "stuffing". Default is "basic", alternative is "soxr". Use "soxr" only if you have a reasonably fast processor.

ALSAドライバーを使うので、一応明示しておきます。

output_backend = "alsa";

なんか音が小さいな、と思ったら、ボリュームコントロールが有効でした。無効にします。

ignore_volume_control = "yes";

 

続いて

 alsa =

{

で始まる項目を設定していきます。

USB Audioにアウトプットするので、

output_device = "hw:1,0"; 

mixer_control_name = "PCM";

mixer_device = "default";

に変更します。("hw:1,0"は"plughw:1,0"でもOK。)

 

では、nanoを閉じましょう。ctrl + xでyですね。

 

次に、デーモンを自動起動する設定と、実際に起動するところです。

まずは、raspbian起動時に自動起動するように設定。

root@raspberrypi:~# systemctl enable shairport-sync

では、最後、祈るように起動しましょう。

root@raspberrypi:~# systemctl start shairport-sync

 

なにかいろいろ(最初にgithub版をmakeしたものをインストールしたので、アンインストールしてパッケージをapt-getしたため)やっているうちに、"Failed to attach mixer"と出てきました。

Raspbian自体を再起動するとしっかりと直りました。

このFailed to attach mixerは、ユーザーshairport-syncをグループaudioに入れていないことによって、ミキサーが操作できないために起こるエラーです。

 

さて、iPadから再生してみましょう。

f:id:naoyukinagano:20171031001331j:plain

 

ちゃんと選べて再生できるようになりました!!