2019年7月18日木曜日

Arduino+74HC595+8×8nマトリクスLED(8.2)

2.駅別時刻表をHTTP経由で取得・解析する

マイコンのメモリは限られていますので、大規模な文字列解析はやらないほうがよさそうです。

そこで、LAN上にWEBサーバーを立ててCGIを利用することにしました。①公式サイトのデータ取得→時刻表データファイルを作成するスクリプトと、②時刻表データファイル→直近1列車を表示するスクリプトがあれば要件を満たせます。
スクリプトを分割したおかげで、それぞれの機能のみ考えればよいのでコーディングも楽になりました。時刻表のデータそのものの更新頻度はせいぜいダイヤ改正ごと、つまり長期に渡って更新されないので①については、都度手動で取得・整形する形でもなんとかなります。

ESP32からは②直近1列車表示スクリプトからHTTPでゲットする形なので、固定の形式で取得できることが保証され、名鉄公式サイトが大規模改修したとしてもESP32側の解析ロジックを書き直す必要がなくなります。

①・②とも20年くらい前に遊んでいたperlで書くことにしました。当時はperlのcgiでBBS作るの流行りましたね。perlで作る掲示板なんて本まで出版されたような記憶が。
とはいえ、ど忘れしていて別の言語で書いても良かったんではないかと後悔(^^;;
今時の動的htmlってどんな言語を使うのかしら?




【各スクリプトについて】

①公式サイト取得スクリプト
取得先URLを内部で直打ちするという頭の悪い仕様です。せめて引数で平日/土日祝日と上り/下りを指定できるようにしておかないと、使い勝手悪いですね。引数にリクエストをユニーク化するようなものがあったのですが、どういう形式なのかわかりませんので、そのまま使いました。日付が入れ込んであるので有効期限を超えるとなにか怒られるのかもしれません(^^;;
htmlを取ってきて、必要なところだけ抜き出したら、カンマ区切りのテキストファイル(いわゆるCSV)で保存します。平日用と土日祝用それぞれファイルを作成しました。ファイル名を切り替えてつけたりはしないので、作成したファイルをリネームして、次を作るという泥臭い仕様です。

#!/usr/bin/perl

use LWP::UserAgent;
use HTTP::Request;
use HTTP::Response;
use HTML::TreeBuilder;
use Encode;
use utf8;

$dw = 1;

$header = "Content-type: text/html; charset=UTF-8\n\n";
$body = getTimeTable("ここに名鉄サイトのURLを貼る");


$savestr = parseHtml($body);
saveTimeTable($savestr);

print $header;
print "Time table\n";
$savestr =~ s/\n/
\n/g;
print $savestr;
print "End of data.";

sub getTimeTable {
 my $url = $_[0];
 my $proxy = new LWP::UserAgent;
 my $req = HTTP::Request->new('GET' => $url);
 my $res = $proxy->request($req);
 my $str = $res->content;

 return $str;
}

sub parseHtml {
 my $html_body = $_[0];
 my $tree = HTML::TreeBuilder->new;
 my $buf = "";<

 $tree->parse($html_body);

 my @items = $tree->look_down('class','timebody')->find('li');
 for my $li (@items) {
  $buf = $buf.$li->find('dt')->as_text.",".$li->find('dd')->as_text."\n";
 }

 #--- replace non display charcter.
 $buf =~ s/\x{fffd}/''/g;
 $buf =~ s/\xa0/,/g;
 $buf =~ s/,,/,/g;
 $buf =~ s/^,//g;
 $buf =~ s/\n,/\n/g;

 return $buf;
}

sub saveTimeTable {
 my $temp = $_[0];
 my $safix = "_";

 if ( $dw == 0 ) {
  $safix = $safix."w";
 } else {
  $safix = $safix."h";
 }

 open (SAVEFILE, "> TimeTable".$safix.".txt") or die("error :$1");
 print SAVEFILE $temp;
 close SAVEFILE;
}

②直近1列車取得スクリプト
サーバーのマシンタイムを取得、平日・今ここ土日祝日を判断して適切なファイルから時刻表を取得、マシンタイム以降の最初に現れたレコードからカンマをスペースに置換してhtmlで返します。
マイコンで扱いやすいデータを返すのが目的ですので、シンプルに<html><head><title><body>しか使いませんでした。Content-typeはtext/htmlのutf-8です。
#!/usr/bin/perl

use utf8;
use Encode;
use Time::Piece;

$timetable_week = "TimeTable_w.txt";
$timetable_holiday = "TimeTable_h.txt";


$header = "Content-type: text/html; charset=UTF-8\n\n";
$header = $header."Train at Time\n";

$info = getTrainInfo();
$body ="".$info."\n";
print $header;
print $body;


sub getTrainInfo {
 my $now_obj = localtime;

 my $filename;

 if ($now_obj->_wday == 0 || $now_obj->_wday == 6 ) {
  $filename = $timetable_holiday;
 } else {
  $filename = $timetable_week;
 }
 
 open (TIMETABLE, "< $filename") or die print("error: $1");

 my $buf = "";
 my $is_find = 0;

 while() {
  chomp($_);
  my @items = split(/,/, $_);

  ($hour, $min) = split(/:/, @items[0]);

  if ( isNearBy($hour, $min, $now_obj->hour, $now_obj->min) == 1 ) {
   foreach $item (@items){
    $buf = $buf." ".$item;
   }
   $is_find = 1;
   last;
  }
 }

 if ( $is_find == 0 ) {
  $buf = 'Train not found.';
 }

 return $buf;
}

sub isNearBy {
 my ($hour_a, $min_a, $hour_b, $min_b) = @_;

 if ( $hour_a > $hour_b ) {
  return 1;
 } elsif ( ($hour_a == $hour_b) && ($min_a > $min_b) ) {
  return 1;
 } else {
  return 0;
 }
}

どちらも技術的には見るべきものが無いと思います。

【動作環境】

サーバとして少なくとも在宅時は常時動いていたほうがよいかな、ということで押入れにてホコリをかぶっていたネットノートを引っ張り出して、LinuxMint 1.9をぶちこんでApache2でwebサーバを構築しました。Linux + Apche2 + CGI 環境の構築については、ネット上に山のようにありますので例のごとく解説しません。大昔はconfが一本化していて設定が楽だったけど、Linux Mintだと分割するのが作法みたいで戸惑いました。
結局開発環境もLinuxに移行することにして、中古でデスクトップを買ってしまいましたが(^^;;

【スケッチについて】

ESP32からWiFi経由でHTTP経由でサイトアクセス、というのはあっさりとできてしまいます。WiFi.hとHTTPClient.hをインクルードしてよろしくやればあっさりとHTTP GETできてしまいます。ネット上に沢山作例あるし、公式サンプルもあるから特に解説する必要を感じませんw。
参考までに作成した、html文字列取得関数を載せておきます。

//-----------------------------------------------------------------------------
// HTTP経由で指定URLのページを取得する
// 当然HTMLソースが返ってくるので、加工して表示に必要な情報のみ抜き出す必要あり
//-----------------------------------------------------------------------------
String getHTTPString(const char* hostURL) {
    HTTPClient http;
    String ret = "";


    //取得開始
    http.begin(hostURL);
    int httpCode = http.GET();

    if ( httpCode > 0 )  {
        if ( httpCode == HTTP_CODE_OK ) {
            ret = http.getString();
        }
        
    } else {
        ret = http.errorToString(httpCode);
    }

    //取得終了
    http.end();
    
    return ret;
}

で、HTTPClient.getString()で取得したhtml形式の文字列から、<body></body>で挟まれた文字列を抜き出して、LEDマトリクスのスクロール処理に引き渡しておしまい。
html文字列を加工する関数はこんな感じ。 単なる文字列加工にすぎませんが...
body置換のところは実際は<body></body>を指定しています。
また、//終電と思われるのところは、②のスクリプトが時刻表データファイルを末尾まで検索して現在時刻より大きいレコードがなければ Train not found.という文字列を返すようになっていて、Trainが見つかればレコードがなかった=終電だろうということです。
田舎なもんで最寄り駅は0時以降の電車がないからハンドリングが楽なんですよ(笑)

//-----------------------------------------------------------------------------
// 1列車時刻取得スクリプトの実行結果を解析する
//-----------------------------------------------------------------------------
// getTrainTimeスクリプトの戻り値から余分な情報を削除して、必要な情報のみ返す
//-----------------------------------------------------------------------------
String parseTimeTable(String &localCgi) {
    int findPos = 0;
    String buf = "";

    findPos = localCgi.indexOf("");
    buf = localCgi.substring(findPos, localCgi.length());
    findPos = buf.indexOf("");
    buf = buf.substring(0, findPos);
    
    buf.replace("/body", ""); #ホントはタグを指定している
    buf.replace("body", ""); #ホントはタグを指定している

    if ( buf.indexOf("Train") >= 0 ) {
        //終電と思われる
        buf = "本日の運転は終了しました。";
    } else {
        buf = "次の電車は" + buf + "行です。";
    }

    return buf;
}

WEBアクセス機能が驚くほど簡単に作れてしまいます。こんなに簡単でいいのかしら? 
すごいよESP32w

1.データどこから取得する?
2.データの取得と解析はどうする? <-- 今ここ
3.RTCないけどどうする

0 件のコメント:

コメントを投稿

ESP32 Devkit C での疑問点

 前回の投稿から放置状態にあった当ブログですが、再び何かしら作ろうということで、スマートコンセントもどきに取り組んでいます。 回路なども一応動作するものができたのですが、ブレッドボードから移行するために基板に用意しておいたピンソケットにESP32を取り付けたところ、なぜか動作しま...