コンテンツへスキップ

VNCサーバ導入

基本的にコマンドラインで操作していたRaspberry Piですが、WEBカメラの導入や開発環境の利用に先立ち、VNCサーバを導入しました。
VNC ViewerをWindowsなどのクライアントにインストールし、Raspberry PiのVNCサーバの有効化を行います。
さらに、Raspberry Piに「xrdp」「remmina」をインストールしました。

VNC Viewerのインストール

以下のURLよりVNC Viewerをダウンロードし、クライアントにインストールします。

VNC® Connect consists of VNC® Viewer and VNC® Server

Raspberry PiのVNCサーバを有効化

Raspberry Pi の「raspi-config」コマンドで、「Raspberry Pi Software Configuration Tool (raspi - config)」を開きます。画面には、Raspberry Pi 3 Model B Plus Rev 1.3が表示されています。

sudo raspi-config

「5 Interfacing Options Configure connections to peripherals」を選択します。

「P3 VNC Enable/Disable graphical remote access to your Pi using RealVNC」を選択し、VNCサーバをEnable(有効化)します。

「xrdp」および「remmina」導入

@raspberrypi: $ sudo apt-get install xrdp
[sudo] password for:
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
libboost-system1.62.0 libboost-thread1.62.0
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
libglu1-mesa x11-apps x11-session-utils xbitmaps xfonts-75dpi xfonts-base
xfonts-scalable xorg xorg-docs-core xorgxrdp
Suggested packages:
mesa-utils xorg-docs x11-xfs-utils guacamole xrdp-pulseaudio-installer
The following NEW packages will be installed:
libglu1-mesa x11-apps x11-session-utils xbitmaps xfonts-75dpi xfonts-base
xfonts-scalable xorg xorg-docs-core xorgxrdp xrdp
0 upgraded, 11 newly installed, 0 to remove and 1 not upgraded.
Need to get 11.0 MB of archives.
After this operation, 17.8 MB of additional disk space will be used.
Do you want to continue? [Y/n]
@raspberrypi: $ sudo apt-get install remmina
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
libboost-system1.62.0 libboost-thread1.62.0
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
libavahi-ui-gtk3-0 libayatana-appindicator3-1 libayatana-ido3-0.4-0
libayatana-indicator3-7 libdbusmenu-glib4 libdbusmenu-gtk3-4
libfreerdp-client2-2 libfreerdp2-2 libssh-4 libvncclient1 libwinpr2-2
remmina-common remmina-plugin-rdp remmina-plugin-secret remmina-plugin-vnc
Suggested packages:
freerdp2-x11 remmina-plugin-exec remmina-plugin-nx remmina-plugin-spice
remmina-plugin-telepathy remmina-plugin-xdmcp
The following NEW packages will be installed:
libavahi-ui-gtk3-0 libayatana-appindicator3-1 libayatana-ido3-0.4-0
libayatana-indicator3-7 libdbusmenu-glib4 libdbusmenu-gtk3-4
libfreerdp-client2-2 libfreerdp2-2 libssh-4 libvncclient1 libwinpr2-2
remmina remmina-common remmina-plugin-rdp remmina-plugin-secret
remmina-plugin-vnc
0 upgraded, 16 newly installed, 0 to remove and 1 not upgraded.
Need to get 3274 kB of archives.
After this operation, 8144 kB of additional disk space will be used.
Do you want to continue? [Y/n]

トラブルシュート(Cannot currently show the desktop)

インストール時のブートオプションで、Text Consoleを選択していたのが原因で、VNCクライントからアクセスした際に「Cannot currently show the desktop」と表示され、VNC経由でのRaspberry Piにアクセスが出来ませんでした。

  1. 「raspi-config」でRaspberry Pi Software Configuration Tool(raspi - config)を起動します
  2. 「3 Boot Options Configure options for start-up」を選択します
  3. 「B1 Desktop /CLI Choose whether to boot into a desktop enviroment or the command line」を選択します
  4. 「B3 Desktop Desktop GUI, requiring user to login」を選択肢、GUIモードで起動するように変更します。

メールサーバー(Mail Plus)でSMTP構築

25番ポートブロック(Outbound Port25 Blocking)が設定されていたり、GmailのSMTP利用基準が厳しくなったりと、何かとIoT家電などからメール送信する際に独自のメール送信SMTPサーバが利用したい場面が出てきます。今回は、SynologyにSMTPメール送信サーバを構築します。Synologyの通知機能で用いるメールサーバも今回構築するSMTPメールサーバ経由に設定します。
また、迷惑メール防止で、自宅サーバのメール送信もIP逆引きが確認されたりと厳しくなっているので、Synologyでリレーメール設定(SMTPリレー)を行い、利用しているプロバイダのSMTPサーバ経由(サブミッションポート:Port 587、SMTP-Auth)でメールを設定します。
ちなみに、メールのメインの受信サーバおよびIMAP/POPサーバは、サブドメイン無制限のレンタルサーバを運用している環境があるので、MXレコードを利用している外部のサーバにしております。自宅のネットワーク環境+Synologyでメールサーバを本番運用するまでには至っておりません。
外部サーバがない方も、個人的には、メール転送を利用して、Gmailに転送し、Gmailから独自ドメインのメールも送信出来るように設定し運用することをおすすめします。
[ITmedia]Gmailで差出人を別のメールアドレスに変更して送信する

メールサーバ(Mail Plus)をインストール

「パッケージセンター」よりMail Serverをインストールします。

SMTPサーバを設定

メールサーバーの設定画面を開きます

メールサーバーインストール後、設定画面を開きます。TLS / SSLプロファイルレベルの警告が出ます。

SMTPの設定画面を開き、SMTPサーバ設定を入力します

SMTPを有効にするにチェックを入れます。

SMTP認証を有効にするにチェックを入れ、LAN接続の認証を無視するにチェックを入れます(利用する環境に合わせて設定下さい)。
ホスト名(FQDN)に、利用するメールのドメイン名を入力します。
ローカルのみで利用するので、ポートは、SMTP標準の25を設定します。

SMTPリレー設定を行います

SMTP設定画面より「SMTPリレー」設定画面を開きます。
SMTPリレー設定画面では、プロバイダのSMTPサーバ、ポートとアカウント情報を入力して、SMTPリレー設定を行います。

Synologyの通知設定のメールサーバを変更

「コントロールパネル」「通知」を開きます

「コントロールパネル」「通知」の「電子メール」タブを設定します

電子メール通知を有効にするにチェックを入れます。
通知先メールアドレスを受信者のEメールアドレスに設定します。
SMTPサーバに「localhost」を設定し、SMTPポートに「25」を設定します。

送信者の電子メールに、SMTPサーバで設定したFQDNで利用可能なメールアドレスをしてします。

すべて設定後、「テストメールの送信」でテストメールが受信者のEメールアドレスで設定したアドレスで受信できるか確認して下さい。

 

Raspberry Piのバックアップ

SDカードのみの運用、他のコンポーネントの信頼性も不明瞭なので、とりあえずまるごとSynologyへバックアップを行い、故障などが起きた際に備えます。

Synologyでrsync利用設定

「コントロールパネル」「ファイルサービス」「rsync」タブの「rsyncサービスを有効にする」にチェックを入れます。。設定変更時には「適用」ボタンを押して設定を有効化する必要があります。

Raspberry Piのバックアップ設定(Rsync)

バックアップ対象

現在のボリューム構成は、以下となります。(「df -h」コマンド結果)
/ ルートフォルダ全体をバックアップ対象とし、バックアップから除外するファイルリストをファイルで準備します。「/rsync-exclude.txt」として準備しました。

@raspberrypi:~# df -h
ファイルシス サイズ 使用 残り 使用% マウント位置
/dev/root 116G 5.8G 105G 6% /
devtmpfs 459M 0 459M 0% /dev
tmpfs 464M 0 464M 0% /dev/shm
tmpfs 464M 14M 450M 3% /run
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 464M 0 464M 0% /sys/fs/cgroup
/dev/mmcblk0p6 253M 40M 213M 16% /boot
tmpfs 93M 0 93M 0% /run/user/1000
tmpfs 93M 0 93M 0% /run/user/1001
@raspberrypi:~# sudo vi /rsync-exclude.txt
@raspberrypi:~#
@raspberrypi:~# sudo cat /rsync-exclude.txt
/proc/*
/sys/*
/dev/*
/boot/*
/tmp/*
/run/*
/mnt/*
/var/tmp/*
/var/log/*
/media/*

rsyncコマンド

今回は、以下のコマンドを利用することにしました。

バックアップ先 /mnt/synology/raspberry_backup (フォルダは事前に作成して置きます)

sudo rsync -aEv --delete-during --exclude-from=/rsync-exclude.txt / /mnt/synology/raspberry_backup/ --log-file=/home/rsync.log
rsync  version 3.1.3  protocol version 31
Copyright (C) 1996-2018 by Andrew Tridgell, Wayne Davison, and others.
Web site: http://rsync.samba.org/
Capabilities:
    64-bit files, 64-bit inums, 32-bit timestamps, 64-bit long ints,
    socketpairs, hardlinks, symlinks, IPv6, batchfiles, inplace,
    append, ACLs, xattrs, iconv, symtimes, prealloc

rsync comes with ABSOLUTELY NO WARRANTY.  This is free software, and you
are welcome to redistribute it under certain conditions.  See the GNU
General Public Licence for details.

rsync is a file transfer program capable of efficient remote update
via a fast differencing algorithm.

Usage: rsync [OPTION]... SRC [SRC]... DEST
  or   rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST
  or   rsync [OPTION]... SRC [SRC]... [USER@]HOST::DEST
  or   rsync [OPTION]... SRC [SRC]... rsync://[USER@]HOST[:PORT]/DEST
  or   rsync [OPTION]... [USER@]HOST:SRC [DEST]
  or   rsync [OPTION]... [USER@]HOST::SRC [DEST]
  or   rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST]
The ':' usages connect via remote shell, while '::' & 'rsync://' usages connect
to an rsync daemon, and require SRC or DEST to start with a module name.

Options
 -v, --verbose               increase verbosity
     --info=FLAGS            fine-grained informational verbosity
     --debug=FLAGS           fine-grained debug verbosity
     --msgs2stderr           special output handling for debugging
 -q, --quiet                 suppress non-error messages
     --no-motd               suppress daemon-mode MOTD (see manpage caveat)
 -c, --checksum              skip based on checksum, not mod-time & size
 -a, --archive               archive mode; equals -rlptgoD (no -H,-A,-X)
     --no-OPTION             turn off an implied OPTION (e.g. --no-D)
 -r, --recursive             recurse into directories
 -R, --relative              use relative path names
     --no-implied-dirs       don't send implied dirs with --relative
 -b, --backup                make backups (see --suffix & --backup-dir)
     --backup-dir=DIR        make backups into hierarchy based in DIR
     --suffix=SUFFIX         set backup suffix (default ~ w/o --backup-dir)
 -u, --update                skip files that are newer on the receiver
     --inplace               update destination files in-place (SEE MAN PAGE)
     --append                append data onto shorter files
     --append-verify         like --append, but with old data in file checksum
 -d, --dirs                  transfer directories without recursing
 -l, --links                 copy symlinks as symlinks
 -L, --copy-links            transform symlink into referent file/dir
     --copy-unsafe-links     only "unsafe" symlinks are transformed
     --safe-links            ignore symlinks that point outside the source tree
     --munge-links           munge symlinks to make them safer (but unusable)
 -k, --copy-dirlinks         transform symlink to a dir into referent dir
 -K, --keep-dirlinks         treat symlinked dir on receiver as dir
 -H, --hard-links            preserve hard links
 -p, --perms                 preserve permissions
 -E, --executability         preserve the file's executability
     --chmod=CHMOD           affect file and/or directory permissions
 -A, --acls                  preserve ACLs (implies --perms)
 -X, --xattrs                preserve extended attributes
 -o, --owner                 preserve owner (super-user only)
 -g, --group                 preserve group
     --devices               preserve device files (super-user only)
     --copy-devices          copy device contents as regular file
     --specials              preserve special files
 -D                          same as --devices --specials
 -t, --times                 preserve modification times
 -O, --omit-dir-times        omit directories from --times
 -J, --omit-link-times       omit symlinks from --times
     --super                 receiver attempts super-user activities
     --fake-super            store/recover privileged attrs using xattrs
 -S, --sparse                turn sequences of nulls into sparse blocks
     --preallocate           allocate dest files before writing them
 -n, --dry-run               perform a trial run with no changes made
 -W, --whole-file            copy files whole (without delta-xfer algorithm)
     --checksum-choice=STR   choose the checksum algorithms
 -x, --one-file-system       don't cross filesystem boundaries
 -B, --block-size=SIZE       force a fixed checksum block-size
 -e, --rsh=COMMAND           specify the remote shell to use
     --rsync-path=PROGRAM    specify the rsync to run on the remote machine
     --existing              skip creating new files on receiver
     --ignore-existing       skip updating files that already exist on receiver
     --remove-source-files   sender removes synchronized files (non-dirs)
     --del                   an alias for --delete-during
     --delete                delete extraneous files from destination dirs
     --delete-before         receiver deletes before transfer, not during
     --delete-during         receiver deletes during the transfer
     --delete-delay          find deletions during, delete after
     --delete-after          receiver deletes after transfer, not during
     --delete-excluded       also delete excluded files from destination dirs
     --ignore-missing-args   ignore missing source args without error
     --delete-missing-args   delete missing source args from destination
     --ignore-errors         delete even if there are I/O errors
     --force                 force deletion of directories even if not empty
     --max-delete=NUM        don't delete more than NUM files
     --max-size=SIZE         don't transfer any file larger than SIZE
     --min-size=SIZE         don't transfer any file smaller than SIZE
     --partial               keep partially transferred files
     --partial-dir=DIR       put a partially transferred file into DIR
     --delay-updates         put all updated files into place at transfer's end
 -m, --prune-empty-dirs      prune empty directory chains from the file-list
     --numeric-ids           don't map uid/gid values by user/group name
     --usermap=STRING        custom username mapping
     --groupmap=STRING       custom groupname mapping
     --chown=USER:GROUP      simple username/groupname mapping
     --timeout=SECONDS       set I/O timeout in seconds
     --contimeout=SECONDS    set daemon connection timeout in seconds
 -I, --ignore-times          don't skip files that match in size and mod-time
 -M, --remote-option=OPTION  send OPTION to the remote side only
     --size-only             skip files that match in size
 -@, --modify-window=NUM     set the accuracy for mod-time comparisons
 -T, --temp-dir=DIR          create temporary files in directory DIR
 -y, --fuzzy                 find similar file for basis if no dest file
     --compare-dest=DIR      also compare destination files relative to DIR
     --copy-dest=DIR         ... and include copies of unchanged files
     --link-dest=DIR         hardlink to files in DIR when unchanged
 -z, --compress              compress file data during the transfer
     --compress-level=NUM    explicitly set compression level
     --skip-compress=LIST    skip compressing files with a suffix in LIST
 -C, --cvs-exclude           auto-ignore files the same way CVS does
 -f, --filter=RULE           add a file-filtering RULE
 -F                          same as --filter='dir-merge /.rsync-filter'
                             repeated: --filter='- .rsync-filter'
     --exclude=PATTERN       exclude files matching PATTERN
     --exclude-from=FILE     read exclude patterns from FILE
     --include=PATTERN       don't exclude files matching PATTERN
     --include-from=FILE     read include patterns from FILE
     --files-from=FILE       read list of source-file names from FILE
 -0, --from0                 all *-from/filter files are delimited by 0s
 -s, --protect-args          no space-splitting; only wildcard special-chars
     --address=ADDRESS       bind address for outgoing socket to daemon
     --port=PORT             specify double-colon alternate port number
     --sockopts=OPTIONS      specify custom TCP options
     --blocking-io           use blocking I/O for the remote shell
     --stats                 give some file-transfer stats
 -8, --8-bit-output          leave high-bit chars unescaped in output
 -h, --human-readable        output numbers in a human-readable format
     --progress              show progress during transfer
 -P                          same as --partial --progress
 -i, --itemize-changes       output a change-summary for all updates
     --out-format=FORMAT     output updates using the specified FORMAT
     --log-file=FILE         log what we're doing to the specified FILE
     --log-file-format=FMT   log updates using the specified FMT
     --password-file=FILE    read daemon-access password from FILE
     --list-only             list the files instead of copying them
     --bwlimit=RATE          limit socket I/O bandwidth
     --stop-at=y-m-dTh:m     Stop rsync at year-month-dayThour:minute
     --time-limit=MINS       Stop rsync after MINS minutes have elapsed
     --outbuf=N|L|B          set output buffering to None, Line, or Block
     --write-batch=FILE      write a batched update to FILE
     --only-write-batch=FILE like --write-batch but w/o updating destination
     --read-batch=FILE       read a batched update from FILE
     --protocol=NUM          force an older protocol version to be used
     --iconv=CONVERT_SPEC    request charset conversion of filenames
     --checksum-seed=NUM     set block/file checksum seed (advanced)
     --noatime               do not alter atime when opening source files
 -4, --ipv4                  prefer IPv4
 -6, --ipv6                  prefer IPv6
     --version               print version number
(-h) --help                  show this help (-h is --help only if used alone)

Use "rsync --daemon --help" to see the daemon-mode command-line options.
Please see the rsync(1) and rsyncd.conf(5) man pages for full documentation.
See http://rsync.samba.org/ for updates, bug reports, and answers

バックアップ実行コマンドと定期実行

バックアップコマンド(rsyncコマンド)

sudo rsync -aEv --delete-during --exclude-from=/rsync-exclude.txt / /mnt/synology/raspberry_backup/ --log-file=/home/rsync.log

crontabを編集

以下のcronジョブを登録します。
午前3時にrysyncコマンドを実行するように登録しました。
「sudo crontab -e」で編集します。

@raspberrypi:~ $ sudo crontab -e
0 3 * * * sudo rsync -a --delete-during --exclude-from=/rsync-exclude.txt / /mnt/synology/raspberry_backup/ --log-file=/home/rsync.log

[参考]cron コマンド

crontabファイルで、cron コマンドの実行を記述する行は、6つのフィールドで形成されています。(システムの crontabファイル(/etc/crontab)は、7つ(分、時、日、月、曜日、ユーザ名、コマンド))、コマンドの実行時間をさまざまな形式で指定することが出来ます。
初めてcrontabファイルを編集した場合には、何も書かれていませんので、以下のフォーマットで記述します。また、各フィールドでは、 '*' を使用することが可能です。

 分 時 日 月 曜日 コマンド
0~59
0~23
1~31
1~12 or jan~dec
曜日0~7 [0,7は日曜日] or sun~sat
コマンド有効なコマンドを記述します。空白を含むことも可能ですが、標準のBourne Shellの書式に従って記述します。

 

参考URL

Raspberry Piから外部ボリューム(NASをNFSマウント)利用

Raspberry Piからも信頼性の高いボリュームを利用したい、より大きなサイズのボリュームを利用したい、Raspberry Piの内容を外部にバックアップしたいなど、外部NASを利用したい利用シーンはたくさんあると思います。
今回は、WEBサーバなど各種サーバー機能のメインマシンであるSynologyのボリュームをNFSマウントし、利用する方法を記事にしておきます。

SynologyでNFS利用に向けた設定

SynologyでNFSを利用可能にする設定

「コントロールパネル」「ファイルサービス」「SMB/AFP/NFS」タブの「NFS」セッションの「NFSを有効にする」にチェックを入れて下さい。チェック後は、「適応」ボタンを押し、設定を反映して下さい。

SynologyでNFS利用設定1

共有フォルダにNFS権限付与

Synologyで作成した共有フォルダに対して、NFS権限の付与を行います。
「コントロールパネル」「共有フォルダ」対象の共有フォルダを選択して「編集」、「NFS権限」「編集」でNFSルールを指定します。
以下の例では、Raspberry Pi1台向け(IPアドレス:192.168.0.200)にNFS権限を付与しています。

SynologyでNFS利用設定2

共有フォルダのNFS権限画面に表示されている「マウントパス」は、実際のマウント時に必要となるので、内容のメモをお願いします。
マウント時のパス:<Synology_IPアドレス>:<マウントパス>
192.168.0.100:/volume1/raspberry

SynologyでNFS利用設定3

Raspberry PiからNASボリュームMount

Raspberry PiからmountコマンドでNASボリュームをNFSマウント

@raspberrypi:~ $ sudo apt-get install nfs-common
[sudo] password for:
nfs-common is already the newest version (1:1.3.4-2.5).
0 upgraded, 0 newly installed, 0 to remove and 26 not upgraded.

@raspberrypi:~ $ sudo showmount -e 192.168.0.100
Export list for 192.168.0.100:
/volume1/raspberry 192.168.0.200

@raspberrypi:~ $ sudo mkdir /mnt/synology

@raspberrypi:~ $ sudo mount -t nfs 192.168.0.100:/volume1/raspberry /mnt/synology

Raspberry Pi起動・再起動時のNFS自動マウント

/etc/fstabにNFSボリュームのマウント情報を記載

@raspberrypi:~ $ cat /etc/fstab
proc /proc proc defaults 0 0
/dev/mmcblk0p6 /boot vfat defaults 0 2
/dev/mmcblk0p7 / ext4 defaults,noatime 0 1
# a swapfile is not a swap partition, no line here
# use dphys-swapfile swap[on|off] for that
192.168.0.100:/volume1/raspberry /mnt/synology nfs defaults,_netdev 0 0

Raspberry PiのBootオプション変更(NASマウントタイミング変更)

Raspberry PiのBoot Option「Wait for Network at Boot Choose whether to wait for network」を変更します。「raspi-config」を用いて、設定を行います。

@raspberrypi:~ $ sudo raspi-config
  1. 3 Boot Options Configure options for start-up を選択
  2. B2 Wait for Network at Boot Chose whether to wait for network connection を選択
  3. <Yes> を選択
  4. <Ok> を選択

/etc/rc.localに3秒Sleep後マウント

上記の設定後も、起動・再起動時の自動マウントが実行されませんでした。
シスログ(/var/log/syslog)を確認したところ、やはり起動時のマウント時にNASへのネットワークマウントが失敗しておりました。
mount.nfs: Network is unreachable

Jul 9 06:36:22 raspberrypi rpc.mountd[473]: Version 1.3.3 starting
Jul 9 06:36:23 raspberrypi mount[489]: mount.nfs: Network is unreachable
Jul 9 06:36:23 raspberrypi systemd[1]: mnt-synology.mount: Mount process exited, code=exited, status=32/n/a
Jul 9 06:36:23 raspberrypi systemd[1]: mnt-synology.mount: Failed with result 'exit-code'.
Jul 9 06:36:23 raspberrypi systemd[1]: Failed to mount /mnt/synology.

よって、/etc/rc.localに3秒のスリープとマウントコマンドを追加しました。
sleep 3
sudo mount -t nfs 192.168.0.100:/volume1/raspberry /mnt/synology

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"
fi

#After Sleep 3, then execute a mount command
sleep 3
sudo mount -t nfs 192.168.0.100:/volume1/raspberry /mnt/synology

exit 0

その他ブートシーケンスに関係すると
(/etc/rc.localにスリープおよびマウントを入れた際には試す必要ないです)

systemd-networkd-wait-onlineを利用し、確実にネットワークの起動後にサービスを起動が出来るようにwaitが入るようです。
systemd-networkdとsystemd-networkd-wait-onlineをenableにします。

@raspberrypi:~ $ sudo systemctl enable systemd-networkd
Created symlink /etc/systemd/system/dbus-org.freedesktop.network1.service → /lib/systemd/system/systemd-networkd.service.
Created symlink /etc/systemd/system/multi-user.target.wants/systemd-networkd.service → /lib/systemd/system/systemd-networkd.service.
Created symlink /etc/systemd/system/sockets.target.wants/systemd-networkd.socket → /lib/systemd/system/systemd-networkd.socket.
Created symlink /etc/systemd/system/network-online.target.wants/systemd-networkd-wait-online.service → /lib/systemd/system/systemd-networkd-wait-online.service.
@raspberrypi:~ $ sudo systemctl enable systemd-networkd-wait-online

雨が降り出す前に通知①Google Homeスピーカーとライン通知

Use Case(ユースケース)

約2−3週間利用してみてのベストプラクティスです。

YahooのYOLP(地図)気象情報APIを利用し、緯度経度で指定した地点の10分間隔の天気予報を定期的にチェックし、トリガーとします。(日本ならではの細かなWEBサービスを利用します)
天候が「Rain(雨)」に変化した際に、Webhooks経由でトリガーを受け、LINEに通知および、トリガー発生時間(日中)を判定しGoogle Homeスピーカーで雨が降り出す旨のアナウンスを実行します。Google Homeスピーカーは就寝時間などには音声を出させたくないので、時間判定を入れました。

前提条件

  • Yahoo! JAPANのアプリケーションID Yahoo!JAPANアプリケーションの管理
    アプリケーションIDはYahoo! JAPAN IDをお持ちの方ならどなたでも登録できます。アプリケーションIDは、各リクエスト送信時に必要で、開発者自身ではなく、アプリケーションを特定します。
  • (Raspberry Piの)Google Home Notifier導入が終わっている「google-home-notifier」導入
  • (Raspberry Piの)PHPが動作するWEBサーバがセットアップされている
  • (Raspberry Piの)WEBサーバの公開設定が終わっている
  • IFTTTサービスの利用登録が実施済みである
  • IFTTTサービスにおいて、Webhooksが利用可能である IFTTT(イフト)でWebhooksの利用
  • IFTTTサービスにおいて、LINE Notifyの利用設定を実施している

全体の流れ

  1. Weather.phpファイルの準備
  2. Weather.phpの定期実行設定、雨を検知した時点でWEB APIへトリガー
  3. WEB APIよりLINEへメッセージ送信
  4. Google Homeスピーカーでアナウンス(Google Home Notifier経由)

Weather.phpファイルの準備

こちらのページを参照して下さい。

Weather.phpの定期実行

Synologyの「コントロールパネル」「タスクスケジューラー」で10分毎の実行で、以下のコマンドラインを登録して、定期実行しております。

/usr/local/bin/php72 /var/services/homes/user/weather.php

トリガー

Weather.phpよりRaspberry PiのWEB APIへ直接トリガーされます。3つのパラメータを設定します。

    • APIKEY=apikey
    • KEY=Weather
    • text="$text"
APIKEY=apikey&KEY=Weather&text="$text"

アクション

以下が、準備したサンプルのPHPスクリプトとなります。

//@HOME_API_LOG_NAME@ ログファイル名、書き込み権限が必要です
//@HOME_API_KEY@ POST受信時の簡易的なAPI-KEYのトークン確認を行います
//@IFTTT_POST_API_KEY@ IFTTTのWebhooks用API-KEY
//@GOOGLE_HOME_1@ http://192.168.0.200:9081 などgoogle home notifier向けのURL
//@GOOGLE_HOME_2@ google home notifier向けのURL
//@GOOGLE_HOME_3@ google home notifier向けのURL

//各種設定
//ログのファイル名
define("HOME_API_LOG_NAME","@HOME_API_LOG_NAME@");
//home-api-key
define("HOME_API_KEY","@HOME_API_KEY@");
//IFTTT用webhookパラメータ
define("IFTTT_POST_API_KEY","/with/key/@IFTTT_POST_API_KEY@");
define("IFTTT_POST_API_BASE","https://maker.ifttt.com/trigger/");
//IFTTT用puchLINE
define("IFTTT_LINE_KEY","pushLINE");

//google-home-notifier
define("GOOGLE_HOME_1",'@GOOGLE_HOME_1@');
define("GOOGLE_HOME_2",'@GOOGLE_HOME_2@');
define("GOOGLE_HOME_3",'@GOOGLE_HOME_3@');

function checkTime($startTime, $endTime) {
	$currentTime = date('H:i');
	if(strtotime($startTime) <= strtotime($currentTime) and strtotime($currentTime) <= strtotime($endTime)) {
		return true;
	}else{
		return false;
	}
}

function pushLINE($value1, $value2) {
	logger("Start pushLINE value1={$value1},value2={$value2}","INFO");
	$url = IFTTT_POST_API_BASE.IFTTT_LINE_KEY.IFTTT_POST_API_KEY;
	$data = array(
		'value1' => $value1,
		'value2' => $value2
	);
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_POST, TRUE);
	curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // jsonデータを送信
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
	$response = curl_exec($curl);
	$result = json_decode($response, true);
	curl_close($curl);
	return $result;
}

function announce($api_url, $text) {
	logger("Start announce target={$api_url},text={$text}","INFO");
	$message = 'text='.$text;
	$data = array(
		'text' => "$text"
	);
	$path = '/google-home-notifier';
	$url = $api_url.$path;
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_POST, TRUE);
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 証明書の検証を行わない
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  // curl_execの結果を文字列で返す
	curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // jsonデータを送信
	$response = curl_exec($curl);
	$result = json_decode($response, true);
	curl_close($curl);
	return $result;
}

function logger($text, $level) {
	$datetime = date('Y-m-d H:i:s');
	$date = date('Ym');
	$file_name = __DIR__ . "/log/log-home-{$date}.log";
	$text = "{$datetime} [{$level}] {$text}" . PHP_EOL;
	echo $text;
	if(!(file_exists($file_name))){
		touch($file_name);
		chmod($file_name, 0777);
	}
	return error_log(print_r($text, TRUE), 3, $file_name);
}

logger("Start API","INFO");

if(isset($_POST['KEY']) && strcmp($_POST['APIKEY'], HOME_API_KEY) == 0) {
	logger("KEY : ".$_POST['KEY'],"INFO");
	logger("TEXT : ".$_POST['TEXT'],"INFO");
	$text = $_POST['TEXT'];
	switch ($_POST['KEY']) {
	case 'Weather':
		logger("Start Weather","INFO");
		//Rain alert
		pushLine('【天気情報】',$text);
		if(checkTime('7:00','19:00')) {
			announce(GOOGLE_HOME_2, $text);
			announce(GOOGLE_HOME_3, $text);
		}
		if(checkTime('6:00','23:00')) {
			announce(GOOGLE_HOME_1, $text);
		}
		break;
	default:
		logger("This is private API. (in Default)","ERROR");
	}
}else{
	logger("This is private API. (in else)","ERROR");
}

アクション1

LINE送信のアクションを設定します。すでに、他ユースケースなどでIFTTT側にLINE送信のレシピを導入されている方は、アクション2の定義に進んで下さい。

アクション1:トリガー

アクセスキーなどの初期設定値は、IFTTTより取得して下さい。

IFTTT(イフト)でWebhooksの利用

//IFTTT用webhookパラメータ
define("IFTTT_POST_API_KEY","/with/key/アクセスキー");
define("IFTTT_POST_API_BASE","https://maker.ifttt.com/trigger/");
//IFTTT用puchLINE
define("IFTTT_LINE_KEY","pushLINE");

function pushLINE($value1, $value2) {
	$url = IFTTT_POST_API_BASE.IFTTT_LINE_KEY.IFTTT_POST_API_KEY;
	$data = array(
		'value1' => $value1,
		'value2' => $value2
	);
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_POST, TRUE);
	curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // jsonデータを送信
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
	$response = curl_exec($curl);
	$result = json_decode($response, true);
	curl_close($curl);
	return $result;
}

アクション1:IFTTTでのトリガー


Webhooksを{event}:pushLINEで設定します。

アクション1:アクション


LINE送信のアクションを定義します。
Recipientでラインの送付先を指定します。すでに作成しているLINEのグループにも送信することが出来ます。
Message部分は、自由に変更出来ます。今回は、PHPより2つの引数を渡しているので、2つの引数をMessageに入れております。pushLine('【テスト】',$text);

アクション2

Google Home Notifier経由で、Google Homeからアナウンスを流します。「google-home-notifier」導入

//@GOOGLE_HOME_1@ http://192.168.0.200:9081 などgoogle home notifier向けのURL
define("GOOGLE_HOME_1",'@GOOGLE_HOME_1@');

function announce($api_url, $text) {
	logger("Start announce target={$api_url},text={$text}","INFO");
	$message = 'text='.$text;
	$data = array(
		'text' => "$text"
	);
	$path = '/google-home-notifier';
	$url = $api_url.$path;
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_POST, TRUE);
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 証明書の検証を行わない
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  // curl_execの結果を文字列で返す
	curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // jsonデータを送信
	$response = curl_exec($curl);
	$result = json_decode($response, true);
	curl_close($curl);
	return $result;
}

announce(GOOGLE_HOME_1, $text);

時間判定について checkTime関数

対象時間の開始と終了を指定して、true, falseを戻り値とする関数を準備しました。
日付を跨る設定などは考慮しておりません。

checkTime('6:00','23:00')とすれば、朝6時から夜23時まで「True」となります。
このチェック関数を用いて、Google Homeのアナウンス対象時間か否かを確認しています。

function checkTime($startTime, $endTime) {
	$currentTime = date('H:i');
	if(strtotime($startTime) <= strtotime($currentTime) and strtotime($currentTime) <= strtotime($endTime)) {
		return true;
	}else{
		return false;
	}
}

 

Use Case(ユースケース)

Yahoo Japan防災速報の情報を利用します。YahooのIoTプラットフォームである「myThings」の利用も考えたのですが、トリガー登録の単位が細かすぎたりと煩雑だったので、防災速報のメール登録を行い、メール受信をトリガーにライン通知とGoogle Homeからのアナウンスを実現しております。Yahooからの防災速報メールの着信監視とメールの簡単な加工はMicrosoft Flowを利用しております。

前提条件

  • (Raspberry Piの)Google Home Notifier導入が終わっている「google-home-notifier」導入
  • (Raspberry Piの)PHPが動作するWEBサーバがセットアップされている
  • (Raspberry Piの)WEBサーバの公開設定が終わっている
  • IFTTTサービスの利用登録が実施済みである
  • IFTTTサービスにおいて、Webhooksが利用可能である IFTTT(イフト)でWebhooksの利用
  • IFTTTサービスにおいて、LINE Notifyの利用設定を実施している
  • Microsoft Flowの利用登録が実施済みである (YahooJapan防災速報メール受信をトリガー出来たらば別の方法で問題ないです)

全体の流れ

  1. Yahoo Japan防災速報のメール版に登録
    「通知先の設定(メールアドレス)」「地域の設定」および「通知する情報を選択」
  2. 今回は、GoogleのGmailを用いたので、Microsoft Flowを用いて、Yahoo Japan防災速報のメールをトラップします
  3. Yahoo Japan防災速報のメールタイトルのみを自分自身の防災速報専用のツイッタアカウントに発信します
  4. IFTTTのTwitterで自分自身の発信した防災速報情報をトラップし、防災速報情報受信時のアクションを実行(Raspberry PiのWEB APIへ発信)
  5. WEB APIよりLINEへメッセージ送信
  6. Google Homeスピーカーでアナウンス(Google Home Notifier経由)

トリガー

Yahoo Japan防災速報のメールをトラップし、Twitterで発信

MicrosoftFlowのGmailで準備されている「新しいメールが届いたとき」のトリガーに、「開始」にYahoo Japan防災速報が送付されてくるメールアドレスを登録します。
「alerts-emg@mail.yahoo.co.jp」
メールにはID情報などが含まれているため、メールの件名をツイートします。

Tweetをトリガーに設定

トリガー(this)に自分のTweetを受信した際をトリガーとします。「New tweet by you」のトリガーを選択します。

アクション

IFTTTのWebhooksよりRaspberry PiのWEB APIへPUSH通知

IFTTT(イフト)でWebhooksの利用

  • URL:準備したAPIのURLを指定します。外部からWEBアクセス可能なURLを指定する必要があります。また、APIトークンを送信するので、https:// での指定を強くオススメします。
  • Method:今回は、POST受信に対応したPHPを用いるので、POSTを指定します。
  • Content Type:application/x-www-form-urlencoded: キーと値は、その間に '=' がある形でキーと値の組になり、 '&' で区切られてエンコードされます。キーや値の英数字以外の文字は、パーセントエンコーディングされます。
  • Body:準備したPHPの仕様に合わせて、3つのパラメータを設定します。
    • APIKEY=apikey
    • KEY=Weather
    • text=" {{Text}}"
APIKEY=apikey&KEY=UrgentInfo&TEXT=" {{Text}}"

以下が、準備したサンプルのPHPスクリプトとなります。

//@HOME_API_LOG_NAME@ ログファイル名、書き込み権限が必要です
//@HOME_API_KEY@ POST受信時の簡易的なAPI-KEYのトークン確認を行います
//@IFTTT_POST_API_KEY@ IFTTTのWebhooks用API-KEY
//@GOOGLE_HOME_1@ http://192.168.0.200:9081 などgoogle home notifier向けのURL
//@GOOGLE_HOME_2@ google home notifier向けのURL
//@GOOGLE_HOME_3@ google home notifier向けのURL

//各種設定
//ログのファイル名
define("HOME_API_LOG_NAME","@HOME_API_LOG_NAME@");
//home-api-key
define("HOME_API_KEY","@HOME_API_KEY@");
//IFTTT用webhookパラメータ
define("IFTTT_POST_API_KEY","/with/key/@IFTTT_POST_API_KEY@");
define("IFTTT_POST_API_BASE","https://maker.ifttt.com/trigger/");
//IFTTT用puchLINE
define("IFTTT_LINE_KEY","pushLINE");

//google-home-notifier
define("GOOGLE_HOME_1",'@GOOGLE_HOME_1@');
define("GOOGLE_HOME_2",'@GOOGLE_HOME_2@');
define("GOOGLE_HOME_3",'@GOOGLE_HOME_3@');

function checkTime($startTime, $endTime) {
	$currentTime = date('H:i');
	if(strtotime($startTime) <= strtotime($currentTime) and strtotime($currentTime) <= strtotime($endTime)) {
		return true;
	}else{
		return false;
	}
}

function pushLINE($value1, $value2) {
	logger("Start pushLINE value1={$value1},value2={$value2}","INFO");
	$url = IFTTT_POST_API_BASE.IFTTT_LINE_KEY.IFTTT_POST_API_KEY;
	$data = array(
		'value1' => $value1,
		'value2' => $value2
	);
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_POST, TRUE);
	curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // jsonデータを送信
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
	$response = curl_exec($curl);
	$result = json_decode($response, true);
	curl_close($curl);
	return $result;
}

function announce($api_url, $text) {
	logger("Start announce target={$api_url},text={$text}","INFO");
	$message = 'text='.$text;
	$data = array(
		'text' => "$text"
	);
	$path = '/google-home-notifier';
	$url = $api_url.$path;
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_POST, TRUE);
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 証明書の検証を行わない
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  // curl_execの結果を文字列で返す
	curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // jsonデータを送信
	$response = curl_exec($curl);
	$result = json_decode($response, true);
	curl_close($curl);
	return $result;
}

function logger($text, $level) {
	$datetime = date('Y-m-d H:i:s');
	$date = date('Ym');
	$file_name = __DIR__ . "/log/log-home-{$date}.log";
	$text = "{$datetime} [{$level}] {$text}" . PHP_EOL;
	echo $text;
	if(!(file_exists($file_name))){
		touch($file_name);
		chmod($file_name, 0777);
	}
	return error_log(print_r($text, TRUE), 3, $file_name);
}

logger("Start API","INFO");

if(isset($_POST['KEY']) && strcmp($_POST['APIKEY'], HOME_API_KEY) == 0) {
	logger("KEY : ".$_POST['KEY'],"INFO");
	logger("TEXT : ".$_POST['TEXT'],"INFO");
	$text = $_POST['TEXT'];
	switch ($_POST['KEY']) {
	case 'UrgentInfo':
		logger("Start info from tweet","INFO");
		pushLine('【緊急通知】',$text);
		if(checkTime('7:00','19:00')) {
			announce(GOOGLE_HOME_2, $text);
			announce(GOOGLE_HOME_3, $text);
		}
		if(checkTime('6:00','23:00')) {
			announce(GOOGLE_HOME_1, $text);
		}
		break;
	default:
		logger("This is private API. (in Default)","ERROR");
	}
}else{
	logger("This is private API. (in else)","ERROR");
}

アクション1

LINE送信のアクションを設定します。すでに、他ユースケースなどでIFTTT側にLINE送信のレシピを導入されている方は、アクション2の定義に進んで下さい。

アクション1:トリガー

アクセスキーなどの初期設定値は、IFTTTより取得して下さい。

IFTTT(イフト)でWebhooksの利用

//IFTTT用webhookパラメータ
define("IFTTT_POST_API_KEY","/with/key/アクセスキー");
define("IFTTT_POST_API_BASE","https://maker.ifttt.com/trigger/");
//IFTTT用puchLINE
define("IFTTT_LINE_KEY","pushLINE");

function pushLINE($value1, $value2) {
	$url = IFTTT_POST_API_BASE.IFTTT_LINE_KEY.IFTTT_POST_API_KEY;
	$data = array(
		'value1' => $value1,
		'value2' => $value2
	);
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_POST, TRUE);
	curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // jsonデータを送信
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
	$response = curl_exec($curl);
	$result = json_decode($response, true);
	curl_close($curl);
	return $result;
}

アクション1:IFTTTでのトリガー


Webhooksを{event}:pushLINEで設定します。

アクション1:アクション


LINE送信のアクションを定義します。
Recipientでラインの送付先を指定します。すでに作成しているLINEのグループにも送信することが出来ます。
Message部分は、自由に変更出来ます。今回は、PHPより2つの引数を渡しているので、2つの引数をMessageに入れております。pushLine('【テスト】',$text);

アクション2

Google Home Notifier経由で、Google Homeからアナウンスを流します。「google-home-notifier」導入

//@GOOGLE_HOME_1@ http://192.168.0.200:9081 などgoogle home notifier向けのURL
define("GOOGLE_HOME_1",'@GOOGLE_HOME_1@');

function announce($api_url, $text) {
	logger("Start announce target={$api_url},text={$text}","INFO");
	$message = 'text='.$text;
	$data = array(
		'text' => "$text"
	);
	$path = '/google-home-notifier';
	$url = $api_url.$path;
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_POST, TRUE);
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 証明書の検証を行わない
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  // curl_execの結果を文字列で返す
	curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // jsonデータを送信
	$response = curl_exec($curl);
	$result = json_decode($response, true);
	curl_close($curl);
	return $result;
}

announce(GOOGLE_HOME_1, $text);

時間判定について checkTime関数

対象時間の開始と終了を指定して、true, falseを戻り値とする関数を準備しました。
日付を跨る設定などは考慮しておりません。

checkTime('6:00','23:00')とすれば、朝6時から夜23時まで「True」となります。
このチェック関数を用いて、Google Homeのアナウンス対象時間か否かを確認しています。

function checkTime($startTime, $endTime) {
	$currentTime = date('H:i');
	if(strtotime($startTime) <= strtotime($currentTime) and strtotime($currentTime) <= strtotime($endTime)) {
		return true;
	}else{
		return false;
	}
}

 

Use Case(ユースケース)

Google音声コマンド経由で別のGoogle Homeデバイスにメッセージ送信を行います。(Google Home Notifier経由)

前提条件

  • (Raspberry Piの)Google Home Notifier導入が終わっている「google-home-notifier」導入
  • (Raspberry Piの)PHPが動作するWEBサーバがセットアップされている
  • (Raspberry Piの)WEBサーバの公開設定が終わっている
  • IFTTTサービスの利用登録が実施済みである
  • IFTTTサービスにおいて、Webhooksが利用可能である IFTTT(イフト)でWebhooksの利用

全体の流れ

  1. IFTTTのGoogle Assistantより指定スピーカーへの音声コマンドをトリガートリガー登録
  2. IFTTTのWebhooksでPCシャットダウン時の音声コマンド受領時のアクションを実行(Raspberry PiのWEB APIへ発信)
  3. Google Homeスピーカーでアナウンス(Google Home Notifier経由)

トリガー

トリガー(this)にGoogle Assistantを選択し「Say a phrase with a text ingredient」のトリガーを選択します。これで、コマンドに加えて、不定形のテキストメッセージを追加出来ます。

音声コマンドを登録します。
「リビングにアナウンス $」
「リビングに連絡 $」などを登録します。
$部分が、不定形のテキスト部分となります。

コマンド受付時のGoogle Homeからのレスポンスも登録します。
「リビングにアナウンスします」

Lanuguage(言語)は、Japanese(日本語)を選択します。

アクション

IFTTTのWebhooksよりRaspberry PiのWEB APIへPUSH通知

IFTTT(イフト)でWebhooksの利用

  • URL:準備したAPIのURLを指定します。外部からWEBアクセス可能なURLを指定する必要があります。また、APIトークンを送信するので、https:// での指定を強くオススメします。
  • Method:今回は、POST受信に対応したPHPを用いるので、POSTを指定します。
  • Content Type:application/x-www-form-urlencoded: キーと値は、その間に '=' がある形でキーと値の組になり、 '&' で区切られてエンコードされます。キーや値の英数字以外の文字は、パーセントエンコーディングされます。
  • Body:準備したPHPの仕様に合わせて、3つのパラメータを設定します。
    • APIKEY=apikey
    • KEY=ExecAnnounce1 3台のスピーカーがあり、リビングを1としています。ExecAnnounce2を子供1、ExecAnnounce3を子供2としているので、それぞれ別のレシピとして登録します。
    • text={{TextField}} 不定形テキスト「$」部分となります。
APIKEY=apikey&KEY=ExecAnnounce1&text= {{TextField}}

以下が、準備したサンプルのPHPスクリプトとなります。

//@HOME_API_LOG_NAME@ ログファイル名、書き込み権限が必要です
//@HOME_API_KEY@ POST受信時の簡易的なAPI-KEYのトークン確認を行います
//@IFTTT_POST_API_KEY@ IFTTTのWebhooks用API-KEY
//@GOOGLE_HOME_1@ http://192.168.0.200:9081 などgoogle home notifier向けのURL
//@GOOGLE_HOME_2@ google home notifier向けのURL
//@GOOGLE_HOME_3@ google home notifier向けのURL

//各種設定
//ログのファイル名
define("HOME_API_LOG_NAME","@HOME_API_LOG_NAME@");
//home-api-key
define("HOME_API_KEY","@HOME_API_KEY@");

//google-home-notifier
define("GOOGLE_HOME_1",'@GOOGLE_HOME_1@');
define("GOOGLE_HOME_2",'@GOOGLE_HOME_2@');
define("GOOGLE_HOME_3",'@GOOGLE_HOME_3@');

function checkTime($startTime, $endTime) {
	$currentTime = date('H:i');
	if(strtotime($startTime) <= strtotime($currentTime) and strtotime($currentTime) <= strtotime($endTime)) {
		return true;
	}else{
		return false;
	}
}

function announce($api_url, $text) {
	logger("Start announce target={$api_url},text={$text}","INFO");
	$message = 'text='.$text;
	$data = array(
		'text' => "$text"
	);
	$path = '/google-home-notifier';
	$url = $api_url.$path;
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_POST, TRUE);
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 証明書の検証を行わない
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  // curl_execの結果を文字列で返す
	curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // jsonデータを送信
	$response = curl_exec($curl);
	$result = json_decode($response, true);
	curl_close($curl);
	return $result;
}

function logger($text, $level) {
	$datetime = date('Y-m-d H:i:s');
	$date = date('Ym');
	$file_name = __DIR__ . "/log/log-home-{$date}.log";
	$text = "{$datetime} [{$level}] {$text}" . PHP_EOL;
	echo $text;
	if(!(file_exists($file_name))){
		touch($file_name);
		chmod($file_name, 0777);
	}
	return error_log(print_r($text, TRUE), 3, $file_name);
}


logger("Start API KEY=".$_POST['KEY'],"INFO");

if(isset($_POST['KEY']) && strcmp($_POST['APIKEY'], HOME_API_KEY) == 0) {
	if(isset($_POST['TEXT'])){
		$text = $_POST['TEXT'];
	}
	switch ($_POST['KEY']) {
	case 'ExecAnnounce1': //Living
		logger("Start ExecAnnounce1","INFO");
		if(checkTime('6:00','22:00')) {
			announce(GOOGLE_HOME_1, $text);
		}
		break;
	case 'ExecAnnounce2': //Son
		logger("Start ExecAnnounce2","INFO");
		if(checkTime('6:00','22:00')) {
			announce(GOOGLE_HOME_2, $text);
		}
		break;
	case 'ExecAnnounce3': //Doughter
		logger("Start ExecAnnounce3","INFO");
		if(checkTime('6:00','22:00')) {
			announce(GOOGLE_HOME_3, $text);
		}
		break;
	default:
		logger("This is private API. (in Default)","ERROR");
	}
}else{
	logger("This is private API. (in else)","ERROR");
}

アクション

準備したPHPファイルより「@GOOGLE_HOME_1@ http://192.168.0.200:9081 などgoogle home notifier向けのURL」に設定したgoogle home notifier経由で対象スピーカーよりアナウンスを実施します。
夜間に音を出したくないので、checkTime($startTime, $endTime)関数を用いて、実行時の時間を確認しています。

Use Case(ユースケース)

ツイッターで電車の運行情報をトリガーとして、LINEに通知および、トリガー発生時間(日中)を判定しGoogle Homeスピーカーで雨が降り出す旨のアナウンスを実行します。Google Homeスピーカーは就寝時間などには音声を出させたくないので、時間判定を入れました。

前提条件

  • (Raspberry Piの)Google Home Notifier導入が終わっている「google-home-notifier」導入
  • (Raspberry Piの)PHPが動作するWEBサーバがセットアップされている
  • (Raspberry Piの)WEBサーバの公開設定が終わっている
  • IFTTTサービスの利用登録が実施済みである
  • IFTTTサービスにおいて、Webhooksが利用可能である IFTTT(イフト)でWebhooksの利用
  • IFTTTサービスにおいて、LINE Notifyの利用設定を実施している
  • IFTTTサービスにおいて、Twitterの利用設定を実施している

全体の流れ

  1. IFTTTのTwitterサービスで列車遅延情報をトリガー
  2. IFTTTのWebhooksで列車遅延情報取得時のアクションを実行(Raspberry PiのWEB APIへ発信)
  3. WEB APIよりLINEへメッセージ送信
  4. Google Homeスピーカーでアナウンス(Google Home Notifier経由)

トリガー

トリガー(this)にTwitterを選択します。

Twitterサービスには、多くのトリガーが準備されています。ここでは、New tweet from search(ツイートの検索)をトリガーとしています。

Twitterで検索するSearchワードを登録します。
「From:JRE_F_Tokaido 東海道線:『遅延』」
この例では、JR東海の東海道線に関するツイートより、更に東海道線の遅延情報に絞ってトリガーとして登録しています。

アクション

IFTTTのWebhooksよりRaspberry PiのWEB APIへPUSH通知

IFTTT(イフト)でWebhooksの利用

  • URL:準備したAPIのURLを指定します。外部からWEBアクセス可能なURLを指定する必要があります。また、APIトークンを送信するので、https:// での指定を強くオススメします。
  • Method:今回は、POST受信に対応したPHPを用いるので、POSTを指定します。
  • Content Type:application/x-www-form-urlencoded: キーと値は、その間に '=' がある形でキーと値の組になり、 '&' で区切られてエンコードされます。キーや値の英数字以外の文字は、パーセントエンコーディングされます。
  • Body:準備したPHPの仕様に合わせて、3つのパラメータを設定します。
    • APIKEY=apikey
    • KEY=TransportationInfo
    • text={{Text}}
APIKEY=apikey&KEY=TransportationInfo&TEXT={{Text}}

以下が、準備したサンプルのPHPスクリプトとなります。

//@HOME_API_LOG_NAME@ ログファイル名、書き込み権限が必要です
//@HOME_API_KEY@ POST受信時の簡易的なAPI-KEYのトークン確認を行います
//@IFTTT_POST_API_KEY@ IFTTTのWebhooks用API-KEY
//@GOOGLE_HOME_1@ http://192.168.0.200:9081 などgoogle home notifier向けのURL
//@GOOGLE_HOME_2@ google home notifier向けのURL

//各種設定
//ログのファイル名
define("HOME_API_LOG_NAME","@HOME_API_LOG_NAME@");
//home-api-key
define("HOME_API_KEY","@HOME_API_KEY@");
//IFTTT用webhookパラメータ
define("IFTTT_POST_API_KEY","/with/key/@IFTTT_POST_API_KEY@");
define("IFTTT_POST_API_BASE","https://maker.ifttt.com/trigger/");
//IFTTT用puchLINE
define("IFTTT_LINE_KEY","pushLINE");

//google-home-notifier
define("GOOGLE_HOME_1",'@GOOGLE_HOME_1@');
define("GOOGLE_HOME_2",'@GOOGLE_HOME_2@');

function checkTime($startTime, $endTime) {
	$currentTime = date('H:i');
	if(strtotime($startTime) <= strtotime($currentTime) and strtotime($currentTime) <= strtotime($endTime)) {
		return true;
	}else{
		return false;
	}
}

function pushLINE($value1, $value2) {
	logger("Start pushLINE value1={$value1},value2={$value2}","INFO");
	$url = IFTTT_POST_API_BASE.IFTTT_LINE_KEY.IFTTT_POST_API_KEY;
	$data = array(
		'value1' => $value1,
		'value2' => $value2
	);
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_POST, TRUE);
	curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // jsonデータを送信
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
	$response = curl_exec($curl);
	$result = json_decode($response, true);
	curl_close($curl);
	return $result;
}

function announce($api_url, $text) {
	logger("Start announce target={$api_url},text={$text}","INFO");
	$message = 'text='.$text;
	$data = array(
		'text' => "$text"
	);
	$path = '/google-home-notifier';
	$url = $api_url.$path;
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_POST, TRUE);
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 証明書の検証を行わない
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  // curl_execの結果を文字列で返す
	curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // jsonデータを送信
	$response = curl_exec($curl);
	$result = json_decode($response, true);
	curl_close($curl);
	return $result;
}

function logger($text, $level) {
	$datetime = date('Y-m-d H:i:s');
	$date = date('Ym');
	$file_name = __DIR__ . "/log/log-home-{$date}.log";
	$text = "{$datetime} [{$level}] {$text}" . PHP_EOL;
	echo $text;
	if(!(file_exists($file_name))){
		touch($file_name);
		chmod($file_name, 0777);
	}
	return error_log(print_r($text, TRUE), 3, $file_name);
}

logger("Start API KEY=".$_POST['KEY'],"INFO");
if(isset($_POST['KEY']) && strcmp($_POST['APIKEY'], HOME_API_KEY) == 0) {
	if(isset($_POST['TEXT'])){
		$text = $_POST['TEXT'];
	}
	switch ($_POST['KEY']) {
	case 'TransportationInfo':
		logger("Start TransportatioknInfo","INFO");
		//Delay warning
		$text = mb_substr($text,0,mb_strpos($text,'#',0,"UTF-8"),"UTF-8");
		pushLine('【公共交通機関情報】',$text);
		if(checkTime('6:00','22:00')) {
			announce(GOOGLE_HOME_1, $text);
		}
		if(checkTime('6:00','8:00')) {
			announce(GOOGLE_HOME_2, $text);
		}
		break;
	default:
		logger("This is private API. (in Default)","ERROR");
	}
}else{
	logger("This is private API. (in else)","ERROR");
}

ここでのポイントは、ツイート内容からハッシュタグ以降のURLリンクなどを切り取っている部分です。Twitterから取得したメッセージを、LINE送信やアナウンスに必要な部分に切り取って、次の処理に渡すとGoogle Homeからのアナウンスなどがスマートになります。

$text = mb_substr($text,0,mb_strpos($text,'#',0,"UTF-8"),"UTF-8");

アクション1

LINE送信のアクションを設定します。すでに、他ユースケースなどでIFTTT側にLINE送信のレシピを導入されている方は、アクション2の定義に進んで下さい。

アクション1:トリガー

アクセスキーなどの初期設定値は、IFTTTより取得して下さい。

IFTTT(イフト)でWebhooksの利用

//IFTTT用webhookパラメータ
define("IFTTT_POST_API_KEY","/with/key/アクセスキー");
define("IFTTT_POST_API_BASE","https://maker.ifttt.com/trigger/");
//IFTTT用puchLINE
define("IFTTT_LINE_KEY","pushLINE");

function pushLINE($value1, $value2) {
	$url = IFTTT_POST_API_BASE.IFTTT_LINE_KEY.IFTTT_POST_API_KEY;
	$data = array(
		'value1' => $value1,
		'value2' => $value2
	);
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_POST, TRUE);
	curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // jsonデータを送信
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
	$response = curl_exec($curl);
	$result = json_decode($response, true);
	curl_close($curl);
	return $result;
}

アクション1:IFTTTでのトリガー


Webhooksを{event}:pushLINEで設定します。

アクション1:アクション


LINE送信のアクションを定義します。
Recipientでラインの送付先を指定します。すでに作成しているLINEのグループにも送信することが出来ます。
Message部分は、自由に変更出来ます。今回は、PHPより2つの引数を渡しているので、2つの引数をMessageに入れております。pushLine('【テスト】',$text);

アクション2

Google Home Notifier経由で、Google Homeからアナウンスを流します。「google-home-notifier」導入

//@GOOGLE_HOME_1@ http://192.168.0.200:9081 などgoogle home notifier向けのURL
define("GOOGLE_HOME_1",'@GOOGLE_HOME_1@');

function announce($api_url, $text) {
	logger("Start announce target={$api_url},text={$text}","INFO");
	$message = 'text='.$text;
	$data = array(
		'text' => "$text"
	);
	$path = '/google-home-notifier';
	$url = $api_url.$path;
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_POST, TRUE);
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 証明書の検証を行わない
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);  // curl_execの結果を文字列で返す
	curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // jsonデータを送信
	$response = curl_exec($curl);
	$result = json_decode($response, true);
	curl_close($curl);
	return $result;
}

announce(GOOGLE_HOME_1, $text);

時間判定について checkTime関数

対象時間の開始と終了を指定して、true, falseを戻り値とする関数を準備しました。
日付を跨る設定などは考慮しておりません。

checkTime('6:00','23:00')とすれば、朝6時から夜23時まで「True」となります。
このチェック関数を用いて、Google Homeのアナウンス対象時間か否かを確認しています。

function checkTime($startTime, $endTime) {
	$currentTime = date('H:i');
	if(strtotime($startTime) <= strtotime($currentTime) and strtotime($currentTime) <= strtotime($endTime)) {
		return true;
	}else{
		return false;
	}
}

 

LINE通知のみはこちら

Use Case(ユースケース)

Nature Remoの室温室温センサーが30℃を超えた時点で、LINEに通知およびPCのシャットダウンを行う。

前提条件

  • Nature Remoがセットアップ済み
  • (Raspberry Piの)PHPが動作するWEBサーバがセットアップされている
  • (Raspberry Piの)WEBサーバの公開設定が終わっている
  • IFTTTサービスの利用登録が実施済みである
  • IFTTTサービスにおいて、Webhooksが利用可能である IFTTT(イフト)でWebhooksの利用
  • IFTTTサービスにおいて、LINE Notifyの利用設定を実施している
  • シャットダウン対象のPCにSSHサーバがインストールされている必要があります Raspberry Pi 3 B +にPHPのSSH2インストール

全体の流れ

  1. IFTTTのNature Remoより室温30℃以上をトリガートリガー登録
  2. IFTTTのWebhooksで天候変化時のアクションを実行(Raspberry PiのWEB APIへ発信)
  3. WEB APIよりLINEへメッセージ送信
  4. WEB APIよりPCをシャットダウン(SSH2経由でシャットコマンドを実行)

トリガー

トリガー(this)にNature Remoをを選択し「Temperature rises above」(室温上昇)のトリガーを選択します。
トリガーは以下が準備されております。

  • Temperature rises above : 室温上昇
  • Temperature drops below : 室温低下
  • Humidity rises above : 湿度上昇
  • Humidity drops below : 湿度低下
  • Becomes brighter : 部屋の照度上昇
  • Becomes darker : 部屋の照度低下

対象のNature Remoデバイスを選択

what value でトリガー対象の室温を設定します。今回は、30℃を指定しています。

アクション

IFTTTのWebhooksよりRaspberry PiのWEB APIへPUSH通知

IFTTT(イフト)でWebhooksの利用

  • URL:準備したAPIのURLを指定します。外部からWEBアクセス可能なURLを指定する必要があります。また、APIトークンを送信するので、https:// での指定を強くオススメします。
  • Method:今回は、POST受信に対応したPHPを用いるので、POSTを指定します。
  • Content Type:application/x-www-form-urlencoded: キーと値は、その間に '=' がある形でキーと値の組になり、 '&' で区切られてエンコードされます。キーや値の英数字以外の文字は、パーセントエンコーディングされます。
  • Body:準備したPHPの仕様に合わせて、3つのパラメータを設定します。
    • APIKEY=apikey
    • KEY=Weather
    • text="室温が{{ThresholdValue}}を超えます。現在の {{CreatedAt}}の室温は、 {{Value}}です。"
APIKEY=apikey&KEY=TemperatureWarn&TEXT=室温が{{ThresholdValue}}を超えます。現在の {{CreatedAt}}の室温は、 {{Value}}です。

以下が、準備したサンプルのPHPスクリプトとなります。

//@HOME_API_LOG_NAME@ ログファイル名、書き込み権限が必要です
//@HOME_API_KEY@ POST受信時の簡易的なAPI-KEYのトークン確認を行います
//@IFTTT_POST_API_KEY@ IFTTTのWebhooks用API-KEY
//@WINDOWS_IP@ WindowsのIPアドレス
//@WINDOWS_USER@ Windowsのユーザー名
//@WINDOWS_PASS@ Windowsのパスワード

//各種設定
//ログのファイル名
define("HOME_API_LOG_NAME","@HOME_API_LOG_NAME@");
//home-api-key
define("HOME_API_KEY","@HOME_API_KEY@");
//IFTTT用webhookパラメータ
define("IFTTT_POST_API_KEY","/with/key/@IFTTT_POST_API_KEY@");
define("IFTTT_POST_API_BASE","https://maker.ifttt.com/trigger/");
//IFTTT用puchLINE
define("IFTTT_LINE_KEY","pushLINE");
//Windows PC
define("WINDOWS_IP","@WINDOWS_IP@");
define("WINDOWS_USER","@WINDOWS_USER@");
define("WINDOWS_PASS","@WINDOWS_PASS@");

function pushLINE($value1, $value2) {
	logger("Start pushLINE value1={$value1},value2={$value2}","INFO");
	$url = IFTTT_POST_API_BASE.IFTTT_LINE_KEY.IFTTT_POST_API_KEY;
	$data = array(
		'value1' => $value1,
		'value2' => $value2
	);
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_POST, TRUE);
	curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // jsonデータを送信
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
	$response = curl_exec($curl);
	$result = json_decode($response, true);
	curl_close($curl);
	return $result;
}

function shutdownPC($target_ip, $target_user, $target_pass) {
	logger("Start shutdown target_ip:".$target_ip." target_user:".$target_user,"INFO");
	$shutdown_time = 60;
	$comment = "シャットダウン開始します。キャンセル「-a」";
	$cmd = 'shutdown /s /f /t '.$shutdown_time.' /c '.$comment;
	$connection = ssh2_connect($target_ip);
	ssh2_auth_password($connection, $target_user, $target_pass);
    $stream = ssh2_exec($connection, $cmd);
    $errorstream = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
    stream_set_blocking($stream, true);
    stream_set_blocking($errorstream, true);
    $ok =  stream_get_contents($stream);
   $ng =  stream_get_contents($errorstream); //エラーがあれば表示
   echo $ok;
   echo $ng;
}

function logger($text, $level) {
	$datetime = date('Y-m-d H:i:s');
	$date = date('Ym');
	$file_name = __DIR__ . "/log/log-home-{$date}.log";
	$text = "{$datetime} [{$level}] {$text}" . PHP_EOL;
	echo $text;
	if(!(file_exists($file_name))){
		touch($file_name);
		chmod($file_name, 0777);
	}
	return error_log(print_r($text, TRUE), 3, $file_name);
}

logger("Start API KEY=".$_POST['KEY'],"INFO");

if(isset($_POST['KEY']) && strcmp($_POST['APIKEY'], HOME_API_KEY) == 0) {
	if(isset($_POST['TEXT'])){
		$text = $_POST['TEXT'];
	}
	switch ($_POST['KEY']) {
	case 'TemperatureWarn':
		logger("Start TemperatureWarn","INFO");
		//Over 30
		pushLine('【リビング室温警告】',$text);
		shutdownPC(WINDOWS_IP, WINDOWS_USER, WINDOWS_PASS);
		break;
}else{
	logger("This is private API. (in else)","ERROR");
}

アクション1

LINE送信のアクションを設定します。すでに、他ユースケースなどでIFTTT側にLINE送信のレシピを導入されている方は、アクション2の定義に進んで下さい。

アクション1:トリガー

アクセスキーなどの初期設定値は、IFTTTより取得して下さい。

IFTTT(イフト)でWebhooksの利用

//IFTTT用webhookパラメータ
define("IFTTT_POST_API_KEY","/with/key/アクセスキー");
define("IFTTT_POST_API_BASE","https://maker.ifttt.com/trigger/");
//IFTTT用puchLINE
define("IFTTT_LINE_KEY","pushLINE");

function pushLINE($value1, $value2) {
	$url = IFTTT_POST_API_BASE.IFTTT_LINE_KEY.IFTTT_POST_API_KEY;
	$data = array(
		'value1' => $value1,
		'value2' => $value2
	);
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_POST, TRUE);
	curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($data)); // jsonデータを送信
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
	$response = curl_exec($curl);
	$result = json_decode($response, true);
	curl_close($curl);
	return $result;
}

アクション1:IFTTTでのトリガー


Webhooksを{event}:pushLINEで設定します。

アクション1:アクション


LINE送信のアクションを定義します。
Recipientでラインの送付先を指定します。すでに作成しているLINEのグループにも送信することが出来ます。

アクション2

準備したPHPファイルより「@WINDOWS_IP@:WindowsのIPアドレス、 @WINDOWS_USER@:Windowsのユーザー名、@WINDOWS_PASS@:Windowsのパスワード 」に設定したPCへSSH2用いてPCへログインし、シャットダウンコマンドを実行します。

google-tts-apiからHOYA社のVoice Text Web APIへ

Google Home Notifierは、テキストをgoogle-tts-apiを用いて、音声変換を実施しております。十分に内容は把握できますし、実利用には支障はないのですが、HOYA社より優れたWeb APIが公開されているので、HOYA社のVoice Text Web APIを用いた音声に変更を実施します。HOYA株式会社 VoiceText Web API

  1. HOYA社Voice Text Web APIの利用登録
  2. 「voicetext」のインストール
  3. google-home-notifierソースコード修正

参考サイト

前提条件

  • (Raspberry Piの)Google Home Notifier導入が終わっている「google-home-notifier」導入
  • (Raspberry Piの)PHPが動作するWEBサーバがセットアップされている

導入手順

1.HOYA社Voice Text Web APIの利用登録

HOYA株式会社 VoiceText Web APIの「無料利用登録」より利用登録を実施して下さい。利用登録後に、API KEYがメールで送付されて来ます。

2.「voicetext」のインストール

以下のコマンドで、「voicetext」をインストールします。

@raspberrypi:~ $ cd google-home-notifier/
@raspberrypi:~/google-home-notifier $ sudo npm update
@raspberrypi:~/google-home-notifier $ sudo npm install voicetext
npm WARN deprecated superagent@0.18.2: Please note that v5.0.1+ of superagent removes User-Agent header by default, therefore you may need to add it yourself (e.g. GitHub blocks requests without a User-Agent header). This notice will go away with v5.0.2+ once it is released.
+ voicetext@0.0.7
updated 1 package and audited 293 packages in 5.994s
found 8 vulnerabilities (3 low, 3 moderate, 2 high)
run `npm audit fix` to fix them, or `npm audit` for details

╭────────────────────────────────────────────────────────────────╮
│ │
│ New minor version of npm available! 6.9.0 → 6.10.0 │
│ Changelog: https://github.com/npm/cli/releases/tag/v6.10.0 │
│ Run npm install -g npm to update! │
│ │
╰────────────────────────────────────────────────────────────────╯

@raspberrypi:~/google-home-notifier $ sudo npm install -g npm
/usr/local/bin/npm -> /usr/local/lib/node_modules/npm/bin/npm-cli.js
/usr/local/bin/npx -> /usr/local/lib/node_modules/npm/bin/npx-cli.js
+ npm@6.10.0
added 14 packages from 10 contributors, removed 5 packages and updated 17 packages in 30.759s

3.google-home-notifierソースコード修正

「google-home-notifier.js」ファイルの修正

以下を参考に2箇所追加および1箇所コメントアウト(削除)を実施します。

var Client = require('castv2-client').Client;
var DefaultMediaReceiver = require('castv2-client').DefaultMediaReceiver;
var mdns = require('mdns');
var browser = mdns.createBrowser(mdns.tcp('googlecast'));
var deviceAddress;
var language;

//Add (((((((((((
var VoiceTextWriter = require('./VoiceTextWriter');
var voiceTextWriter = new VoiceTextWriter();
//Add ))))))))))

var device = function(name, lang = 'en') {
    device = name;
    language = lang;
    return this;
};

var ip = function(ip, lang = 'en') {
  deviceAddress = ip;
  language = lang;
  return this;
}

var googletts = require('google-tts-api');
var googlettsaccent = 'us';
var accent = function(accent) {
  googlettsaccent = accent;
  return this;
}

var notify = function(message, callback) {
  if (!deviceAddress){
    browser.start();
    browser.on('serviceUp', function(service) {
      console.log('Device "%s" at %s:%d', service.name, service.addresses[0], service.port);
      if (service.name.includes(device.replace(' ', '-'))){
        deviceAddress = service.addresses[0];
        getSpeechUrl(message, deviceAddress, function(res) {
          callback(res);
        });
      }
      browser.stop();
    });
  }else {
    getSpeechUrl(message, deviceAddress, function(res) {
      callback(res);
    });
  }
};

var play = function(mp3_url, callback) {
  if (!deviceAddress){
    browser.start();
    browser.on('serviceUp', function(service) {
      console.log('Device "%s" at %s:%d', service.name, service.addresses[0], service.port);
      if (service.name.includes(device.replace(' ', '-'))){
        deviceAddress = service.addresses[0];
        getPlayUrl(mp3_url, deviceAddress, function(res) {
          callback(res);
        });
      }
      browser.stop();
    });
  }else {
    getPlayUrl(mp3_url, deviceAddress, function(res) {
      callback(res);
    });
  }
};

var getSpeechUrl = function(text, host, callback) {
//Delete ((((((((((
//  googletts(text, language, 1, 1000, googlettsaccent).then(function (url) {
//    onDeviceUp(host, url, function(res){
//      callback(res)
//    });
//  }).catch(function (err) {
//    console.error(err.stack);
//  });
//Delete )))))))))
//Add (((((((((
	voiceTextWriter.convertToText(text).then(function(result, reject){
        onDeviceUp(host, result, function(res){
            callback(res)
        });
    }).catch(function onRejected(error){
		console.error(error);
	});
//Add )))))))))
};

var getPlayUrl = function(url, host, callback) {
    onDeviceUp(host, url, function(res){
      callback(res)
    });
};

var onDeviceUp = function(host, url, callback) {
  var client = new Client();
  client.connect(host, function() {
    client.launch(DefaultMediaReceiver, function(err, player) {

      var media = {
        contentId: url,
        contentType: 'audio/mp3',
        streamType: 'BUFFERED' // or LIVE
      };
      player.load(media, { autoplay: true }, function(err, status) {
        client.close();
        callback('Device notified');
      });
    });
  });

  client.on('error', function(err) {
    console.log('Error: %s', err.message);
    client.close();
    callback('error');
  });
};

exports.ip = ip;
exports.device = device;
exports.accent = accent;
exports.notify = notify;
exports.play = play;

「VoiceTextWriter.js」ファイルの新規作成

  • 「@APIKEY@」を利用登録時に受信したAPIKEYに変更
  • 「@WEBPAHT@」にWEBサーバの公開フォルダに格納する音声ファイルを指定
    例:/var/www/html/voice-text.wav
  • 「@URL@」にWEBサーバの音声ファイルへアクセスする際のURLを指定
    例:http://192.168.0.200/voice-text.wav
var fs = require('fs');
var VoiceText = require('voicetext');
var voice = new VoiceText('@APIKEY@');
var OUT_PATH = '@WEBPAHT@';
var OUTPUT_URL = '@URL@';

class VoiceTextWriter{

	convertToText(text){
	return new Promise(function(resolve,reject){
	voice
	.speaker(voice.SPEAKER.HIKARI)
	.emotion(voice.EMOTION.HAPPINESS)
//	.emotion_level(voice.EMOTION_LEVEL.HIGH)
	.emotion_level(2)
        .speed(100)
	.volume(120)
	.speak(text, function(e, buf){
	    if(e){
	      console.error(e);
	      reject(e);

	    }else{
	   	 fs.writeFileSync(OUT_PATH, buf, 'binary');
	  	 resolve(OUTPUT_URL);
	    }
	  });
	});
	}
}
module.exports = VoiceTextWriter;

VoiceText Web APIマニュアルを参考に、speedやvolumeなどのオプション指定を変更して、好みの音声に変更して下さい。設定ファイル変更時には、Google Home Notifierの再起動が必要となります。