ElasticsearchにデータをPOSTするアプリを作る時にcucumberのhookを使って立ち上げるやり方メモ。

やりたいこと

  • cucumberのテストを走らせる前にElasticsearchの環境をDockerコンテナを用いて準備する
  • 準備した環境はelasticsearchのGemから使いやすいように環境変数ELASTICSEARCH_URLにセットする
  • 既存のElasticsearchとも接続できるように、テスト実行時にELASTICSEARCH_URLがセットされている場合はDockerを使わず与えられた環境を利用する
  • テスト終了時にはコンテナを破棄する

処理の全容

処理自体はcucumberのhookを利用する形でfeatures/support/hooks.rbに記載する

require 'uri'
require 'cgi'
require 'docker'

$container_id = nil

AfterConfiguration do |config|
  if ENV['ELASTICSEARCH_URL'].nil? || ENV['ELASTICSEARCH_URL'].length == 0
    if ENV['DOCKER_HOST'].nil? || ENV['DOCKER_HOST'].length == 0
      raise 'Please set DOCKER_HOST or ELASTICSEARCH_URL'
    end

    Cucumber.logger.debug "Elasticsearch with Docker:\n"
    container = Docker::Container.create(
      'Image'      => 'elasticsearch:5',
      'Env'        => ['ES_JAVA_OPTS=-Xms512m -Xmx512m'],
      'Cmd'        => %w(-E bootstrap.ignore_system_bootstrap_checks=true),
      'HostConfig' => { 'PublishAllPorts' => true }
    )
    $container_id = container.id
    u = URI.parse(container.connection.url)
    Cucumber.logger.debug "  * Create Container[#{$container_id}] at #{u}.\n"

    container.start
    Cucumber.logger.debug "  * Container[#{$container_id}] starting...\n"

    100.times.each do
      break if container.streaming_logs(stdout: true) =~ /started$/
      sleep 1
    end
    Cucumber.logger.debug "  * Container[#{$container_id}] was started.\n"

    container = Docker::Container.get $container_id
    port = container.info['NetworkSettings']['Ports']['9200/tcp'][0]['HostPort']
    Cucumber.logger.debug "  * Binding port 9200/tcp to #{port}.\n"

    ENV['ELASTICSEARCH_URL'] = "#{u.host}:#{port}"
    Cucumber.logger.debug "  * Set ELASTICSEARCH_URL to #{ENV['ELASTICSEARCH_URL']}.\n\n"
  end
end

at_exit do
  unless $container_id.nil?
    Cucumber.logger.debug "\nElasticsearch with Docker:\n"
    ENV['ELASTICSEARCH_URL'] = nil
    container = Docker::Container.get $container_id
    Cucumber.logger.debug "  * Container[#{$container_id}] stopping...\n"
    container.stop
    Cucumber.logger.debug "  * Container[#{$container_id}] was stopped.\n"
    container.delete
    Cucumber.logger.debug "  * Container[#{$container_id}] was deleted.\n"
  end
end

解説:hookのタイミング

Dockerコンテナの作成・削除のタイミングはcucumberのhookを利用します。

環境変数としてELASTICSEARCH_URLが設定されている場合は何もしません。

環境変数にELASTICSEARCH_URLが設定されていない場合はDockerを利用しますが、環境変数にDOCKER_HOSTが設定されていない場合はDockerも使えないと判断し、エラー終了します

AfterConfiguration do |config|
  if ENV['ELASTICSEARCH_URL'].nil? || ENV['ELASTICSEARCH_URL'].length == 0
    if ENV['DOCKER_HOST'].nil? || ENV['DOCKER_HOST'].length == 0
      raise 'Please set DOCKER_HOST or ELASTICSEARCH_URL'
    end

    {コンテナ作成・起動とかの処理}
  end
end

at_exit do
  {コンテナの停止とか破棄の処理}
end

参考: https://github.com/cucumber/cucumber/wiki/Hooks

解説:ログの出力

ログはCucumber.loggerを使って出力しています。

Cucumber.logger.debug "Elasticsearch with Docker:\n"

メッセージはcucumberのコマンド実行時に-vをつけることで表示されるようになります

$ bundle exec cucumber -v features/
Code:
  * features/support/env.rb
  * features/support/hooks.rb

Elasticsearch with Docker:
  * Create Container[ae51d0a059672f600949813726ce83459930b8496a8d4d085ad4c1f543f0da11] at tcp://192.168.131.138:2376.
  * Container[ae51d0a059672f600949813726ce83459930b8496a8d4d085ad4c1f543f0da11] starting...
  * Container[ae51d0a059672f600949813726ce83459930b8496a8d4d085ad4c1f543f0da11] was started.
  * Binding port 9200/tcp to 32771.
  * Set ELASTICSEARCH_URL to 192.168.131.138:32771.

Features:

{中略}

Elasticsearch with Docker:
  * Container[ae51d0a059672f600949813726ce83459930b8496a8d4d085ad4c1f543f0da11] stopping...
  * Container[ae51d0a059672f600949813726ce83459930b8496a8d4d085ad4c1f543f0da11] was stopped.
  * Container[ae51d0a059672f600949813726ce83459930b8496a8d4d085ad4c1f543f0da11] was deleted.

解説:コンテナの作成

コンテナはelasticsearch:5を利用します

コンテナ内の環境変数ES_JAVA_OPTSでJavaVMに割り当てるメモリを設定します。今回は512MBを当てています。

Elasticsearchの5.0.0-alpha5時点ではネットワークインターフェースをバインドする時にオプションを指定しないと立ち上がらなかったので、起動オプションにbootstrap.ignore_system_bootstrap_checksを設定します。

コンテナが公開しているポートは全て使用するため、PublishAllPortsを指定します。

最後に、試験終了後にコンテナを停止・破棄する必要があるため、コンテナIDを$container_idに記憶します。

container = Docker::Container.create(
  'Image'      => 'elasticsearch:5',
  'Env'        => ['ES_JAVA_OPTS=-Xms512m -Xmx512m'],
  'Cmd'        => %w(-E bootstrap.ignore_system_bootstrap_checks=true),
  'HostConfig' => { 'PublishAllPorts' => true }
)
$container_id = container.id

参考: https://www.elastic.co/blog/elasticsearch-5-0-0-alpha3-released

解説:コンテナの起動

作成したコンテナをcontainer.startで起動します。

ただ、この関数が戻ったタイミングでは、まだコンテナ内でElasticsearchのプロセスが起動仕切っていません。そのためcontainer.streaming_logsを使ってElasticsearchのプロセス起動ログを定期的に取得し、startedが出力されるまで待ちます

container.start
Cucumber.logger.debug "  * Container[#{$container_id}] starting...\n"
100.times.each do
  break if container.streaming_logs(stdout: true) =~ /started$/
  sleep 1
end
Cucumber.logger.debug "  * Container[#{$container_id}] was started.\n"

解説:ELASTICSEARCH_URLの生成

ELASTICSEARCH_URLはIPアドレス:ポートの形式で設定する必要がある。この場合のIPアドレスはDockerが動いているホスト(docker-machine)のIPアドレスになり、ポートはdocker-machineの公開ポートになります。

  • docker-machineのIPアドレスはcontainer.connection.urlから取得します。
  • docker-machineの公開ポートはランダムで割り当てられる、改めてDockerから起動したコンテナの情報を取得し、対象となる公開ポートを取得します

上記2つを組み合わせてELASTICSEARCH_URLを生成し、環境変数に設定します。

u = URI.parse(container.connection.url)
Cucumber.logger.debug "  * Create Container[#{$container_id}] at #{u}.\n"
container = Docker::Container.get $container_id
port = container.info['NetworkSettings']['Ports']['9200/tcp'][0]['HostPort']
Cucumber.logger.debug "  * Binding port 9200/tcp to #{port}.\n"

ENV['ELASTICSEARCH_URL'] = "#{u.host}:#{port}"
Cucumber.logger.debug "  * Set ELASTICSEARCH_URL to #{ENV['ELASTICSEARCH_URL']}.\n\n"

参考: http://www.rubydoc.info/gems/elasticsearch-transport#Setting_Hosts

解説:コンテナの停止と削除

$container_idにコンテナIDが記憶されている場合、既存のElasticsearchではなくDockerで作ったElasticsearchを利用しており、ELASTICSEARCH_URLに設定されているのはこのテストのみで利用するELASTICSEARCH_URLです。

そのためELASTICSEARCH_URLを破棄し、合わせて$container_idで取得したコンテナを停止、削除を行います

unless $container_id.nil?
  Cucumber.logger.debug "\nElasticsearch with Docker:\n"
  ENV['ELASTICSEARCH_URL'] = nil
  container = Docker::Container.get $container_id
  Cucumber.logger.debug "  * Container[#{$container_id}] stopping...\n"
  container.stop
  Cucumber.logger.debug "  * Container[#{$container_id}] was stopped.\n"
  container.delete
  Cucumber.logger.debug "  * Container[#{$container_id}] was deleted.\n"
end

使ってみる

実際に使用する場合、初めてコンテナを作成する時や、毎回のコンテナ停止に若干時間がかかるため、まずは-vオプションありで使ってみることをお勧めします。

$ bundle exec cucumber -v features/

また、このhookを使ったコードを下記で使用しています。

5月にRails5がリリースされてだんだんgemもこなれてきつつあるので満を持してアプリケーションテンプレートを作ってみました。これはBootstrap4とDeviseの認証を持ったシンプルなRails5アプリケーションを生成します。

https://github.com/tk-hamaguchi/rails_templates

細かい話はWikiにまとめてあるので、今回は使い方とできることをば。

使い方

今回はhoge_appという名前のアプリをnewしてみます

### 1. Rails new の時に-mでテンプレートアドレスを指定する
$ rails new hoge_app -d mysql -T --skip-bundle -m https://raw.github.com/tk-hamaguchi/rails_templates/master/rails5_template.rb

### 2. bootstrapを使う場合は途中の質問にyesを入力
      Do you want to use bootstrap? yes

### 3. 出来上がったアプリのフォルダに移動
$ cd hoge_app

###4. DBを作成してマイグレーション
$ bundle exec rake db:drop db:create db:migrate && RAILS_ENV=test rake db:migrate

### 5. サーバーを立ち上げる
$ rails s

※デフォルトでデータベースは自ホストのmysqlにソケット経由で接続する設定になっています。必要に応じてconfig/database.ymlを編集してください

この5ステップでbootstrap4のRails5アプリケーションが立ち上がります

できたもの

このアプリは下記の機能を持ったシンプルなアプリケーションです

  • ログイン・ログアウト
  • アカウント登録
  • セッションタイムアウト
  • ログイン情報の記憶
  • メールからのパスワード再設定

各々の機能はcucumberでテストが書かれているため、下記のコマンドで内容は確認できます

$ bundle exec cucumber features/

また、docker-compose用のファイルも合わせて生成するため、Railsアプリのproductionモードも下記のコマンドで試すことができます

$ docker-compose up

この場合コンテナが上がっているdocker-machineのホストIPにhttpアクセスを行うことで、ブラウザで動作を確認することができます。

※終了する場合はCtrl+Cで抜けます

もしうまくいかない場合は、Wikiの環境構築ページも参照してみるとヒントがあるかもしれません

また、このテンプレートで生成されたアプリはデフォルトのrakeタスクでユニットテスト(rspec)、インテグレーションテスト(cucumber)、詳細設計書(yard)、コーディング規約確認(rubocop)、セキュリティスキャン(breakman)を一通り実行されるのでpush前に一通り確認することができます。

$ bundle exec rake

Azure IoT-Hub につなぐときに、WindowsでVisualStudioが使えるか、 azure-iot-sdks が使える言語環境でのTipsはちょこちょこ見かけたけど、Linux上のRubyから繋ぐときのサンプルは見かけなかったので備忘録として書いてみるなど。 Azure IoT-HubはHTTPS, AMQPS, MQTTをサポートしいますが、今回はHTTPSでの接続を試みます。

※ この記事を書いている時点でAzure IoT-HUBプレビュー版となっており、アカウント登録することで無料でトライアル利用することができました。

デバイスの登録

デバイスの登録にはWindows環境ではDeviceExplorerが使えますが、今回はnode.jsで動くiothub-explorerを使ってコマンドラインでデバイス登録をします。

記事を書いている時点でiothub-explorerは1.0.0-preview.9で、これを使うためにはnode.jsのv0.12系が必要だったので、ダウンロード、インストールしました。

その後npmコマンドでiothub-explorerをインストールします

$ npm install -g iothub-explorer@latest

次にAzureポータルからiothubownerポリシーConnection Stringを取得します。

その後取得したConnection String登録したいデバイスIDをINPUTとして、下記のコマンドで登録します。

$ iothub-explorer "{取得したConnectionString}" create {登録したいデバイスID} --connection-string

このコマンドの実行結果として出力される登録したデバイスのConnectionStringを使って、認証情報を生成し、データをパブリッシュします。

認証情報の生成

IoT-Hubの認証にはSASトークンという認証情報を使っています。HTTPSの場合はAuthorizationヘッダに下記のように記載します。

Authorization: SharedAccessSignature sig={SIGNATURE}&se={EXPIRY}&sr={IOT_HUB_HOST}/devices/{DeviceID}

またINPUTとしては先ほどの登録したデバイスのConnectionStringを利用します。Azureポータルdeviceポリシーのものではありません。

HostName={IOT_HUB_HOST};DeviceId={DEVICE_ID};SharedAccessKey={DEVICE_KEY}

SASトークンとConnectionStringの間で共通の部分である{IOT_HUB_HOST}及び{DEVICE_ID}はそのままの値を当てはめることで利用できます。 {EXPIRY}及び{SIGNATURE}は生成してあげる必要があります。

{EXPIRY}についてはこのトークンの有効期限を秒で指定するもののようで、例えば10分間の場合はUNIXタイム(秒)+600を指定します。

{SIGNATURE}はかなり複雑で、下記の手順で生成します。

  1. {DEVICE_KEY}をBASE64でデコードする
  2. UTF-8の文字列として、{IOT_HUB_HOST}/devices/{DEVICE_ID}\n{EXPIRY}という文字列を作成する
  3. 1を鍵、2を計算対象の文字列としてHMAC-SHA256でハッシュ化する
  4. 3の出力をBASE64エンコードする
  5. 4の出力をURLエンコードする

この結果を{SIGNATURE}として利用し、Authorizationヘッダを構築します。

データの登録

データの登録は下記のようなHTTPパケットを利用して行うようです。

POST https://{IOT_HUB_HOST}/devices/{DEVICE_ID}/messages/events?api-version=2015-08-15-preview HTTP/1.1
Content-Type: application/json
Authorization: SharedAccessSignature sig={SIGNATURE}&se={EXPIRY}&sr={IOT_HUB_HOST}/devices/{DeviceID}
Accept: */*
Host: {IOT_HUB_HOST}
Content-Length: 50

{"name":"web","config":{"url":"hogehogehogehoge"}}

Content-Lengthヘッダ及びリクエストボディはサンプルです。

Rubyでベタ書きするとこんな感じ

require 'openssl'
require 'cgi'
require 'base64'
require 'time'
require 'net/https'
require 'json'


CONNECTION_STRING = "{デバイス登録時に取得したConnectionString}"
DATA = {name: "web", config: {url: "hogehogehogehoge"}}


CONNECTION_STRING.split(";").each do |elm|
  key = elm[0, elm.index('=')].gsub(/([A-Z])/, '_\1').gsub(/^_/, '').downcase
  self.instance_variable_set(
    "@#{key}".to_sym,
    elm[(elm.index('=') + 1), elm.length]
  )
end

expiry = Time.now.to_i + 60 * 10

raw = OpenSSL::HMAC.digest(
  'sha256',
  Base64.strict_decode64(@shared_access_key),
  "#{@host_name}/devices/#{@device_id}\n#{expiry}".encode(Encoding::UTF_8)
)
signature = CGI.escape(Base64.encode64(raw).strip)

sas_header = format(
  "SharedAccessSignature sig=%s&se=%s&sr=%s",
  signature,
  expiry,
  "#{@host_name}/devices/#{@device_id}"
)

request = Net::HTTP::Post.new(
  "https://#{@host_name}/devices/#{@device_id}/messages/events?api-version=2015-08-15-preview",
  { 
    'Content-Type'  => 'application/json',
    'Authorization' => sas_header
  }
)
request.body = DATA.to_json

response = nil
https = Net::HTTP.new(@host_name, 443)
https.use_ssl = true
https.start {
  response = https.request(request)
}

p response

レスポンスコードに204が返ってきますが、データ自体は登録できています。 登録件数はAzureポータルで確認することができます。

礎(いしずえ)はRuby on Railsで開発したアプリケーションのステージング環境構築を支援します。

礎でできること

下記のミドルウェアをインストールまたは設定するためのItamae cookbookテンプレートを作成します。

  • Nginx
  • MariaDB(10.1) Server
  • Redis
  • RVM
  • Capistrano
  • Monit
  • Munin
  • NTP
  • OpenSSH Server

※デプロイ先はCentOS6/CentOS7のみ確認できています

デプロイテンプレートはItamaeとCapistranoで作成されるため、作成後に好みに応じて編集することができます。 また、MuninやMonitがモニタリングするためのWeb画面のセットアップまで行うため、稼働状況の確認までできます。 redisおよびSidekiqの監視も含めているため、ActiveJob + Sidekiq + Redisにも対応します。

礎のインストール

1.Gemfileに下記を追記する

group :development do
  gem 'itamae-plugin-recipe-rvm', github: 'tk-hamaguchi/itamae-plugin-recipe-rvm', require: false
  gem 'ishizue', github: 'tk-hamaguchi/ishizue'
end

2.bundle installでインストールし、gereratorでishizueをinstallする

bundle install
bundle exec rails g ishizue:install

3.構築後の環境にログインするための公開鍵をmisc/ssh_keysフォルダにコピーする

cp ~/.ssh/id_rsa.pub misc/ssh_keys

これで準備は完了です。

礎を試す

デプロイテンプレートにはVagrantfileも含まれているため、それを使ってCentOS7のboxにインストールしてみます

1.作業ディレクトリをmisc/itamaeに移し、仮想マシンを作成します

cd misc/itamae/
vagrant up

仮想マシンはVagrantfileの中に定義されている通り192.168.33.10のIPで上がってきます。上がってこなかった場合は、後述する別ホストへのデプロイを試してください。

2.Itamaeを使ってインストールを実施します

itamae ssh -h 192.168.33.10 -u root --ohai roles/staging.rb --node-yaml=nodes/staging.yml
 INFO : Starting Itamae...
root@192.168.33.10's password:

仮想マシンのパスワードは『vagrant』が設定されています。実行に失敗する場合は何度かリトライしてみてください。-l debug 付与するとデバックログが出ます。

3.実行終了後はWebサーバーからのアクセスが可能になります。

Main: http://192.168.33.10

Monitoring: http://192.168.33.10:8282

別ホストへのデプロイ

192.168.33.10以外にデプロイする場合は、下記の手順を踏んでください

1.Capistranoの設定ファイルを編集する。

vi misc/capistrano/config/deploy/staging.rb
        server 'YOUR_SERVER_IP', user: 'deploy', roles: %w{app db web}

2.その後Itamaeの実行時IPを変更先のIPに変更して実行する

cd misc/itamae/
itamae ssh -h YOUR_SERVER_IP -u root --ohai roles/staging.rb --node-yaml=nodes/staging.yml

Railsアプリのアップデートに追従する

Rails アプリをバージョンアップした時にはcapistranoの実行のみで対応可能です

cd misc/capistrano
cap staging deploy

http://192.168.33.10:8282 については、適宜Nginxの設定を書き直してください

RAPIRO( http://www.rapiro.com/ja/ )をRubyから制御してみたかったのでラッパー作ってみた。

https://github.com/tk-hamaguchi/rapiro_wrapper

このライブラリはシリアルコマンドの#M0〜#M9( http://wiki.rapiro.com/page/serial-command_ja/ )を使わずに直接サーボモーターやLEDの制御をする#Pコマンドを使ってRAPIROを動かすライブラリです。(※まだテストしてないのでRubyGemsに上げてません。)

  1. できること

このライブラリを使うことでこんな感じでRAPIROの動きを制御できるようになります。

require 'rapiro_wrapper'


commander = RapiroWrapper::Commander.new('/dev/tty.usbserial-DA00HMG6')

sleep 20

commander.execute!([
  RapiroWrapper::Head.new( left: 30 ),
  RapiroWrapper::Waist.new( right: 30 ),
  RapiroWrapper::RightSholderRoll.new( up: 160 ),
  RapiroWrapper::RightSholderPitch.new( up: 20 ),
  RapiroWrapper::RightHandGrip.new( hold: 0 ),
  RapiroWrapper::LeftSholderRoll.new( up: 30 ),
  RapiroWrapper::LeftSholderPitch.new( up: 20 ),
  RapiroWrapper::LeftHandGrip.new( open: 0 ),
  RapiroWrapper::RightFootYaw.new( open: 30 ),
  RapiroWrapper::RightFootPitch.new( open: 5 ),
  RapiroWrapper::LeftFootYaw.new( close: 20 ),
  RapiroWrapper::LeftFootPitch.new( close: 0 ),
  RapiroWrapper::Eyes.new('#00ff00')
], 5)

RapiroWrapper::CommanderはシリアルI/Oを管理するクラスで、シリアルポートを引数に与えて初期化します。

その後インスタンスメソッドのexecute!()により各モーター、LEDに対して操作を行います。execute!の第一引数は各サーボモーターの状態を表すRapiroWrapper::ServoMotorのサブクラスの配列を指定し、第二引数は指定した位置に何秒後に達するかをミリ秒(1〜255)で指定します。

RapiroWrapper::ServoMotorのサブクラスと初期化時の引数は下記の通りで、各々の値は角度です。

クラス 位置 引数と値の範囲
RapiroWrapper::Head 首(頭) left:0〜90 または right:0〜90
RapiroWrapper::Waist left:0〜90 または right:0〜90
RapiroWrapper::RightSholderRoll 右肩 up:0〜180 (default:0)
RapiroWrapper::RightSholderPitch 右腕 up:0〜90 (default:0)
RapiroWrapper::RightHandGrip 右手 hold:0〜50 または open:0〜50
RapiroWrapper::LeftSholderRoll 左肩 up:0〜180 (default:0)
RapiroWrapper::LeftSholderPitch 左腕 up:0〜90 (default:0)
RapiroWrapper::LeftHandGrip 左手 hold:0〜50 または open:0〜50
RapiroWrapper::RightFootYaw 右足の開き close:0〜60 または open:0〜60
RapiroWrapper::RightFootPitch 右足 close:0〜40 または open:0〜40
RapiroWrapper::LeftFootYaw 左足の開き close:0〜60 または open:0〜60
RapiroWrapper::LeftFootPitch 左足 close:0〜40 または open:0〜40

また目のLEDについては下記のとおりです。

クラス 引数と値の範囲
RapiroWrapper::Eyes Webカラーの16進記述 または red:0〜255, green:0〜255, blue:0〜255

値を省略した場合は#M0同様各値を初期化します。

commander.execute!([])
  1. インストール手順

インストールは(1)直接ダウンロードしてインストールする か、(2)Gemfileを使ってインストールします。

(1)の手順:

# git clone -b master --depth 1 https://github.com/tk-hamaguchi/rapiro_wrapper.git
# cd rapiro_wrapper/
# gem build rapiro_wrapper.gemspec
# gem install rapiro_wrapper-*.gem

(2)のGemfileへの追記内容:

gem 'rapiro_wrapper', github: 'tk-hamaguchi/rapiro_wrapper'
  1. 読み込みと初期化

シリアルポートをRapiroWrapper::Commanderの引数に与えて初期化します。 ※RaspberyPiを接続しているときはシリアルポートが’/dev/ttyAMA0’場合引数を省略できます。

require 'rapiro_wrapper'


commander = RapiroWrapper::Commander.new('/dev/tty.usbserial-DA00HMG6')
  1. しぐさサンプル

とりあえず全部動かしてみる

commander.execute!([
  RapiroWrapper::Head.new( left: 30 ),
  RapiroWrapper::Waist.new( right: 30 ),
  RapiroWrapper::RightSholderRoll.new( up: 160 ),
  RapiroWrapper::RightSholderPitch.new( up: 20 ),
  RapiroWrapper::RightHandGrip.new( hold: 0 ),
  RapiroWrapper::LeftSholderRoll.new( up: 30 ),
  RapiroWrapper::LeftSholderPitch.new( up: 20 ),
  RapiroWrapper::LeftHandGrip.new( open: 0 ),
  RapiroWrapper::RightFootYaw.new( open: 30 ),
  RapiroWrapper::RightFootPitch.new( open: 5 ),
  RapiroWrapper::LeftFootYaw.new( close: 20 ),
  RapiroWrapper::LeftFootPitch.new( close: 0 ),
  RapiroWrapper::Eyes.new('#00ff00')
])

sleep 5

commander.execute!([])

首ふり。

duration = 7

2.times.each do

  commander.execute!([
    RapiroWrapper::Head.new( left: 30 )
  ], duration)

  sleep duration * 0.1

  commander.execute!([
    RapiroWrapper::Head.new( right: 30 )
  ], duration)

  sleep duration * 0.1

end

commander.execute!([],10)

あばれてみる。

duration = 5

5.times.each do

  commander.execute!([
    RapiroWrapper::Head.new( left: 10 ),
    RapiroWrapper::RightSholderRoll.new( up: 160 ),
    RapiroWrapper::LeftSholderRoll.new( up: 160 ),
    RapiroWrapper::RightSholderPitch.new( up: 5 ),
    RapiroWrapper::LeftSholderPitch.new( up: 5 ),
    RapiroWrapper::RightFootPitch.new( open: 0 ),
    RapiroWrapper::LeftFootPitch.new( close: 0 ),
    RapiroWrapper::Eyes.new('#ff0000')
  ], duration)

  sleep duration * 0.1

  commander.execute!([
    RapiroWrapper::Head.new( right: 10 ),
    RapiroWrapper::RightSholderRoll.new( up: 170 ),
    RapiroWrapper::LeftSholderRoll.new( up: 170 ),
    RapiroWrapper::RightSholderPitch.new( up: 40 ),
    RapiroWrapper::LeftSholderPitch.new( up: 40 ),
    RapiroWrapper::RightFootPitch.new( close: 0 ),
    RapiroWrapper::LeftFootPitch.new( open: 0 ),
    RapiroWrapper::Eyes.new('#330000')
  ], duration)

  sleep duration * 0.1

end

commander.execute!([],10)

ドヤァ〜

commander.execute!([
  RapiroWrapper::Head.new( left: 40 ),
  RapiroWrapper::Waist.new( right: 40 ),
  RapiroWrapper::LeftHandGrip.new( hold: 0 ),
  RapiroWrapper::RightHandGrip.new( hold: 0 ),
  RapiroWrapper::RightSholderRoll.new( up: 90 ),
  RapiroWrapper::LeftSholderRoll.new( up: 20 ),
  RapiroWrapper::LeftFootYaw.new( open: 30 ),
  RapiroWrapper::RightFootYaw.new( open: 20 ),
  RapiroWrapper::Eyes.new('#003300')
], 5)

sleep 1

commander.execute!([
  RapiroWrapper::Head.new( right: 30 ),
  RapiroWrapper::Waist.new( left: 30 ),
  RapiroWrapper::LeftHandGrip.new( hold: 0 ),
  RapiroWrapper::RightHandGrip.new( hold: 0 ),
  RapiroWrapper::RightSholderPitch.new( up: 30 ),
  RapiroWrapper::RightSholderRoll.new( up: 90 ),
  RapiroWrapper::LeftSholderRoll.new( up: 0 ),
  RapiroWrapper::LeftFootYaw.new( open: 30 ),
  RapiroWrapper::RightFootYaw.new( open: 20 ),
  RapiroWrapper::Eyes.new('#00ff00')
], 2)

sleep 5

commander.execute!([],10)

というわけで、人によっては使いやすいかも?

ぼちぼちテスト書くか〜