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

先日、Raspberry Pi Zero Wで、リビングのライトのスイッチを、壁のプッシュスイッチからも、iOSのHomeKitからもON/OFFできるように、まずはRaspberry Piに接続したプッシュスイッチでリレーを、とりあえずON/OFF出来るようにしました。

gentoolinux.hatenablog.com

しかし、このときはadd_event_detectをうまく使えず、while:ループの中で細かなsleepでごまかしながら力業で、プルアップされたGPIOインプットがGNDに落ちたかどうかを検出していました。

sleepを挟んでいるとはいえ、かなりCPUリソースを消費していたようです。

今は良いですが、夏は熱でスタックしそう・・・。

f:id:naoyukinagano:20171121223443j:plain

 

で、add_event_detectで立ち上がりエッジを検出後にだけ、該当のGPIOインプットがGNDに落ちているかを判定し、該当のインプットなら処理続行、該当のインプットでなければ処理中断をしてあげれば、問題なく動かせることに気づきました。

そこで、スクリプトの書き直しです。

また、killコマンドで終了したときにもしっかりとGPIOのクリーンナップが出来るように、シグナルハンドラも入れてみました。

こんな感じになりました。

#! /usr/bin/python

 

#必要なモジュールをインポートします。requestsはその1の記事を参照してインストールしてください。

import signal
import sys
import RPi.GPIO as GPIO
import time
import requests

 

#スイッチのGPIOとリレーのGPIOを指定します。

switch = 23
relay = 26

 

#リレーはwebiopiのほうからコントロールします。

#なぜならRPiライブラリでGPIO.setupすると、webiopiからはコントロール出来なくなるからです。

#リレーのURLを生成するための変数です。

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をプルアップされたインプットとして設定します。

GPIO.setmode(GPIO.BCM)
GPIO.setup(switch, GPIO.IN, pull_up_down=GPIO.PUD_UP)

 

#webiopiからリレーに接続されているGPIOをアウトプットとして設定します。

#設定にはPOSTメソッドを使います。
gpio_function = requests.post(url_function)

 

#エッジ検出後の動作をコールバック関数として記述します。

def switch_callback(switch):

    if GPIO.input(switch)==0:    #スイッチのGPIOがGNDに落ちているかどうかを判定。落ちていたら次の処理へ
         status = requests.get(url)    #リレーのGPIOの状態をwebiopi経由で取得するためにurlをgetで叩く
         if status.text=='1':    #getでリクエストしたBODY部分は.testに入るので、その値が(文字の)1かどうか判定
             requests.post(url0)    #1ならwebiopi経由で0にするためにPOST
             time.sleep(0.5)    #誤動作防止のため0.5秒スリープ
         else:
             requests.post(url1)    #getでリクエストしたBODYが1以外(つまり文字の0)だったら、webiopi経由で1に
             time.sleep(0.5)    #誤動作防止のため0.5秒スリープ

 

#killを受け取ったときの動作を関数として記述

def handler(signal, frame):   
     GPIO.cleanup()    #設定したGPIOを解放(解放しないと次に使えない)
     print "switch.py terminated for GPIO",switch
     sys.exit(0)    #終了

 

#スイッチとなるGPIOのエッジ(電位差の立ち上がりか立ち下がり)を検出したらswitch_callback関数を呼び出す。

GPIO.add_event_detect(switch, GPIO.RISING, callback=switch_callback, bouncetime=300)

#killされたらhandler関数を呼び出す。

signal.signal(signal.SIGTERM, handler)

 

#エラーが検出されても続行させる。エラー検出後の動作はexceptに記載

try:

     while True:    #永遠とループ
     time.sleep(0.1)    #イベントかエラーが出るまで0.1秒スリープの繰り返し

 

#ctrl+Cで中止が発生した場合の処理

except KeyboardInterrupt:
     print '\nswitch.py Stopped'
     GPIO.cleanup()    #設定したGPIOを解放(解放しないと次に使えない

 できるだけ細かくコメントを書いてみました。

実際にはコメントははずしています。

30分で0.5℃、CPU温度が下がりました。誤差かな?

 

さて、homebridgeの記載をwebiopiに合うように直します。

config.jsonの該当部分のみを出してみましょう。

{
"accessory": "Http",
"name": "Spot and Bracket Light",
"switchHandling": "realtime",
"http_method": "POST",
"on_url": "http://192.168.x.y:8000/GPIO/26/value/0",
"off_url": "http://192.168.x.y:8000/GPIO/26/value/1",
"status_url": "http://192.168.x.y:8000/GPIO/26/value",
"status_on": "0",
"status_off": "1",
"service": "Switch",
"brightnessHandling": "no",
"brightness_url": "",
"brightnesslvl_url": "",
"sendimmediately": "",
"username" : "",
"password" : ""
},

IPアドレスのx.yはご自身の環境に合わせて読み替えてください。

http_methodですが、EspEasyの場合はgetメソッドでコントロールしていたのに対し、webiopiはPOSTメソッドでコントロールします。

URLの中にある26は、対象のリレーが接続されているGPIOです。

サインスマートのリレーは0でON、1でOFFなので、"status_on": "0","status_off": "1"を明確に定義しています。

ServiceはLightにした方がよかったかも・・・。

 

これでhomebridgeを再起動すると、壁スイッチからもHomekitからも使えるようになりました。

しかも、EspEasyと違い、webiopiの動作が速いため、同時に複数のリクエストを捌くことができるようで、HomeKitのシーンで、3つのスイッチを同時に消灯することが可能になりました。

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