2015/12/04

MacとRaspberry Pi2をTTLシリアル通信で繋いでお手軽開発環境

前の投稿と同じ類です。
ラズパイ開発にディスプレイとかUSBキーボードとか接続するのメンドウなのでシンプルにUSB挿すだけで接続できる環境を構築したい。

今回は前回よりもシンプル。
ケーブル(若干特殊なUSBケーブル)1本だけ。

USBシリアル接続すればいいよね。
ついでにソコから電源取れたら1本でラズパイとMacを繋げられるよねって話。
※これもググったらすぐ出てくる情報ですが自分用のメモとして。


準備するもの
Raspberry Pi2 Model B(たぶん普通のラズパイでもOK)
・micro SDカード(ラズパイのOS焼く用。私はコレを買った)
・Macbook(Mac OSXが動けば何でもOK)
・USB-COM変換ケーブル(ラズパイにシリアル接続するケーブル。私はコレを買った)


0. ラズパイOSをセットアップ(ラズパイ動かせてる人は不要)
前回の投稿と同じ


1. ドライバをインストール
ドライバをダウンロードする
ドライバをインストールする
Macを再起動する


2. ラズパイのGPIOにケーブルを接続
赤色の線を04番ピン(DC Power 5V)に挿す
黒色の線を06番ピン(Ground)に挿す
白色の線を08番ピン(GPIO14)に挿す
緑色の線を10番ピン(GPIO15)に挿す

ピン番号はググれば沢山出てきますが、このサイトとかが良いかも。
GPIOから電源を投入することによる危険性については色々と議論があるようなので、心配な場合は赤色の線を接続せず、既存のmicro-USBケーブルなどで電源を取るとよいと思います。線が2本になるのが残念だけど…


3. ラズパイにシリアルTTL接続
ターミナルを開いてコマンドで一発
    % screen /dev/tty.usbserial 115200

何も表示されないがキーボードの任意のキーを叩くとユーザ名とパスワードを聞かれるのでそれぞれ「pi」「raspberry」と入力
ログインできたはず

ログアウトは以下のコマンド
    % exit


ちなみに、minicomを使う場合は以下のコマンド
    % minicom -b 115200 -o -D /dev/tty.usbserial


できた


参考


2015/11/17

Mac上でディスプレイもUSBキーボードも使わないRaspberry Pi2開発環境を整える

ラズパイ使うのにHDMIでディスプレイ繋いだり、USBキーボード持って行ったりするのメンドウ。
MacからSSHとかで繋げたら楽でいいね。
一回もHDMIディスプレイ使わない方法でラズパイ2の環境構築をやってみた。
※そこかしこで書かれている内容なので今更感あるけど、自分用のメモとして。


準備するもの
Raspberry Pi2 Model B(たぶん普通のラズパイでもOK)
・micro SDカード(ラズパイのOS焼く用。私はコレを買った)
・Macbook(Mac OSXが動けば何でもOK)
Thunderboltイーサネットアダプタ(有線LANでラズパイとつなぐ)
・有線LANケーブル(Mac <-> (アダプタ) <-> (有線LAN) <-> ラズパイという形。巻取り式が便利)
・USBケーブル(ラズパイに電源を供給する。巻取り式がコンパクトで便利)


0. ラズパイOSをセットアップ(ラズパイ動かせてる人は不要)
・micro SDカードをMacbookにセット(SDカードアダプタなどで)
・コマンドラインでディスク名を調べる
    % diskutil list
    /dev/disk1とか/dev/disk2とかがそれ。/dev/disk2s1とかはパーティションなので違う。
・Macのディスクユーティリティを起動してマウントしているSDカードがFAT32フォーマットされているかを確認し、「マウント解除」。「取り出し」じゃないので注意
・ラズパイのOS「RASPBIAN」をダウンロードして展開して.imgファイルを取得。私はWHEEZYの方をダウンロード
・SDカードに以下のコマンドで焼き込む。私の環境で約330秒かかった
    % sudo dd bs=1m if=2015-05-05-raspbian-wheezy.img of=/dev/disk2
    2015-05-05-raspbian-wheezy.imgはダウンロードして展開したファイルのPATH。/dev/disk2はdiskutil listで調べたディスク名
・焼き終わったらディスクユーティリティでSDカードを「取り出し」


1. ネットワーク環境を整える
・Macbook ThunderboltイーサネットアダプタをMacbookに挿しLANケーブルを挿す
・システム環境設定の「共有」で「イーサネット共有」を開き(チェックボックスはOFFのまま)、「共有する接続経路」を「Wi-Fi」にする(Thunderbolt経由じゃなければ何でもOK)。相手のコンピュータが使用するポートを「Thunderbolt Ethernet」のチェックボックスをON。
・「イーサネット共有」のチェックボックスをON


2. ラズパイ起動
・RASPBIANを焼いたmicro SDカードをラズパイに挿す
・MacbookのThunderboltイーサネットアダプタを経由して有線LANをラズパイにつなぐ
・USBケーブルでラズパイに通電


3. ラズパイにSSH接続
・コマンドでThunderboltイーサネット共有のIP番号を調べる。
    % ifconfig
    bridge100という項目。192.168.2.1とか192.168.3.1とかになっているんじゃないかな?
・コマンドでラズパイに割り当てられているIPを調べる
    % arp -a
    「on bridge100」という項目で割り当てられているIPを調べる。
    たぶん、192.168.2.1が大元で192.168.2.2がラズパイ。
・SSHでアクセスする
    % ssh pi@192.168.2.2
    うまくアクセスできたらパスワードを聞かれるので「raspberry」
    sshできなかったらIPが間違っているので別のIPを試す


できた




参考

2015/09/02

Udacityを使ってAndroid勉強会を開催する方法


はじめに


GoogleがUdacity上でオフィシャルに提供している学習コンテンツを使って勉強会を開くStudy Jamsを活用し、GDG石巻でAndroid開発勉強会で講師をしてきました。


Udacityを利用する勉強会では今までの講師の役割が全く違ったものになりますので、私が体験した講師の存在意義についてをご紹介し、今後、同様の勉強会を開催する方の参考になればと思います。


Udacityとは?

簡単に説明するなら「オンライン授業」です。あらゆる授業が録画ビデオで受講できるようになっています。
Google関連技術についてはGoogleがオフィシャルで授業コンテンツを作成しているため、質の高いコンテンツが揃っています。
ただし、ほとんどが英語。YouTubeを活用しているので、字幕を付けることが出来る授業もあり、なかには日本語の字幕の付いている授業もります。
Google JapanのStudy Jamsがサポートしている授業だと比較的入りやすいと思います。私はこの中から「Developing Android Apps」を選び、勉強会を開催しました。


勉強会講師の役割

勉強会開催にあたって、下記の疑問が自然と生まれました。


・Udacityの動画内に講師(技術を教えてくれる人)がいるので、講師は不要では?
・個人で受講できるのに、勉強会を開催して集まる必要ある?


勉強会を開催する前に、自分でUdacityの授業を幾つかうけてみました。
ただ受けるのではなく、勉強会で講師をするつもりで受講し、上記疑問に対する答えを探しました。


答えは「両方とも意義はある」と思い、実際に開催し、その仮説の検証を行いました。


勉強会のスタイル

いろいろなスタイルが考えられると思いますが、以下のスタイルがやりやすいのではないかと思います。

スタイル1:個人のPCで各個人がUdacityの授業を受講する
スタイル2:授業を複数準備し、興味ある授業ごとにグループを作り、それぞれのグループであつまり各授業を受講する
スタイル3:プロジェクターなどで1つの画面にUdacityの授業を映しだし、全員で受講する
スタイル4:スタイル3に加えて、授業内で説明される内容を実際に手を動かし確認する


勉強会の規模、設備、サポートしてくれる人・人数によって実施スタイルが異なると思います。今回は規模が10人程度に対して、サポートしてくれるスタッフ(GDG石巻のマネージャなど)がそろっていたのでスタイル4で勉強会を実施し、スタイル1で継続して勉強を続けられるよう意識した内容にしました。


設備としてプロジェクターがあったので、Udacityの授業をプロジェクターで映しだし、全員で1つの画面に向かい受講しました。


勉強会講師の存在意義

勉強会講師(今回は私)は講師ではなくサポータという役割が適切じゃないかと思いました。
動画内の講師が説明する内容について

・受講者の顔を見ながら、理解度にあわせて分かりやすく解説し直す
・開発経験から得られた「ここ重要!」な内容を強調する
・ハンズオン形式の場合のファシリテーション
・実装確認(バグっていたら一緒に原因を特定する、特定してあげるのではなく、特定の方法を教える。プリントデバッグ、スタックトレースの読み方、デバッガの使い方など)
・質問がある場合、それに答える
・補足情報(経験した事例や、専門用語の意味など)を加える


すべて技術的な知識や開発経験があるからこその付加情報なので、勉強会講師の存在意義はある程度あるのではないかと思いました。


勉強会の開催意義

オンライン授業の大きな利点の一つ「いつでも、どこでも受講できる」をなくしてまで勉強会を開催する意義についてですが、

・他の人の意見を聞ける
・他の人に教えてもらったり、教えたりして理解を深めることができる
・他の人の疑問点やつまづくポイントを知ることができる(自分がその疑問に気付かなかっただけ。授業を作業化して自分で考える事をやめ、自分の理解に対して無自覚になることは多い。)
・モチベーションが続く(他の人が頑張っていると自分も!ってなる)
・時間のコントロールを勉強会主催者に委ねることで確実に勉強が進む(長時間勉強で疲れないように適宜休憩が入ったり、サボれない)


という理由により、意義はあると思いました。
特に他の受講者の存在により、自分の理解の深化が最大のメリットじゃないかと思いました。


勉強会講師の準備

普段の勉強会だと、資料作成が必要ですが、Udacityを利用するとそれが必要なくなります。その代わり、Udacityの授業をサポートする資料が必要になります。
私が準備した資料は3つです。


・自己紹介
・Udacityの使い方
・授業の進行表


自己紹介は言わずもがなですね。サポートしてくれる人が何者なのか分からないままだと安心して質問できません。


Udacityの使い方ですが、受講生の全員がUdacityを始めて使う状況だったので、どのように活用すると、今後の勉強に役立つかを知ってもらうために使い方の説明を加えました。勉強会で勉強したことを自宅で復習したり、継続して勉強するのにも役立ててもらう意図もありました。


最後の進行表ですが、これが一番大事だと思います。私は勉強会で使う授業を事前に2,3回受講し、

・どのように授業が進むのか
・どこが大事なのか
・どこが難しく、再生を止めて追加の解説を加えるのか


を事前にメモしておきました。
Udacityは1レッスンが複数のセクションに分かれており、セクションごとにタイトルが付いています。タイトル毎に上記をメモしておきました。これのおかげで、授業を邪魔しないようなサポートがある程度できたのではないかと思います。
※事前にリハーサルなどをしていくとよりクォリティが上がると思います


勉強会のすすめ方

今回の授業は所要時間が4〜5時間(実際受講するとそこまで長くない)と表記されていて長めなので、適宜休憩を入れたり、授業内のミニクイズをみんなで考えたり、補足情報を加えて飽きない(寝ない)ようにするなどの工夫をしました。

また、実際に実装を通してAndroidのアプリ開発の基礎と基本概念を理解する授業でしたので、受講者にAndroid Studioを開いてもらい、授業の進行と平行して実際にコードを書いてもらいました。当然、何らかのミスによりうまくいかないことも有るため、GDG石巻のメンバーや私がサポートし不具合を解消しながら進めました。
実際、個人で勉強を続けるとしても、手を動かす内容であれば手を動かしたほうが確実に身につくと思い、サポートコストが高くなりますし授業の進行としてはリズムが悪くなるのですが、このような方法をとりました。

もっと長い授業もあるので、勉強会の時間を鑑みて、飛ばすセクションを決めておく、2回に分割するなどの工夫も効果的かもしれません。


先の進行表に休憩のタイミングやミニクイズに充てる時間、また難しいクイズの場合はヒントなども記載しておきました。


まとめ

Udacityを使ったAndroid開発勉強会を開催する1手法について説明しました。
GDG石巻で実際に行った経験から、Udacityを使った勉強会の意義や講師としての役割などについての考察をまとめました。

Udacityは個人で受けることのできるオンライン授業ではありますが、勉強会講師などに質問したり、他者から刺激(付加情報)を得るなど、勉強会の価値はある程度あると思われます。

勉強会講師は一般的な勉強会とは異なり、事前に資料の準備は必要ありませんが、授業コンテンツの理解と、進行管理の事前準備を行い、受講者のサポートに回る役割が求められるのではないかと思いました。

最後に、
GDG石巻で使った資料を公開しておきます。

2015/06/22

Web Animations API入門(ハンズオン資料)

1. はじめに

この資料はGoogle IO2015のコードラボ「Web Animations Transitions and Playback Control」を基に、DevFest Japan 2015 Summer - Google I/O 2015 報告会における九州会場ブレイクアウトセッションのハンズオン資料として使いやすいよう、手を加えたものです。

single-page WebアプリのためのWeb Animations APIのコードラボです。 コンテンツ間の遷移アニメーションやスクロールポジションに応じたアニメーションの使い方を学びます。

最終的には以下の様なページを作ることができます。

web-animations-nextというpolyfillを利用するので動作環境は以下のモダンブラウザです。
      Chrome
      Safari
      Firefox
      IE11+

2. サンプルコードを取得

以下のURLかGitレポジトリからサンプルコードを取得して下さい。
https://github.com/googlesamples/io2015-codelabs/archive/master.zip
or
% git clone https://github.com/googlesamples/io2015-codelabs.git


web-animationというフォルダーが今回のハンズオンの対象です。
今回のハンズオンのためstartというフォルダをコピーして「breakout」というフォルダを作っておきます。これが今回のハンズオンの作業スペースになります。


3. サンプルサイトを開いてみましょう

web-animations/breakoutフォルダーのindex.htmlを開いてみましょう。
そこで右上のメニューを幾つかクリックすると、ページ遷移なしに表示が切り替わる事が確認できます。
さらに、ブラウザの「戻る」「進む」などをクリックしてみてください。
別ページに移動するのではなく、先ほどの表示を「戻る」「進む」ができている事がわかると思います。
1つのページでコンテンツだけが入れ替わり、ページロードが発生しないため、サクサクとした使い心のサイトです。


コラム

モバイルならどうなるのか?テストしてみた。 http://goo.gl/yWJ88Y
Android 5.1(Nexus5)のChromeで開き「戻るボタン」をタップすると確かに戻る!
ただし、メニューのハイライトが変更されません。
「About」を表示しているのにメニューは「Projects」のまま…

4. Web Animationsを追加しよう

web-animations-nextというpolyfillを使います。
web-animations/breakout/index.htmlを開いて下記のweb-animations-next.min.jsのスクリプトタグを<head>タグ内の下記に示す場所に記入して下さい。
<link href="site.css" rel="stylesheet" type="text/css"></link>
<script src="https://cdn.rawgit.com/web-animations/web-animations-js/2.0.0/web-animations-next.min.js"></script>
<script src="../shared/codelab.js"></script>

CDNを利用したくない場合は、web-animations-nextからweb-animations-next.min.jsをダウロードします。
GitHubの右下にある「Download ZIP」からプロジェクトの全ファイルをダウンロードしてきても良いですし、gitを使ってrepoから
% git clone https://github.com/web-animations/web-animations-js.git
としてダウンロードしてきても良いです。
ダウンロードしてきたフォルダの第一階層に「web-animations-next.min.js」というファイルがあることを確認して下さい。

5. コンテンツ素材のローディング効果アニメーションを追加しよう

Google I/O 2015のサイトのように、メニューをクリックするとコンテンツが広がるようなエフェクトを付けてみましょう。
まずsite.jsファイルを開きanimateToSection()関数の中に下記のコードを書きます。
function animateToSection(link, current, previous) {
    var effectNode = document.createElement('div');
    effectNode.className = 'circleEffect';

    var bounds = link.getBoundingClientRect();
    effectNode.style.left = bounds.left + bounds.width / 2 + 'px';
    effectNode.style.top = bounds.top + bounds.height / 2 + 'px';

    var header = document.querySelector('header');
    header.appendChild(effectNode);
}

アニメーションエフェクトをつけるdivノードを作り、そのクラス名をcircleEffectとします。
linkはメニューのことで、その外形からアニメーションが始まる場所を先ほど作ったdivノードのスタイルに設定し、最後にheaderノードにこのdivノードを追加しています。

なお、animateToSection()関数はshared/codelab.jsから呼び出される関数です。あたる引数がどういうオブジェクトなのか、どういう仕組みで呼び出しているのかなどなど、興味のある人は参考にしてみてください。

アニメーション用のdivノードに追加したcircleEffectクラスのCSSをsite.cssに追加します。 site.cssを開き、下記のコードを記入します。

div.circleEffect {
    width: 240vw;
    height: 240vw;
    margin-left: -120vw;
    margin-top: -120vw;
    border-radius: 100%;
    position: absolute;
    will-change: transform;
}
※vwはview-port widthの略でview-portの幅に対する割合を意味します

このCSSはwill-changeでtransformを指定しているので、CSSアニメーションにより形状が変更される設定になっています。
site.jsにanimateToSection()関数を追加して、CSSをアニメーションさせます。
var newColor = 'hsl(' + Math.round(Math.random() * 255) + ', 46%, 42%)';
effectNode.style.background = newColor;

var scaleSteps = [{transform: 'scale(0)'}, {transform: 'scale(1)'}];
var timing = {duration: 2500, easing: 'ease-in-out'};

var anim = effectNode.animate(scaleSteps, timing);

anim.addEventListener('finish', function() {
    header.style.backgroundColor = newColor;
    header.removeChild(effectNode);
});

アニメーションが終わったら、背景をエフェクトをかけた色に変更したいので、終了(finish)イベントをハンドリングする関数をEventListenerに登録し、効果を与えるeffectNodeを削除しています。
完成したsite.jsは以下のようになります。

/**
 * Called when a new section has been loaded.
 *
 * @param {Element} link element corresponding to new section
 * @param {Element} current now visible 
* @param {Element} previous previously visible
*/ function animateToSection(link, current, previous) { var effectNode = document.createElement('div'); effectNode.className = 'circleEffect'; var bounds = link.getBoundingClientRect(); effectNode.style.left = bounds.left + bounds.width / 2 + 'px'; effectNode.style.top = bounds.top + bounds.height / 2 + 'px'; var header = document.querySelector('header'); header.appendChild(effectNode); var newColor = 'hsl(' + Math.round(Math.random() * 255) + ', 46%, 42%)'; effectNode.style.background = newColor; var scaleSteps = [{transform: 'scale(0)'}, {transform: 'scale(1)'}]; var timing = {duration: 2500, easing: 'ease-in-out'}; var anim = effectNode.animate(scaleSteps, timing); anim.addEventListener('finish', function() { header.style.backgroundColor = newColor; header.removeChild(effectNode); }); }

ブラウザをリロードしてweb-animations/breakout/index.htmlを再表示し、右上のメニューをクリックしてみましょう。

6. フェードインアニメーションを付けてみよう

I/Oのサイトでは新しいコンテンツを開くとフェードインしてくるエフェクトがあり、ポップで楽しい気分になります。そのエフェクトを付けてみましょう。

先ほど作ったsite.jsのanimateToSectionに少し手を加えるだけです。

まずは下準備として、先ほど作成したエフェクトをカプセル化して同時実行できるようにしておきます。
var scaleEffect = new KeyframeEffect(effectNode, scaleSteps, timing);
var allEffects = [scaleEffect];
// Play all animations within this group.
var groupEffect = new GroupEffect(allEffects);
var anim = document.timeline.play(groupEffect);


animの位置と生成方法が変わっていることに注意が必要です。
KeyframeEffectという聞きなれない関数が出てきました。
これはeffectNodeの変更をカプセル化し覆い隠すことができる便利な関数です。
GroupEffectはグループに登録されたエフェクトを平行して実行する便利関数です。

準備は整いました、次にフェードインするアニメーションエフェクトを作ります。

function buildFadeIn(target) {
  var steps = [
    {opacity: 0, transform: 'translate(0, 20em)'},
    {opacity: 1, transform: 'translate(0)'}
  ];
  return new KeyframeEffect(target, steps, {
    duration: 500,
    easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)'
  });
}
そして、GroupEffectに登録します。
var allEffects = [scaleEffect, buildFadeIn(current)];
完成です。早速、web-animations/breakout/index.htmlを再読み込みして、メニューをクリックしてみましょう。
コンテンツがフェードインしてくるのが確認できましたか?

コラム

buildFadeIn()関数のKeyframeEffectで指定しているアニメーションの動作を規定するイージング関数が先ほどと少し異なりcubic-bezier(3次ベジエ曲線)を利用しています。
イージングによりアニメーションのタイムラインの進行速度をコントトロールする事ができますが、プリセットのイージング(例えばease-in-outなど)に加えて3次ベジエ曲線をしていきることで、かなり自由にアニメーションをコントロールできるようになっています。

ここまでのsite.jsは下記のようになっています。
/**
 * Called when a new section has been loaded.
 *
 * @param {Element} link element corresponding to new section
 * @param {Element} current now visible 
* @param {Element} previous previously visible
*/ function animateToSection(link, current, previous) { var effectNode = document.createElement('div'); effectNode.className = 'circleEffect'; var bounds = link.getBoundingClientRect(); effectNode.style.left = bounds.left + bounds.width / 2 + 'px'; effectNode.style.top = bounds.top + bounds.height / 2 + 'px'; var header = document.querySelector('header'); header.appendChild(effectNode); var newColor = 'hsl(' + Math.round(Math.random() * 255) + ', 46%, 42%)'; effectNode.style.background = newColor; var scaleSteps = [{transform: 'scale(0)'}, {transform: 'scale(1)'}]; var timing = {duration: 2500, easing: 'ease-in-out'}; var scaleEffect = new KeyframeEffect(effectNode, scaleSteps, timing); var allEffects = [scaleEffect, buildFadeIn(current)]; // Play all animations within this group. var groupEffect = new GroupEffect(allEffects); var anim = document.timeline.play(groupEffect); anim.addEventListener('finish', function() { header.style.backgroundColor = newColor; header.removeChild(effectNode); }); } function buildFadeIn(target) { var steps = [ {opacity: 0, transform: 'translate(0, 20em)'}, {opacity: 1, transform: 'translate(0)'} ]; return new KeyframeEffect(target, steps, { duration: 500, easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)' }); }


7. フェードアウトアニメーションを付けてみよう

フェードインのアニメーションがあったら、フェードアウトのアニメーションも欲しくなります。早速buildFadeOut()関数でアニメーションするKeyframeEffectを作りアニメーションを追加しましょう。
function buildFadeOut(target) {
  var angle = Math.pow((Math.random() * 16) - 6, 3);
  var offset = (Math.random() * 20) - 10;
  var transform =
      'translate(' + offset + 'em, 20em) ' +
      'rotate(' + angle + 'deg) ' + 
      'scale(0)';
  var steps = [
    {visibility: 'visible', opacity: 1, transform: 'none'},
    {visibility: 'visible', opacity: 0, transform: transform}
  ];
  return new KeyframeEffect(target, steps, {
    duration: 1500,
    easing: 'ease-in'
  });
}

さて、ここで困りました。
フェードアウトしてからフェードインのアニメーションをして欲しいのですが、GroupEffectでは、同時にアニメーションを実行してしまいます。
そこで活躍するのがSequenceEffectです。これはアニメーションに順番をもたせる関数です。これを利用することで、フェードアウトした後にフェードインさせることができます。

var fadeEffect = new SequenceEffect([buildFadeOut(previous), buildFadeIn(current)]);
var allEffects = [scaleEffect, fadeEffect];


これでweb-animations/breakout/index.htmlを再読み込みしてみてください。
フェードアウトでコンテンツが落ちていくエフェクトがあった後にフェードインのアニメーションが実行されているのがわかると思います。


どうじに、アニメーションに少し違和感を覚えるのではないでしょうか?
実は、よく見るフェードアウトとフェードインはクロスオーバー(一部動作が重なっている)しているのですが、ここでは順番に実行されているので、違和感があります。

そこで、エフェクトをクロスオーバーさせるために、フェードインの実行タイミングを前方向に1秒(つまり-1秒)移動します。
return new KeyframeEffect(target, steps, {
  duration: 500,
  delay: -1000,
  fill: 'backwards',
  easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)'
});



さらに重要なのが「fill: ‘backwards’」オプションです。
これはアニメーションが始まっていなくても、そのスタート地点のエフェクトをすぐさま描画に適用することをAPIに伝えるオプションです。
今回の例ではフェードアウトアニメーションのスタート位置のエフェクトは「不透明度ゼロ(opacity: 0)」なので、fill: backwardオプションにより、フェードアウトアニメーションが始まる前は全て透明になります。

ここまでのsite.jsの全コードは下記のようになります。
/**
 * Called when a new section has been loaded.
 *
 * @param {Element} link element corresponding to new section
 * @param {Element} current now visible 
* @param {Element} previous previously visible
*/ function animateToSection(link, current, previous) { var effectNode = document.createElement('div'); effectNode.className = 'circleEffect'; var bounds = link.getBoundingClientRect(); effectNode.style.left = bounds.left + bounds.width / 2 + 'px'; effectNode.style.top = bounds.top + bounds.height / 2 + 'px'; var header = document.querySelector('header'); header.appendChild(effectNode); var newColor = 'hsl(' + Math.round(Math.random() * 255) + ', 46%, 42%)'; effectNode.style.background = newColor; var scaleSteps = [{transform: 'scale(0)'}, {transform: 'scale(1)'}]; var timing = {duration: 2500, easing: 'ease-in-out'}; var scaleEffect = new KeyframeEffect(effectNode, scaleSteps, timing); var fadeEffect = new SequenceEffect([buildFadeOut(previous), buildFadeIn(current)]); var allEffects = [scaleEffect, fadeEffect]; // Play all animations within this group. var groupEffect = new GroupEffect(allEffects); var anim = document.timeline.play(groupEffect); anim.addEventListener('finish', function() { header.style.backgroundColor = newColor; header.removeChild(effectNode); }); } function buildFadeIn(target) { var steps = [ {opacity: 0, transform: 'translate(0, 20em)'}, {opacity: 1, transform: 'translate(0)'} ]; return new KeyframeEffect(target, steps, { duration: 500, delay: -1000, fill: 'backwards', easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)' }); } function buildFadeOut(target) { var angle = Math.pow((Math.random() * 16) - 6, 3); var offset = (Math.random() * 20) - 10; var transform = 'translate(' + offset + 'em, 20em) ' + 'rotate(' + angle + 'deg) ' + 'scale(0)'; var steps = [ {visibility: 'visible', opacity: 1, transform: 'none'}, {visibility: 'visible', opacity: 0, transform: transform} ]; return new KeyframeEffect(target, steps, { duration: 1500, easing: 'ease-in' }); }

8. Playbackコントロールをつけよう

最後に、アニメーションにPlaybackコントロールを付けてみます。
アニメーションは基本的に時間軸で前方に動作します。AからBへと状態が変化する様に。しかしAnimations APIは非常に多彩で、アニメーションの時間方向を変更できるのです。

今回はスクロールの位置によってアイコンの大きさや色を変更するアニメーションです。
まずページロード時に左下のアイコンにアニメーションを設定します。
window.addEventListener('load', function() {
  var icon = document.querySelector('.icon');

  var steps = [
    {color: 'hsl(206, 46%, 89%)', transform: 'scale(0.5)'},
    {color: 'hsl(13, 79%, 96%)', transform: 'scale(2)'},
    {color: 'red', transform: 'scale(1)'}
  ];
  var timing = {duration: 1, fill: 'both'};
  var anim = icon.animate(steps, timing);
});
iconオブジェクトを取得し、そのtransformアニメーションを設定しています。
「HSL(206, 46%, 89%)、大きさ0.5」からスタートし、「色HSL(13, 79%, 96%)、大きさ2.0」を経由して、「色RED, 大きさ1.0」に遷移するアニメーションです。
このaminオブジェクトに下記のようにPlaybackコントロールを付けます。

anim.pause();  // never play this animation forward

function updatePlayer() {
    var top = window.scrollY;
    var height = document.body.scrollHeight - window.innerHeight;
    anim.currentTime = top / height;
  }
  updatePlayer();
  window.addEventListener('scroll', updatePlayer);
まず、animationをpauseし、前方向へアニメーションしないようにしておきます。
updatePlayer()関数はスクロールの位置に応じてanimの時間を変更する、いわばアニメーションの進行位置を変更する関数です。
これをスクロールイベントに設定して完了です!

さぁ、web-animations/breakout/index.htmlを開いてみましょう!
いかがでしょうか?左下のハートのアイコンがスクロール位置に応じて、大きくなったり、色が変わったりするのが確認出来ましたか?


これで、本ハンズオンは終了です。
Web Animationsはまだまだ発展途上ですが、どんどん面白いことができるようになるだけでなく、Webアプリがより軽快でスムースに利用しやくすなるために重要な要素だと思います。

最後に、完成したsite.jsは以下のようになります。お疲れ様でした!
/**
 * Called when a new section has been loaded.
 *
 * @param {Element} link element corresponding to new section
 * @param {Element} current now visible 
* @param {Element} previous previously visible
*/ function animateToSection(link, current, previous) { var effectNode = document.createElement('div'); effectNode.className = 'circleEffect'; var bounds = link.getBoundingClientRect(); effectNode.style.left = bounds.left + bounds.width / 2 + 'px'; effectNode.style.top = bounds.top + bounds.height / 2 + 'px'; var header = document.querySelector('header'); header.appendChild(effectNode); var newColor = 'hsl(' + Math.round(Math.random() * 255) + ', 46%, 42%)'; effectNode.style.background = newColor; var scaleSteps = [{transform: 'scale(0)'}, {transform: 'scale(1)'}]; var timing = {duration: 2500, easing: 'ease-in-out'}; var scaleEffect = new KeyframeEffect(effectNode, scaleSteps, timing); var fadeEffect = new SequenceEffect([buildFadeOut(previous), buildFadeIn(current)]); var allEffects = [scaleEffect, fadeEffect]; // Play all animations within this group. var groupEffect = new GroupEffect(allEffects); var anim = document.timeline.play(groupEffect); anim.addEventListener('finish', function() { header.style.backgroundColor = newColor; header.removeChild(effectNode); }); } function buildFadeIn(target) { var steps = [ {opacity: 0, transform: 'translate(0, 20em)'}, {opacity: 1, transform: 'translate(0)'} ]; return new KeyframeEffect(target, steps, { duration: 500, delay: -1000, fill: 'backwards', easing: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)' }); } function buildFadeOut(target) { var angle = Math.pow((Math.random() * 16) - 6, 3); var offset = (Math.random() * 20) - 10; var transform = 'translate(' + offset + 'em, 20em) ' + 'rotate(' + angle + 'deg) ' + 'scale(0)'; var steps = [ {visibility: 'visible', opacity: 1, transform: 'none'}, {visibility: 'visible', opacity: 0, transform: transform} ]; return new KeyframeEffect(target, steps, { duration: 1500, easing: 'ease-in' }); } window.addEventListener('load', function() { var icon = document.querySelector('.icon'); var steps = [ {color: 'hsl(206, 46%, 89%)', transform: 'scale(0.5)'}, {color: 'hsl(13, 79%, 96%)', transform: 'scale(2)'}, {color: 'red', transform: 'scale(1)'} ]; var timing = {duration: 1, fill: 'both', easing: 'ease-in-out'}; var anim = icon.animate(steps, timing); anim.pause(); // never play this animation forward function updatePlayer() { var top = window.scrollY; var height = document.body.scrollHeight - window.innerHeight; anim.currentTime = top / height; } updatePlayer(); window.addEventListener('scroll', updatePlayer); });

2015/06/11

SDKを使わずAmazon S3のファイル名を変更してダウンロードするスニペット

S3のファイルをダウンロードするとき、期限付きのURLやダウンロードファイルの名前を変更したりできるpre-signed URLという機能があります。

AWS系のライブラリを使えば簡単なのですが(参考)、使うと必要ないものが沢山ついてきてdeploy作業とかが重くなるのいやなので、シンプルにPure Pythonで書いたのでそのコードをココで公開しておきます。
※シンプルなコードなのでPythonじゃなくても真似すればどんな言語でも動きそう。


def generate_url_s3(bucket, path, output_filename):
    import time
    import hmac
    import hashlib
    import base64
    import urllib
    access_key = 'YOUR_AWS_ACCESS_KEY'
    secret_key = 'YOUR_AWS_SECRET_KEY'
    expire_in = 86400  # 1 day (60 x 60 x 24)
    expire = int(time.time() + expire_in)

    c_string = 'GET\n\n\n%d\n/%s/%s?response-content-disposition=attachment; filename="%s"' % (expire, bucket, path, output_filename)
    sign0 = base64.b64encode(hmac.new(secret_key, c_string, hashlib.sha1).digest())
    sign = urllib.quote(sign0, '')

    q = 'Signature=%s&Expires=%d&AWSAccessKeyId=%s' % (sign, expire, access_key)
    extra = 'response-content-disposition=%s' % urllib.quote('attachment; filename="%s"' % output_filename)
    url = "http://%s.s3.amazonaws.com/%s?%s&%s" % (bucket, path, q, extra)
    return url


このURLはGETリクエストのみに対応しています。
POSTに対応したい場合はc_string文字列の中のGETをPOSTとかにすると動くかもです(未確認)。
access_keyとsecret_keyはご自身のAWSのモノをご利用下さい。
c_stringはリクエストの内容の文字列で、これをSHA1でAWSのSecret Keyで暗号化した文字列をhmacでハッシュ化してHTTPリクエストの文字列に耐えられるようbase64エンコードと特殊文字のエスケープ(urllib.quote)により署名とします。
これらのエンコード処理を行わないと、SignatureにはURIパラメータに適さない文字列を作成することがあり、AWS側でそれをデコードしてしまいSignatureが一致しない「SignatureDoesNotMatch」というエラーが発生することがあります。

あとはリクエストURLを構築するだけです。
S3に格納されたファイルのベースのURLに署名や有効期限(本スニペットではURL作成時から1日)、AWSのAccess Keyを付け、最後にダウンロードファイル名の指定します。
これでOKです。
c_stringがちょっとクセモノですが、他の署名については一般的なOAuth2.0などの方法と同じでシンプルで分かりやすかったです。

使い方は簡単で例えば以下のような使い方です。
bucket = "mybucket"
path = "tmp/myfile.zip"
output_filename = "sample.zip"
url = generate_url_s3(bucket, path, output_filename)
print(url)

bucketにS3のバケット名を指定。
pathはダウンロードファイルのkey名です。
output_filenameはダウンロードファイル名を指定します。
この3つのパラメータから、期限付きかつダウンロードするファイル名を変更したURLを生成できます。

たった十数行のコードで巨大なbotoを使わずに済みました。

蛇足ですが、
ファイル名を変更する必要がない場合は全てのresponse-content-dispositionのパラメータを外せばOKです。
response-content-dispositionを外さず元のファイル名を指定しておけば済む話ですが(汗)

2015/05/27

GCEのPreemptibleインスタンスの停止時に処理を実行する方法

GCEのPreemptible VMインスタンスが魅力。
24時間で絶対止められるし、24時間以内でも止められる可能性ある。
けど、値段が最大70%OFF

ザックリですがn1-standard-16インスタンス(2015/05/27時点で$0.608/h)だと
1ヶ月使い続けたら約$452(約5.5万円)のところが約$179(約2.2万円)。
これだと2台構成にしてもまだ安い。

まだ試していませんがオートスケーラーもちゃんと使えるっぽいので、Terminateされたとしても複数台構成でオートスケール設定していれば格安でクラスタを構築できてウハウハ!

でもterminateされた時に何を処理していて、その処理が完了していないかもしれないことが知りたい。システムの安定性とかサービスクォリティとかそういう面でも。
ということでやり方を調べてちゃんと動作することが確認できたのでご紹介します。

Preemptionプロセス

GCE側からシャットダウンされるときにACPIのG2(Soft Off)シグナルが送られてくるので、それをトラップしてやれば終了処理を行える。
ただし、このシグナルが送られてから30秒後にインスタンスがShutdownしてなければACPIのG3(Mechanical Off)が送信され、インスタンスが「停止状態」に移行する。
つまり終了処理が許されるのは30秒。

終了処理として何をやるか?

どのインスタンスが停止させられたのかだけを外部に知らせる事で、リカバリやレジューム処理などを外部に任せられると考えて、ACPI G2シグナルを受け取ったら、その時間とVMのインスタンスIDをGCSに保存するようにします。(外部サーバがあればcurlとかでhttpリクエスト投げるとかも可能です)

実装

※OSはubuntuです。それ以外のOSは違う方法かも知れないので注意

1. ACPI G2シグナルを受けたときの動作


G2シグナルを受けたら「/etc/acpi/events/powerbtn」の内容に従って処理が実行されます。
なので、ファイルを一度確認しておきます。
 
% cat /etc/acpi/events/powerbtn

# /etc/acpi/events/powerbtn
# This is called when the user presses the power button and calls
# /etc/acpi/powerbtn.sh for further processing.

# Optionally you can specify the placeholder %e. It will pass
# through the whole kernel event message to the program you've
# specified.

# We need to react on "button power.*" and "button/power.*" because
# of kernel changes.

event=button[ /]power
action=/etc/acpi/powerbtn.sh
actionがその処理です。/etc/acpi/powerbtn.shが実行されます。それを確認します。
 
% cat /etc/acpi/powerbtn.sh

#!/bin/sh
# /etc/acpi/powerbtn.sh
# Initiates a shutdown when the power putton has been
# pressed.

[ -r /usr/share/acpi-support/power-funcs ] && . /usr/share/acpi-support/power-funcs

# If logind is running, it already handles power button presses; desktop
# environments put inhibitors to logind if they want to handle the key
# themselves.
if pidof systemd-logind >/dev/null; then
    exit 0
fi
・
・
・
# If all else failed, just initiate a plain shutdown.
/sbin/shutdown -h now "Power button pressed"
結構長いですが、普通のシェルスクリプトで最後にshutdownしているだけです。
なので、shutdownする前に外部にインスタンスIDを送信するスクリプトを埋め込みます。
直書きだとメンテナンス性が下がるので、
# Preemption
/bin/bash /home/ubuntu/bin/_preemption.sh 
という2行を加えて、/home/ubuntu/bin/_preemption.shファイルを実行するようにします。

2. 終了処理時にGCSにデータを送信する


_preemption.shのファイルを下記のようにしました。
 
#!/bin/bash

id=$(curl http://169.254.169.254/computeMetadata/v1/instance/id -H "Metadata-Flavor: Google")
pwd=$(pwd)
dt=$(date +"%Y-%m-%d_%H-%M-%S")
file="${pwd}/preemption_${id}_${dt}.txt"
echo $dt > $file

gsutil cp $file gs://GCSバケット名/preemptible/
メタデータサーバからVMインスタンスのIDを取得して、現在時刻をファイル名にしてGCSにアップしています。
今回の修正ではおそらく不要な処理なのですが、安全のためacpidを再起動しておきます。acpi系の処理に変更があった場合、再読み込みのためにacpidを再起動する必要があるみたいです。
% sudo service acpid stop
% sudo service acpid start


まとめ

VMがShutdownされるのをトラップできた。
Preemptible VMインスタンスを使った場合、そのVMがなんのタスクを処理しているのかを外部で管理できていれば、このトラップの仕組みを使って、タスクを別VMに振り分けるなどの処理ができそうな見込み。
ただし、何らかの理由で30秒以上の時間を要してしまった場合、GCEにより強制的に終了させられるので注意が必要。