2015年7月4日

【FHC】天気予報を喋ってもらうスクリプトを更新しました

我が家のFHCで恐らく一番使われている機能が「今日の天気予報を喋ってくれる機能」です。

2年前に最初のスクリプトを作り、半年後に作りなおしてからというもの、全く手を付けていませんでしたが、天気情報を取得していたYahoo!Pipesが9月末でサービス終了してしまうということで、今回の更新ということになりました。

2年前に書いたスクリプトともなると、もはや他人のスクリプトと化しているので、イチから作り直しました。基本的な操作感は前回のスクリプトを踏襲しつつ、バージョンアップさせています。


■前回からの変更点

・天気予報取得APIの変更
鹿せんべい氏(@shika_senbei)がYahoo!Pipesで制作したマッシュアップAPIを利用していましたが、当サービスが終了してしまうため新たにdrk7.jpで配信しているJapan Weather Forecast xmlを使わせてもらいました。
また、前のスクリプトではJSONをパースしてましたが、今回はXMLを使ってみることにしました。drk7.jpではJSON形式でも同じ情報を配信しているので別にそのまま使えばよかったのですが、XMLもちょっと触ってみようかと思ったのと、FHCで提供しているhtml_selector関数の使用感も見てみたかったのがあります。
(というか、「チョット動作おかしかったんだけど、ここどうなの?」って問い合わせたら、「確認するからちょっと待っててね」って言われてしまった…やっぱりJSONパースしたほうが良かった…?)

・明日の天気にも対応
Yahoo!Pipesで取得する情報は当日分のものしかなかったため、翌日以降の天気については取得できませんでした。(ただし、18時以降になると翌日の天気に切り替わる謎仕様。海外サービスだからかしらん)
drk7.jpは気象庁のホームページに掲載されている情報を全部取得しており、翌日以降の天気についても含まれています。なので更新ついでに翌日の天気についてもしゃべるようにしてみました。

以前の謎仕様18時以降は翌日の予報に切り替わるところはそのまま踏襲しています。そのため「今日の天気は?」と聞いて翌日の天気になってしまうのはご愛嬌で。
→「これからの天気は?」とかにするといいのかもしれない。

■使い方

1.スクリプト(weather_drk7.jp)の作成
「設定画面」→「コマンド設定を行う」→「新しいスクリプトを新規作成」の順に選択。
新規ファイル名を「weather_drk7.js」にして「OK」を押下。

以下のスクリプトをコピペしてください。


///@args1@must@都道府県/地域@沖縄県/石垣島地方@text
function get_pref_id(pref) {
  var pref_name = [
    "北海道", "青森", "岩手", "宮城", "秋田", "山形", "福島", "茨城", "栃木",
    "群馬", "埼玉", "千葉", "東京", "神奈川", "新潟", "富山", "石川", "福井",
    "山梨", "長野", "岐阜", "静岡", "愛知", "三重", "滋賀", "京", "大阪", "兵庫",
    "奈良", "和歌山", "鳥取", "島根", "岡山", "広島", "山口", "徳島", "香川",
    "愛媛", "高知", "福岡", "佐賀", "長崎", "熊本", "大分", "宮崎", "鹿児島", "沖縄"
  ];
  var id_pref = 0;

  pref = pref.replace(/都|府|県/, "");

  for (var i = 0; i < 47; i++) {
    if (pref == pref_name[i]) {
      id_pref = i + 1;
      break;
    }
  }
  return id_pref;
}

function get_area_id(id_pref, area) {
  var pref_area = [
    ["上川", "北見", "十勝", "宗谷", "後志", "日高", "根室", "檜山", "渡島", "留萌", "石狩", "空知", "紋別", "網走", "胆振", "釧路"],
    ["三八上北", "下北", "津軽"],
    ["内陸", "沿岸北", "沿岸南"],
    ["東", "西"],
    ["内陸", "沿岸"],
    ["庄内", "最上", "村山", "置賜"],
    ["中通り", "会津", "浜通り"],
    ["北", "南"],
    ["北", "南"],
    ["北", "南"],
    ["北", "南", "秩父"],
    ["北東", "北西", "南"],
    ["伊豆諸島北", "伊豆諸島南", "小笠原諸島", "東京"],
    ["東", "西"],
    ["上越", "下越", "中越", "佐渡"],
    ["東", "西"],
    ["加賀", "能登"],
    ["嶺北", "嶺南"],
    ["中・西", "東・富士五湖"],
    ["中", "北", "南"],
    ["美濃", "飛騨"],
    ["中", "伊豆", "東", "西"],
    ["東", "西"],
    ["北中", "南"],
    ["北", "南"],
    ["北", "南"],
    ["大阪府"],
    ["北", "南"],
    ["北", "南"],
    ["北", "南"],
    ["中・西", "東"],
    ["東", "西", "隠岐"],
    ["北", "南"],
    ["北", "南"],
    ["中", "北", "東", "西"],
    ["北", "南"],
    ["香川県"],
    ["中予", "南予", "東予"],
    ["中", "東", "西"],
    ["北九州", "福岡", "筑後", "筑豊"],
    ["北", "南"],
    ["五島", "北", "南", "壱岐・対馬"],
    ["天草・芦北", "熊本", "球磨", "阿蘇"],
    ["中", "北", "南", "西"],
    ["北山沿い", "北平野", "南山沿い", "南平野"],
    ["大隅", "奄美", "種子島・屋久島", "薩摩"],
    ["与那国島", "久米島", "大東島", "宮古島", "本島中南", "本島北", "石垣島"]
  ];
  var area_name = pref_area[id_pref - 1];
  var id_area = 0;
  area = area.replace(/部|地方/, "");
  for (var i = 0; i < area_name.length; i++) {
    if (area == area_name[i]) {
      id_area = i;
      break;
    }
  }
  return id_area;

}

function get_date_id(my_date) {
  var wday_arr = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fry", "Sut", "Sun"];
  var result = my_date.getFullYear();
  result = result + "/" + ("0" + (my_date.getMonth() + 1)).slice(-2);
  result = result + "/" + ("0" + (my_date.getDate())).slice(-2);
  
  var arr_list = {
    "date": result,
    "wday": wday_arr[my_date.getDay()],
  }
  return arr_list;
}



function main(pref_area) {

  var my_date;
  var id_date;
  var list_date;
  
  pref_area = pref_area.split("/");
  var pref = pref_area[0];
  var area = pref_area[1];
  
  if(typeof pref != "string")
  {
    pref = "";
  }
  if(typeof area != "string")
  {
    area = "";
  }

  var id_pref = get_pref_id(pref);
  var id_area = get_area_id(id_pref, area);

  if ((id_pref < 0) || (id_area < 0)) {
    dump("Error: Invalid ID.");
    return null;
  }

  var id_pref_name = "0" + id_pref;
  id_pref_name = id_pref_name.slice(-2);

  var list_pref = http_get("http://www.drk7.jp/weather/xml/" + id_pref + ".xml");
  var list_area = html_selector(list_pref, "area")[id_area];

  var dateList = new Array();

  for (var i = 0; i < 2; i++) {
    my_date = new Date();

    my_date.setDate(my_date.getDate() + i);
    my_date.setHours(my_date.getHours() + 9);
    id_date = get_date_id(my_date);
    list_date = html_selector(list_area, "info[@date='" + id_date["date"] + "']")[0];

    dateList[i] = {
      "date": id_date["date"].replace(/[0-9]{4}\//, ""),
      "wday": id_date["wday"],
      "weather": html_selector(list_date, "/info/weather/text()")[0],
      "maxTemp": html_selector(list_date, "/info/temperature/range[@centigrade='max']/text()")[0].trim(),
      "minTemp": html_selector(list_date, "/info/temperature/range[@centigrade='min']/text()")[0].trim(),
      "rain00-06": html_selector(list_date, "/info/rainfallchance/period[@hour='00-06']/text()")[0].trim(),
      "rain06-12": html_selector(list_date, "/info/rainfallchance/period[@hour='06-12']/text()")[0].trim(),
      "rain12-18": html_selector(list_date, "/info/rainfallchance/period[@hour='12-18']/text()")[0].trim(),
      "rain18-24": html_selector(list_date, "/info/rainfallchance/period[@hour='18-24']/text()")[0].trim()
    }

  }
  return dateList;
  //return {};
}


2.スクリプト(read_weather.js)の作成
同様に「設定画面」→「コマンド設定を行う」→「新しいスクリプトを新規作成」の順に選択。
新規ファイル名を「read_weather.js」にして「OK」を押下。

※僕が以前作成したスクリプトが残っているなら同じファイル名のスクリプトが残っていると思います。fhc_tenki用に作成したものですが、もう使えなくなるのでそのまま上書きしてしまって大丈夫です。

///@args1@must@ 地域 (都道府県地域)@沖縄県石垣島地方@text
///@args2@must@ 日時 (0読み上げない、1今日の天気、2明日の天気)@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)
{
  var voice = ;
  var pref_area = arg1;
  var wdate = Number(arg2);
  var weather = Number(arg3);
  var Temp = Number(arg4);
  var rainRate = Number(arg5);
  
  if(pref_area.length == 1) {
    pref = pref_area[0];
    area = ;
  } else if(pref_area.length  1) {
    pref = pref_area[0];
    area = pref_area[1];
  } else {
    pref = ;
    area = ;
  }
  
  var arr = weather_info(pref_area);
  
  if(wdate  0) {
    arr = arr[wdate - 1];
  } else {
    arr = arr[0];
  }
  
  if (wdate) {
    voice = kanaDate(arr[date]) + 、;
    voice += kanaWeek(arr[wday]) + ようび;
    if (weather + Temp) {
      voice += の;
    }
    voice +=  ;
  }
  
  if (weather) {
    voice += てんきは ;
    voice += kanaTenki(arr[weather]);
    if (Temp + rainRate) {
      voice += 、;
    }
  }
  
  if (Temp) {
    voice += さいこうきおんは ;
    voice += kanaNum(arr[maxTemp], 1) + ど ;
    if (Temp + rainRate) {
      voice += 、;
    }
  }
  if (Temp && arr[minTemp] != --) {
    voice += さいていきおんは ;
    voice += kanaNum(arr[minTemp], 1) + ど ;
    if (rainRate) {
      voice += です。;
    }
  }
  if (rainRate) {
    voice += こうすいかくりつは;
    if (arr[rain06-12] != --) {
      voice += 、ごぜんちゅう  + kanaNum(arr[rain06-12]) +  ぱーせんと;
    }
    if (arr[rain12-18] != --) {
      voice += 、ごご  + kanaNum(arr[rain12-18]) +  ぱーせんと;
    }
    voice += 、よる  + kanaNum(arr[rain18-24], 0) +  ぱーせんと ;
  }
  voice += です;
  speak(voice);
  dump(voice);
}
function kanaNum(num, hatsu)
{
  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(n10)];
    }
    if (n = 10) {
      if (hatsu)
      {
       Kana += じゅう;
      } else {
        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 = tenki.replace(時々, ときどき);
  tenki = tenki.replace(晴, は);
  tenki = tenki.replace(雨, あめ);
  tenki = tenki.replace(雪, ゆき);
  return tenki;
}

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


3.機器登録
マッシュアップAPIの「weather_info()」関数にweather_drk7.jsを使えるようにスクリプトを登録します。
わざわざweather_infoで使えるようにする必要はないのですが、それ用にスクリプトを組んでしまったので、なんとなくです。

「設定画面」→「システム全般の設定を行う」→「天気取得スクリプト」の順に選択
「コマンドを選択する」から「weather_drk7.js」を選択します。

登録が完了したら、「preview」から適当な地域名を入力して実行してみましょう。
地域名は、スクリプト「weather_drk7.js」のソースコード中にあるpref_nameとpref_areaの一覧から取得するのでも、気象庁の天気予報ページから地域を探してきてもいいです。
天気情報が取得(戻り値に天気情報っぽい文字列が表示)できればOKです。

あとは、普通に「リモコンを編集」から音声認識とスクリプトの登録を行います。

「リモコンを編集」→「新規作成する」から、以下のとおりに設定していきます。

→種類:その他
→アイコン:任意
→リモコン画面に表示:任意
→音声認識対象から消す:チェック外す
→クリック動作ではなくメニューダイアログを開く:チェック入れる

「動作を新規に追加する」からは以下のとおりに設定します。

→種類:その他
→種類名:任意(今日の天気、など)
→音声認識:任意(今日の天気を教えて、など)
→実行時しゃべる:任意(文字列:今日の天気を調べます、など)

→操作方法:コマンド実行
→コマンドを選択する:read_weather.js
 →地域:自分の住んでいる地域を入力
  ※入力方法は例に倣って「都道府県/地域名」を気象庁の地域名と同じふうに入力すること。 →日時:今日の天気を読み上げて欲しい時は1、明日の天気は2を入力
 →天気:1
 →気温:1
 →時間別降水確率:1
※previewを押して予報を読み上げてくれたらOKです。

→メニューの色を変更する:任意
→状態をアイコンで表示する:任意
→リモコン画面に表示する:任意
→状態を遷移させない:チェック入れる

保存して戻ります。
おこのみで、翌日の天気や降水確率だけの予報などを登録しましょう。

以上で登録は完了なので、実際に呼び出してみて、ちゃんと動いてくれることを確かめてみましょう。(読み上げるのにチョット時間がかかります)
ちゃんと動いてくれるはずなのですが、実はすでにひとつ不具合があるのを把握してたりします。(直せよって話ですが)

曜日読み上げるところが、たまにUndefined曜日になってしまいます。そのうち、JSONで書きなおすときに一緒に修正してアップデートするつもりなので、それまでは我慢するか、自力で何とかしてください。どうせこないなもん誰も使わへんやろ。みたいな感じなので、自分の気分でそのうち修正します。