2013年4月6日

【FHC】今日の天気を教えてもらう


題名の通りです。

今回はFHCに今日の天気を教えてもらえるようにしました。

案外便利ですね。そして要ネット接続です。



【①天気フィードを取得してみる】

今回の記事ですが、ミクさん記事の時と同様、自分の元ネタではないです。

今回は鹿せんべいさん (TwitterID: @shika_senbei ) がYahoo!Pipesで作ってくれた天気予報フィードを利用して作成しました。

FHC TENKI - Yahoo! Pipes

ブログ記事を書いてもいいか、お伺いを立てたら快く承諾してもらえたんで僕も快く書き殴ろうと思います。
要望として、「突然の仕様変更の対応は保証できないこと」を注意されましたので、付け加えておきます。
一応僕も(忘れてなければ)ちょくちょくアップデートはしていこうとは思っています。

手順としては以下のとおりだそうです。

①エリアコードは次のウェザーマップのページから http://www.weathermap.co.jp/hitokuchi_rss/ 都道府県と地域を選択して表示されるRSSのURL http://feedproxy.google.com/hitokuchi_1234 から1234の数字を入力して"Run Pipe"を押してください。

②予報が正しく表示されたらこの下にある"Get as JSON"のリンクURLをコピー。


③FHCのhttp_get()で②のURLを渡す。


④http_getの戻り値をobj=JSON.parse()でパース。


⑤obj.value.items[0].descriptionに天気予報と気温・降水確率が入っています。


⑥あとはFHCが話しても聞きやすいように適当に編集してください("/"を"月"に変換するとか"12-18時:"を"昼は"とか)。


思ったより簡単ですね。
実は僕も天気予報フィードを取得してFHCに喋らせてみようかしら。なんて考えていたことがあったんですが、フィードをどうやって取得しようかという事でめんd時間ができた時にでもやってみようかという結論にいたり、投げdこの件は保留という事になってたんですね。

そもそもが、JavaScriptをほとんど触ったことがなかったのでJSON.parse()すら知らなかった等云々……

話を戻します。

【②JSONの参照場所を変える】

で、今回この天気予報のフィードをJSONに加工して取り出しているわけですが、上記方法では取り出している部分がdescription(概要)の部分になってます。
「フィードからとってるのにディスクリプションしかないのかな?」と疑問に思ったので、ちょっくらフィードをバラして見ることにしました。

(長いです。適当に色のついてるとこに注目してください)


>JSON分解図
{
  "count":1,
  "value":{
    "title":"FHC TENKI","description":"①エリアコードは次のウェザーマップのページからhttp://www.weathermap.co.jp/hitokuchi_rss/ 都道府県と地域を選択して表示されるRSSのURL http://feedproxy.google.com/hitokuchi_1234から1234の数字を入力して"Run Pipe"を押してください。②予報が正しく表示されたらこの下にある"Get as JSON"のリンクURLをコピー。③FHCのhttp_get()で②のURLを渡す。④http_getの戻り値をobj=JSON.parse()でパース。⑤obj.value.items[0].descriptionに天気予報と気温・降水確率が入っています。⑥あとはFHCが話しても聞きやすいように適当に編集してください("/"を"月"に変換するとか"12-18時:"を"昼は"とか)。",
    "link":"http://pipes.yahoo.com/pipes/pipe.info?_id=11aaf6db1647c2155de5739ff587db1f",
    "pubDate":"Fri, 05 Apr 2013 16:24:19 +0000",
    "generator":"http://pipes.yahoo.com/pipes/",
    "callback":"",
    "items":[{
      "title":"4/6(土) くもりのち雨 19℃/15℃",
      "link":"http://www.weathermap.co.jp/monkeyweather/",
      "guid":{
        "isPermaLink":"false",
        "content":"201304051745100406"
      },
      "pubDate":"Fri, 05 Apr 2013 17:00:00 +0900",
      "wm:forecast":{
        "announce":"2013-04-05 17:00:00",
        "term":"kyo_asu",
        "wm:area":{
          "wm:prefecture":"千葉県",
          "wm:region":"北西部",
          "wm:city":"千葉"
        },
        "wm:content":{
          "date":"4/6",
          "wday":"Sat",
          "wm:weather":"くもりのち雨",
          "wm:temperature":{
            "unit":"℃",
            "wm:max":"19",
            "wm:min":"15"
          },
          "wm:rainfall":{
            "unit":"%",
            "wm:prob":[{
              "hour":"00-06",
              "content":"20"
            },{
              "hour":"06-12",
              "content":"20"
            },{
              "hour":"12-18",
              "content":"70"
            },{
              "hour":"18-24",
              "content":"90"
            }]
          }
        }
      },
      "description":"4/6(土) くもりのち雨<br />予想最高気温:19℃<br />予想最低気温:15℃<br /><br />降水確率<br />00-06時:20%<br />06-12時:20%<br />12-18時:70%<br />18-24時:90%<br />",
      "y:published":{
        "hour":"8",
        "timezone":"UTC",
        "second":"0",
        "month":"4",
        "month_name":"April",
        "minute":"0",
        "utime":"1365148800",
        "day":"5",
        "day_ordinal_suffix":"th",
        "day_of_week":"5",
        "day_name":"Friday",
        "year":"2013"
      },
      y:id":{
        "permalink":"false",
        "value":"201304051745100406"
      },
      "y:title":"4/6(土) くもりのち雨 19℃/15℃"  
    }]
  }
}


赤文字で書いたところが上記手順⑥に書かれているdescriptionを参照している部分ですが、ここで青色の部分に注目。

すでに情報が分解されているみたいです。こっちのほうが便利そうです。

あまり文字列操作の得意でない僕ですので、今回はこちらの情報を加工して使うことにします。

【③FHC内部コマンドをガリガリ】

書きます。
FHCにログインをし、コマンドを新規作成します。

[FHCトップ画面]→[設定画面を開く]→[コマンド設定を行う]→[新規作成]の順で選択していきます。



ファイル名は適当に[read_weather.js]とでもしておきます。

編集画面に入ったら、デフォルトで記入されているコードは全て消して、下記コードを貼り付けてください。

その際、最初に載せていた鹿せんべいさんの手順②でコピーしたURLを手順①を行い自分の地域の4桁コードを下記コード赤文字の数字部分と置き換えてください。


[read_weather.js]


///@args1@must@ 日時 (0:読み上げない、1:読み上げる)@0 or 1@int
///@args2@must@ 天気 (0:読み上げない、1:読み上げる)@0 or 1@int
///@args3@must@ 最高気温 (0:読み上げない、1:読み上げる)@0 or 1@int
///@args4@must@ 最低気温 (0:読み上げない、1:読み上げる)@0 or 1@int
///@args5@must@ 時間別降水確率 (0:読み上げない、1:読み上げる)@0 or 1@int
function main(arg1, arg2, arg3, arg4, arg5)
{
  code = "4510";
  result = http_get("http://pipes.yahoo.com/pipes/pipe.run?_id=11aaf6db1647c2155de5739ff587db1f&_render=json&code=" + code);
  obj = JSON.parse(result);
  arr = obj.value.items[0]["wm:forecast"]["wm:content"];
  rainRateArr = new Array();
  voice = "";
  date = Number(arg1);
  weather = Number(arg2);
  maxTemp = Number(arg3);
  minTemp = Number(arg4);
  rainRate = Number(arg5);
  if (date) {
    voice = kanaDate(arr["date"]) + "、";
    voice += kanaWeek(arr["wday"]) + "ようび";
    if (weather + maxTemp + minTemp) {
      voice += "の";
    }
    voice += " ";
  }
  if (weather) {
    voice += "てんきは ";
    voice += kanaTenki(arr["wm:weather"]);
    if (maxTemp + minTemp + rainRate) {
      voice += "、";
    }
  }
  if (maxTemp) {
    voice += "さいこうきおんは ";
    voice += kanaNum(arr["wm:temperature"]["wm:max"]) + "ど ";
    if (minTemp + rainRate) {
      voice += "、";
    }
  }
  if (minTemp && arr["wm:temperature"]["wm:min"] != "--") {
    voice += "さいていきおんは ";
    voice += kanaNum(arr["wm:temperature"]["wm:min"]) + "ど ";
    if (rainRate) {
      voice += "です。";
    }
  }
  if (rainRate) {
    for (i = 0; i < arr["wm:rainfall"]["wm:prob"].length; i++){
      rainRateArr[arr["wm:rainfall"]["wm:prob"][i]["hour"]] = arr["wm:rainfall"]["wm:prob"][i]["content"];
    }
    voice += "こうすいかくりつは、ごぜんちゅう ";
    voice += kanaNum(rainRateArr["06-12"]) + "ぱーせんと、ごごは ";
    voice += kanaNum(rainRateArr["12-18"]) + "ぱーせんと、よるは ";
    voice += kanaNum(rainRateArr["18-24"]) + "ぱーせんと ";
  }
  voice += "です";
  speak(voice);
  dump(obj.value.items[0]["description"]);
  dump(voice);
}
function kanaNum(num)
{
  KanaArr = new Array("ぜろ","いち","に","さん","よん","ご","ろく","なな","はち","きゅう");
  n = Number(num);
  Kana = "";
  if (n == 0) {
    Kana = "ぜろ";
  } else {
    if (n < 0) {
      Kana = "マイナス";
      n = -n;
    }
    if (n >= 100) {
      Kana += "ひゃく";
    }
    n %= 100;
    if (n >= 20) {
      
      Kana += KanaArr[Math.floor(n/10)];
    }
    if (n >= 10) {
      Kana += "じゅう";
      n %= 10;
    }
    if (n > 0){
      Kana += KanaArr[n];
    }
  }
  return Kana;
}

function kanaDate(dateValue)
{
  month = new Array("","いちがつ ","にがつ ","さんがつ ","しがつ ","ごがつ ","ろくがつ ","しちがつ ",
                    "はちがつ ","くがつ ","じゅうがつ ","じゅういちがつ ","じゅうにがつ ");
  day = new Array("","ついたち","ふつか","みっか","よっか","いつか","むいか","なのか","ようか",
                  "ここのか","とおか","じゅういちにち","じゅうににち","じゅうさんにち","じゅうよんにち",
                  "じゅうごにち","じゅうろくにち","じゅうしちにち","じゅうはちにち","じゅうくにち",
                  "はつか","にじゅういちにち","にじゅうににち","にじゅうさんにち","にじゅうよっか",
                  "にじゅうごにち","にじゅうろくにち","にじゅうしちにち","にじゅうはちにち",
                  "にじゅうくにち","さんじゅうにち","さんじゅういちにち");
  dateArr = dateValue.split("/");
  return month[Number(dateArr[0])] + day[Number(dateArr[1])];
}

function kanaTenki(tenki)
{
  tenki.replace("時々", "ときどき");
  tenki.replace("晴", "は");
  tenki.replace("雨", "あめ");
  tenki.replace("雪", "ゆき");
  return tenki;
}

function kanaWeek(week){
  KanaArr = {Mon: "げつ", Tue: "か", Wed: "すい", Thu: "もく", Fri: "きん", Sat: "ど", Sun: "にち"};
  return KanaArr[week];
}

//ここまで。


貼り付けたら「セーブして実行」ボタンを押して、正しく動くか試してみましょう。


ダイアログが出るので、各項目に0または1を入力して、FHCに読み上げてもらう項目を設定します。

0を入力で読み上げません。1を入力すればその項目を読み上げてくれます。


読みあげてもらう文章が長いので実行から少々時間がかかります。
(下手したら1分近くかかります)


正しく読みあげてもらうことができたらひとまず準備完了です。

【④リモコンを編集する】

普通に家電を登録するのと同様にリモコンを編集します。

[FHCトップ画面]→[リモコンを編集]→[新規作成する]から新たな家電を登録しましょう。


[動作を新規に追加する]から以下のように設定していきます。

種類: その他
その他種類名: 任意


音声認識: 任意

実行時しゃべる: 読み上げない

操作方法: コマンド実行
コマンド: read_weather.js
各種項目: 任意

メニューへの表示: 任意

あとは [保存して戻る] で完了です。

話しかけて動作確認をしてみましょう。

【⑤未来に一歩前進?】

したような気はします。
…が、この程度の事なら普通にSiriさんがやってくれますしね。

いちおう、なにか動作不良があったら言ってもらえれば対応できると思います。

あと、コードが汚いところがあるのはごめんなさい。


――[2013/04/06 15:58 追記]――

コメントで指摘されたところを修正しました。

4 件のコメント:

  1. 鹿せんべい2013年4月6日 12:41

    記事ありがとうございました。きっちり関数化されていて感心しました。私が書かなくて良かった^^;

    うちでは以下のような関数をonvoice()から呼び出すだけの簡単なものです。はずかしぃ…

    ちなみにhttp_get()はtry-catchして例外発生を喋らせた方が良いです。現在作成中の電車運行情報もそうなんですが、ときどき失敗するみたいなので。

    //天気予報をしゃべる
    function tenki() {
    try {
    var pipe = http_get("~省略~");
    } catch(ex) {
    speak("天気予報の取得に失敗しました");
    alert(ex);
    }
    var obj = JSON.parse(pipe);
    var tenki = obj.value.items[0].description;
    tenki = tenki.replace(/\
    /g,"。");
    tenki = tenki.replace("/","がつ");
    tenki = tenki.replace("(","にち");
    tenki = tenki.replace(")","ようびのてんきは、");
    tenki = tenki.replace(/℃/g,"どです");
    tenki = tenki.replace("00-06時","朝");
    tenki = tenki.replace("06-12時","午前");
    tenki = tenki.replace("12-18時","午後");
    tenki = tenki.replace("18-24時","夜");
    tenki = tenki.replace(/:/g,"は");
    tenki = tenki.replace(/%/g,"ぱーせんと");
    tenki = tenki + "です。";
    speak(tenki);
    }

    返信削除
    返信
    1. なるほど、try catchってjavascriptにもあるんですね。参考にさせてもらいます!

      削除
  2. 鹿せんべい2013年4月6日 15:30

    何度も済みません。前のコメントの訂正と、記事中の説明の誤りを見つけたのでご報告です。

    前コメントのソースの最初のreplace()部分が変になっています。これはBRタグを置換しているのですが、タグ記述がそのまま解釈されてしまったようです。
    replace()部だけ全角で書くと、次のようなものです。
    replace(/¥<br ¥/¥>/g,”。”);

    あと記事中の次の説明ですが、

    >その際、最初に載せていた鹿せんべいさんの手順②でコピー
    >したURLを下記コード赤文字の数字部分と置き換えてください。

    ②は全URLのコピーなのでまずいですね。
    「①のコード番号部分を置き換えて」のような説明の方が良いと思います。
    記事のソースの方式なら手順②は不要ですね。

    返信削除
    返信
    1. ぎゃあ、文章間違えてました!

      元々②を行うコード書いてたんですが、ブログ記事書いてるうちに「数字だけ置き換えるほうが楽じゃね?」って言うんで書き換えたのを、修正し忘れてました。

      記事書きなおしておきました。
      どもです。。。。

      削除