本記事は、株式会社 函館ラボラトリが運営する「Bラボ」における、大人向け「看板アプリ(デジタルサイネージ)を作るコース」用教材テキストです。
- 【Bラボデジタルサイネージ1】Processing+ラズパイでデジタルサイネージを作る!
- 【Bラボデジタルサイネージ2】ラズパイ初期設定
- 【Bラボデジタルサイネージ3】準備プログラム作成&機能実現の方針決定
- 【Bラボデジタルサイネージ4】レイアウトの基準になるグリッドを表示する「GridModule」
- 【Bラボデジタルサイネージ5】レイアウトの基準になる枠を表示する「PlaceholderModule」
- 【Bラボデジタルサイネージ6】画像を全画面表示する「FullImageModule」
- 【Bラボデジタルサイネージ7】ページの自動切り替え
- 【Bラボデジタルサイネージ8】設置されている場所の名前を表示する「LocationModule」
- 【Bラボデジタルサイネージ9】現在の時間を表示する「DateModule」(本記事)
- 【Bラボデジタルサイネージ10】ページ切り替えの時間が分かる「ProgressBarModule」
- 【Bラボデジタルサイネージ11】現在の表示中のページが分かる「PageControlModule」
- 【Bラボデジタルサイネージ12】現在の天気を表示する「WeatherRModule」
- 【Bラボデジタルサイネージ13】直近2件のバス時刻表を表示する「BusRModule」
- 【Bラボデジタルサイネージ14】ごみ出しカレンダーを表示する「GomiRModule」
- 【Bラボデジタルサイネージ15】(発展編)ツイートを表示する「TwitterRModule」
- 【Bラボデジタルサイネージ16】開店/閉店を表示する「OpenCloseRModule」
- 【Bラボデジタルサイネージ17】部屋の温度を表示する「TemperatureRModule」
- 【Bラボデジタルサイネージ18】部屋の明るさを表示する「BrightnessRModule」
- 【Bラボデジタルサイネージ19】起動画面を表示する「LaunchingScreenModule」
- 【Bラボデジタルサイネージ20】RModuleの影を実装
作るもの
今日の日付や今の時刻を表示できるようにします。
日付と時刻の取得
Processingには、日付と時刻を取得するための関数が定義されています。
| 取得したい値 | 使用する関数 | 返り値の型 | 
|---|---|---|
| 年(西暦) | year() | 整数型 | 
| 月 | month() | 整数型 | 
| 日 | day() | 整数型 | 
| 時(24時間表示) | hour() | 整数型 | 
| 分 | minute() | 整数型 | 
| 秒 | second() | 整数型 | 
これらの値を取得して保存するための変数を定義します。
/* 略 */
color GREEN_COLOR;
int year; /* 追加 */
int month; /* 追加 */
int day; /* 追加 */
int hour; /* 追加 */
int minute; /* 追加 */
int second; /* 追加 */
/* 略 */
定義した6つの変数に対して、現在の時刻を取得し代入する処理を書きます。
新しいファイルM_Date.pdeを作成し、その中に新しい関数updateDate()を作ります。
void updateDate() {
  year = year();
  month = month();
  day = day();
  hour = hour();
  minute = minute();
  second = second();
}
これで6つの変数を更新する処理ができたので、関数updateDate()を関数updateDatas()の中で呼び出すように書き換えましょう。
関数updateDate()内でsecond()を使っている部分は、定義した変数secondに置き換えましょう。
/* 略 */
void updateDatas() {
  updateDate(); /* 追加 */
  if (second % STAY_SECOND == 0) { /* second()をsecondに変更 */
    updateNowPageID(true);
  }
}
/* 略 */
これで、毎秒時刻を取得できるようになりました。
曜日の取得
日付が取得できたら曜日も取得したいものです。
しかし、Processingでは、曜日を取得するための関数は用意されていません。
自分で関数を定義し、日付から曜日を算出できるようにします。
まず、7つの曜日を、enum(列挙型)を使って定義します。
/* 略 */
  area7, 
  area8
}
/* ここから追加 */
enum Youbi {
  Sun, 
  Mon, 
  Tue, 
  Wed, 
  Thu, 
  Fri, 
  Sat
}
/* ここまで追加 */
/* 略 */
次に、曜日を表す変数を定義します。
/* 略 */
int minute;
int second;
String youbiString; /* 追加 */
Youbi youbi; /* 追加 */
/* 略 */
youbiStringは、曜日の日本語表記で"日" "月"などが入ります。
youbiは、列挙型で定義した曜日のことで、Youbi.Sun Youbi.Monなどが入ります。
次に、曜日を算出する関数を定義します。
M_Date.pde内に、新しい関数calcYoubi()を定義します。
/* 略 */
/* ここから追加 */
Youbi calcYoubi(int year, int month, int day) {
}
/* ここまで追加 */
曜日の算出には、Zellerの公式を使います。
Zellerの公式を使うと、指定した年月日が日曜なら0、月曜なら1...という風に値が得られます。
公式をプログラムで実装します。
/* 略 */
Youbi calcYoubi(int year, int month, int day) {
  /* ここから追加 */
  final Youbi[] youbi = Youbi.values();
  if (month < 3) {
    year--;
    month += 12;
  }
  return youbi[(year+year/4-year/100+year/400+(13*month+8)/5+day)%7];
  /* ここまで追加 */
}
関数内のyoubiという配列には、Youbi.SunからYoubi.Satまでの7つの曜日が入っています。
Zellerの公式の計算結果を配列の添字に使い、正しい曜日を返します。
曜日の算出のための関数が実装できたので、この関数を実際に呼び出すよう書き換えます。
/* 略 */
void updateDatas() {
  updateDate();
  youbi = calcYoubi(year, month, day); /* 追加 */
  if (second % STAY_SECOND == 0) {
    updateNowPageID(true);
  }
}
この時点で、年月日が何曜日かを算出できました。
次は、youbiに入っている値を"日" "月"のような日本語表記に変換してみましょう。
M_Date.pde内に、新しい関数youbiToString()を作ります。
/* 略 */
/* ここから追加 */
String youbiToString(Youbi youbi) {
  String str = "";
  return str;
}
/* ここまで追加 */
引数で持ってきたyoubiに対応する日本語表記は、以下の通りです。
| Youbi youbi | String youbiString |
| :-- | :-- | :-- |
| Youbi.Sun | "日" |
| Youbi.Mon | "月" |
| Youbi.Tue | "火" |
| Youbi.Wed | "水" |
| Youbi.Thu | "木" |
| Youbi.Fri | "金" |
| Youbi.Sat | "土" |
表のように対応する条件分岐を入れると、関数はこのように書けます。
/* 略 */
String youbiToString(Youbi youbi) {
  String str = "";
  /* ここから追加 */
  switch (youbi) {
    case Sun:
      str = "日";
      break;
    case Mon:
      str = "月";
      break;
    case Tue:
      str = "火";
      break;
    case Wed:
      str = "水";
      break;
    case Thu:
      str = "木";
      break;
    case Fri:
      str = "金";
      break;
    case Sat:
      str = "土";
      break;
  }
  /* ここまで追加 */
  return str;
}
曜日を日本語表記にする関数が実装できたので、実際に呼び出してみましょう。
/* 略 */
void updateDatas() {
  updateDate();
  youbi = calcYoubi(year, month, day);
  youbiString = youbiToString(youbi); /* 追加 */
  if (second % STAY_SECOND == 0) {
    updateNowPageID(true);
  }
}
初期化関数の作成
起動時に値を取得できるようにします。
新しい関数initializeDate()を作ります。
/* 略 */
void initializeDate() {
  updateDate();
  youbi = calcYoubi(year(), month(), day());
  youbiString = youbiToString(youbi);
}
この関数をinitialize()内の一番最初に呼び出すようにします。
void initialize() {
  initializeDate(); /* 追加 */
  initializeImage();
  initializeGrid();
  initializePlaceholder();
}
処理が必要以上に実行されないようにする
void updateDatas() {
  updateDate();
  youbi = calcYoubi(year, month, day);
  youbiString = youbiToString(youbi);
  if (second % STAY_SECOND == 0) {
    updateNowPageID(true);
  }
}
関数updateDatas()では、1秒に1回、日時の更新と曜日の算出を実行しています。
この中で、「曜日の算出」については、1日に1回しか実行する必要のない処理になります(日付は24時間に1回しか変わらないため)。
また、今後frameRateを1よりも大きくする場合(アニメーションを実装したいときなど)には、1秒に何回も日時を更新しなくても良いはずです。
必要以上に関数の実行回数を増やすと処理が重くなってしまうため、必要なときに実行されるようにしてみましょう。
実装するのは2つのパターンです。
- 日付が変わったときに実行
- 1秒間隔で実行
日付が変わったときに実行
日付が変わったときの判定は、「1フレーム前のdayと、今のdayが異なる値である」という条件で実現できます。
このときの「1フレーム前のday」を保持しておく変数を追加します。
/* 略 */
int year;
int month;
int day;
int hour;
int minute;
int second;
int beforeDay = day(); /* 追加 */
/* 略 */
定義したbeforeDayを使い、「1フレーム前のdayと、今のdayが異なる値である」という条件分岐を実現します。
1日に1回だけ更新が必要な変数はyoubiとyoubiStringです。
void updateDatas() {
  updateDate();
  
  /* ここから追加 */
  final boolean isUpdatedDay = (day != beforeDay);
  if (isUpdatedDay) {
    println("日付が変わりました。");
    youbi = calcYoubi(year, month, day);
    youbiString = youbiToString(youbi);
    beforeDay = day;
  }
  /* ここまで追加 */
  if (second % STAY_SECOND == 0) {
    updateNowPageID(true);
  }
}
isUpdatedDayは、条件に当てはまればtrueになります。
trueのとき(日付が更新されたとき)だけ、if文で囲まれた部分の処理を実行します。
if文の最後で、必ずbeforeDayを今のdayに更新します。
1秒間隔で実行、1時間間隔で実行
秒が更新されたかの判定は、「1フレーム前のsecondと、今のsecondが異なる値である」という条件で実現できます。
このときの「1フレーム前のsecond」を保持しておく変数を追加します。
/* 略 */
int year;
int month;
int day;
int hour;
int minute;
int second;
int beforeDay = day();
int beforeSecond = second(); /* 追加 */
/* 略 */
定義したbeforeSecondを使い、「1フレーム前のsecondと、今のsecondが異なる値である」という条件分岐を実現します。
1秒に1回だけ実行するのは、「画面切り替えの処理」です。
void updateDatas() {
  updateDate();
  
  final boolean isUpdatedDay = (day != beforeDay);
  final boolean isUpdatedSecond = (second != beforeSecond); /* 追加 */
  if (isUpdatedDay) {
    println("日付が変わりました。");
    youbi = calcYoubi(year, month, day);
    youbiString = youbiToString(youbi);
    beforeDay = day;
  }
  
  /* ここから追加 */
  if (isUpdatedSecond) {
    if (second % STAY_SECOND == 0) {
      updateNowPageID(true);
    }
    beforeSecond = second;
  }
  /* ここまで追加 */
}
isUpdatedSecondは、条件に当てはまればtrueになります。
trueのとき(1秒経ったとき)だけ、if文で囲まれた部分の処理を実行します。
if文の最後で、必ずbeforeSecondを今のsecondに更新します。
ついでに、「1時間経過したとき」の条件も実装してみます。
「1時間経過したとき」は、「1秒経ったとき」かつ「分と秒がどちらも0のとき」です。
void updateDatas() {
  updateDate();
  
  final boolean isUpdatedDay = (day != beforeDay);
  final boolean isUpdatedSecond = (second != beforeSecond);
  if (isUpdatedDay) {
    println("日付が変わりました。");
    youbi = calcYoubi(year, month, day);
    youbiString = youbiToString(youbi);
    beforeDay = day;
  }
  
  if (isUpdatedSecond) {
    if (second % STAY_SECOND == 0) {
      updateNowPageID(true);
    }
    /* ここから追加 */
    if (minute + second == 0) {
      println(hour + "時になりました。");
    }
    /* ここまで追加 */
    beforeSecond = second;
  }
}
「1時間経過したとき」に実行するのは、あとの章で実装する「WeatherModule」などのデータ取得処理です。
祝日判定
平日とされる曜日(月〜金)であっても、祝日になっている日があります。
実は祝日なのに平日として過ごすと、バスの時刻表などを間違えて見てしまうことに繋がります。
このような間違いにつながらないよう、「今日が祝日なのか」を判定する処理を実装します。
祝日の場合、各曜日は以下のような表記にします。
(なお、判定ロジックの仕様上、土日のときは祝日かどうか判定できないため、祝日であっても「・祝」は付きません。)
| Youbi youbi | String youbiString |
| :-- | :-- | :-- |
| Youbi.Sun | "日" |
| Youbi.Mon | "月・祝" |
| Youbi.Tue | "火・祝" |
| Youbi.Wed | "水・祝" |
| Youbi.Thu | "木・祝" |
| Youbi.Fri | "金・祝" |
| Youbi.Sat | "土" |
祝日かどうかの判定には、今回は**holiday api**を利用します。
APIには、下記からアクセスできます。
今日が土日祝のいずれかであれば、「holiday」と表示されます。
土日祝のいずれでもなければ、それ以外の表示になります。
祝日判定では、出力が「holiday」かどうかだけ考えます。
APIの出力を取得するにあたって、Javaのライブラリを二つ使用します。
| ライブラリ | 用途 | 
|---|---|
| java.io | 入出力に関わる操作に使う。APIの出力結果を読み込む。 | 
| java.net | ネットワーク通信で使う。Web上のAPIへのアクセス。 | 
プログラム内でライブラリを読み込んでおきます。
import java.io.*; /* 追加 */
import java.net.*; /* 追加 */
/* 略 */
次に、「祝日かどうか」を保持する変数を定義します。
/* 略 */
int second;
int beforeDay = day();
int beforeSecond = second();
String youbiString;
Youbi youbi;
boolean isHoliday; /* 追加 */
/* 略 */
祝日判定用の関数updateIsHoliday()を作ります。
この関数は、実行したときにisHolidayの値を更新します。
関数の返り値では、APIからの出力の取得が成功したときにtrue、失敗したときにfalseを返します。
/* 略 */
/* ここから追加 */
boolean updateIsHoliday() {
  return true;
}
/* ここまで追加 */
この関数に、APIの出力を取得するための処理を書きます。
/* 略 */
boolean updateIsHoliday() {
  /* ここから追加 */
  URL url = new URL("http://s-proj.com/utils/checkHoliday.php");
  InputStream is = url.openStream();
  InputStreamReader isr = new InputStreamReader(is, "utf8");
  BufferedReader br = new BufferedReader(isr);
  isHoliday = br.readLine().equals("holiday");
  /* ここまで追加 */
  return true;
}
InputStreamとInputStreamReaderとBufferedReaderを使い、指定したURL先にあるテキストを読み込んでいます。
br.readLine().equals("holiday")では、読み込んだテキストがholidayかどうかを判定しています。
このようにして値を取得できれば判定は成功ですが、ネットワーク通信を用いるときには、何らかの問題でアクセスできない場合があります。
たとえば、機器がネットワークに接続されていないときには、APIから値を取得することができません。
エラーが起きた時の例外に対処できなければ、プログラムの停止につながります。
そこで、APIから値を取得できなかったときの処理も書きます。
try-catch文を使います。
/* 略 */
boolean updateIsHoliday() {
  /* ここから変更 */
  try {
    URL url = new URL("http://s-proj.com/utils/checkHoliday.php");
    InputStream is = url.openStream();
    InputStreamReader isr = new InputStreamReader(is, "utf8");
    BufferedReader br = new BufferedReader(isr);
    isHoliday = br.readLine().equals("holiday");
  } catch (Exception e) {
    println("isHoliday(): 土日祝判定を取得できませんでした。" + e);
    return false;
  }
  /* ここまで変更 */
  return true;
}
APIからの値が取得できなかったとき、catchで囲まれた部分でエラーを出力します。
エラーが起きなかった時の出力も実装します。
/* 略 */
boolean updateIsHoliday() {
  try {
    URL url = new URL("http://s-proj.com/utils/checkHoliday.php");
    InputStream is = url.openStream();
    InputStreamReader isr = new InputStreamReader(is, "utf8");
    BufferedReader br = new BufferedReader(isr);
    isHoliday = br.readLine().equals("holiday");
  } catch (Exception e) {
    println("isHoliday(): 土日祝判定を取得できませんでした。" + e);
    return false;
  }
  /* ここから追加 */
  print("isHoliday(): 土日祝判定を取得しました。");
  if (isHoliday) {
    println("今日は土日祝に含まれます。");
  } else {
    println("今日は平日です。");
  }
  /* ここまで追加 */
  return true;
}
これで関数が実装できました。
関数updateIsHoliday()を実際に呼び出しましょう。
呼び出す場所とタイミングは以下の2点です。
- 
initializeDate()で、日時が初期化されたとき
- 
updateDatas()で、日付が更新されたとき
それぞれ実装しましょう。
/* 略 */
void initializeDate() {
  updateDate();
  /* ここから追加 */
  // 祝日APIの値が取得できなければ、ネットワークかAPI側のどちらかの問題
  if (!updateIsHoliday()) println("initializeDate(): ネットワークに接続できているか確認してください。");
  /* ここまで追加 */
  youbi = calcYoubi(year(), month(), day());
  youbiString = youbiToString(youbi);
}
/* 略 */
void updateDatas() {
  updateDate();
  
  final boolean isUpdatedDay = (day != beforeDay);
  final boolean isUpdatedSecond = (second != beforeSecond);
  if (isUpdatedDay) {
    println("日付が変わりました。");
    /* ここから追加 */
    if (!updateIsHoliday()) {
      println("ネットワークに接続できていない可能性があります。接続できているか確認してください。");
    }
    /* ここまで追加 */
    youbi = calcYoubi(year, month, day);
    youbiString = youbiToString(youbi);
    beforeDay = day;
  }
  
  if (isUpdatedSecond) {
    if (second % STAY_SECOND == 0) {
      updateNowPageID(true);
    }
    if (minute + second == 0) {
      println(hour + "時になりました。");
    }
    
    beforeSecond = second;
  }
}
これで祝日かどうかを示すisHolidayが更新されるようになったので、曜日の日本語表記もisHolidayに応じて変わるようにしましょう。
たとえば、月曜日かつ祝日の場合には、「月・祝」のように、末尾に「・祝」を追加します。
関数youbiToString()に追記します。
/* 略 */
String youbiToString(Youbi youbi) {
  String str = "";
  switch (youbi) {
    case Sun:
      str = "日";
      break;
    case Mon:
      str = "月";
      if (isHoliday) str += "・祝"; /* 追加 */
      break;
    case Tue:
      str = "火";
      if (isHoliday) str += "・祝"; /* 追加 */
      break;
    case Wed:
      str = "水";
      if (isHoliday) str += "・祝"; /* 追加 */
      break;
    case Thu:
      str = "木";
      if (isHoliday) str += "・祝"; /* 追加 */
      break;
    case Fri:
      str = "金";
      if (isHoliday) str += "・祝"; /* 追加 */
      break;
    case Sat:
      str = "土";
      break;
  }
  return str;
}
/* 略 */
DateModuleを表示する
表示に必要な変数はすべて揃いました。
DateModuleを表示するための関数drawDateModule()を作ります。
/* ここから追加 */
void drawDateModule() {
  blendMode(EXCLUSION);
  
  String timeText = month + "月" + day + "日" + "(" + youbiString + ")" + nf(hour, 2) + ":" + nf(minute, 2) + ":" + nf(second, 2);
  drawText(LEFT, BASELINE, WHITE_COLOR, 36, timeText, 100, 30);
  
  blendMode(BLEND);
}
/* ここまで追加 */
/* 略 */
drawDateModule()は、drawModules()の中で呼び出します。
/* 略 */
void drawModules() {
  if (nowPageID == 0) {
    drawFullImageModule(background);
    drawGridModule();
    drawPlaceholderModule();
  } else if (nowPageID == 1) {
    drawFullImageModule(adImage[0]);
  }
  drawDateModule(); /* 追加 */
  drawLocationModule();
}
/* 略 */
実行してみましょう。
下図のような出力になっていれば、DateModuleの実装は完了です。
最後に
これで、現在の時間を表示する「DateModule」が実装できました。
次は、**ページ切り替えの時間が分かる「ProgressBarModule」**を実装してみましょう。


