本記事は、株式会社 函館ラボラトリが運営する「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の影を実装
前提
GomiRModuleでは、バス時刻表のためのデータにGoogleスプレッドシートを使用します。
あらかじめGoogleアカウントを取得しておいてください。
作るもの
Googleスプレッドシート上に打ち込んだごみカレンダーを使い、直近1週間のごみ収集の予定を表示します。
(ごみカレンダーは、Code for Hakodateによって、CC-BYライセンスのもとで提供されています。)
画像の準備
GomiRModuleの背景画像には、下の写真を使用します。
画像を保存し、「background.jpg」という名前をつけます。
(.png形式など別の形式の場合は、.jpg形式に変換してください。)
Processingの画面上のタブで、「スケッチ > スケッチフォルダーを開く」を選ぶと、開いている.pdeファイルが一覧で表示されます。
「data」フォルダの中に、新たに「gomi」フォルダを作ってください。
先ほど名前をつけて保存した「background.jpg」を「data/gomi」フォルダの直下においてください。(ドラッグ&ドロップ)
ここで新しく作った「data/gomi」フォルダの位置を示すパスを、定数として定義します。
/* 略 */
final String AD_PATH = "ad/";
final String WEATHER_PATH = "weather/";
final String BUS_PATH = "bus/";
final String GOMI_PATH = "gomi/"; /* 追加 */
/* 略 */
GomiRModuleの背景を保持する変数を定義します。
/* 略 */
/* ここから追加 */
// GomiRModule
PGraphics gomiBackground;
/* ここまで追加 */
/* 略 */
background.jpg
を用いて、モジュールの背景画像を生成します。
initializeRModuleBackground()
内で実装します。
/* 略 */
void initializeRModuleBackground() {
/* 中略 */
busBackground.rect(0, 0, w, h);
busBackground.endDraw();
busBackground.mask( sizeToModuleMask( moduleSize(module) ) );
/* ここから追加 */
module = RModule.Gomi;
w = moduleWidth( moduleSize(module) );
h = moduleHeight( moduleSize(module) );
back = loadImage(GOMI_PATH + "background.jpg");
gomiBackground = createGraphics(w, h);
gomiBackground.beginDraw();
gomiBackground.colorMode(HSB, 360, 100, 100, 100);
gomiBackground.image( pImageCut(back, CENTER, CENTER, w, h) , 0, 0);
gomiBackground.fill(0, 0, 0, 40);
gomiBackground.noStroke();
gomiBackground.rect(0, 0, w, h);
gomiBackground.endDraw();
gomiBackground.mask( sizeToModuleMask( moduleSize(module) ) );
/* ここまで追加 */
/* 略 */
GomiRModule用関数の準備
新しいファイルRM_Gomi.pde
を作ります。
GomiRModuleの描画用関数drawGomiRModule()
、ごみカレンダー更新用の関数updateGomi()
を作ります。
void drawGomiRModule(Area area) {
}
boolean updateGomi() {
return false;
}
ごみカレンダースプレッドシートの作成
ごみカレンダーのスプレッドシートは、Code for Hakodateが作成しています。
本テキスト内では、Code for Hakodateが作成しているごみカレンダーのスプレッドシートから、データを抜き出したものを使用します。
上記のスプレッドシートを、ご自身のアカウントのGoogleドライブ内に複製してください。
スプレッドシートに含まれる値についての説明は以下の通りです。
変数名 | 意味 |
---|---|
Date(YYYY/MM/DD) | 日付 |
dow | 曜日 |
area:n(nは1〜17の数字) | 地域ごとのごみカレンダー |
Code for Hakodateの提供するデータを参照すると、「MIRAI BASE」のある「美原2丁目」は「area:2」にあたります。
「area:n」の列に存在するごみ種別は下記です。
ごみ種別 | プログラム中で用いる変数名 |
---|---|
燃やせるごみ | GomiTarget.Moyaseru |
燃やせないごみ | GomiTarget.Moyasenai |
プラスチック容器包装 | GomiTarget.Plastic |
缶・びん・ペットボトル | GomiTarget.CanBinPet |
スプレッドシート上にあるデータをWeb API経由で取得するために、BusRModuleと同様にGoogle App Scriptを使用します。
スプレッドシート内のタブから、「拡張機能 > App Script」を選択してください。
エディタが表示されるので、下記のプログラムを入力します。
function getData(id, sheetName) {
var sheet = SpreadsheetApp.openById(id).getSheetByName(sheetName);
var rows = sheet.getDataRange().getValues();
var keys = rows.splice(0, 1)[0];
return rows.map(function(row) {
var obj = {}
row.map(function(item, index) {
obj[keys[index]] = item;
});
return obj;
});
}
function doGet(e) {
var data = getData('URLから取り出した文字列', 'index');
var output = ContentService.createTextOutput(JSON.stringify(data, null, 2));
output.setMimeType(ContentService.MimeType.TEXT);
return output;
}
URLから取り出した文字列
の部分は、下記がスプレッドシートのURLのとき、
https://docs.google.com/spreadsheets/d/1B4EFM564k4pHl6GIi6_jf4Lo035YzOb5l5er8rn4sCQ/edit#gid=0
d/
と/edit
の間にある、1B4EFM564k4pHl6GIi6_jf4Lo035YzOb5l5er8rn4sCQ
に置換します。
これはあくまで例ですから、実装するときには、先ほど作成したスプレッドシートのURLを参照してください。
次に、画面上部の「デプロイ」ボタンをクリックし、「新しいデプロイ」を選択します。
設定用のウィンドウが出ます。
自分として実行する設定にした上で、アクセスできるユーザを全員にします。
デプロイをクリックすると、APIのURLがクリックできるようになります。
URLを開くと、下のようなデータが返ってきます。
[
{
"Date(YYYY/MM/DD)": "2020/04/01",
"dow": "水",
"area:1": "缶・びん・ペットボトル",
"area:2": "燃やせないごみ",
"area:3": "",
"area:4": "",
"area:5": "缶・びん・ペットボトル",
"area:6": "燃やせないごみ",
"area:7": "燃やせないごみ",
"area:8": "燃やせないごみ",
"area:9": "",
"area:10": "",
"area:11": "",
"area:12": "缶・びん・ペットボトル",
"area:13": "缶・びん・ペットボトル",
"area:14": "缶・びん・ペットボトル",
"area:15": "缶・びん・ペットボトル",
"area:16": "",
"area:17": "缶・びん・ペットボトル"
},
/* 略 */
得られたURLは、プログラムの中にも入れておきます。
/* 略 */
final String WEATHER_API_KEY = "取得したKey";
final String BUS_API_URL = "取得したURL";
final String GOMI_API_URL = "取得したURL" /* 追加 */
/* 略 */
これでWeb APIの実装が完了しました。
ごみカレンダーAPIを叩く
関数updateGomi()
で、バスの時刻表を取得します。
GomiRModuleのときと同様に、JSONArrayやJSONObjectを取得します。
取り出した値は、配列に格納します。
GomiRModuleで使う変数を定義しておきます。
RModuleのリストに、GomiModuleを表す「Gomi」を追加します。
/* 略 */
enum RModule {
Weather,
Bus,
Gomi /* 追加 */
}
/* 略 */
ごみの種別をenum(列挙型)で定義しておきます。
/* 略 */
/* ここから追加 */
enum GomiTarget {
Moyaseru,
Moyasenai,
Plastic,
CanBinPet,
None
}
/* ここまで追加 */
/* 略 */
配列などの変数を定義します。
/* 略 */
/* ここから追加 */
// GomiModule
PGraphics gomiBackground;
String[] gomiDows = new String[7];
GomiTarget[] gomiTargets = new GomiTarget[7];
boolean isUpdatedGomi = false;
/* ここまで追加 */
/* 略 */
次にupdateGomi()
内で、データの取得を実装します。
Web API上で使われている変数名を配列で定義します。
/* 略 */
boolean updateGomi() {
final String[] keys = {"Date(YYYY/MM/DD)", "dow", "area:2"}; /* 追加 */
return true;
}
直近1週間の曜日とごみ種別を入れる配列を初期化します。
今日の日付を文字列にする処理も実装しておきます。
/* 略 */
boolean updateGomi() {
final String[] keys = {"Date(YYYY/MM/DD)", "dow", "area:2"};
/* ここから追加 */
gomiDows = new String[7];
gomiTargets = new GomiTarget[7];
String today = year + "/" + nf(month, 2) + "/" + nf(day, 2);
println(today);
/* ここまで追加 */
return true;
}
データ取得処理の実装の準備として、try-catch文を準備します。
/* 略 */
boolean updateGomi() {
final String[] keys = {"Date(YYYY/MM/DD)", "dow", "area:2"};
gomiDows = new String[7];
gomiTargets = new GomiTarget[7];
String today = year + "/" + nf(month, 2) + "/" + nf(day, 2);
println(today);
/* ここから追加 */
try {
println("updateGomi(): ごみカレンダーを取得しました。");
} catch (Exception e) {
println("updateGomi(): 本日分のごみカレンダーを取得できませんでした。" + e);
return false;
}
/* ここまで追加 */
return true;
}
Web APIからJSON形式のデータを取得してみましょう。
/* 略 */
boolean updateGomi() {
final String[] keys = {"Date(YYYY/MM/DD)", "dow", "area:2"};
gomiDows = new String[7];
gomiTargets = new GomiTarget[7];
String today = year + "/" + nf(month, 2) + "/" + nf(day, 2);
println(today);
try {
/* ここから追加 */
processing.data.JSONArray json;
json = loadJSONArray(GOMI_API_URL);
/* ここまで追加 */
println("updateGomi(): ごみカレンダーを取得しました。");
} catch (Exception e) {
println("updateGomi(): 本日分のごみカレンダーを取得できませんでした。" + e);
return false;
}
return true;
}
今日の日付が含まれているデータが、取得したデータの何個目にあるかをtodayRowNum
に保持しておきます。
もし、今日の日付のデータがない場合は、取得できなかった扱いにします。
/* 略 */
boolean updateGomi() {
final String[] keys = {"Date(YYYY/MM/DD)", "dow", "area:2"};
gomiDows = new String[7];
gomiTargets = new GomiTarget[7];
String today = year + "/" + nf(month, 2) + "/" + nf(day, 2);
println(today);
try {
processing.data.JSONArray json;
json = loadJSONArray(GOMI_API_URL);
/* ここから追加 */
int todayRowNum = Integer.MAX_VALUE;
for (int row = 0; row < json.size(); row++) {
processing.data.JSONObject obj = json.getJSONObject(row);
if (obj.getString(keys[0]).equals(today)) {
todayRowNum = row;
break;
}
}
if (todayRowNum == Integer.MAX_VALUE) throw new Exception();
/* ここまで追加 */
println("updateGomi(): ごみカレンダーを取得しました。");
} catch (Exception e) {
println("updateGomi(): 本日分のごみカレンダーを取得できませんでした。" + e);
return false;
}
return true;
}
今日の日付のデータが何個目にあるか特定できたら、そこから7日分のデータを取得します。
"燃やせるごみ"
などのごみ回収対象がデータとして入っていた場合は、対応するGomiTarget.Moyaseru
などの変数を、配列に入れておきます。
/* 略 */
boolean updateGomi() {
final String[] keys = {"Date(YYYY/MM/DD)", "dow", "area:2"};
gomiDows = new String[7];
gomiTargets = new GomiTarget[7];
String today = year + "/" + nf(month, 2) + "/" + nf(day, 2);
println(today);
try {
processing.data.JSONArray json;
json = loadJSONArray(GOMI_API_URL);
int todayRowNum = Integer.MAX_VALUE;
for (int row = 0; row < json.size(); row++) {
processing.data.JSONObject obj = json.getJSONObject(row);
if (obj.getString(keys[0]).equals(today)) {
todayRowNum = row;
break;
}
}
if (todayRowNum == Integer.MAX_VALUE) throw new Exception();
/* ここから追加 */
for (int i = 0; i < 7; i++) {
int row = todayRowNum + i;
processing.data.JSONObject obj = json.getJSONObject(row);
String gomiTargetString = obj.getString(keys[2]);
switch (gomiTargetString) {
case "燃やせるごみ":
gomiTargets[i] = GomiTarget.Moyaseru;
break;
case "燃やせないごみ":
gomiTargets[i] = GomiTarget.Moyasenai;
break;
case "プラスチック容器包装":
gomiTargets[i] = GomiTarget.Plastic;
break;
case "缶・びん・ペットボトル":
gomiTargets[i] = GomiTarget.CanBinPet;
break;
default:
gomiTargets[i] = GomiTarget.None;
}
String gomiDowString = obj.getString(keys[1]);
gomiDows[i] = gomiDowString;
println("[" + gomiDowString + "] " + gomiTargetString);
}
/* ここまで追加 */
println("updateGomi(): ごみカレンダーを取得しました。");
} catch (Exception e) {
println("updateGomi(): 本日分のごみカレンダーを取得できませんでした。" + e);
return false;
}
return true;
}
関数updateGomi()
が実装できたので、この関数を呼び出すようにします。
ごみカレンダーを更新するタイミングは、以下の2つとします。
- デジタルサイネージを起動したとき
- 1日経過したとき
関数updateGomi()
で正しく値を取得できたかを表す変数isUpdatedGomi
への代入として呼び出します。
/* 略 */
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);
isUpdatedBus = updateBus();
isUpdatedGomi = updateGomi(); /* 追加 */
beforeDay = day;
}
if (isUpdatedSecond) {
if (second % STAY_SECOND == 0) {
updateNowPageID(true);
}
if (minute + second == 0) {
println(hour + "時になりました。");
}
beforeSecond = second;
}
}
/* 略 */
/* 略 */
void initialize() {
initializeDate();
initializeImage();
initializeGrid();
initializePlaceholder();
initializeRModuleBackground();
isUpdatedWeather = updateWeather();
isUpdatedBus = updateBus();
isUpdatedGomi = updateGomi(); /* 追加 */
}
/* 略 */
これでデータの更新処理が実装できました。
GomiRModuleの描画
データが変数に代入できている状態になったので、画面上に表示するところまで実装します。
置換可能なReplaceableModuleを実装するとき、最低限定めなければいけないことは以下です。
- 描画時の基準となるエリア(Area.area1〜Area.area8のいずれか)
- 描画時のサイズ(Size.S〜Size.Lのいずれか)
まずはGomiRModuleの描画時の基準となるエリアについて説明します。
GomiRModuleの描画の基準になるのは、8つある表示エリアのうちArea.area5
です。
関数moduleSize()
をGomiRModuleに対応させます。
GomiRModuleのサイズはSize.M
です。
Size moduleSize(RModule module) {
if (module == RModule.Weather) return Size.M;
if (module == RModule.Bus) return Size.L;
if (module == RModule.Gomi) return Size.M; /* 追加 */
return Size.S;
}
ごみの種類に応じて表示するときの色を使い分けます。
表示する色を返す関数を作ります。
/* 略 */
color gomiTargetToColor(GomiTarget target) {
switch (target) {
case Moyaseru:
return color(340, 30, 100);
case Moyasenai:
return color(130, 30, 80);
case Plastic:
return color(30, 30, 100);
case CanBinPet:
return color(210, 30, 100);
default:
return color(0, 0, 90);
}
}
ごみの種類の表示名を文字列として返すために、関数を定義します。
/* 略 */
String gomiTargetToString(GomiTarget target) {
switch (target) {
case Moyaseru:
return "燃やせるごみ";
case Moyasenai:
return "燃やせないごみ";
case Plastic:
return "プラスチック容器包装";
case CanBinPet:
return "缶・びん・ペットボトル";
default:
return "";
}
}
次に関数drawGomiRModule()
を実装します。
Area.area5
の左上の座標、GomiRModuleの幅と高さを取得します。
void drawGomiRModule(Area area) {
/* ここから追加 */
RModule module = RModule.Gomi;
Size size = moduleSize(module);
int x = layoutGuideX(area);
int y = layoutGuideY(area);
int w = moduleWidth(size);
int h = moduleHeight(size);
/* ここまで追加 */
}
/* 略 */
GomiRModuleのタイトルと背景画像をを表示します。
void drawGomiRModule(Area area) {
RModule module = RModule.Gomi;
Size size = moduleSize(module);
int x = layoutGuideX(area);
int y = layoutGuideY(area);
int w = moduleWidth(size);
int h = moduleHeight(size);
/* ここから追加 */
image(gomiBackground, x, y, w, h);
drawText(LEFT, BASELINE, WHITE_COLOR, 32, "美原2丁目のごみカレンダー", x+50, y+50);
drawText(LEFT, BASELINE, WHITE_COLOR, 16, "ごみカレンダー提供元: Code for Hakodate", x+50, y+100);
/* ここまで追加 */
}
/* 略 */
updateGomi()
で取得した、1週間分のデータを表示します。
今日のごみ回収対象は大きく表示し、明日以降の6日分は小さく表示します。
void drawGomiRModule(Area area) {
RModule module = RModule.Gomi;
Size size = moduleSize(module);
int x = layoutGuideX(area);
int y = layoutGuideY(area);
int w = moduleWidth(size);
int h = moduleHeight(size);
image(gomiBackground, x, y, w, h);
drawText(LEFT, BASELINE, WHITE_COLOR, 32, "美原2丁目のごみカレンダー", x+50, y+50);
drawText(LEFT, BASELINE, WHITE_COLOR, 16, "ごみカレンダー提供元: Code for Hakodate", x+50, y+100);
/* ここから追加 */
if (isUpdatedGomi) {
textAlign(LEFT, TOP);
// 今日の回収ごみは大きく表示する
for (int i = 0; i <= 0; i++) {
GomiTarget target = gomiTargets[i];
fill(gomiTargetToColor(target));
rect(x+50, y+150+(h-150)*i/7.0, w-100, 70);
drawText(LEFT, BASELINE, BLACK_COLOR, 40, gomiDows[i]+" "+gomiTargetToString(target), x+50+10, y+160+(h-150)*i/7);
}
// 1日後〜3日後までの回収ごみは小さく3行で左側に表示する
for (int i = 1; i <= 3; i++) {
GomiTarget target = gomiTargets[i];
fill(gomiTargetToColor(target));
rect(x+50, y+240+(h-150)*(i-1)/7.0, (w-100)/2-20, 32);
drawText(LEFT, BASELINE, BLACK_COLOR, 24, gomiDows[i]+" "+gomiTargetToString(target), x+50+10, y+242+(h-150)*(i-1)/7);
}
// 4日後〜6日後までの回収ごみは小さく3行で右側に表示する
for (int i = 4; i <= 6; i++) {
GomiTarget target = gomiTargets[i];
fill(gomiTargetToColor(target));
rect(x+50+(w-100)/2+20, y+240+(h-150)*(i-4)/7.0, (w-100)/2-20, 32);
drawText(LEFT, BASELINE, BLACK_COLOR, 24, gomiDows[i]+" "+gomiTargetToString(target), x+50+10+(w-100)/2+20, y+242+(h-150)*(i-4)/7);
}
} else {
fill(0, 0, 0, 50);
noStroke();
rect(x, y, w, h, MODULE_RECT_ROUND);
drawText(CENTER, BASELINE, WHITE_COLOR, 24, "GomiModule\nデータを取得できません", x+w/2, y+h/2);
}
/* ここまで追加 */
}
これで描画用関数が実装できたので、drawModules()
内で呼び出すようにします。
/* 略 */
void drawModules() {
if (nowPageID == 0) {
drawFullImageModule(background);
drawGridModule();
drawPlaceholderModule();
drawWeatherRModule(Area.area1);
drawBusRModule(Area.area3);
drawGomiRModule(Area.area5); /* 追加 */
} else if (nowPageID == 1) {
/* 略 */
GomiRModuleを呼び出せるようになりました。
実行してみると、1週間分のごみカレンダーを表示できるようになっています。
最後に
これでごみ出しカレンダーを表示する「GomiRModule」が実装できました。
**(発展編)ツイートを表示する「TwitterRModule」**を作ってみましょう。