LoginSignup
4
1

More than 3 years have passed since last update.

ビルドが成功したら緑に光るやつ(物理)を作った

Posted at

はじめに

現代的なソフトウェア開発においてビルドやデプロイの仕組みは欠かせないものですが、手動でやるととても大変です。
そのあたりを色々とサポートしてくれるツールやサービスは様々あり、今回はAWSのCodeシリーズ(CodeCommit、CodeBuild、CodeDeploy、CodePipeline)を使ってみました。
こんな感じでCI/CDパイプラインが割と簡単に作れます。いい感じですね。

image.png

とりあえずgitリポジトリにソースコードをPushしたら、.NET Coreのプロジェクトが自動的にビルドされて、EC2にデプロイされる、というのが作れました。

こういうのができると、CI/CDがうまくいってるかどうかを可視化したくなります。
もちろんメールやSlackで通知するのもいいのですが、一目でパッとわかるようにしたい。
以前作ったマイコンの遠隔操作プログラムeasiがありますので、これでうまくいってたら緑、失敗してたら赤く光るようにすれば、一目でわかっていいのではないかと思ったので作りました。制作時間30分くらいです。

success.jpg

必要なもの

パイプラインはすでにできているものとします。(そこまで書いてるとすごく長くなるので)

  • 遠隔操作プログラムが書き込まれたWio LTE(通信はSORACOM Air)
  • デバイス操作権限を持つSAMユーザー
  • SNSから通知を受けてSORACOM APIにコマンドを送るLambda
  • CodePipelineからの通知を受け取りLambdaを起動するSNSトピック

pipeline.png

それぞれ準備していきましょう。

Wio LTE

easiのWioLTE用コードをダウンロードして展開し、easi.inoを以下のように修正します。

#include "easi.h"

#define EASI_WIO_LTE
#define EASI_VERSION "V1.1.0"

Lwm2m lwm2m;
#include <WioLTEforArduino.h>
WioLTE wio;
static bool greenOn = false;
static uint32 greenStartTime = 0;

void setup() {
  // 初期化
  delay(200);
  wio.Init();
  wio.PowerSupplyLTE(true);
  logText("POWER ON LTE");
  delay(500);
  if (!wio.TurnOnOrReset()) {
    logText("Turn on error");
    return;
  }

  // 接続
  if (!wio.Activate("soracom.io", "sora", "sora")) {
    logText("Connect error");
    return;
  }
  delay(1000);

  // LWM2Mエンドポイントとブートストラップサーバの設定
  lwm2mInit(&lwm2m, "wiolte");
  udpInit(&lwm2m.bootstrapUdp, "bootstrap.soracom.io", 5683);

  // ブートストラップをせず払い出したデバイスIDとキーを使用する場合はこちら
  // char identity[] = "d-01234567890123456789";
  // uint8 psk[16] =  { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
  // lwm2mSetSecurityParam(&lwm2m, identity, &psk[0]);

  // オブジェクトの設定のサンプル
  // 以下のオブジェクトリストのうちID:0 〜 ID:9、ID:3200 〜 3203、ID:3300 〜 3350を登録済み
  // http://www.openmobilealliance.org/wp/omna/lwm2m/lwm2mregistry.html

  // デバイスオブジェクトをインスタンス0番として登録
  addInstance(3, 0); 

  // Light Controlオブジェクトをインスタンス0番として登録
  addInstance(3311, 0);

  // オペレーションとメソッドを対応づけのサンプル
  setReadResourceOperation   (   3, 0,    2, &getSerial);      // READ    /3/0/2       でgetSerialが呼ばれるよう設定
  setWriteResourceOperation  (3311, 0, 5850, &turnOnOffLight); // WRITE   /3311/0/5850 でturnOnOffLightが呼ばれるよう設定
  setWriteResourceOperation  (3311, 0, 5706, &setLEDColor);    // WRITE   /3311/0/5706 でsetLEDColorが呼ばれるよう設定
  setExecuteResourceOperation(   3, 0,    4, &reboot);         // EXECUTE /3/0/3       でrebootが呼ばれるよう設定

  // ブートストラップ(接続情報取得)実行
  // 成功するまで繰り返す
  while (!lwm2mBootstrap(&lwm2m)){ }
}

void loop() {
  // LWm2mのイベントが無いかチェックし、イベントを処理したらtrue、イベントが無ければfalseを返す
  if (!lwm2mCheckEvent(&lwm2m)){
    if (greenOn && (getNowMilliseconds() - greenStartTime) > 60000){
      greenOn = false;
      wio.LedSetRGB(0, 0, 0);
    }
    delay(100);
  }
}

// READの場合は値をtlvの各要素に代入する
// Integer / Boolean / Timeはtlv->intValue
// Floatはtlv->floatValue
// Stringはtlv->bytesValue
// Opaqueはバイナリをtlv->bytesValue、長さをtlv->bytesLen
// ObjlnkはオブジェクトIDをtlv->ObjectLinkValue、インスタンスIDをtlv->InstanceLinkValue
// にそれぞれ代入する
void getSerial(Lwm2mTLV *tlv){
  strcpy((char *)&tlv->bytesValue[0], "123456789");
};

// WRITEの場合は値がtlvの各要素から渡される
// 対応する要素はREADと同じ
void turnOnOffLight(Lwm2mTLV *tlv){
  if (tlv->intValue){
    wio.LedSetRGB(255, 0, 0);
  } else {
    wio.LedSetRGB(0, 0, 0);
  }
};

void setLEDColor(Lwm2mTLV *tlv){
  if (strncmp((const char*)tlv->bytesValue, "red", 3) == 0){
    wio.LedSetRGB(255, 0, 0);
  } else if (strncmp((const char*)tlv->bytesValue, "green", 5) == 0){
    greenOn = true;
    greenStartTime = getNowMilliseconds();
    wio.LedSetRGB(0, 255, 0);
  } else if (strncmp((const char*)tlv->bytesValue, "orange", 6) == 0){
    wio.LedSetRGB(255, 165, 0);
  } else {
    wio.LedSetRGB(0, 0, 0);
  }
};

// EXECUTEの場合、
// パラメータはOpaqueと同じ形式で渡す(使わなくてもよい)
void reboot(Lwm2mTLV *tlv){
  NVIC_SystemReset();
};

LEDの色を指定するsetLEDColorメソッドを追加しています。
これをWio LTEに書き込んで動作させるとSORACOM Inventoryに接続されます。

SORACOMコンソールの[SORACOM Inventory] > [デバイス管理]の画面にて、EndpointがwiolteとなっているデバイスのIDを確認しておきます。あとでLambdaの環境変数に使用します。

スクリーンショット 2020-11-29 21.12.19.png

SAMユーザー

以下の記事を参考にSAMユーザー(SORACOMを利用するためのユーザー)を作成し、認証キーを作成します。
https://dev.soracom.io/jp/start/sam/

権限はできるだけ狭くした方がよいでしょう。
今回はDevice:writeDeviceResourceだけで良いので、以下の権限とします。

{
  "statements": [
    {
      "api": [
        "Device:writeDeviceResource"
      ],
      "effect": "allow"
    }
  ]
}

作成した認証キーはLambdaの環境変数に使用します。

Lambda

Ruby2.7にてLambda関数を作成します。
以下の3つの環境変数を設定します。

キー
SORACOM_AUTH_KEY_ID SAMユーザーの認証キーID(keyId-で始まる)
SORACOM_AUTH_KEY_SECRET SAMユーザーの認証キーシークレット(secret-で始まる)
SORACOM_DEVICE_ID SORACOM InventoryのデバイスID(d-で始まる)

コードは以下のようになります。
eventにはSNSトピックからのイベントが入り、そのMessageの中にPipelineからの情報が入っています。
その中のstatusにビルドやデプロイなどが成功したか、失敗したかが入っています。
このコードでは開始時にオレンジ、成功したら緑、失敗したら赤を指定するようにしています。

require 'json'
require 'net/http'
require 'uri'

def lambda_handler(event:, context:)
  puts JSON.generate(event)
  token = get_soracom_token
  event['Records'].each do |record|
    message = JSON.parse(record['Sns']['Message'])
    color =
      case message['detail']['state']
      when 'SUCCEEDED' then 'green'
      when 'FAILED' then 'red'
      when 'STARTED' then 'orange'
      else 'off'
      end
    send_set_color_command(token, color)
  end
end

def get_soracom_token
  uri = URI.parse("https://api.soracom.io/v1/auth")
  request = Net::HTTP::Post.new(uri)
  request.content_type = "application/json"
  request["Accept"] = "application/json"
  request.body = JSON.generate({
    "authKeyId" => ENV['SORACOM_AUTH_KEY_ID'],
    "authKey" => ENV['SORACOM_AUTH_KEY_SECRET']
  })
  response = Net::HTTP.start(uri.hostname, uri.port, { use_ssl: true } ) do |http|
    http.request(request)
  end
  raise "fail to get soracom token" if response.code != '200'
  JSON.parse(response.body)
end

def send_set_color_command(token, color)
  uri = URI.parse("https://api.soracom.io/v1/devices/#{ENV['SORACOM_DEVICE_ID']}/3311/0/5706")
  request = Net::HTTP::Put.new(uri)
  request.content_type = "application/json"
  request["Accept"] = "application/json"
  request["X-Soracom-Api-Key"] = token['apiKey']
  request["X-Soracom-Token"] = token['token']
  request.body = JSON.generate({"value" => color})

  response = Net::HTTP.start(uri.hostname, uri.port, { use_ssl: true } ) do |http|
    http.request(request)
  end
  raise "fail to send command #{response.code} #{response.body}" if response.code[0] != '2'
  response.code
end

SNSトピック

SNSトピックはCodePipelineのコンソールから作成するとスムーズです。

パイプラインの画面から[通知] > [通知ルールの作成]をクリックします。
スクリーンショット 2020-11-29 21.57.34.png

通知名、詳細タイプ、トリガーするイベント、ターゲットを入力して、Submitします。
詳細タイプは「フル」のままで良いです。
イベントはどのくらい細かく通知するかですが、とりあえずパイプライン全体で成功したか/失敗したかを見たいので、パイプライン関連のイベントだけにします。ビルドとデプロイで表示を変える、などが必要であれば、ステージ関連のイベントも通知させれば良いでしょう。

スクリーンショット 2020-11-29 21.59.27.png

ターゲットはここで作成できます。種類はSNSトピックとして、トピック名は適当に決めて作成します。
スクリーンショット 2020-11-29 22.00.30.png

作成したら通知ルールのターゲットにSNSが出てきますので、リンクをクリックしてSNSのページに移動します。
スクリーンショット 2020-11-29 22.05.32.png

SNSとLambdaを関連づけるのはサブスクリプションです。「サブスクリプションの作成」をクリックします。
スクリーンショット 2020-11-29 22.06.47.png

トピックARNに作成したSNSトピック(入力済み)、プロトコルにAWS Lambda、エンドポイントに作成したLambda関数を指定し、「サブスクリプションの作成」をクリックします。
スクリーンショット 2020-11-29 22.08.41.png

これでPipelineのステータスが変わるとSNSに通知され、SNSがそのステータス変化を入力としてLambdaを起動されるようになりました。Lambdaはステータスに応じてWio LTEのLEDの色を変えます。

やってみましょう。

動作確認

まず成功する場合です。
ビルドが成功するコミットをプッシュしてしばらくするとオレンジ(というか黄色っぽい)に光ります。この時パイプラインは進行中です。
success-start.jpg

成功で終了すると緑に光ります。(緑は1分経つと消してます。ずっと光らせててもいいのですがかなり眩しいので)
success.jpg

次にビルドが失敗するコミットをプッシュしてしばらくするとまたオレンジに光り、
fail-start.jpg

ビルドが失敗すると赤く光ります。(赤は見過ごさないように時間が経っても消えないようにしています)
failed.jpg

パイプラインの状況が一目で分かりますね!

おわりに

今はCI/CDがこんな簡単にできるようになってるのはいいですね。こういうのも身に付けていかないといけない。
クラウドに情報が集まっていればPC/スマホで確認できますがやや手間がかかりますので、IoTデバイスで物理的にわかりやすく見せられるのはこれはこれでいい感じだと思います。
今回はビルドやテスト、デプロイでしたが、例えば監視エラーが出たら赤く光らせるとか、他の使い方も考えられますね。色々やってみたいです。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1