google spread sheetでRedmineのチケットを操作してみる
こんにちは。Akerunエンジニアの @ishturk です。
Akerun Advent Calendar17日目の記事です。遅刻です。
弊社ではプロジェクト管理にRedmineを活用しています。
ありますよね?
さて、
チケットの内容をspreadsheetで表示したいと思ったことありませんか?
チケットをspreadsheetから発行したいと思ったことありませんか?
ありますよね?ありまくりますよね?
二重管理になるじゃん、、、と思ったアナタ!Redmineをマスターデータとしていれば二重管理は防げるのです!
たとえばこんな機能
- チケット番号を入力して、タイトル、内容、担当者が表示する
- spreadsheetに内容を入力したら「チケット発行」ボタンでチケット発行できる
これは捗りますね!
それ、API + GASでできます
実はRedmineにはREST APIが用意されており、チケットの参照・発行・編集など色々な事ができます。
google spreadsheetには google apps script(GAS)と連携できます。
Redmineの設定
上に貼ったリンクにも書いていますが、REST API を使用するにはRedmineのシステム管理者権限で有効にする必要があるので、管理者にお願いをしてください。有効にさえしてもらえば、使うのは一般ユーザーで大丈夫です。
「アクセスキー」が表示されてない場合は、無効になっている可能性が高いです。
チケットを参照するGASを書いてみる
番号からチケットタイトルを取得する
function ticket_name(id){ var url = "https://redmineのURL/issues/" + id + ".json?key=個人のアクセスキー"; // basic認証をかけている場合はヘッダに認証情報を入れる var user = "ユーザー名"; var pass = "パスワード"; var options = { "headers" : {"Authorization" : " Basic " + Utilities.base64Encode(user + ":" + pass)} }; var response = UrlFetchApp.fetch(url, options); var results = JSON.parse(response.getContentText()); //レスポンスをjson解析してresultsを取り出す return results.issue.subject }
ベーシック認証をかけている場合はヘッダに認証情報を付与します。(自分は素人なのでココでハマりました) アクセスキーを使うので、Redmine自体のユーザーid/passwordは不要です。 取得できる内容は以下を参照してくださいね。
チケットを作成する
function createIssue() { var redmine_url = 'https://RedmineのURL/issues.json'; var project_id = プロジェクトのid var owner = チケット発行者のid var issue = { 'subject': 'タイトル', 'description': '説明', 'tracker_id': トラッカーid 'assigned_to_id': アサインするユーザーのid, 'status_id': 7, セットするステータスのid } var payload = { 'issue': issue, 'project_id': project_id, }; payload = JSON.stringify(payload); var user = "ユーザー名"; var pass = "パスワード"; var headers = { "Authorization" : " Basic " + Utilities.base64Encode(user + ":" + pass), 'X-Redmine-API-Key': 個人のアクセスキー, 'X-Redmine-Switch-User': owner, 'Content-Type': 'application/json', }; var options = { 'method': 'POST', 'headers': headers, 'payload': payload, 'contentType': 'application/json', 'muteHttpExceptions': true }; var response = UrlFetchApp.fetch(redmine_url, options); return response; }
issue変数でチケットの諸々の値を設定しています。この辺の値をセルから取得するようにすれば、任意の内容でチケット発行ができますね。もちろん、カスタムフィールドも設定可能です。
実際に運用しているとチケットオーナーを 指定したいことが多々あるので、指定できるようにしています。この方法もちょっとハマりました。
また、プロジェクトのid、トラッカーのid、ユーザーのidなど、数値で指定しなくてはなりません。 調べる方法はいろいろあるので、またどこかでまとめたいと思います(本記事では割愛します)
チケット参照してみた
こんな感じでサクサク動きます。 捗りますね。
おわりに
RedmineのREST APIや、GASなど、非webエンジニアでも気軽に触れる環境が増えて、とても楽しい今日このごろです。
プロマネも、この辺りのエンジニアリングできると楽しくなるんじゃないでしょうか。躓いたときにwebエンジニアに相談すると会話もはずんでますますプロジェクトが捗りますね。
告知
弊社ではエンジニアを募集しています!
腰に優しいIoTテスト 〜 Akerunつくったエンジニアのそこそこ大規模なIoTプログラミング 〜
こんにちは。Akerunエンジニアの @ishturk です。
Akerun Advent Calendar13日目の記事です。
弊社フォトシンスは、ご存知のようにAkerunProKitというIoTサービスを提供しています。
昨年はラズパイを使った腰に優しい開発方法を紹介しました。
今回はさらに踏み込んで自動テストまで組み込んだ話です。
構成
- akerun web APIs
- AkerunProの動作ログをモニタリングする治具
- ラズパイ
- Logger
- テストランナー
akerun web APIをコールするとAkerun Remote を経由して、指示がAkerun Proに到達して処理を実行します。 Loggerは起動後テキストでファイルに吐き出し続ける構成にしています。 テストランナーでAPIをコールし、Logに期待する結果するまで待つという構成です。 テストランナーはGoを使って書いてみました。
コード
package main import ( "fmt" "strings" "os/exec" "strconv" "encoding/json" "io/ioutil" "log" "net/http" "net/url" ) func isWait(current int) bool { cmdstr := "wc -l autotest_log.log | cut -d \" \" -f 1" out, err := exec.Command("sh", "-c", cmdstr).Output() _ = err; s := string(out) s = strings.TrimRight(s, "\n") filelen, err := strconv.Atoi(s) if (current > filelen) { return true } else { return false } } func callAPI() { values := url.Values{} values.Add("param", "hogehoge") client := &http.Client{} req, err := http.NewRequest("POST", "https://あけるんAPI/hoge/hogera", nil) if err != nil { log.Fatal(err) } req.Header.Add("Authorization", "Bearer hogehogehogehogehogehogehogehogehogehogehogehogehogehogehoge") req.URL.RawQuery = values.Encode() res, err := client.Do(req) if err != nil { log.Fatal(err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { log.Fatal(res) } body, err := ioutil.ReadAll(res.Body) if err != nil { log.Fatal(err) } } func isContain(line int, interval int, str string) bool { cmdstr := "sed " + strconv.Itoa(line) + "," + strconv.Itoa(line + interval) + "p autotest_log.log" out, err := exec.Command("sh", "-c", cmdstr).Output() _ = err; if (strings.Contains(string(out), str)) { return true } else { return false } } func main() { line := 0 interval := 20 timeout := 6000 count := 0 for count < timeout { if (isWait(line)) { continue } if (isContain(line, interval, "期待するログ")) { break; } count++ line += interval } if (cout < timeout) { fmt.Println("PASS") else { fmt.Println("FAIL") } }
APIは認証ヘッダ、パラメータ付与などしてPOSTしてます。もちろんいろいろできます。ggったらいろいろできるのでお試しあれ。 また、ログファイルの監視は sed コマンドを使って順番にキャプチャする方法で実現しました。無理してます。もっといい方法がある気がします。
おわりに
今回は APIを使ってE2Eテストを書いています。APIさえあればいろんなテストバリエーションを自動化できるのでいいですね。 エンジニアが拘束される作業時間が短くなるのでとても腰に優しいです。
また、Goは初めてコードを書いたのですが、とても書きやすいですね。組み込み畑のエンジニアがAPIをカジュアルに叩きたい場合は、オススメです。
告知
弊社ではエンジニアを募集しています!
Redmineでカンバンを見やすくしたら3倍捗った話
こんにちは。Akerunエンジニアの @ishturk です。
Akerun Advent Calendar11日目の記事です。
みなさん、スクラムしてますか?
弊社ではプロジェクト管理にRedmineを活用しています。
背景はこちら。
僕のレイヤーでは開発はAgile(っぽい)進め方をしています。
Redmine + Agile 。結構多いのではないでしょうか。そして僕らが悩んだようなコトを同じように悩んだ経験があるのではないかと。
- デイリースタンドアップが、なんとなくだるい
- カンバンツールとチケットが2重管理になって詰んだ
- 付箋でカンバンをやっていたが、字が汚くて詰んだ←
- アサインミスでチケットが迷子になって漏れた
- イテレーションが終わったのにチケットがたくさん残っている
それ、解決しましょう。
プラグインの導入
チケットをカンバン化するプラグイン
最初は無料のLite版を使っていましたが、最近ボスにおねだりをしてPRO版を導入しました。
ユーザーに顔(アバター)を表示するプラグイン
これがとても重要です。 わかりやすさが3倍になります。アバターは赤くなくても大丈夫です。
はじめの一歩 バージョンの作成とチケットへの設定
僕のおすすめの運用は、イテレーション毎に、Redmineの標準機能の「バージョン」を作成します。最低、直近のイテレーションとバックログ分を作成しましょう。
タスクをチケット化するときは作成したバージョンを設定します
できたカンバンがこちら
見やすいです。弊社では毎朝このカンバンをiPadと大型ディスプレイでミラーリング表示してデイリースタンドアップを実施しています。 ここでアバターを設定しておくと、カンバンに表示されます。責任感が3倍です。
動きます。だいたいその場でスクラムマスターがステータス更新してます(良いとは言ってない)
なんと!アサインもドラッグでいけます。アバター大活躍です。(機能自体はアバターがなくても使えます)
(弊社環境では他のプラグインが干渉してドラッグしたメンバーがカンバンの裏に行っていまいますがw ちゃんと使えます)
また、Lite版では以下の機能がないです。
- チケットカンバン、ステータスなどの色分け
- チケットステータスの2段(SubColumn)表示
- カンバンのSwimLaneの設定
- カスタムクエリの保存
- チケットカンバン表示数(500制限)
弊社は最初に表示500制限にぶちあたり、PRO版を導入しましたが、期待以上に見易くなってgoodです。
なんとなくやっていたデイリースタンドアップの効果が大幅に上がっていることを実感できています。
体感3倍です。赤くなくても大丈夫です。
チケット全体の見通しも良くなるので漏れもなくなりました。
イテレーションが終わったら
振り返りと同時に持ち越しチケットを次のバージョンに移動しましょう
おわりに
Redmineでカンバンを活用するツールとして紹介しました。
僕的には先天的にとても字が汚いので、とてもとても重宝しています。
また、PRO版の本領は各種Agileに使えるチャート系の機能が拡充されていますが、そちらはまだ使いこなせていないので、またどこかで。
告知
弊社ではエンジニアを募集しています!
治具に役立つ RPi3 x node x I2C電子部品 3選
回路設計・ファーム開発担当のす〜(@ksksue)です。
技術を磨くな、治具を作れ
ということで今回は基板検査治具で役立った電子部品の話題です。
治具はざっくり以下のような構成をよく取ります。
- Raspberry Pi 3 : 汎用性が高い、Webと親和性が高い、nodeが使える
- nodejsでドライバ設計 : C/C++等に比べ圧倒的に開発スピード早い、Webと親和性が高い
- センサは秋月・ストリナ・スイッチサイエンスなど市販モジュールを使用 : 入手性が良い、壊れてもすぐ交換/購入できる
- 通信方式はなるべくI2C : SW側でインタフェース統一できる
開発スピード早く、組合せでさっと環境が整うのが良いです。
よく使っているI2C電子部品は以下の3つ
- IOエキスパンダ
- ADC
- 電流センサ
それぞれ1つずつ紹介していきます。
IOエキスパンダ
16bit I2C I/Oエキスパンダー MCP23017: 半導体 秋月電子通商 電子部品 ネット通販
RPiのIOを使えばOKといえばOKなのですが
- RPiのIOピンだけだと足りない
- RPiのピン保護(故障した場合IO Expanderの交換でOK)
という理由でIO Expanderを使う場合があります。
16個のIOが増えるので重宝します。
ライブラリコードは以下
var i2c = require('i2c-bus'); var debug = require('debug')('IoEx'); const IOEX_ADDR = 0x20; var self; const IOEX_BANK_A_GPIO = 0x12; const IOEX_BANK_B_GPIO = 0x13; const IOEX_GPIO_HOGE_ON = 0x01; function IoEx() { if(!(this instanceof IoEx)) { return new IoEx(); } self = this; this._i2c = i2c.openSync(1); this._i2c.writeByteSync(IOEX_ADDR, 0x00, 0xFF); // BANK A 全て input this._i2c.writeByteSync(IOEX_ADDR, 0x01, 0x00); // BANK B 全て output } function setGpio(gpioVal) { let val = self._i2c.readByteSync(IOEX_ADDR, IOEX_BANK_B_GPIO); val = val | gpioVal; self._i2c.writeByteSync(IOEX_ADDR, IOEX_BANK_B_GPIO, val); } function clearGpio(gpioVal) { let val = self._i2c.readByteSync(IOEX_ADDR, IOEX_BANK_B_GPIO); val = val & (~gpioVal); self._i2c.writeByteSync(IOEX_ADDR, IOEX_BANK_B_GPIO, val); } function readGpio(gpioVal) { let val = self._i2c.readByteSync(IOEX_ADDR, IOEX_BANK_A_GPIO); val = val & gpioVal; if(val === 0) { return 0; } else { return 1; } } IoEx.prototype.setHoge = function() { setGpio(IOEX_GPIO_HOGE_ON); } IoEx.prototype.clearHoge = function() { clearGpio(IOEX_GPIO_HOGE_ON); } module.exports = new IoEx();
使う時
var ioex = require('./lib/IoEx'); // Hoge ピン ON ioex.setHoge(); // Hoge ピン OFF ioex.clearHoge();
ADC
このADCは12ビット4チャネルと精度そこそこでチャネル数が多いので汎用性が高いです。 npmにドライバがあります。
ADS1015使用12ビット4チャンネルADコンバータ(プログラマブルゲインアンプ付): 半導体 秋月電子通商 電子部品 ネット通販
先述したとおりnpmモジュールがあるのでインストール
npm i node-ads1x15 --save
それを使いやすくラップ
var ads1x15 = require('node-ads1x15'); var deasync = require('deasync'); var debug = require('debug')('Ads1015'); const CHANNEL_3_3V = 2; const ADC_SAMPLES_PER_SECOND = '250'; const ADC_GAIN_AMP = '4096'; function Ads1015() { if(!(this instanceof Ads1015)) { return new Ads1015(); } self = this; this._adc = new ads1x15(0); // 0 for ads1015, 1 for ads1115 this._done = false; this._result = false; } function readAdc(channel, callback) { if(self._adc.busy) { return process.nextTick(function() { callback(new Error('ADC is busy')); }); } else { self._adc.readADCSingleEnded(channel, ADC_GAIN_AMP, ADC_SAMPLES_PER_SECOND, function(err, data) { if(err) { console.error(err); self._result = 0; self._done = true; if(callback) { callback(err); } } else { debug('data : ' + data + ' mV'); self._result = data; self._done = true; if(callback) { callback(null, data); } } }); } } Ads1015.prototype.read3V = function(callback) { readAdc(CHANNEL_3_3V, callback); } Ads1015.prototype.readSync3V = function() { readAdc(CHANNEL_3_3V, null); deasync.loopWhile(function(){ return !self._done; }); return self._result; } module.exports = new Ads1015();
使う時
var adc = require('./lib/Ads1015'); result = adc.readSync3V(); console.log(result + 'mV');
結果
3300mV
電流センサ
INA226PRC 【精密タイプ】I2Cディジタル電流・電圧・電力計モジュール - INA226PRC - ネット販売
0.1mAの分解能で電流値をセンシングすることができます。
電源ラインに流れる電流値の確認に使用しています。
詳細な使い方はストロベリーリナックスさんのモジュール説明書参照してもらうとして。。。
ライブラリコードは以下
var i2c = require('i2c-bus'); var debug = require('debug')('Ina226'); const INA226_ADDR = 0x41; const MA_PER_LSB = 0.1; // 1 LSB 当たりの mA const MV_PER_LSB = 1.25; // 1 LSB 当たりの mV const INA226_CURRENT= 0x01; const INA226_VOLT= 0x02; var self; function INA226() { if(!(this instanceof INA226)) { return new INA226(); } self = this; this._i2c = i2c.openSync(1); } function i2cRead(cmd) { let buf = new Buffer(2); self._i2c.readI2cBlockSync(INA226_ADDR, cmd, 2, buf); debug(buf); return buf.readInt16BE(); } INA226.prototype.readCurrentMa = function() { let val = i2cRead(INA226_CURRENT); debug('current raw : ' + val); return (val * MA_PER_LSB); } INA226.prototype.readVoltMv = function() { let val = i2cRead(INA226_VOLT); debug('volt raw : ' + val); return (val * MV_PER_LSB); } module.exports = new INA226();
使い方
var isense = require('./lib/Ina226'); result = isense.readCurrentMa(); console.log(result + ' mA');
結果
52.2 mA
配線
そういえばRPiとの繋ぎを描いてなかったので 以下の絵のように Raspberry Pi 3と繋ぐことで使えます。プルアップ抵抗描いてませんが適当(2.2k - 10k)な値のものを入れればOKかと。
I2Cで統一するとなにかとラクですね。
告知
フォトシンスではエンジニアを募集しています
Nordic nRF52を使った省電力でステートマシンを実装する方法
こんにちは。Akerunエンジニアの @ishturk です。
Akerun Advent Calendar7日目の記事です。昨日に引き続き、よろしくお願いします。
IoT製品の避けて通れない道「電池持ち・省電力」。今日のネタは、ここを強化する一つのメソッドです。
弊社でもいろんなところで使っているBLE SoCにNordic Semiconductor社の nRF52があります。 このChipで省電力で動作するステートマシンを実装する方法を紹介します。
動作確認環境
nRF5 SDK v13 + softdevice s132 v2
SDKに含まれているexamples/ble_peripheral/ble_app_uart
のサンプルコードをベースにしてみます。
元のソースコード
/* Copyright (c) 2014 Nordic Semiconductor. All Rights Reserved. * * The information contained herein is property of Nordic Semiconductor ASA. * Terms and conditions of usage are described in detail in NORDIC * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT. * * Licensees are granted free, non-transferable use of the information. NO * WARRANTY of ANY KIND is provided. This heading must NOT be removed from * the file. * */ /** @file * * @defgroup ble_sdk_uart_over_ble_main main.c * @{ * @ingroup ble_sdk_app_nus_eval * @brief UART over BLE application main file. * * This file contains the source code for a sample application that uses the Nordic UART service. * This application uses the @ref srvlib_conn_params module. */ === 省略 === /**@brief Function for placing the application in low power state while waiting for events. */ static void power_manage(void) { uint32_t err_code = sd_app_evt_wait(); APP_ERROR_CHECK(err_code); } === 省略 === /**@brief Application main function. */ int main(void) { uint32_t err_code; bool erase_bonds; // Initialize. APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_OP_QUEUE_SIZE, false); uart_init(); buttons_leds_init(&erase_bonds); ble_stack_init(); gap_params_init(); services_init(); advertising_init(); conn_params_init(); printf("\r\nUART Start!\r\n"); err_code = ble_advertising_start(BLE_ADV_MODE_FAST); APP_ERROR_CHECK(err_code); // Enter main loop. for (;;) { power_manage(); } } /** * @} */
シーケンス
下記の動作になるコードを書いてみます。
nRF52のSDKでは sd_app_evt_wait()
呼び出しでsleepし、その間は省電力モードになります。
割り込み(GPIOやタイマーなどのイベント)でmainがwakeupして、sd_app_evt_wait()
の後から復帰します。
ステートマシンのコード
割り込みのコンテキストでは、イベントを配送するだけで、処理はなるべくしません。
割り込み処理後、mainがコンテキストにスイッチし、polling()
が呼ばれます。
polling()
では、イベントキューが空になるまでステートマシンにイベントを配送し、空になったら再びsleepします。
/** * @brief イベント型 */ typedef struct Event_s { Signal s; EventDataT p; } Event; /** * @brief MainStateMachine にイベントを通知する * @param e [in] 通知するイベント */ void send_event(const Event *e); /** * @brief mainループで呼び出す */ bool polling(void); /** * @brief 状態のラベル */ typedef enum State_e { STATE_A, STATE_B, } State; /** * @brief ステート関数の型 */ typedef uint8_t (*StateFunc)(const Event *e); static State main_state_machine; ///< ステート変数< // StateMachine のイベントキュー #define SM_EVENT_QUEUE_LENGTH (10) ///< イベントキューサイズ static Event event_queue[EVENT_QUEUE_LENGTH]; ///< イベントキューの実体。リングバッファとして利用。 static uint8_t event_queue_head = 0; ///< リングバッファの先頭 static uint8_t event_queue_empty_head = 0; ///< リングバッファの未使用領域の先頭 // ステートに対応する関数の管理テーブル static StateFunc state_func_table[] = {StateFunc_A, StateFunc_B}; static uint8_t dispatch(const Event *e) { while (state_func_table[main_state_machine](e)) { // イベントが消化されなければCommonに配送 StateFunc_Common(e); break; } } void send_event(const Event *e) { if (((event_queue_empty_head + 1) == event_queue_head) || ((event_queue_empty_head == (EVENT_QUEUE_LENGTH-1)) && (event_queue_head == 0))) { // queue full ERROR_PRINT("Event queue is full!! FatalError!!!\n"); return RETURN_QUEUE_FULL; } event_queue[event_queue_empty_head] = *e; event_queue_empty_head = (event_queue_empty_head >= (EVENT_QUEUE_LENGTH -1)) ? 0 : event_queue_empty_head + 1; return; } bool polling() { if (event_queue_empty_head == event_queue_head) { // empty return false; } dispatch(&event_queue[event_queue_head]); event_queue_head = (event_queue_head >= (EVENT_QUEUE_LENGTH -1)) ? 0 : event_queue_head + 1; return true; }
ステートのコード
// ステート関数 /** * @brief Aステート */ uint8_t StateFunc_A(const Event *e) { switch(e->s) { case EVENT_SIG_HOGE: // 何かの処理 break; default: // イベントを消化しない場合はCommonに配送するようにtrueを返す return true; } return false; } /** * @brief Bステート */ uint8_t StateFunc_B(const Event *e) { switch(e->s) { case EVENT_SIG_HOGERA: // 何かの処理 break; default: // イベントを消化しない場合はCommonに配送するようにtrueを返す return true; } return false; } /** * @brief 共通ステート */ uint8_t StateFunc_Common(const Event *e) { { switch(e->s) { case EVENT_SIG_HOGE: // 何かの処理 break; case EVENT_SIG_HOGERA: // 何かの処理 break; default: break; } return false; }
イベントハンドラのコード
処理はせず、ステートマシンにイベントを通知します。
void timer_event_handler(void* param) { Event e = {EVENT_SIG_HOGE, param}; send_event(&e); } void ble_event_handler(void* param) { Event e = {EVENT_SIG_HOGERA, param}; send_event(&e); }
ステートマシンの実装方法は過去にこちらの記事でも紹介しています。
main.c のコード
前置きが長くなりましたが。。。 ここに1行追加します。
diff --git a/main.c b/main.c --- a/main.c +++ b/main.c @@ -538,6 +538,7 @@ int main(void) // Enter main loop. for (;;) { + while(polling()); power_manage(); } }
だめな例
以下の例は、sleepしないので、省電力になりません。
- ビジーループ
diff --git a/main.c b/main.c --- a/main.c +++ b/main.c @@ -538,6 +538,7 @@ int main(void) // Enter main loop. for (;;) { + while(polling()); - power_manage(); } }
- waitしてるだけのビジーループ
diff --git a/main.c b/main.c --- a/main.c +++ b/main.c @@ -538,6 +538,7 @@ int main(void) // Enter main loop. for (;;) { + while(polling()) { + nrf_delay_ms(1); + } - power_manage(); } }
終わりに
いかがでしょうか。今回は必要なときだけwakeupしてステートマシンをドライブする方法を具体的に書いてみました。 nRF52を使った省電力のテクニックは他にもいろいろあるので、またまとめてみたいと思います。
告知
弊社ではエンジニアを募集しています!
イベントドリブンな設計 〜 Akerunつくったエンジニアのそこそこ大規模な組み込みプログラミング
こんにちは。Akerunエンジニアの @ishturk です。
Akerun Advent Calendar 6日目の記事です。よろしくお願いします。
今日は、AkerunのようなIoTデバイスの組み込み設計のお話です。
以前、このような記事を書きました。
今回は、もう少し具体的な内容を書いてみたいと思います。
イベントドリブンな設計
IoTガジェットや家電など、人が「操作」するものは、イベントドリブンで考えると、とても設計しやすくなります。Akerun Proも然り。結果、ステートマシンとの親和性が最高!
イベントドリブンとは
「Aという事象が発生したらαという処理をする」というものをイベント駆動とかイベントドリブンとか呼びます。ここで事象のことを「イベント」と呼びます。 これらを組み合わせてソフトウェアを組み上げていくことをイベントドリブンな設計と(僕は)呼んでます。
プログラマはイベントが発生することをしばしば「発火する」という表現を好んで使います。 代表的なイベントの種類としては、
- ユーザー操作
- センサ値などの値の変化
- 時限(定期)イベント
があります。次項以降では、これらのイベントを扱うケースを具体的に考えてみます。
イベントドリブンな設計の利点
モジュールをカプセル化しやすい
直接的ではないですが、結果としてモジュールの責任を分離しやすいです。イベントを送る側と受ける側が独立(状態変数などを参照しないように)するのが重要です。
処理を追加しやすい
ユースケースが増えた場合、どう組込むか悩む場面が多いですね。この設計の場合、ユースケースの入り口を「イベントの発生」と捉えれば、一連の流れを実装しやすいです。また、処理を使いまわす場合は、同一のイベント(パラメータを変えることでその後の処理をフィットさせる)で実現できるのでコードの冗長化も回避し易いです。
コードの可読性
(慣れもありますね)
どう設計するのか [概要]
以前の記事で書いたように、ステートマシンで設計するのがベスプラです。 イベントが発火したとき、どのように振る舞うかが状態に依って決定され、処理を行います。
どう設計するのか [モジュール設計]
各モジュールが受け持つ範囲を明確にしておくことがポイントです。
たとえば次のような感じ
- イベントを発生させるモジュール
- イベントを配送するためのインターフェース
- イベントを受け取るステートマシン
- ステートマシンがアクションとして呼び出すアプリ
という責任分担をして、下のような関連にするとシンプルに実装できそうです。
どう実装するのか
サンプルコードを書いてみましょう。先述のモジュール構成とほぼ対応しています。
// アプリ処理 void application_unlock(void) { // unlock処理 } void application_lock(void) { // lock処理 } // ステートで判断してアプリ処理を呼び出す void state_func_locked(event e) { switch (e.signal) { case E_EVENT_BUTTON: if (e.param == PUSHED) { application_unlock(); } break; default: break; } } void state_func_unlocked(event e) { switch (e.signal) { case E_EVENT_BUTTON: if (e.param == PUSHED) { application_lock(); } break; default: break; } } // イベントをステートに配送 state_valiable v = STATE_LOCKED; void state_machine_dispatch(event e) { switch (v) { case STATE_LOCKED: state_func_locked(e); break; case STATE_UNLOCKED: state_func_unlocked(e); break; default: break; } } // イベントをステートマシンに配送 void event_manager_notify_event(event e) { state_machine_dispatch(e); } // ボタンが押されたイベントを発生 void operation_facade_proc(operation_type_e type) { switch (type) { case BUTTON_PUSHED: { event e = {E_EVENT_BUTTON, PUSHED} event_manager_notify_event(e); break; } default: break; } } // ボタンが押されたら呼ばれる関数 void button_driver_pushed(void) { operation_facade_proc(BUTTON_PUSHED); }
最後に
ステートマシン信者の僕(ら)にはイベントドリブンな考えはとても強い武器になりますね。
明日も自分が担当です!
告知
弊社ではエンジニアを募集しています!