Bラボデジタルサイネージ（長期用テキスト3/3）

![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fuser-images.githubusercontent.com%2F29194025%2F137117015-af05fe4f-7c32-4e38-876f-e153ff2f2f7d.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&w=1400&fit=max&s=2bbf46fb5c1d8dec16f2fcb5d089fde2)

https://github.com/ohayota/digital-signage-template

本記事は、[株式会社 函館ラボラトリ](http://www.hakolab.co.jp)が運営する「[Bラボ](http://www.hakolab.co.jp/b-lab/index.html)」における、大人向け「看板アプリ（デジタルサイネージ）を作るコース」用テキストです。
一からコードを書くわけではなく、あらかじめ用意したコードの一部を改変しながら、デジタルサイネージを作っていきましょう。

http://www.hakolab.co.jp/b-lab/index.html

テンプレートプログラムは、下記GitHubリポジトリの`LongTerm/Part3_Start`ディレクトリに入っています。

https://github.com/ohayota/BLabo-digital-signage-sample/tree/main/LongTerm/Part3_Start


（12/16時点、本テキストは執筆中です）


# 前回のおさらい

Part2では、DateModuleやProgressBarModuleなど、時間の経過によって変化するモジュールを作りました。

ここからは、スクリーンショットが表示されているだけの状態であるRModuleを、実際のデータに連動するようにします。

https://qiita.com/ohayota/items/5f36360b03dd229ba47b


# 必要なもの

- Part1で配置した電子部品
- OpenWeatherのアカウント
    - API Key
- Googleアカウント
    - バス時刻表APIのURL
    - ごみカレンダーAPIのURL
- Twitterアカウント（Developer Accountとして、Twitter APIへの申請が承認されているもの）
    - Consumer Key
    - Consumer Key Secret
    - Access Token
    - Access Token Secret

OpenWeatherのAPI Keyを取得する方法については、下記記事「必要な変数の定義」を参照してください。

https://qiita.com/ohayota/items/d9b1d21037dcedcfd78c#必要な変数の定義


バス時刻表API、ごみカレンダーAPIを用意する方法については、下記2記事の手順を参照してください。

https://qiita.com/ohayota/items/93d7c9b5328188b3c5b6#時刻表スプレッドシートの作成

https://qiita.com/ohayota/items/40ed5db352a688637f83#ごみカレンダースプレッドシートの作成


Twitter APIのキー4つを取得する手順については、下記記事「キーの取得」を参照してください。

https://qiita.com/ohayota/items/6367fad7beaf9ad9ea1c#キーの取得


# RModuleの背景を作る

Part1でスクリーンショットだけ表示されるようにしたRModuleですが、このあとはスクリーンショットを置き換えていきます。
手始めに、各RModuleの背景を生成できるようにしましょう。

背景画像の生成には、PGraphicsの関数`mask`というものを使います。
下記記事の「pgraphicsのmask関数」を参照してください。

https://qiita.com/ohayota/items/d9b1d21037dcedcfd78c#pgraphicsのmask関数

mask関数で使う「型紙」にあたる部分を、関数で生成できるようにします。

``` java:DigitalSignage.pde
// RModuleの背景画像をマスクするための図形（角丸の四角）を返す。
PGraphics sizeToModuleMask(Size size) {
  int w = moduleWidth(size);
  int h = moduleHeight(size);
  PGraphics moduleBackgroundMask = createGraphics(w, h);
  
  moduleBackgroundMask.beginDraw();
  
  moduleBackgroundMask.noStroke();
  moduleBackgroundMask.fill(255);
  moduleBackgroundMask.rect(0, 0, w, h, RMODULE_RECT_ROUND);
  
  moduleBackgroundMask.endDraw();
  
  return moduleBackgroundMask;
}
```

初期化の段階で、`initializeRModuleBackground()`という関数を作り、RModuleの背景を一括生成します。

``` java:Initialize.pde
// 各RModuleの背景を作成。
void initializeRModuleBackground() {
  RModule module;
  int w;
  int h;
  PImage back;
  
  module = RModule.Weather;
  w = moduleWidth( moduleSize(module) );
  h = moduleHeight( moduleSize(module) );
  back = loadImage(WEATHER_PATH + "background.jpg");
  weatherBackground = createGraphics(w, h);
  weatherBackground.beginDraw();
  weatherBackground.colorMode(HSB, 360, 100, 100, 100);
  weatherBackground.image( pImageCut(back, CENTER, CENTER, w, h) , 0, 0);
  weatherBackground.fill(0, 0, 0, 40);
  weatherBackground.noStroke();
  weatherBackground.rect(0, 0, w, h);
  weatherBackground.endDraw();
  // 背景画像を角丸四角の形に合わせてくり抜く。
  weatherBackground.mask( sizeToModuleMask( moduleSize(module) ) );
  
  module = RModule.Bus;
  w = moduleWidth( moduleSize(module) );
  h = moduleHeight( moduleSize(module) );
  back = loadImage(BUS_PATH + "background.jpg");
  busBackground = createGraphics(w, h);
  busBackground.beginDraw();
  busBackground.colorMode(HSB, 360, 100, 100, 100);
  busBackground.image( pImageCut(back, CENTER, CENTER, w, h) , 0, 0);
  busBackground.fill(0, 0, 0, 40);
  busBackground.noStroke();
  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) ) );
  
  module = RModule.Twitter;
  w = moduleWidth( moduleSize(module) );
  h = moduleHeight( moduleSize(module) );
  twitterBackground = createGraphics(w, h);
  twitterBackground.beginDraw();
  twitterBackground.colorMode(HSB, 360, 100, 100, 100);
  PImage twitterLogo = loadImage(TWITTER_PATH + "2021 Twitter logo - blue.png");
  twitterBackground.background(0, 0, 100);
  // Twitterのロゴを縮小し、右上に表示。
  final float logoRatio = twitterLogo.height / float(twitterLogo.width); // 縦横比
  final float logoWidth = 80.0;
  final float logoHeight = logoWidth * logoRatio;
  twitterBackground.image(twitterLogo, w-logoWidth-50, 50, logoWidth, logoHeight);
  twitterBackground.endDraw();
  // 背景画像を角丸四角の形に合わせてくり抜く。
  twitterBackground.mask( sizeToModuleMask( moduleSize(module) ) );
  
  module = RModule.OpenClose;
  w = moduleWidth( moduleSize(module) );
  h = moduleHeight( moduleSize(module) );
  back = loadImage(OPENCLOSE_PATH + "background_open.jpg");
  openCloseBackgroundOpen = createGraphics(w, h);
  openCloseBackgroundOpen.beginDraw();
  openCloseBackgroundOpen.colorMode(HSB, 360, 100, 100, 100);
  openCloseBackgroundOpen.image( pImageCut(back, CENTER, CENTER, w, h) , 0, 0);
  openCloseBackgroundOpen.fill(0, 0, 0, 40);
  openCloseBackgroundOpen.noStroke();
  openCloseBackgroundOpen.rect(0, 0, w, h);
  openCloseBackgroundOpen.endDraw();
  // 背景画像を角丸四角の形に合わせてくり抜く。
  openCloseBackgroundOpen.mask( sizeToModuleMask( moduleSize(module) ) );
  
  back = loadImage(OPENCLOSE_PATH + "background_close.jpg");
  openCloseBackgroundClose = createGraphics(w, h);
  openCloseBackgroundClose.beginDraw();
  openCloseBackgroundClose.colorMode(HSB, 360, 100, 100, 100);
  openCloseBackgroundClose.image( pImageCut(back, CENTER, CENTER, w, h) , 0, 0);
  openCloseBackgroundClose.fill(0, 0, 0, 40);
  openCloseBackgroundClose.noStroke();
  openCloseBackgroundClose.rect(0, 0, w, h);
  openCloseBackgroundClose.endDraw();
  // 背景画像を角丸四角の形に合わせてくり抜く。
  openCloseBackgroundClose.mask( sizeToModuleMask( moduleSize(module) ) );
  
  module = RModule.Temperature;
  w = moduleWidth( moduleSize(module) );
  h = moduleHeight( moduleSize(module) );
  back = loadImage(TEMPERATURE_PATH + "background.jpg");
  temperatureBackground = createGraphics(w, h);
  temperatureBackground.beginDraw();
  temperatureBackground.colorMode(HSB, 360, 100, 100, 100);
  temperatureBackground.image( pImageCut(back, CENTER, CENTER, w, h) , 0, 0);
  temperatureBackground.fill(0, 0, 0, 40);
  temperatureBackground.noStroke();
  temperatureBackground.rect(0, 0, w, h);
  temperatureBackground.endDraw();
  // 背景画像を角丸四角の形に合わせてくり抜く。
  temperatureBackground.mask( sizeToModuleMask( moduleSize(module) ) );
  
  module = RModule.Brightness;
  w = moduleWidth( moduleSize(module) );
  h = moduleHeight( moduleSize(module) );
  back = loadImage(BRIGHTNESS_PATH + "background_bright.jpg");
  brightnessBackgroundBright = createGraphics(w, h);
  brightnessBackgroundBright.beginDraw();
  brightnessBackgroundBright.colorMode(HSB, 360, 100, 100, 100);
  brightnessBackgroundBright.image( pImageCut(back, CENTER, CENTER, w, h) , 0, 0);
  brightnessBackgroundBright.fill(0, 0, 0, 40);
  brightnessBackgroundBright.noStroke();
  brightnessBackgroundBright.rect(0, 0, w, h);
  brightnessBackgroundBright.endDraw();
  // 背景画像を角丸四角の形に合わせてくり抜く。
  brightnessBackgroundBright.mask( sizeToModuleMask( moduleSize(module) ) );
  
  back = loadImage(BRIGHTNESS_PATH + "background_not_bright.jpg");
  brightnessBackgroundNotBright = createGraphics(w, h);
  brightnessBackgroundNotBright.beginDraw();
  brightnessBackgroundNotBright.colorMode(HSB, 360, 100, 100, 100);
  brightnessBackgroundNotBright.image( pImageCut(back, CENTER, CENTER, w, h) , 0, 0);
  brightnessBackgroundNotBright.fill(0, 0, 0, 40);
  brightnessBackgroundNotBright.noStroke();
  brightnessBackgroundNotBright.rect(0, 0, w, h);
  brightnessBackgroundNotBright.endDraw();
  // 背景画像を角丸四角の形に合わせてくり抜く。
  brightnessBackgroundNotBright.mask( sizeToModuleMask( moduleSize(module) ) );
}
```

関数`initialize()`の中で呼び出すようにします。

``` java:Initialize.pde
void initialize() {
  initializeImage();
  initializeRModuleBackground(); // 追加
  
  updateNowPageID(true);
}
```

# 各RModuleの実装

ここからは、スクリーンショットだけ表示されているRModuleを、APIやセンサなどと連動できるようにします。
このあとの実装のために、現在RModuleの描画に使っている7関数の中で、`image`関数が使われている記述を削除（またはコメントアウト）してください。

``` java:例
void drawWeatherRModule(Area area) {
  RModule module = RModule.Brightness;
  Size size = moduleSize(module);

  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);

  //image(loadImage(WEATHER_PATH + "sample.jpg"), x, y); // 削除かコメントアウト
}
```

下記記事の解説を参照しながら進めてください。

https://qiita.com/ohayota/items/d9b1d21037dcedcfd78c

https://qiita.com/ohayota/items/93d7c9b5328188b3c5b6

https://qiita.com/ohayota/items/40ed5db352a688637f83

https://qiita.com/ohayota/items/6367fad7beaf9ad9ea1c

https://qiita.com/ohayota/items/284b5c7e7101f602016f

https://qiita.com/ohayota/items/386c27a021601fb3ccfd

https://qiita.com/ohayota/items/76279927b4ef35eaf628


# 起動画面を表示できるようにする

起動画面については、下記記事で解説しています。

https://qiita.com/ohayota/items/0b6bdeda68c36707f762

起動時に、「どの項目まで読み込みが完了したか」を示す変数を、順番に`true`にしていきます。
「どの項目まで読み込みが完了したか」を示す変数は以下です。

``` java:DigitalSignage.pde
// 起動画面用の初期化済フラグ
boolean isInitializedImages = false;
boolean isInitializedDates = false;
boolean isInitializedWeather = false;
boolean isInitializedBus = false;
boolean isInitializedGomi = false;
boolean isInitializedTwitter = false;
```

初期化用関数を下記のように書き換えます。

``` java:Initialize.pde
void initialize() {
  initializeDate();
  isInitializedDates = true;

  initializeImage();
  initializeRModuleBackground();
  isInitializedImages = true;

  isUpdatedWeather = updateWeather();
  isInitializedWeather = true;

  isUpdatedBus = updateBus();
  isInitializedBus = true;

  isUpdatedGomi = updateGomi();
  isInitializedGomi = true;

  isUpdatedTwitter = updateTwitter();
  isInitializedTwitter = true;

  isUpdatedOpenClose = updateOpenClose();
  isUpdatedTemperature = updateTemperature();
  isUpdatedBrightness = updateBrightness();
}
```

起動画面描画用の関数を作成します。

``` java:M_LaunchingScreen.pde
void drawLaunchingScreenModule() {
  drawFullImageModule(background);

  drawText(CENTER, BASELINE, GREEN_COLOR, 148, "DIGITAL SIGNAGE", width/2, height/2-380);
  drawText(CENTER, BASELINE, BLACK_COLOR, 48, LOCATION, width/2, height/2-180);
  stroke(BLACK_COLOR);
  strokeWeight(5);
  noFill();
  line(300, height/2-200, width-300, height/2-200);

  /* ここから追加 */
  if (isInitializedDates) {
    drawText(CENTER, BASELINE, GREEN_COLOR, 36, "カレンダー取得", width/2, height/2+40);
  } else {
    drawText(CENTER, BASELINE, BLACK_COLOR, 36, "カレンダー取得", width/2, height/2+40);
  }

  if (isInitializedImages) {
    drawText(CENTER, BASELINE, GREEN_COLOR, 36, "画像取得", width/2, height/2+100);
  } else {
    drawText(CENTER, BASELINE, BLACK_COLOR, 36, "画像取得", width/2, height/2+100);
  }

  if (isInitializedWeather) {
    drawText(CENTER, BASELINE, GREEN_COLOR, 36, "天気情報取得", width/2, height/2+160);
  } else {
    drawText(CENTER, BASELINE, BLACK_COLOR, 36, "天気情報取得", width/2, height/2+160);
  }

  if (isInitializedBus) {
    drawText(CENTER, BASELINE, GREEN_COLOR, 36, "バス情報取得", width/2, height/2+220);
  } else {
    drawText(CENTER, BASELINE, BLACK_COLOR, 36, "バス情報取得", width/2, height/2+220);
  }

  if (isInitializedGomi) {
    drawText(CENTER, BASELINE, GREEN_COLOR, 36, "ごみカレンダー取得", width/2, height/2+280);
  } else {
    drawText(CENTER, BASELINE, BLACK_COLOR, 36, "ごみカレンダー取得", width/2, height/2+280);
  }

  if (isInitializedTwitter) {
    drawText(CENTER, BASELINE, GREEN_COLOR, 36, "ツイート取得", width/2, height/2+340);
  } else {
    drawText(CENTER, BASELINE, BLACK_COLOR, 36, "ツイート取得", width/2, height/2+340);
  }
  /* ここまで追加 */
}
```

関数`draw()`内で、起動画面を表示できるようにします。

``` java:DigitalSignage.pde
void draw() {
  if (nowPageID == -1) {
    // データの初期化が完了するまで、起動画面を表示する。
    drawLaunchingScreenModule(); // 追加
  } else {
    // データを更新したあと、各モジュールを描画する。
    updateDatas();
    drawModules();
  }
}
```

これで起動画面が表示されるようになります。

![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-image-store.s3.ap-northeast-1.amazonaws.com%2F0%2F368192%2F56680223-5eb5-892a-1713-7e1d4315cde9.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&w=1400&fit=max&s=e295fc311f2615c60b0cc61b255df4f3)


# RModuleの影を表示する

実装してきたRModuleを立体的に見せるため、影をつくります。
下記記事の通りに進めてください。

https://qiita.com/ohayota/items/57242f18653ccf7eb83b


# 最後に

これでデジタルサイネージの実装は完了です。
もし余力があれば、各モジュールをカスタマイズしてみてください。

お疲れ様でした！