コンテンツへスキップ

Twitterの影響力

WEBページのアクセスログを見ていると、Twitterからの訪問者が多く関心しています。昔のSEOとはかなり異なるなと体感しております。

システム公開後、数日後よりIFTTTを使ってTwitterへめざましじゃんけんの結果のTweetを開始しました。
当初、ホームページへの結果更新のついでにTwitterへ投稿をポストするぐらいの、ついで処理だったのですが、SNSらしいコミュニケーションがあったりと、文字数制限やハッシュタグなど各種成約の基で的確に内容を伝えるなど、Twitterワールドに興味を持ちました。(IFTTTでも問題なく投稿できます、ただ、自分でのTwitterアプリ作成が気になったので、PHPからのTweetを実装しました)

Twitterへアプリ登録。(PHPから実行時に必要となる、API Key、API Secret Key、Access Token、Access Token Secretの取得を行います

PHPからTwitterへの操作は、TwitterOAuthというライブラリを用います。

  1. Twitterへの開発者登録とApp登録
  2. TwitterOAuth導入

Twitterへの開発者登録とApp登録

Twitterの以下のURLへアクセスし、登録をすすめます。

上のURLよりサービスにログインします。

ログイン後の画面です。右上の「Create an app」をクリックします。

「Create an app」を押すと、最初に開発者アカウントへの登録へ誘導されます。

一番の開発理由を聞かれます。「Professional」これは通常ビジネス目的、「Hobbyist」趣味なので、個人利用は通常このカテゴリとおもいます。
今回は、API経由でTweet・Postを行いたいので、Making a botを選択しました。BOTの作成。

アカウント情報の確認をされました。通常問題ないと思います。

1,居住国、2.呼び名を聞かれました。それぞれ、Japanと「(適当に)mikiie」と登録しました。

アプリの説明をフリーフォーマットで記載しました。
WEB翻訳などで準備した内容を貼り付ければOKです。

追加の質問事項に回答します。
Twitter情報を分析Analyzeするか? Tweet,Retweet, Like, Followなどを実施するか?
また、フリーフォーマットで、開発対象の機能内容の記載が必要でした。

これまでに記載や選択した内容のサマリ画面が表示され、間違いがないか確認を行い、「Looks good!」を選択します。

最後に利用規約に同意し、Submit Application(申請を送信)すると、利用申請の結果がメールで届きます。

利用申請後に着信した利用申請結果メールのリンクよりアクセスすると、Welcome画面が表示されます。

開発登録者完了後に、Create an appを実施するとアプリの作成が行えます。
コールバックURLを登録する必要がありまうが、PHPが動作するサーバで問題ないとおもますので、http://127.0.0.1/と登録しておけば問題ないです。

最後に、Keys and TokensでアプリのAPI Keysを取得します。
(API Key、API Secret Key、Access Token、Access Token Secretの取得を行います

TwitterOAuth導入

TwitterOAuth

GitHub-twitteroauth

V1.1 Source code (tar.gz)

TwitterOAuthをダウンロードし、PHPを利用したいサーバに配置します。

  1. ライブラリのダウンロード
  2. tar.gzの解凍
  3. フォルダ名の変更
$ curl -OL https://github.com/abraham/twitteroauth/archive/1.0.1.tar.gz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 127 0 127 0 0 285 0 --:--:-- --:--:-- --:--:-- 294
100 714k 0 714k 0 0 475k 0 --:--:-- 0:00:01 --:--:-- 1674k

$ tar -zxvf 1.0.1.tar.gz
$ mv twitteroauth-1.0.1/ twitteroauth/

以下、一番簡単なサンプルPHPです。

<?php
require "twitteroauth/autoload.php";
use Abraham\TwitterOAuth\TwitterOAuth;

$consumerKey = "your consumer key";
$consumerSecret = "your consumer secret";
$accessToken = "your access token";
$accessTokenSecret = "your access token secret";

$twitter = new TwitterOAuth($consumerKey, $consumerSecret, $accessToken, $accessTokenSecret);

$result = $twitter->post(
        "statuses/update",
        array("status" => "Tweetテスト")
);

if($twitter->getLastHttpCode() == 200) {
    print "tweeted\n";
} else {  &nbsp; 
 &nbsp;  print "tweet failed\n";
}
以下が私が作成したスクリプトです。
  • 利用したいハッシュタグを配列登録 (ランダムで選択)
  • 280バイト(日本語140文字)対応
    文字数に関しては、投稿できる文字数が多くなっているようです。
    $max_char = 280 + $url_char - 11.5;の「$max_char」を大きくして、投稿可能な最大文字数を確認してください。
  • ハッシュタグの「ー(ハイフン)」を「_(アンダースコアー)」へ
  • URL1件投稿時の最大文字数対応(必要に応じてハッシュタグ部分を削減で対応)
  • めざましじゃんけん固有
    • DBより最新のめざましじゃんけん結果を取得
    • ハッシュタグ時に不要なキーワードを削除
      「アナウンサー」「○○さん」「生じゃんけん!」
<?php
require "twitteroauth/autoload.php";
use Abraham\TwitterOAuth\TwitterOAuth;
define("TARGET_TWEET",0);
##TARGET_TWEET 0 is newest record
$host_name = '127.0.0.1:3307';
$user_name = '@DB_User@';
$password = '@DB_Pass@';
$database_name ='@DB_Name@';
$table_name = '@DB_Table@';

function postTweet($value1, $value2) {
    $consumerKey = "@consumerKey@";
    $consumerSecret = "@consumerSecret@";
    $accessToken = "@accessToken@";
    $accessTokenSecret = "@accessTokenSecret@";
    $tweet_text = $value1.PHP_EOL.$value2;
    //文字数 URL=11.5文字、全角140文字、半角のみ280文字
    $url_char = strlen('https://www.miki-ie.com/mezamashi-jyanken/');
    $max_char = 280 + $url_char - 11.5;
    while(strlen($tweet_text) > $max_char){
        $tweet_text = mb_strrchr($tweet_text,'#',true,'UTF-8');
    }
    $twitter = new TwitterOAuth($consumerKey, $consumerSecret, $accessToken, $accessTokenSecret);
    $result = $twitter->post(
     "statuses/update",
             array("status" => $tweet_text)
                );
    if($twitter->getLastHttpCode() == 200) {
        return true;
    } else {
        return false;
    }
}

$mysqli = new mysqli($host_name, $user_name, $password, $database_name);
if ($mysqli->connect_error) {
echo$mysqli->connect_error;
exit();
} else {
$mysqli->set_charset("utf8");
}
$table = "";
$sql = "SELECT id, times, tv, who FROM janken order by datetime desc limit 30";
if ($result = $mysqli->query($sql)) {
    $result = $mysqli->query($sql);
    $counter = 0;
while ($row = $result->fetch_assoc()) {
        if ($counter == TARGET_TWEET){
            $value1 = "【".$row["times"]."戦目】めざましじゃんけん結果速報:".PHP_EOL;
            if($row["tv"] == 1){
                $value1 = $value1."グー(Goo)"."でした。パーが勝ちです。";
            }elseif($row["tv"] == 2){
                $value1 = $value1."チョキ(Chyoki)"."でした。グーが勝ちです。";
            }else{
                $value1 = $value1."パー(Pa)"."でした。チョキが勝ちです。";
            }
            $value1 = $value1.PHP_EOL."今回の相手:".$row["who"];
            $who = $row["who"];
            $num_week = date('w', strtotime($row["datetime"]));
        }
        $counter++;
}
$result->close();
}
$mysqli->close();

$who = str_replace('さん','',$who);
$who = str_replace('アナウンサー','',$who);
$who = str_replace('リポーター','',$who);
$who = str_replace('キャスター','',$who);
$who = str_replace('生じゃんけん!','',$who);
//全角スペースを半角スペースに変換、句読点やカッコも半角スペースに変換
$who = str_replace(' ', ' ', $who);
$who = str_replace('、', ' ', $who);
$who = str_replace('(', ' ', $who);
$who = str_replace(')', ' ', $who);
//前後のスペース削除
$who = trim($who);
//連続する半角スペースを半角スペースひとつに変換
$who = preg_replace('/\s+/', ' ', $who);
//半角スペースで分割
$whos = explode(' ',$who);
$addhash = "";
if($num_week == 6) $addhash = " #めざましどようび";
foreach($whos as $this_who){
    $this_who = str_replace('-', '_', $this_who);
    $addhash = $addhash." #".$this_who;
}
$addhash2 = "";
$addhash_ary = array("機械学習", "AI", "深層学習", "DNN", "Darknet", "YOLO", "mikiie", "みきいえ", "ニューラルネットワーク");
shuffle($addhash_ary);
foreach($addhash_ary as $this_hash){
    $addhash2 = $addhash2." #".$this_hash;
}
$value2 = "By https://www.miki-ie.com/mezamashi-jyanken/".PHP_EOL." #めざましじゃんけん #めざましテレビ #人工知能".$addhash.$addhash2." ";
postTweet($value1, $value2);
?>

めざましじゃんけん 画像検出システム

めざましじゃんけん結果

テレビ画像よりフジテレビ、めざましテレビ内で実施されるめざましじゃんけんの結果を蓄積します。めざましじゃんけんは、デジタル放送のコンテンツであり、B-CAS(ビーキャス)カードを用いた受信装置により、データ放送を画面表示させ、リアルタイムに参加する必要があります。番組録画しても、データ放送のコンテンツは録画することは出来ません。

  1. 環境準備(インストール)

  2. Darknet/YOLOで学習モデル準備

  3. システム実装と結果公開

  4. プログラム関連技術

本システム実装に関連した、プログラム関係のTips。

    • PHP
    • Python
  1. 【機械学習導入】ニューラルネットワークChainerフレームワーク

    • 導入方法
    • 機械学習
    • Out of Memoryへの対応
    • 学習モデルのRaspberry Piでの実行

メールデータのマルチバイト文字処理

SendGridのInbound Email Parse向けPHP APIを公開しましたが、受信メールアドレスにより文字化けが発生しました。
SendGrid-Inbound Email Parse Webhookでメール受信をPUSH(第2回)
PHPでメールデータ取り扱い時には、サーバー側のphp.iniなどの文字コード設定など、よく悩まされます。
SendGridでは、POSTでcharsetsデータを送付してきますが、そのcharsetを用いてmb_convert_encoding関数を用いて、エンコーディング変換しても文字化けが発生しました。

文字コード変換の便利な関数を紹介しているサイトがありましたので、紹介しておきます。これらの関数を用いて、文字化けが解消するケースもありました。
基本的に、今回のSendGridnoInbound Email Parseは、IoT向けなので、必要な部分を絞ってメール情報を利用すれば運用回避もできるのですが、文字コードの正確なでコード処理が必要な場面もあり、以下関数を利用させて頂きました。

/**
 * 日本語文字列の文字コード判定(ASCII/JIS/eucJP-win/SJIS-win/UTF-8 のみ)
 */
function detect_encoding_ja( $str )
{
    $enc = @mb_detect_encoding( $str, 'ASCII,JIS,eucJP-win,SJIS-win,UTF-8' );

    switch ( $enc ) {
    case FALSE   :
    case 'ASCII' : 
    case 'JIS'   : 
    case 'UTF-8' : break;
    case 'eucJP-win' :
        // ここで eucJP-win を検出した場合、eucJP-win として判定
        if ( @mb_detect_encoding( $str, 'SJIS-win,UTF-8,eucJP-win' ) === 'eucJP-win' ) {
            break;
        }
        $_hint = "\xbf\xfd" . $str; // "\xbf\xfd" : EUC-JP "雀"

        // EUC-JP -> UTF-8 変換時にマッピングが変更される文字を削除( ≒ ≡ ∫ など)
        mb_regex_encoding( 'EUC-JP' );
        $_hint = mb_ereg_replace( "\xad(?:\xe2|\xf5|\xf6|\xf7|\xfa|\xfb|\xfc|\xf0|\xf1|\xf2)", '', $_hint );

        $_tmp  = mb_convert_encoding( $_hint, 'UTF-8', 'eucJP-win' );
        $_tmp2 = mb_convert_encoding( $_tmp,  'eucJP-win', 'UTF-8' );
        if ( $_tmp2 === $_hint ) {

            // 例外処理( EUC-JP 以外と認識する範囲 )
            if (
                // SJIS と重なる範囲(2バイト|3バイト|iモード絵文字|1バイト文字)
                ! preg_match( '/^(?:'
                    . '[\x8E\xE0-\xE9][\x80-\xFC]|\xEA[\x80-\xA4]|'
                    . '\x8F[\xB0-\xEF][\xE0-\xEF][\x40-\x7F]|'
                    . '\xF8[\x9F-\xFC]|\xF9[\x40-\x49\x50-\x52\x55-\x57\x5B-\x5E\x72-\x7E\x80-\xB0\xB1-\xFC]|'
                    . '[\x00-\x7E]'
                    . ')+$/', $str ) && 

                // UTF-8 と重なる範囲(全角英数字|漢字|1バイト文字)
                ! preg_match( '/^(?:'
                    . '\xEF\xBC[\xA1-\xBA]|[\x00-\x7E]|'
                    . '[\xE4-\xE9][\x8E-\x8F\xA1-\xBF][\x8F\xA0-\xEF]|'
                    . '[\x00-\x7E]'
                    . ')+$/', $str )
            ) {
                // 条件式の範囲に入らなかった場合は、eucJP-win として検出
                break;
            }
            // 例外処理2(一部の頻度の多そうな熟語は eucJP-win として判定)
            // (珈琲|琥珀|瑪瑙|癇癪|碼碯|耄碌|膀胱|蒟蒻|薔薇|蜻蛉)
            if ( mb_ereg( '^(?:'
                . '\xE0\xDD\xE0\xEA|\xE0\xE8\xE0\xE1|\xE0\xF5\xE0\xEF|\xE1\xF2\xE1\xFB|'
                . '\xE2\xFB\xE2\xF5|\xE6\xCE\xE2\xF1|\xE7\xAF\xE6\xF9|\xE8\xE7\xE8\xEA|'
                . '\xE9\xAC\xE9\xAF|\xE9\xF1\xE9\xD9|[\x00-\x7E]'
                . ')+$', $str )
            ) {
                break;
            }
        }

    default :
        // ここで SJIS-win と判断された場合は、文字コードは SJIS-win として判定
        $enc = @mb_detect_encoding( $str, 'UTF-8,SJIS-win' );
        if ( $enc === 'SJIS-win' ) {
            break;
        }
        // デフォルトとして SJIS-win を設定
        $enc   = 'SJIS-win';

        $_hint = "\xe9\x9b\x80" . $str; // "\xe9\x9b\x80" : UTF-8 "雀"

        // 変換時にマッピングが変更される文字を調整
        mb_regex_encoding( 'UTF-8' );
        $_hint = mb_ereg_replace( "\xe3\x80\x9c", "\xef\xbd\x9e", $_hint );
        $_hint = mb_ereg_replace( "\xe2\x88\x92", "\xe3\x83\xbc", $_hint );
        $_hint = mb_ereg_replace( "\xe2\x80\x96", "\xe2\x88\xa5", $_hint );

        $_tmp  = mb_convert_encoding( $_hint, 'SJIS-win', 'UTF-8' );
        $_tmp2 = mb_convert_encoding( $_tmp,  'UTF-8', 'SJIS-win' );

        if ( $_tmp2 === $_hint ) {
            $enc = 'UTF-8';
        }
        // UTF-8 と SJIS 2文字が重なる範囲への対処(SJIS を優先)
        if ( preg_match( '/^(?:[\xE4-\xE9][\x80-\xBF][\x80-\x9F][\x00-\x7F])+/', $str ) ) {
            $enc = 'SJIS-win';
        }
    }
    return $enc;
}


function decode_mimeheader($str) {

	$enc_r = array(  // 変換するエンコーディング文字列を設定
		'iso-2022-jp' => 'iso-2022-jp-ms' ,
		'shift_jis'   => 'sjis-win'       ,
		'euc-jp'      => 'eucjp-win'      ,
		'utf-8'       => 'utf-8'
	);

	$str = str_replace('?==?' , "?=\n=?" , $str);  // 未改行文字対策

	$decode_str = '';
	foreach (preg_split('/\s/', $str) as $split_str) {  // SPACEで分割
		if (strlen($split_str) === 0) {
			$decode_str .= ($decode_str != '' ? ' ' : '');
			continue;
		}
		
		if (preg_match('/(.*?)=\?([^\?]+)\?([^\?]+)\?([^\?]+)\?=(.*?)$/', $split_str, $matches)) {  // =?[Charset]?[Text]?=形式の場合
			$enc_str  = strtolower($matches[2]);
			$from_enc = isset($enc_r[$enc_str]) ? $enc_r[$enc_str] : $enc_str;
			if ($from_enc != '') {
				$match_decode = null;
				switch (strtoupper($matches[3])) {
					case 'B':
						$match_decode = base64_decode($matches[4]);
						break;
					case 'Q':
						$match_decode = quoted_printable_decode($matches[4]);
						break;
				}
				if (!is_null($match_decode)) { // 変換に成功した場合のみ変換後テキストを追加し次の行へ
					$decode_str .= ($decode_str != '' ? ' ' : '') . mb_convert_encoding( $matches[1].$match_decode.$matches[5], mb_internal_encoding(), $from_enc );
					continue;
				}
			}
		}
		// 変換できなかった場合、又は =?[Charset]?[Text]?= 形式でなかった場合は、テキストのキャラクタセットを自動検出して変換する
		$decode_str .= ($decode_str != '' ? ' ' : '') . mb_convert_encoding( $split_str , mb_internal_encoding() , detect_encoding_ja( $split_str ) );
	}

	return $decode_str;
}

PHPのPEARライブラリーをインストール

開発自体は、できる限り手間をかけずに各種実装を進めているのですが、プログラム言語により各種フレームワークが準備されております。
最近の利用実態は分かりませんが、以前はPHPのPEARは、各所で利用されておりました。
Raspberry Piに導入した際の手順を公開します。

マニュアルでインストールしようとしたのですが、apt-getしたソフトで全く問題なかったので、簡単な手順となりました。
DBを例に、個別の機能をインストールする際の例も記載しております。

sudo apt-get install php-pear
sudo pear update-channels
sudo pear upgrade-all
sudo pear install DB

導入時点では、以下のパッケージがインストールされていました。

  • Archive_Tar 1.4.7 stable
  • Console_Getopt 1.4.2 stable
  • PEAR 1.10.9 stable
  • PEAR_Manpages 1.10.0 stable
  • Structures_Graph 1.1.1 stable
  • XML_Util 1.4.3 stable

以下、コマンド抜粋です。

@raspberrypi:~ $ sudo apt-get install php-pear
[sudo] パスワード:
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています
状態情報を読み取っています... 完了
php-pear はすでに最新バージョン (1:1.10.6+submodules+notgz-1.1) です。
php-pear は手動でインストールしたと設定されました。
アップグレード: 0 個、新規インストール: 0 個、削除: 0 個、保留: 0 個。

@raspberrypi:~ $ pear -h
Commands:
build Build an Extension From C Source
bundle Unpacks a Pecl Package
channel-add Add a Channel
channel-alias Specify an alias to a channel name
channel-delete Remove a Channel From the List
channel-discover Initialize a Channel from its server
channel-info Retrieve Information on a Channel
channel-login Connects and authenticates to remote channel server
channel-logout Logs out from the remote channel server
channel-update Update an Existing Channel
clear-cache Clear Web Services Cache
config-create Create a Default configuration file
config-get Show One Setting
config-help Show Information About Setting
config-set Change Setting
config-show Show All Settings
convert Convert a package.xml 1.0 to package.xml 2.0 format
cvsdiff Run a "cvs diff" for all files in a package
cvstag Set CVS Release Tag
download Download Package
download-all Downloads each available package from the default channel
info Display information about a package
install Install Package
list List Installed Packages In The Default Channel
list-all List All Packages
list-channels List Available Channels
list-files List Files In Installed Package
list-upgrades List Available Upgrades
login Connects and authenticates to remote server [Deprecated in favor of channel-login]
logout Logs out from the remote server [Deprecated in favor of channel-logout]
makerpm Builds an RPM spec file from a PEAR package
package Build Package
package-dependencies Show package dependencies
package-validate Validate Package Consistency
pickle Build PECL Package
remote-info Information About Remote Packages
remote-list List Remote Packages
run-scripts Run Post-Install Scripts bundled with a package
run-tests Run Regression Tests
search Search remote package database
shell-test Shell Script Test
sign Sign a package distribution file
svntag Set SVN Release Tag
uninstall Un-install Package
update-channels Update the Channel List
upgrade Upgrade Package
upgrade-all Upgrade All Packages [Deprecated in favor of calling upgrade with no parameters]
Usage: pear [options] command [command-options] <parameters>
Type "pear help options" to list all options.
Type "pear help shortcuts" to list all command shortcuts.
Type "pear help version" or "pear version" to list version information.
Type "pear help <command>" to get the help for the specified command.

@raspberrypi:~ $ sudo pear update-channels
Updating channel "doc.php.net"
could not create lock file: fopen(/usr/share/php/.lock): failed to open stream: Permission denied
Updating channel "pear.php.net"
could not create lock file: fopen(/usr/share/php/.lock): failed to open stream: Permission denied
Updating channel "pecl.php.net"
could not create lock file: fopen(/usr/share/php/.lock): failed to open stream: Permission denied
miki@raspberrypi:~ $ sudo pear update-channels
Updating channel "doc.php.net"
Update of Channel "doc.php.net" succeeded
Updating channel "pear.php.net"
Update of Channel "pear.php.net" succeeded
Updating channel "pecl.php.net"
Update of Channel "pecl.php.net" succeeded

@raspberrypi:~ $ sudo pear upgrade-all
Will upgrade channel://pear.php.net/console_getopt
Will upgrade channel://pear.php.net/archive_tar
Will upgrade channel://pear.php.net/pear
WARNING: "pear/Console_Getopt" is deprecated in favor of "pear/Console_GetoptPlus"
downloading Console_Getopt-1.4.2.tgz ...
Starting to download Console_Getopt-1.4.2.tgz (5,736 bytes)
.....done: 5,736 bytes
downloading Archive_Tar-1.4.7.tgz ...
Starting to download Archive_Tar-1.4.7.tgz (21,134 bytes)
...done: 21,134 bytes
downloading PEAR-1.10.9.tgz ...
Starting to download PEAR-1.10.9.tgz (291,931 bytes)
...done: 291,931 bytes
upgrade-all ok: channel://pear.php.net/Console_Getopt-1.4.2
upgrade-all ok: channel://pear.php.net/Archive_Tar-1.4.7

Warning: "continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"? in PEAR/PackageFile/v1.php on line 1424
PHP Warning: "continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"? in /usr/share/php/PEAR/PackageFile/v1.php on line 1424
upgrade-all ok: channel://pear.php.net/PEAR-1.10.9
PEAR: Optional feature webinstaller available (PEAR's web-based installer)
PEAR: Optional feature gtkinstaller available (PEAR's PHP-GTK-based installer)
PEAR: Optional feature gtk2installer available (PEAR's PHP-GTK2-based installer)
PEAR: To install optional features use "pear install pear/PEAR#featurename"

@raspberrypi:~ $ sudo pear list
Installed packages, channel pear.php.net:
=========================================
Package Version State
Archive_Tar 1.4.7 stable
Console_Getopt 1.4.2 stable
PEAR 1.10.9 stable
PEAR_Manpages 1.10.0 stable
Structures_Graph 1.1.1 stable
XML_Util 1.4.3 stable
miki@raspberrypi:~ $

miki@raspberrypi:~ $ php -v
PHP 7.3.4-2 (cli) (built: Apr 13 2019 19:05:48) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.4, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.3.4-2, Copyright (c) 1999-2018, by Zend Technologies

@raspberrypi:~ $ sudo pear install DB
WARNING: "pear/DB" is deprecated in favor of "pear/MDB2"
downloading DB-1.9.3.tgz ...
Starting to download DB-1.9.3.tgz (132,290 bytes)
.............................done: 132,290 bytes
install ok: channel://pear.php.net/DB-1.9.3

東芝REGZAテレビをWEB APIで操作

テレビの状態が知りたいと思い、東芝のレグザ(REGZA)に何かI/F(インターフェース)がないのか、調べてみました。
残念ながら、テレビの状態は分かりませんでした。電源が付いていなければ、なにかのコマンドやネットワーク接続失敗などで、電源ONかOFFぐらいは分かるかなが現時点の結果です。操作系は、Nature RemoのAPIやNature Remo経由のIFTTTが利用しやすいですし、他デバイスと同じ方式で統一した操作が可能など優位点が多いです。東芝はテレビを頑張っているように見えるので、せっかくI/F(インターフェース)があるようなので、今後IFTTT対応をしてほしいです。Google home搭載モデルなどもありますし、今後に期待!
非公式と思われる情報であり、有用となり得る情報なので設定方法や確かめた内容を記事にしておきます。

利用にあたり、REGZA側に2つ設定を行いました。

  1. IPアドレスの指定(IPアドレスが分かれば、設定方法は問わないです)
  2. レグザAppコネクト設定

1.IPアドレスの指定

「(リモコン)設定ボタン」「初期設定」「ネットワーク設定」「IPアドレス設定」

デフォルト設定のIPアドレス自動取得を「しない」に設定変更しました。
その後、ネットワーク設定に合わせて、IPアドレスの設定を行いました。

2.レグザAppコネクト設定

「(リモコン)設定ボタン」「接続機器設定」「外部連携設定」「レグザAppコネクト設定」

API利用時に、ユーザー名とパスワードの認証が必要となります。
Digest認証ダイジェストにんしょう)が利用されているようで、プログラムからの利用時には、Digest認証に対応した方法で利用する必要があります。

東芝テレビ(レグザ:REGZA)のWEB API仕様

http://[REGZA IP]/remote/remote.htm?key=[command_key]

IE、Chrome、Firefoxなど一般ブラウザから動作確認できます。
戻り値は、テキスト出力として数字が返ってきました。
「0」成功 「0以外」失敗。
ブラウザで操作している際に、テレビの音量を上げようとすると「4」というエラー番号も見ることが出来ました。電源ボタンを押した直後に音量操作実施のタイミング。

テレビのIPアドレスが192.168.0.77の場合のコマンドサンプル

  • 音量を上げたいとき
    http://192.168.0.77/remote/remote.htm?key=40BF1A
  • チャンネル「8」ボタンを押す
    http://192.168.0.77/remote/remote.htm?key=40BF08
  • リモコンの「青」ボタンを押す
    http://192.168.0.77/remote/remote.htm?key=40BF73

[command_key]リスト

ボタン名[command_key]
140BF01
240BF02
340BF03
440BF04
540BF05
640BF06
740BF07
840BF08
940BF09
1040BF0A
1140BF0B
1240BF0C
入力切替40BF0F
消音40BF10
電源40BF12
音声切り替え40BF13
音量↑40BF1A
チャンネル↑40BF1B
画面表示40BF1C
音量↓40BF1E
チャンネル↓40BF1F
ブロードバンド40BF25
クイック40BF27
マルチ画面40BF29
画面サイズ40BF2B
戻る40BF3B
終了40BF3C
決定40BF3D
40BF3E
40BF3F
一時停止(静止)40BF50
40BF5B
40BF5F
CH番号40BF60
サーチ40BF60
ラジオ/データ40BF6D
番組表40BF6E
番組説明40BF71
40BF73
40BF74
40BF75
40BF76
ミニ番組表40BF77
地デジ40BF7A
地アナ40BF7B
BS40BF7C
CS40BF7D
録画40BF86
設定メニュー40BFD0
↑↑40BE20
↓↓40BE21
→→(30秒送り)40BE22
←←(10秒戻し)40BE23
早送りスキップ40BE26
戻るスキップ40BE27
録画リスト40BE28
停止40BE2B
巻き戻し40BE2C
再生40BE2D
早送り40BE2E
レグザメニュー40BE34
タイムシフト40BE35
番組検索40BE36
始めにジャンプ40BE47
イジェクト40BE93
ディスク40BE9E
dデータ43BC14
字幕43BC52

ブラウザで実行した際の画面イメージ

表示されるDigest認証画面

音量を上げるコマンド実行後の結果「0」(成功)

参考にさせていただいたサイトです

REGZAをネットワーク越しに操作する

フジテレビのめざましじゃんけん画像検出

フジテレビのめざましじゃんけんシステムGo-Live!!
本日分が無事に動いたので、早速公開!
めざましじゃんけん結果一覧はこちら

初めての技術習得が結構多く、開発極小がポリシーなのですが、時間かかりました。各種勉強から初めて3週間弱でしょうか。
7月13日の週末に、関連書籍とWEBカメラをアマゾンで購入していました。

Deep Learning(ディープラーニング、深層学習)DNN(ディープニューラルネットワーク)、AI(人工知能)ML(機械学習)を少し触ってみました。初めての技術ばかりで、ライフのデジタル化計画に有益な技術習得を行いました。

  • Python(初めて扱いました)
  • Python関係画像ライブラリ、OpenCVなど(オープンシーヴィ、Open Source Computer Vision Library)
  • Chainer (チェイナー) ディープラーニングフレームワーク
  • Darknet(ダークネット)YOLO(You Only Look Once)物体検出、オープンソースのニューラルネットフレームワーク

以下サンプル画像です。デジタル放送部分のみなので、著作権には配慮しているつもりです。問題のある際は、ご連絡下さい。大手なので、使用許諾にも時間がかかりそうなので、時間を見つけて画像の使用許諾を考えます。

8月からのブログネタは、このシステム構築に関わる技術内容とする予定です。
次の週末にでも記事を書き溜めます。
さて、次は、、、まだ未定。

文字列のエスケープ処理

PHPでWEB APIを利用していると、制御文字や改行文字を削除(エスケープ)したい場面が出てきます。

  1. 改行文字(x0A x0D)含む制御文字を削除したい場合
  2. 改行文字(x0A x0D)は残し制御文字を削除したい場合

1.改行文字を含む制御文字を削除したい場合

x0A Line Feed(改行)およびx0D Carriage Return(復帰)と制御文字を削除したい場合の、PHPコードです。

$string = preg_replace('/[\x00-\x1F\x7F]/','', $string);

改行文字含むコントロール文字は、POSIXキャラクタクラスにおいて [:cntrl:] で定義されており、以下のように記載することも可能です。

$string = preg_replace('/[[:cntrl:]]/', '', $string);

2.改行文字は残し制御文字を削除したい場合

x0A Line Feed(改行)およびx0D Carriage Return(復帰)と制御文字は残し、その他の制御文字を削除する場合の、PHPコードです。

$string = preg_replace('/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F]/', '', $string);

参考にさせて頂いたサイト

Remove control characters from PHP string

[制御文字一覧]

16進略号名称CS
00NUL空白(Null)^@
01SOHヘッディング開始(Start of Heading)^A
02STXテキスト開始(Start of Text)^B
03ETXテキスト終結(End of Text)^C
04EOT伝送終了(End of Transmission)^D
05ENQ問い合わせ(Enquiry)^E
06ACK肯定応答(Acknowledge)^F
07BELベル(Bell)^G
08BS後退(Backspace)^H
09HT水平タブ(Horizontal Tabulation)^I
0ALF改行(Line Feed)^J
0BVT垂直タブ(Vertical Tabulation)^K
0CFF書式送り(Form Feed)^L
0DCR復帰(Carriage Return)^M
0ESOシフトアウト(Shift Out)^N
0FSIシフトイン(Shift In)^O
10DLE伝送制御拡張(Data Link Escape)^P
11DC1装置制御1(Device Control 1)^Q
12DC2装置制御2(Device Control 2)^R
13DC3装置制御3(Device Control 3)^S
14DC4装置制御4(Device Control 4)^T
15NAK否定応答(Negative Acknowledge)^U
16SYN同期信号(Synchronous Idle)^V
17ETB伝送ブロック終結(End of Transmission Block)^W
18CAN取り消し(Cancel)^X
19EM媒体終結(End of Medium)^Y
1ASUB置換(Substitute Character)^Z
1BESC拡張(Escape)^[
1CFSファイル分離(File Separator)^\
1DGSグループ分離(Group Separator)^]
1ERSレコード分離(Record Separator)^^
1FUSユニット分離(Unit Separator)^_
7FDEL抹消(Delete)^?

[POSIXキャラクタクラス]

クラス名意味
[:alnum:]アルファベットと(十進)数字
[:alpha:]アルファベット
[:blank:]空白文字(スペース、タブ等)
[:cntrl:]制御文字
[:digit:]十進数字
[:graph:]印字可能かつ表示可能な文字(スペースは印字可能だが表示可能ではない)
[:lower:]アルファベットの小文字
[:print:]印字可能なキャラクタ(=制御文字以外のキャラクタ)
[:punct:]句読点(通常の文字、数字、制御文字、スペースのいずれでもないキャラクター)
[:space:]スペース、タブ、改ページ
[:upper:]アルファベットの大文字
[:xdigit:]十六進数字

SendGridのInbound Email Parseを利用

SendGridは、メール配信などで利用されている方は多いのかもしれませんが、メール受信トリガーにWEB API経由でPOSTしてくれるサービスも提供されているようです。メール受信時のWEB APIプッシュの実現方法を探している際に見つけました。
グローバルで、企業向けにサービスを展開されているので、サービス品質などに間違いはなさそうです。
IFTTTでメール受信をしっかりとトリガー出来ていれば本サービスを利用することも無かったかもしれませんが、利用してみてPubSubというような使い方が出来そうなのでメインで利用する予定です。一般向け製品では通知手段がメールというサービスが多いです。

SendGrid | メールを成功の原動力に。

DNS設定に関しては、利用しているVALUE-DOMAIN(バリュードメイン)を例に説明を行います。

概要

sendgridで扱うEmail専用のサブドメインを準備します。サブドメイン宛のメールはすべてSendGridのメールサーバーに送信されるように設定し、SendGridでは、メール受信時のアクション(アクセスするWEB APIのURL)を設定します。あとは、受信を受けたWEB APIがメールの件名や差出人などにより実施したい処理を実施します。
現時点では、モーションセンサーの通知などをメール受信し、各種情報のデータ化としてデーターベースへの蓄積を行っております。

  1. SendGridサービス登録
  2. SendGridサービスで利用ドメインの認証
  3. SendGridでメール受信時の動作設定(メールをPOSTするWEB APIのアドレスを設定)
  4. 自ドメインのSendGridへのメール転送設定(DNS設定)

1. SendGridサービス登録

SendGridのサイトよりアカウントの新規登録を行います。
メールアドレスを登録し、簡単なアンケートや情報入力を行い、本登録を待ちます。人の手でサービス品質を保っているようで、以下メールが来て1日程度登録を待ちました。
実際、住所登録に不備があり、住所修正を行い、本登録が終了しました

SendGridにご登録いただき、誠にありがとうございます。

ログイン用のユーザ名につきましては、ご登録内容の確認およびアカウントのご利用準備が整い次第、別途ご連絡いたします(通常、翌2営業日以内)。
※ユーザ名はメールアドレスではございません。

なお、ご登録内容について確認のご連絡をさせていただく場合がございますので、あらかじめご了承ください。

サービス登録後の、利用者向けポータルのトップページ。

2. SendGridサービスで利用ドメインの認証

SendGrid-Inbound Email Parse Webhookを利用すると、利用条件としてドメインの認証が必要と、ドメイン認証の画面に誘導されます。以下の画面が、まずドメインの認証が必要とダイアログが出ています。

ドメインの認証方法は、利用しているドメインサーバのDNS設定にDNSレコードの追加を行います。SendGridで指定されたDNSレコードを追加する必要があります。SendGrid側でDNS変更を確認し、DNS変更の確認が完了すれば、ドメインの認証が終了します。
ドメイン認証は、SendGridのDomain Authentication(Authenticate Your Domain)のダイアログに従い、Install DNS Recourdsまで進み、登録するDNS情報を入手して下さい。

取得した追加するDNSレコードを自分のDNSサーバに追加します。
今回は、利用しているVALUE-DOMAIN(バリュードメイン)での設定例を例示して起きます。
ポイントは、DNS設定には時間を要するので、正しく設定出来た状態で、しばらく待ちましょう。数時間で反映されると思いますが、各種サーバなどの設定に依存します。



追加したDNSレコードが確認され、ドメイン認証が完了される際に表示されるWEB画面です。「Verify Your Domain」It worked!と表示されました。

日本法人 公式サイトの関連するQAです。
独自ドメイン利用(Sender Authentication)および設定時のDNSレコードについて、詳しく教えてください

3. SendGridでメール受信時の動作設定(メールをPOSTするWEB APIのアドレスを設定)

ドメイン認証が完了すると SendGridでSendGrid-Inbound Email Parse Webhook(メール受信時とWeb APIのマッピング)設定となります。
SendGridで受信するメールアドレスのドメインおよびWeb APIのURLを設定します。
オプションで、SendGrid側でのSpamチェック有無と受信するPOSTデータを指定します。

4. 自ドメインのSendGridへのメール転送設定(DNS設定)

最後に、SendGridへ転送したいメールアドレスを自分のDNSサーバに設定します。
この設定が終了すると、DNS情報がインターネットに反映され次第、受信メールのSendGridへ転送、SendGridでメール受信時にWEB APIへのメールデータPOSTが開始されます。
必ず、WEB API側の準備後に、DNSレコードの追加を行って下さい。(最低限WEB受信可能な状態)
以下の例では、MXレコードを追加し、対象のサブドメインをSendGridのメールサーバーに転送されるように指定します。

Inbound Email Parse WebhookのPOST情報

HEADERSThe raw headers of the email.
DKIMA string containing the verification results of any DKIM and domain keys signatures in the message.
CONTENT-IDSA string containing the number of attachments.
TOEmail recipient field, as taken from the message headers.
HTMLHTML body of email. If not set, email did not have an HTML body.
FROMEmail sender, as taken from the message headers.
SENDER_IPA string of the sender’s ip address.
SPAM_REPORTSpam Assassin’s spam report.
ENVELOPEA string containing the SMTP envelope. This will have 2 variables: to, which is a single-element array containing the address that we received the email to, and from, which is the return path for the message.
ATTACHMENTSNumber of attachments included in email.
SUBJECTEmail Subject.
SPAM_SCORESpam Assassin’s rating for whether or not this is spam.
ATTACHMENT-INFOA JSON map where the keys are named attachment{X}. Each attachment key points to a JSON object containing three fields, filenametype, and content-id. The filename field is the name of the file (if it was provided). The type field is the media type of the file. X is the total number of attachments. For example, if the number of attachments is 0, there will be no attachment files. If the number of attachments is 3, parameters attachment1, attachment2, and attachment3 will have file uploads.
CHARSETSA string containing the character sets of the fields extracted from the message.
SPFThe results of the Sender Policy Framework verification of the message sender and receiving IP address.

 

[Date] array(16) {
  ["headers"]=>
  string(1970) "Received: by mx0047p1mdw1.sendgrid.net with SMTP id 6WCVv7KAWn Wed, 27 Jul 2016 20:53:06 +0000 (UTC)
Received: from mail-io0-f169.google.com (mail-io0-f169.google.com [209.85.223.169]) by mx0047p1mdw1.sendgrid.net (Postfix) with ESMTPS id AA9FFA817F2 for <example@example.comom>; Wed, 27 Jul 2016 20:53:06 +0000 (UTC)
Received: by mail-io0-f169.google.com with SMTP id b62so81593819iod.3 for <example@example.comom>; Wed, 27 Jul 2016 13:53:06 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sendgrid.com; s=ga1; h=mime-version:from:date:message-id:subject:to; bh=DpB1CYYeumytcPF3q0Upvx3Sq/oF4ZblEwnuVzFwqGI=; b=GH5YTxjt6r4HoTa+94w6ZGQszFQSgegF+Jlv69YV76OLycJI4Gxdwfh6Wlqfez5yID 5dsWuqaVJZQyMq/Dy/c2gHSqVo60BKG56YrynYeSrMPy8abE/6/muPilYxDoPoEyIr/c UXH5rhOKjmJ7nICKu1o99Tfl0cXyCskE7ERW0=
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=DpB1CYYeumytcPF3q0Upvx3Sq/oF4ZblEwnuVzFwqGI=; b=Sq6LVHbmywBdt3sTBn19U8VOmelfoJltz8IcnvcETZsYwk96RBxN+RKMN5fOZSKw4j 15HrgdIFfyDmp67YK0ygvOITlTvZ6XY5I0PtnvDtAQt79kS3tKjI3QKJoEp/ZjIjSzlL KG7agl6cxFgBbIN0yHWBOvy3O+ZXY8tZdom1yOvULjmjW1U9JkdOs+aJ6zq4qhZX/RM/ tIgLB461eJ5V95iQDDc5Ibj9Cvy4vJfXLQRO0nLVQAT2Yz58tkEO1bDZpWOPAyUNneIL yhIWp+SpbuqhMA68mq0krG1PjmWalUbpVcGJIGuOKB9mQFFo/MqdrUCjvYnyo1jPLPeX psdQ==
X-Gm-Message-State: AEkoousvdxmDoxLlTUYJ1AOmCGJv77xRBBlfKv6YrthH0M2NueMwlOxUD6t8nidE9uonXbdJ/DQy/chmHUnN//a4
X-Received: by 10.107.6.101 with SMTP id 98mr38024553iog.41.1469652785829; Wed, 27 Jul 2016 13:53:05 -0700 (PDT)
MIME-Version: 1.0
Received: by 10.107.48.17 with HTTP; Wed, 27 Jul 2016 13:53:05 -0700 (PDT)
From: Sender Name <example@example.com>
Date: Wed, 27 Jul 2016 14:53:05 -0600
Message-ID: <CAN_P_JMvV7ZpAQhOnDienypLrJmuhN=LQWweu4yScw4jQyXY2w@mail.gmail.com>
Subject: Different File Types
To: example@example.comom
Content-Type: multipart/mixed; boundary=001a113f8ad03e85160538a4343c
"
  ["dkim"]=>
  string(22) "{@sendgrid.com : pass}"
  ["content-ids"]=>
  string(37) "{"ii_1562e2169c132d83":"attachment1"}"
  ["to"]=>
  string(26) "example@example.comom"
  ["html"]=>
  string(479) "<div dir="ltr">Here&#39;s an email with multiple attachments<div><br></div><div><img src="cid:ii_1562e2169c132d83" alt="Inline image 1" width="455" height="544"><br clear="all"><div><br></div>-- <br><div class="gmail_signature" data-smartmail="gmail_signature"><div dir="ltr"><img src="https://sendgrid.com/brand/sg-logo-email.png" width="96" height="17"><br><div><br></div></div></div>
</div></div>
"
  ["from"]=>
  string(33) "Sender Name example@example.com"
  ["text"]=>
  string(139) "Here's an email with multiple attachments
"
  ["sender_ip"]=>
  string(14) "209.85.223.169"
  ["spam_report"]=>
  string(844) "Spam detection software, running on the system "mx0047p1mdw1.sendgrid.net", has
identified this incoming email as possible spam. The original message
has been attached to this so you can view it (if it isn't spam) or label
similar future email. If you have any questions, see
@@CONTACT_ADDRESS@@ for details.

Content preview:  Here's an email with multiple attachments [image: Inline image
   1] -- [...]

Content analysis details:   (2.6 points, 5.0 required)

 pts rule name              description
---- ---------------------- --------------------------------------------------
 0.8 HTML_IMAGE_RATIO_02    BODY: HTML has a low ratio of text to image area
 0.0 HTML_MESSAGE           BODY: HTML included in message
 1.8 HTML_IMAGE_ONLY_08     BODY: HTML: images with 400-800 bytes of words
 0.0 T_MIME_NO_TEXT         No text body parts

"
  ["envelope"]=>
  string(66) "{"to":["example@example.comom"],"from":"example@example.com"}"
  ["attachments"]=>
  string(1) "2"
  ["subject"]=>
  string(20) "Different File Types"
  ["spam_score"]=>
  string(5) "2.597"
  ["attachment-info"]=>
  string(287) "{"attachment2":{"filename":"DockMcWordface.docx","name":"DockMcWordface.docx","type":"application/vnd.openxmlformats-officedocument.wordprocessingml.document"},"attachment1":{"filename":"MG_2359.jpg","name":"_MG_2359.jpg","type":"image/jpeg","content-id":"ii_1562e2169c132d83"}}"
  ["charsets"]=>
  string(77) "{"to":"UTF-8","html":"UTF-8","subject":"UTF-8","from":"UTF-8","text":"UTF-8"}"
  ["SPF"]=>
  string(4) "pass"
}



SendGridのInbound Email Parseを利用

SendGridは、メール配信などで利用されている方は多いのかもしれませんが、メール受信トリガーにWEB API経由でPOSTしてくれるサービスも提供されているようです。メール受信時のWEB APIプッシュの実現方法を探している際に見つけました。
グローバルで、企業向けにサービスを展開されているので、サービス品質などに間違いはなさそうです。
IFTTTでメール受信をしっかりとトリガー出来ていれば本サービスを利用することも無かったかもしれませんが、利用してみてPubSubというような使い方が出来そうなのでメインで利用する予定です。一般向け製品では通知手段がメールというサービスが多いです。

SendGrid | メールを成功の原動力に。

前提条件

  • PHPが動作するWEBサーバがセットアップされている
  • WEBサーバの公開設定が終わっている

SendGridのInbound Email Parse向けPHP APIサンプル

WEBサーバーにPOST受信可能なPHPファイルを配置し、SendGridがメール受信したさいのWEBリクエストを受信するPHP APIを準備します。

以下は、サンプルとなります。受信後に実施したい処理を利用形態に合わせて実装して下さい。本ページの各種ユースケースもサンプルになると思います。ユースケース一覧 V1

  • メール受信時にDBにレコード登録(モーションセンサ感知時にメール通知用)
  • ホームセキュリティ通知をDBにレコード登録(活動ログとして)
  • 各種速報メール受信時のLINE通知など

以下サンプルコードとなります。

<?php
/*************************************************************************
 * sendgrid-api (MAIN)
 * Home Tools for private. Using IFTTT and Google Home etc
 *
 * PHP 5 or later
 *
 * @category  Home IoT
 * @author    Miki
 * @url       https://www.miki-ie.com/
 * @copyright 2019 (c) MIKI-IE All rights Reserved.
 * @license   https://opensource.org/licenses/mit-license.html MIT License
 * @version   1.0
*************************************************************************/
//ログのファイル名
define("SENDGRID_API_LOG_NAME","sendgrid");
//DB
define("DB_HOST","@IP_Adress@:@Port@");
define("DB_USER","@User@");
define("DB_PASS","@Password@");
define("DB_DBNAME","@DB_Name@");
define("DB_TABLENAME1","@TableName1@");
define("DB_TABLENAME2","@TableName2@");


function addDBRecord($table, $datetime, $value1) {
    $mysqli = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_DBNAME);

    if (mysqli_connect_errno()) {
        logger("Connect failed: ".mysqli_connect_error(),"ERROR");
        exit();
	}
	
	if (!$mysqli->set_charset("utf8")) {
        logger("Error loading character set utf8: ".$mysqli->error,"ERROR");
		exit();
	}

	switch ($table) {
		case DB_TABLENAME1:
			// SQL(INSERT)を作成
			$sql = "INSERT INTO $table (
				DATETIME, COUNT
				) VALUES (
				'$datetime', $value1
				)";
			break;
		//addDBRecord(DB_TABLENAME2, $datetime_text, $state_text);
		case DB_TABLENAME2:
		// SQL(INSERT)を作成
		$str = mb_convert_encoding($value1, "UTF-8");
		$sql = "INSERT INTO $table (
			DATETIME2, STATE
			) VALUES (
				'$datetime', N'$str'
			)";
		break;
		default:
			logger("Internal DB Tabel Name Error. table:{$table}","ERROR");
	}

	if (!$mysqli->query($sql)) {
        logger("SQL query error Errormessage: ".$mysqli->error,"ERROR");
	}

	$mysqli->close();
}

function logger($text, $level) {
	$datetime = date('Y-m-d H:i:s');
	$date = date('Ym');
	$file_name = __DIR__ . "/log/".SENDGRID_API_LOG_NAME."-{$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);
}

$post_from = $_POST['from'];
$post_charsets = $_POST['charsets'];
$post_charsets_array = json_decode($post_charsets, true);
$post_charsets_all = print_r($post_charsets_array, true);
$post_encode_subject = mb_convert_encoding($_POST['subject'], "UTF-8", $post_charsets_array["subject"]);
$post_encode_subject = preg_replace('/[\x00-\x1F\x7F]/', '', $post_encode_subject);
$post_encode_text = mb_convert_encoding($_POST['text'], "UTF-8", $post_charsets_array["text"]);
$post_encode_text = preg_replace('/[\x00-\x1F\x7F]/', '', $post_encode_text);
logger("mb_convert_encoding, post_subject : {$post_encode_subject}","DEBUG");
logger("mb_convert_encoding, post_text : {$post_encode_text}","DEBUG");

$text_substr = mb_substr($post_text, 0, 15);
logger("Start sendgrid: key={$key} , from={$post_from} , subject={$post_subject} , text={$text_substr}","INFO");

if(isset($_POST['from'])) {
	switch ($post_from) {
	case 'aaa1@bbb.ccc': //メール受信内容に合わせて個別処理。以下はメール本文から文字列切り出しを実施
		$start = mb_strpos($post_text, "時刻:");
		$datetime_text = mb_substr($post_text, $start + 4, 19,"utf-8");
		logger("Start CAM-A7DE datetime : {$datetime_text}","INFO");
		addDBRecord(DB_TABLENAME1, $datetime_text, 1);
		break;
	case 'aaa2@bbb.ccc': //メール受信内容に合わせて個別処理。以下はメール本文から文字列切り出しを実施
		$datetime_text = mb_substr($post_text, 0, 16,"utf-8");
		$temp_text = mb_substr($post_text, 18);
		$end_point = mb_strpos($temp_text, "。");
		$state_text = mb_substr($temp_text, 0, $end_point,"utf-8");
		logger("end_point : {$end_point} ,state : {$state_text}","DEBUG");
		logger("Start CSP-Security datetime : {$datetime_text} state : {$state_text}","INFO");
		addDBRecord(DB_TABLENAME2, $datetime_text, $state_text);
		break;
	default:
		logger("This is private API. (in Default)","ERROR");
	}
}else{
	logger("This is private API. (in else)","ERROR");
}

?>

Inbound Email Parse WebhookのPOST情報

HEADERSThe raw headers of the email.
DKIMA string containing the verification results of any DKIM and domain keys signatures in the message.
CONTENT-IDSA string containing the number of attachments.
TOEmail recipient field, as taken from the message headers.
HTMLHTML body of email. If not set, email did not have an HTML body.
FROMEmail sender, as taken from the message headers.
SENDER_IPA string of the sender’s ip address.
SPAM_REPORTSpam Assassin’s spam report.
ENVELOPEA string containing the SMTP envelope. This will have 2 variables: to, which is a single-element array containing the address that we received the email to, and from, which is the return path for the message.
ATTACHMENTSNumber of attachments included in email.
SUBJECTEmail Subject.
SPAM_SCORESpam Assassin’s rating for whether or not this is spam.
ATTACHMENT-INFOA JSON map where the keys are named attachment{X}. Each attachment key points to a JSON object containing three fields, filenametype, and content-id. The filename field is the name of the file (if it was provided). The type field is the media type of the file. X is the total number of attachments. For example, if the number of attachments is 0, there will be no attachment files. If the number of attachments is 3, parameters attachment1, attachment2, and attachment3 will have file uploads.
CHARSETSA string containing the character sets of the fields extracted from the message.
SPFThe results of the Sender Policy Framework verification of the message sender and receiving IP address.

 

[Date] array(16) {
  ["headers"]=>
  string(1970) "Received: by mx0047p1mdw1.sendgrid.net with SMTP id 6WCVv7KAWn Wed, 27 Jul 2016 20:53:06 +0000 (UTC)
Received: from mail-io0-f169.google.com (mail-io0-f169.google.com [209.85.223.169]) by mx0047p1mdw1.sendgrid.net (Postfix) with ESMTPS id AA9FFA817F2 for <example@example.comom>; Wed, 27 Jul 2016 20:53:06 +0000 (UTC)
Received: by mail-io0-f169.google.com with SMTP id b62so81593819iod.3 for <example@example.comom>; Wed, 27 Jul 2016 13:53:06 -0700 (PDT)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sendgrid.com; s=ga1; h=mime-version:from:date:message-id:subject:to; bh=DpB1CYYeumytcPF3q0Upvx3Sq/oF4ZblEwnuVzFwqGI=; b=GH5YTxjt6r4HoTa+94w6ZGQszFQSgegF+Jlv69YV76OLycJI4Gxdwfh6Wlqfez5yID 5dsWuqaVJZQyMq/Dy/c2gHSqVo60BKG56YrynYeSrMPy8abE/6/muPilYxDoPoEyIr/c UXH5rhOKjmJ7nICKu1o99Tfl0cXyCskE7ERW0=
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=DpB1CYYeumytcPF3q0Upvx3Sq/oF4ZblEwnuVzFwqGI=; b=Sq6LVHbmywBdt3sTBn19U8VOmelfoJltz8IcnvcETZsYwk96RBxN+RKMN5fOZSKw4j 15HrgdIFfyDmp67YK0ygvOITlTvZ6XY5I0PtnvDtAQt79kS3tKjI3QKJoEp/ZjIjSzlL KG7agl6cxFgBbIN0yHWBOvy3O+ZXY8tZdom1yOvULjmjW1U9JkdOs+aJ6zq4qhZX/RM/ tIgLB461eJ5V95iQDDc5Ibj9Cvy4vJfXLQRO0nLVQAT2Yz58tkEO1bDZpWOPAyUNneIL yhIWp+SpbuqhMA68mq0krG1PjmWalUbpVcGJIGuOKB9mQFFo/MqdrUCjvYnyo1jPLPeX psdQ==
X-Gm-Message-State: AEkoousvdxmDoxLlTUYJ1AOmCGJv77xRBBlfKv6YrthH0M2NueMwlOxUD6t8nidE9uonXbdJ/DQy/chmHUnN//a4
X-Received: by 10.107.6.101 with SMTP id 98mr38024553iog.41.1469652785829; Wed, 27 Jul 2016 13:53:05 -0700 (PDT)
MIME-Version: 1.0
Received: by 10.107.48.17 with HTTP; Wed, 27 Jul 2016 13:53:05 -0700 (PDT)
From: Sender Name <example@example.com>
Date: Wed, 27 Jul 2016 14:53:05 -0600
Message-ID: <CAN_P_JMvV7ZpAQhOnDienypLrJmuhN=LQWweu4yScw4jQyXY2w@mail.gmail.com>
Subject: Different File Types
To: example@example.comom
Content-Type: multipart/mixed; boundary=001a113f8ad03e85160538a4343c
"
  ["dkim"]=>
  string(22) "{@sendgrid.com : pass}"
  ["content-ids"]=>
  string(37) "{"ii_1562e2169c132d83":"attachment1"}"
  ["to"]=>
  string(26) "example@example.comom"
  ["html"]=>
  string(479) "<div dir="ltr">Here&#39;s an email with multiple attachments<div><br></div><div><img src="cid:ii_1562e2169c132d83" alt="Inline image 1" width="455" height="544"><br clear="all"><div><br></div>-- <br><div class="gmail_signature" data-smartmail="gmail_signature"><div dir="ltr"><img src="https://sendgrid.com/brand/sg-logo-email.png" width="96" height="17"><br><div><br></div></div></div>
</div></div>
"
  ["from"]=>
  string(33) "Sender Name example@example.com"
  ["text"]=>
  string(139) "Here's an email with multiple attachments
"
  ["sender_ip"]=>
  string(14) "209.85.223.169"
  ["spam_report"]=>
  string(844) "Spam detection software, running on the system "mx0047p1mdw1.sendgrid.net", has
identified this incoming email as possible spam. The original message
has been attached to this so you can view it (if it isn't spam) or label
similar future email. If you have any questions, see
@@CONTACT_ADDRESS@@ for details.

Content preview:  Here's an email with multiple attachments [image: Inline image
   1] -- [...]

Content analysis details:   (2.6 points, 5.0 required)

 pts rule name              description
---- ---------------------- --------------------------------------------------
 0.8 HTML_IMAGE_RATIO_02    BODY: HTML has a low ratio of text to image area
 0.0 HTML_MESSAGE           BODY: HTML included in message
 1.8 HTML_IMAGE_ONLY_08     BODY: HTML: images with 400-800 bytes of words
 0.0 T_MIME_NO_TEXT         No text body parts

"
  ["envelope"]=>
  string(66) "{"to":["example@example.comom"],"from":"example@example.com"}"
  ["attachments"]=>
  string(1) "2"
  ["subject"]=>
  string(20) "Different File Types"
  ["spam_score"]=>
  string(5) "2.597"
  ["attachment-info"]=>
  string(287) "{"attachment2":{"filename":"DockMcWordface.docx","name":"DockMcWordface.docx","type":"application/vnd.openxmlformats-officedocument.wordprocessingml.document"},"attachment1":{"filename":"MG_2359.jpg","name":"_MG_2359.jpg","type":"image/jpeg","content-id":"ii_1562e2169c132d83"}}"
  ["charsets"]=>
  string(77) "{"to":"UTF-8","html":"UTF-8","subject":"UTF-8","from":"UTF-8","text":"UTF-8"}"
  ["SPF"]=>
  string(4) "pass"
}



雨が降り出す前に通知①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;
	}
}