Prebid.js 導入による Header Bidding 改善の舞台裏

こんにちは。メディアプロダクト開発部の我妻謙樹(@itiskj)です。 サーバーサイドエンジニアとして、広告配信システムの開発・運用を担当しています。好きな言語は Go と TypeScript です。

以前、"Header Bidding 導入によるネットワーク広告改善の開発事情" というタイトルで、

  • Header Bidding の仕組み
  • 弊社の広告配信のクライアント側の設計
  • Transparent Ad Marketplace(以下、TAM)導入の過程

についてご紹介しました。今回は、TAM に次いで Prebid.js をあわせて導入した際の知見についてご紹介します。

What is Prebid.js?

Prebid.js とは、OSS で開発されている Web 向け Header Bidding ライブラリです。 http://prebid.org/ において開発されているサービスの1つで、他にはアプリ向けの Prebid Mobile、サーバーサイド向けの Prebid Server などが開発されています。

https://github.com/prebid/Prebid.js/

Prebid のサービス群を用いると、以下二種類いずれかの Header Bidding に対応できます。

  • Client-to-Server Header Bidding(以下、C2S)
    • Prebid.js 及び Prebid Server を用いる
  • Server-to-Server Header Bidding(以下、S2S)
    • Prebid Server を自前でホスティングするか、すでに提供されている第三者サーバーを利用する

C2S 及び S2S の Pros/Cons は以下の通りです。

Type Pros Cons
C2S 対応事業者数が多い/トータルでの実装コストが低い クライアントのネットワーク帯域をより消費する
S2S サーバーサイドで制御できる/クライアント側からは Single Request 対応事業者が C2S と比べて限られている/Cookie Sync の技術的課題

今回は、「対応事業者数が多い」こと、及び「実装コストの改修」を考慮して、C2S 方式を導入しました。

Glossary

本文で言及している用語のうち、ドメイン知識のため説明が必要と思われる用語の一覧です。

word description
事業者 Header Bidding で入札リクエストを送っている先の事業者のこと。ここでは基本的に SSP(DSP を接続する場合もある)のことを指す。
スロット 広告が実際に表示される枠のこと。
DFP DoubleClick For Publisher の略。新名称は Google Ad Manager。
APS Amazon Publisher Services の略。TAM などの一連の広告サービスを提供する総称。
TAM Transparent Ad Marketplace の略。APS のサービスの一つで、Header Bidding を提供する。

Development with Prebid.js

Prebid.js を導入する際に、基本的に 公式ドキュメント | Getting Started 及び Publisher API Reference を参考することになります。公式ドキュメントがかなり充実しているので、基本的なユースケースであれば、ほぼ嵌らずに実装できるでしょう。

以下は、Getting Started に紹介されている、最小限の実装例です。ここで使用されている API のうち、主要なものについて紹介します。

なお、Google Publisher Tag(以下、GPT)との併合を前提としています。

<html>

    <head>
        <link rel="icon" type="image/png" href="/favicon.png">
        <script async src="//www.googletagservices.com/tag/js/gpt.js"></script>
        <script async src="//acdn.adnxs.com/prebid/not-for-prod/1/prebid.js"></script>
        <script>
            var sizes = [
                [300, 250]
            ];
            var PREBID_TIMEOUT = 1000;
            var FAILSAFE_TIMEOUT = 3000;

            var adUnits = [{
                code: '/19968336/header-bid-tag-1',
                mediaTypes: {
                    banner: {
                        sizes: sizes
                    }
                },
                bids: [{
                    bidder: 'appnexus',
                    params: {
                        placementId: 13144370
                    }
                }]
            }];

            // ======== DO NOT EDIT BELOW THIS LINE =========== //
            var googletag = googletag || {};
            googletag.cmd = googletag.cmd || [];
            googletag.cmd.push(function() {
                googletag.pubads().disableInitialLoad();
            });

            var pbjs = pbjs || {};
            pbjs.que = pbjs.que || [];

            pbjs.que.push(function() {
                pbjs.addAdUnits(adUnits);
                pbjs.requestBids({
                    bidsBackHandler: initAdserver,
                    timeout: PREBID_TIMEOUT
                });
            });

            function initAdserver() {
                if (pbjs.initAdserverSet) return;
                pbjs.initAdserverSet = true;
                googletag.cmd.push(function() {
                    pbjs.setTargetingForGPTAsync && pbjs.setTargetingForGPTAsync();
                    googletag.pubads().refresh();
                });
            }

            // in case PBJS doesn't load
            setTimeout(function() {
                initAdserver();
            }, FAILSAFE_TIMEOUT);

            googletag.cmd.push(function() {
                googletag.defineSlot('/19968336/header-bid-tag-1', sizes, 'div-1')
                   .addService(googletag.pubads());
                googletag.pubads().enableSingleRequest();
                googletag.enableServices();
            });

        </script>

    </head>

    <body>
        <h2>Basic Prebid.js Example</h2>
        <h5>Div-1</h5>
        <div id='div-1'>
            <script type='text/javascript'>
                googletag.cmd.push(function() {
                    googletag.display('div-1');
                });

            </script>
        </div>
    </body>

</html>

pbjs.addAdUnits

事業者ごとの設定項目を、スロットごとに追加します。 実質的には、pbjs.adUnits フィールドに渡された引数を追加しておくだけです。

Source Code: - pbjs.addAdUnits()

この際、事業者ごとの設定項目を設定する必要がありますが、全て以下のドキュメントに記述されています。

入札者ごとの設定項目ドキュメント: http://prebid.org/dev-docs/bidders.html

ただし、以下の注意点があります。

  • 事業者ごとに、パラメータの型が String / Number / Object で差異がある
    • ex. placementId が、文字列のこともあれば数字のこともある
  • ドキュメント上は任意(optional)だが、事業者から「必ず付与してください」と言われる場合がある
  • 一部ドキュメントが古い可能性があり、そのタイミングで事業者に最新のパラメーターを聞く必要がある

pbjs.requestBids

実際に Header Bidding 入札リクエストを行っている、要のメソッドです。

  • 設定された全事業者に対してリクエストを行う準備をする
  • auctionManager クラスを通して、auction を生成する
  • auction.callBids() で実際にリクエストを行い、入札結果レスポンスが返ってきたら、callback を実行する

Source Code: - pbjs.requestBids() - auctionManager.createAuction() - auction.callBids()

pbjs.setTargetingForGPTAsync

Header Bidding 入札結果を、GPT の Key/Value に設定します。したがって、入札が完了した後に呼び出す必要 があります。

Source Code: - pbjs.setTargetingForGPTAsync() - targeting.setTargetingForGPT()

Prebid.js のソースコードを追っていくとわかりますが、入札結果の存在したスロットに対して、gpt.PubAdsService.setTargeting() を呼び出しています。

  /**
   * Sets targeting for DFP
   * @param {Object.<string,Object.<string,string>>} targetingConfig
   */
  targeting.setTargetingForGPT = function(targetingConfig, customSlotMatching) {
    window.googletag.pubads().getSlots().forEach(slot => {
      // ...
      slot.setTargeting(key, value);
    })
  };

Debugging Prebid.js

Prebid.js には、公式で数々のデバッグ方法やベストプラクティスが紹介されています。主に以下のドキュメントに詳しいです。

開発者用デバッグモードやChrome Extension などのツールも揃っており、入札フローが複雑な割には比較的デバッグがしやすい印象です。

主要なものについて紹介します。

Debug Log

pbjs.setConfig API には、Debugging option が提供されています。以下のように Option を渡すと、必要十分なログを出力してくれます。

pbjs.setConfig({ debug: true });

しかし、ブラウザリロード(pbjs の再読込)の度に設定がリセットされてしまうので、ブラウザの Console から打ち込む用途として利用するのでは不便です。

一方、弊社の広告配信サーバーの JavaScript SDK のビルドプロセスでは webpack を利用しており、ビルド環境(production/staging/development)を define-plugin を用いてソースコードに埋め込んでいます。

この仕組を利用し、ステージング及び開発環境では、デフォルトでデバッグログを有効にします。

// index.js
this.pbjs.setConfig({
  debug: process.env.NODE_ENV === "development",
})
// webpack.config.js
plugins: [
  /**
   * DefinePlugin create global constants while compiling.
   *
   * @doc https://webpack.js.org/plugins/define-plugin/
   */
  new webpack.DefinePlugin({
    "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
  }),
],

Snippets

Chrome で提供されている Snippets という機能を利用し、本番環境でも手軽に入札リクエストや入札結果の中身をログに出力することができます。

これらのデータは Network タブから各事業者への入札リクエスト・レスポンスを覗くことでも確認できなくはないのですが、視認性が低いため Snippets を利用しています。

例えば、以下は Tips for Troubleshooting で紹介されている Snippets です。スロットごとに、全事業者に対する入札の結果、最終的に win した入札を表示してくれます。

var bids = pbjs.getHighestCpmBids();
var output = [];
for (var i = 0; i < bids.length; i++) {
    var b = bids[i];
    output.push({
        'adunit': b.adUnitCode, 'adId': b.adId, 'bidder': b.bidder,
        'time': b.timeToRespond, 'cpm': b.cpm
    });
}
if (output.length) {
    if (console.table) {
        console.table(output);
    } else {
        for (var j = 0; j < output.length; j++) {
            console.log(output[j]);
        }
    }
} else {
    console.warn('No prebid winners');
}

Chrome Extension

Prebid.js の公式ツールとして、Headerbid Expert という Chrome Extension が公開されています。

こちらのツールは、エンジニアだけでなくディレクターやプロジェクトマネージャーなども、気軽に自社の Header Bidding 入札結果を確認できるツールです。このツールを使うことで、事業者ごとの選別や、各社ごとのタイムアウト設定の見直し、インプレッション損失のリスクの洗い出しなどに利用できます。

f:id:itiskj:20190212120755j:plain
headerbid expert screenshhot

分析結果の見方については、Prebid.js Optimal Header Bidding Setup というドキュメントページに詳しく紹介されています。ある特定の事業者のタイムアウトに引っ張られて機会損失をしているパターン、何らかの設定ミスで DFP へのリクエストが遅れて機会損失をしているパターンなどが紹介されています。

Prebid.js Modules

Prebid.js は Module Architecture を導入しており、各事業者ごとのアダプターや通貨関連の共通処理をまとめたモジュールなどが提供されています。そして、ファイルサイズを可能な限り最小限に抑えるため、自社が必要なモジュールのみを Prebid.js Download ページからダウンロードしたものを利用することが基本です。

Source Code: - src/modules/*.js

今回は、そのうちでも特に主要なモジュールについて紹介します。

Currency Module

Prebid.js を開発していると、入札結果の金額に関して、例えば以下のような要件が必ずと言っていいほど発生するはずです。

  • 事業者ごとに、net/gross が違うが、入札結果からオークションする前に net/gross の単位を統一したい
  • 入札金額の粒度をより細かくして、機会損失を最小限に抑えたい
  • JPY/USD などの通貨設定が事業者ごとに違うが、DFP にリクエストする前に通貨単位を統一する必要がある
  • 通貨単位を統一する場合、為替を考慮する必要がある

その場合は、公式で提供されている Currency Module を使うことになります。Currency Module を導入すると、pbjs.setConfig() に以下の設定項目を渡すことができるようになります。

Source Code: - currency.js

例えば、以下は設定例です。

this.pbjs.setConfig({
    /**
     * set up custom CPM buckets to optimize the bidding requests.
     */
    priceGranularity: "high",
    /**
     * setting for the conversion of multiple bidder currencies into a single currency
     * http://prebid.org/dev-docs/modules/currency.html#currency-config-options
     */
    currency: {
        adServerCurrency: 'JPY',
        conversionRateFile: 'https://currency.prebid.org/latest.json',
        bidderCurrencyDefault: {
            bidderA: 'JPY',
            bidderB: 'USD',
        },
        defaultRates: {
            USD: {
                JPY: 110,
            }
        },
    },
});

conversionRateFile には、為替レートを変換する時に参考にする為替レートが格納されたファイルの URL を設定することができます。独自で更新したい場合は、こちらを自社の S3 Bucket などを見るようにしておいて、別途ファイルを更新するような仕組みを導入すればよいでしょう。デフォルトでは、jsDelivr と呼ばれる Open Source CDN に配置されてあるファイルを見に行くようになっています。

// https://github.com/prebid/Prebid.js/blob/master/modules/currency.js#L8
const DEFAULT_CURRENCY_RATE_URL = 'https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json?date=$$TODAY$$';

もちろん、毎回 Network を通じてファイルを取得しているわけではなく、Currency Module 内部でオンメモリに為替レートをキャッシュしています。

// https://github.com/prebid/Prebid.js/blob/c2734a73fc907dc6c97d7694e3740e19b8749d3c/modules/currency.js#L236-L240
function getCurrencyConversion(fromCurrency, toCurrency = adServerCurrency) {
  var conversionRate = null;
  var rates;
  let cacheKey = `${fromCurrency}->${toCurrency}`;
  if (cacheKey in conversionCache) {
    conversionRate = conversionCache[cacheKey];
    utils.logMessage('Using conversionCache value ' + conversionRate + ' for ' + cacheKey);
  }
  // ...

なお、https://currency.prebid.org/latest.json というファイルが、https://currency.prebid.org にて提供されています。curl --verbose した結果が以下のとおりです。

curl --verbose http://currency.prebid.org/ | xmllint --format -
* TCP_NODELAY set
* Connected to currency.prebid.org (54.230.108.205) port 80 (#0)
> GET / HTTP/1.1
> Host: currency.prebid.org
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/xml
< Transfer-Encoding: chunked
< Connection: keep-alive
< Date: Fri, 08 Feb 2019 04:17:03 GMT
< x-amz-bucket-region: us-east-1
< Server: AmazonS3
< Age: 43
< X-Cache: Hit from cloudfront
< Via: 1.1 31de515e55a654c65e48898e37e29d09.cloudfront.net (CloudFront)
< X-Amz-Cf-Id: XEpWTG_WXRO4w44X9eIrOV2r_sR-i9EyoZpUwhIRkzXwzqr71w1GyQ==
<
{ [881 bytes data]
100   869    0   869    0     0  27899      0 --:--:-- --:--:-- --:--:-- 28032
* Connection #0 to host currency.prebid.org left intact
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  <Name>currency.prebid.org</Name>
  <Prefix/>
  <Marker/>
  <MaxKeys>1000</MaxKeys>
  <IsTruncated>false</IsTruncated>
  <Contents>
    <Key>latest-test.json</Key>
    <LastModified>2018-10-15T21:38:13.000Z</LastModified>
    <ETag>"513fe5d930ec3c6c6450ffacda79fb09"</ETag>
    <Size>1325</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>latest.json</Key>
    <LastModified>2019-02-07T10:01:03.000Z</LastModified>
    <ETag>"6e751ac4e7ed227fa0eaf54bbd6c973d"</ETag>
    <Size>1331</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
  <Contents>
    <Key>test.json</Key>
    <LastModified>2018-12-05T11:00:47.000Z</LastModified>
    <ETag>"c4a01460ebce1441625d87ff2ea0af64"</ETag>
    <Size>1341</Size>
    <StorageClass>STANDARD</StorageClass>
  </Contents>
</ListBucketResult>

結果から、次のことがわかります。

  • Amazon S3 に格納されている
    • Server: AmazonS3
  • CloudFront で配信されている
    • X-Cache: Hit from cloudfront
  • latest.json / test.json / latest-test.json が提供されている

特に理由がないのであれば、こちらのファイルを使うことで概ね十分だと言えるでしょう。

また、net/gross の変換には、pbjs.bidderSettings | bidCpmAdjustment を用います。

this.pbjs.bidderSettings = {
    bidderA: {
        bidCpmAdjustment : (bidCpm) => bidCpm * 0.85,
    },
    bidderB: {
        bidCpmAdjustment : (bidCpm) => bidCpm * 0.80,
    },
};

Integration with TAM

"Header Bidding 導入によるネットワーク広告改善の開発事情" でお伝えしたとおり、いくつかの事業者についてはすでに TAM 経由で Header Bidding 入札を行っていました。今回は、TAM と並行する形で Prebid.js の導入をする必要がありました。

以下に、全体のデータフローを示しました。par の部分で、TAM および Prebid.js 経由の Header Bidding 入札を並行して行い、両者から結果が返ってきたら DFP にリクエストします。

type description
ads 社内広告配信サーバ
display.js 広告表示用の JavaScript SDK
cookpad_ads-ruby display.js を埋め込むための Rails 用ヘルパーを定義した簡易な gem
apstag TAM の提供する Header Bidding 用ライブラリ
googletag DFP の提供するアドネットワーク用ライブラリ
pbjs Prebid.js 用ライブラリ
SSP SSP 事業者(実際は複数事業者が存在している)

f:id:itiskj:20190212120829j:plain
Sequence Diagram for Prebid.js and TAM migration

DFP にリクエストをする前に、TAM と Prebid.js 両者の入札を完了させておきたかったので、Promise.all でリクエストを行い、待ち合わせる形で実装しました。以下は、本番で利用しているコードの抜粋です(エラーやロギングなど、本質ではない行を削除したもの)。

  requestHeaderBidding(slots) {
    const prebidPromise = this.requestPrebid(slots);
    const apsPromise = this.requestAPS(slots);

    return Promise.all([prebidPromise, apsPromise])
      .then(() => this.headerBiddingFinishCallback())
      .catch(err => Logger.error(err));
  }

  requestPrebid(slots) {
    return new Promise((resolve) => {
      pbjs.que.push(() => {
        pbjs.addAdUnits(this.getPrebidAdUnits);

        pbjs.requestBids({
          bidsBackHandler: (result) => {
            resolve({
              type: "prebid",
              result: result || [],
            });
          },
          timeout: this.prebid_timeout,
        });
      });
    });
  }

  requestAPS(slots) {
    return new Promise((resolve) => {
      apstag.fetchBids(thihs.apstagBidOption, (bids) => {
        resolve({
          type: "aps",
          bids: bids || [],
        });
      });
    });
  }

  headerBiddingFinishCallback() {
    googletag.cmd.push(() => {
      pbjs.setTargetingForGPTAsync();
      apstag.setDisplayBids();

      googletag.pubads().refresh();
    });
  }

Conclusion

アドテク関連のエンジニア目線での事例紹介や技術詳解はあまり事例が少ないため、この場で紹介させていただきました。特に、Prebid.js は、開発自体はドキュメントが丁寧な分嵌りどころは少ないものの、実際の導入フローにおける知見は、日本においてほとんど共有されていません。そこに問題意識を感じたため、この機会に Prebid.js の導入フローを紹介させていただきました。

広告領域は、技術的にチャレンジングな課題も多く、かつ事業の売上貢献に直結することが多い、非常にエキサイティングな領域です。ぜひ、興味を持っていただけたら、Twitter からご連絡ください。

また、メディアプロダクト開発部では、一緒に働いてくれるメンバーを募集しています。少しでも興味を持っていただけたら、以下をご覧ください。

DroidKaigi 2019 にクックパッド社員が1名登壇&ブースでお待ちしております!

こんにちは!広報部のとくなり餃子大好き( id:tokunarigyozadaisuki )です。

さて、エンジニアが主役のAndroidカンファレンス、DroidKaigi 2019の開催まであと二日となりました!

クックパッドは、本カンファレンスにゴールドスポンサーとして協賛します。そして、クックパッドに所属する @litmonが登壇し、@nshiba@shanonim が当日スタッフとして関わってくれております。 約20名のクックパッド社員が、DroidKaigi 2019 に参加致しますので、会場でお見かけの際にはお声がけいただけますと嬉しいです。

登壇の詳細

はじめに、登壇スケジュールと内容を紹介します。 @litmonが登壇するのは、2日目の最後のセッションです! 

2月8日(金)18:30〜 Room 4

門田福男 (@litmon) : Google Play Consoleのリリーストラックを有効活用してリリースフローの最適化を行った話

概要 : Google Play Consoleにはアプリのリリースを行う際にいくつかのトラック(alpha, beta, production)を選択することができる。また、2018年には新たにinternalトラックが開放された。 クックパッドアプリでは、これらのトラックを有効活用し、リリース自動化を行い、人間によるリリーススケジュールの管理をやめたときの話をする。 また、その際にぶつかった技術的制約などにどう対応したか、リリース自動化に向けて行った様々なTipsを紹介する。

コメント

クックパッドアプリは、複数の部署が協力して一つのアプリを開発しています。他部署とのコミュニケーションコストが肥大化していく中、打開策として機械による毎週自動でリリースを行う仕組みを実現しました。本セッションではどうしてクックパッドが自動リリースを行うようにしたのか、またAndroidアプリではそれをどのように実現したのか、その結果どうだったかなどを発表します。アプリのリリースフローについて悩んでいる方や、自動化に興味のある方、ぜひ遊びに来てください!

ブース

クックパッドは、DroidKaigi 2019 にてブースを出展いたします。Androidカンファレンスならではのクックパッドノベルティを数量限定でご用意いたしております! ぜひ、お立ち寄りくださいね。

Cookpad.apk #2 を開催します

本カンファレンス後、2/18(月)には昨夏#1を実施した「Cookpad.apk」の第2回を開催することにいたしました。DroidKaigi 2019 で惜しくも不採択となってしまったトークを中心に、現在社内で行っているAndroidアプリ開発に関する知見や学びについて共有いたします。懇親会の時間もございますのでお楽しみに! 

※本イベントはDroidKaigi 実行委員会が運営する公式イベントではありません。
※ご好評を頂き、全て満席となりましたのでご了承下さい。たくさんのお申し込みありがとうございました。

cookpad.connpass.com

おわりに

発表内容へのご質問やクックパッドにご興味をお持ちの方は、お気軽にブースまでお越しください! みなさまにお会いできることを楽しみにしております。

Hackarade #05: IoT

こんにちは!スマートキッチン事業部のシュガー(佐藤彩夏)です。趣味は電子部品アクセサリー作りです。

クックパッドでは、Hackaradeというエンジニアの技術力を底上げするための社内ハッカソンを定期的に開催しており、第5回目の今回は「IoT」のテーマで開催することになりました。

f:id:sugar_ayaka:20190201162614j:plain
開催風景

なお、過去には下記のようなテーマで実施しております。

まず簡単にIoTがテーマになった背景をお話します。

クックパッドには、今年の1月にスマートキッチン事業部という新しい事業部ができました(去年1年間は研究開発部の中の1グループでした)。
スマートキッチン事業部では、OiCyという、人と機器とレシピをつなぐことで毎日の料理を楽しみにするサービス&機器の開発を行っております。

つまり、ソフトウェアだけでなくハードウェアの開発もしており、ハードウェアを開発するメンバーもいます(私もその1人です)ので、
今回はハードウェアも絡むIoTをテーマで開催することになりました。

このブログでは開催概要と成果の一部をご紹介します。

開催概要

今回、参加者が約100人いたので、全員で回路を組んだり、ハンダ付けするのはあまり現実的ではないと思い、
M5Stack Grayという回路を組まなくても容易にセンサーをつなげる(Stack)ことができるモジュールを使用することにしました。

具体的には、ESP32(Wi-FiとBluetoothを内蔵する低電力なマイコン)、ディスプレイ、ボタン、バッテリー、SDカードスロット、スピーカー、9軸センサが内蔵していて、サイズは5cm四方くらいです(写真中央の四角いもの)。

f:id:sugar_ayaka:20190201182046j:plain
M5Stackと同梱物

全体としては下記のような流れで実施しました。

  • 事前準備:開発環境を整えてきてもらう&くじ引きタイム

  • 第一部:M5Stackの説明と演習(ハードウェア開発)

  • 第二部:AWS IoTの説明と演習(ソフトウェア開発)

  • 第三部:乾杯&発表会

事前準備:開発環境を整えてきてもらう&くじ引きタイム

まず事前準備として、下記をしてもらいました。

  • Arduino IDEのインストール

  • USBドライバーのインストール

  • Arduino Core for ESP32の導入

詳細はこのあたりの記事に詳しく掲載されてます。

そして当日の朝、運営側で用意したセンサーを1人1個くじ引きで引いてもらい、そのセンサーを使って好きなものを開発してもらうことにしました。

f:id:sugar_ayaka:20190201165036p:plain
ハズレのセンサーを引いてしまったCTO

センサーは、水分センサー、光センサー、アルコールセンサー、火炎センサー、ダストセンサー、温湿度気圧ガスセンサー、測距センサー、ジョイスティックを用意しました。

なお、弊社はビルの関係で火気厳禁なので、火炎センサーは喫煙所に行かないと試せないという罠。

加えて、会場には今回M5Stackとセンサーを購入したスイッチサイエンスさんにもお越しいただき、楽しそうなセンサーやコントローラーなどをお貸し出しいただきました。

f:id:sugar_ayaka:20190201174432p:plain
スイッチサイエンスさんからの差し入れ

第一部:M5Stackの説明と演習(ハードウェア開発)

第一部は、スマートキッチン事業部のハードウェアエンジニアの山本さんが、M5Stackの説明とセンサーの説明とハンズオン演習を行いました。

その資料が下記です。最後のページにサンプルプログラムへのリンクもありますので是非ご活用ください。

ここでの驚きは、だいたいハードウェアのハンズオンは半分くらいの人が引っかかるので、せいぜい10人くらいでやることが多いのですが、
100人規模でやったにも関わらず大きくつまずく人はおらず、見ていた限り全員が午前中に何かしらを動かせるようになっていたことです。
感動。

f:id:sugar_ayaka:20190201170229j:plainf:id:sugar_ayaka:20190201170312p:plain
開発の様子

第二部:AWS IoTの説明と演習(ソフトウェア開発)

続いて、AmazonのAWS担当の方から、AWS IoTを活用してM5Stackと連携するハンズオンを実施していただきました。

AWS IoTとは、インターネットに繋がるデバイス(今回でいうとM5Stack)とAWSクラウドを連携して双方向通信ができるサービスです。

とても丁寧でわかりやすい資料を使って下記の内容をご説明いただきました。

f:id:sugar_ayaka:20190201170733p:plain
Amazonさんのハンズオン資料(一部抜粋)

第三部:乾杯&発表会

ここまでで、全員がM5StackとAWS IoTを使って一通りサンプルを動かせるようになりましたので、
このあと、数時間の自由制作の時間を設け、夕方くらいに乾杯&発表したい人が発表するという形式を取りました。

f:id:sugar_ayaka:20190201171305p:plain
発表前にまずは乾杯&腹ごしらえ

成果物

面白い発表がたくさんあったのですが、全ては紹介しきれない(そして楽しすぎて多くの写真を撮りそこねた)ので一部だけご紹介します。

まずこちらは、測距センサーを用いた姿勢矯正デバイスです。

f:id:sugar_ayaka:20190201171734p:plain
姿勢矯正デバイス

PCの画面上にセンサーを取り付け、一定の距離近づくと姿勢が悪いことを検知してスクリーンセーバーでお知らせしてくれるというもの。

こういったものは世の中にいくつかありますが、ウェアラブルとかカメラを設置したりしなくても、シンプルに実装されているのがいいなと思いました。

続いて、スクワットを検出してレベルアップしていくデバイス。

f:id:sugar_ayaka:20190201174813g:plain:w250
スクワットデバイス

こちらは内蔵している9軸センサーを活用して、スクワットを検出し、回数に応じて音が変わるというもの。
例えば、まず5回スクワットすると音が鳴ってレベルが上がり、次は10回、20回・・・とハードルが上がっていく設計でした。

私も学生時代に運動支援研究をしていた頃、ひたすら縄跳びを飛んでいたことがあるので、
これはデバックが大変だっただろうなぁ、と努力賞を差し上げたい発表でした。

こんな感じで次々に面白い発表が続いて行ったのですが、ここでふと気づいてしまいました。

「あれ?これIoT(Internet of Things)じゃなくて、ただのT(Thing)じゃないか・・・?」

そうですね。これらはArduino(通信機能のついていないマイコン)とか使えばできちゃいますね。

ここで素晴らしい発表が。

システム障害が起きると、M5Stackの画面の色が緑から赤に変わって障害をお知らせしてくれるシステム。

f:id:sugar_ayaka:20190201172911p:plain
障害を通知してくれるシステム

これは、やっとIoT!しかも実務上も使えて素晴らしいシステムです。拍手。

他にも、色合わせゲームや、スマートロックシステムや、植物の水分が足りなくなると通知されるシステムや、Alexaと連動したキッチンタイマーや、素数が出るとゴリラのアスキーアートが画面に表示されるシステム(?)などなど、発想力豊かな発表がたくさんありました。
(そういえば料理に関係する発表はキッチンタイマーくらいしかなかったような)

f:id:sugar_ayaka:20190201180149p:plain:w300
ゴリラのアスキーアート

ちなみに私は、部屋の湿度や温度やガスの数値が一定以下になるとSlackで通知されるという、なんの工夫もない普通に便利なシステムを作りました。

f:id:sugar_ayaka:20190201163738p:plain:h300f:id:sugar_ayaka:20190201180607p:plain:h300
温湿度気圧ガスセンサーの値に応じてSlackに通知

これをきっかけに、社内のエンジニアがハードウェア開発にも目覚めてくれるといいなと願っています。

ということで、以上、第5回Hackaradeのレポートでした☆

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('https://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/