【15分解説】Bluetooth 4.2 → 5 でなにが変わったか?

す〜( @ksksue )です。

"私が知っているのは、自分が何も知らないということだけだ。" ― ソクラテス

ということでBluetooth 5についてまじめに調べました。

Bluetooth 4.2 → 5でなにが変わったのかを、Bluetoothの仕様書を読み解いてスライド10枚ちょっとにまとめています。 理解しにくい点を噛み砕いて解説しています。

※ フォトシンスで8月頃に実施した社内15分勉強会の資料

告知

フォトシンスではエンジニアを募集中

www.wantedly.com

Redmineでカンバンを見やすくしたら3倍捗った話

こんにちは。Akerunエンジニアの @ishturk です。

Akerun Advent Calendar11日目の記事です。

みなさん、スクラムしてますか?

弊社ではプロジェクト管理にRedmineを活用しています。

背景はこちら。

kazuph.hateblo.jp

僕のレイヤーでは開発はAgile(っぽい)進め方をしています。

RedmineAgile 。結構多いのではないでしょうか。そして僕らが悩んだようなコトを同じように悩んだ経験があるのではないかと。

  • デイリースタンドアップが、なんとなくだるい
  • カンバンツールとチケットが2重管理になって詰んだ
  • 付箋でカンバンをやっていたが、字が汚くて詰んだ←
  • アサインミスでチケットが迷子になって漏れた
  • イテレーションが終わったのにチケットがたくさん残っている

それ、解決しましょう。

プラグインの導入

チケットをカンバン化するプラグイン

www.redmine.org

最初は無料のLite版を使っていましたが、最近ボスにおねだりをしてPRO版を導入しました。

ユーザーに顔(アバター)を表示するプラグイン

これがとても重要です。 わかりやすさが3倍になります。アバターは赤くなくても大丈夫です。

github.com

はじめの一歩 バージョンの作成とチケットへの設定

僕のおすすめの運用は、イテレーション毎に、Redmineの標準機能の「バージョン」を作成します。最低、直近のイテレーションバックログ分を作成しましょう。

f:id:photosynth-inc:20171212004824p:plain

タスクをチケット化するときは作成したバージョンを設定します

f:id:photosynth-inc:20171212005320p:plain

できたカンバンがこちら

f:id:photosynth-inc:20171212005339p:plain

見やすいです。弊社では毎朝このカンバンをiPadと大型ディスプレイでミラーリング表示してデイリースタンドアップを実施しています。 ここでアバターを設定しておくと、カンバンに表示されます。責任感が3倍です。

f:id:photosynth-inc:20171212005625p:plain

動きます。だいたいその場でスクラムマスターがステータス更新してます(良いとは言ってない)

f:id:photosynth-inc:20171212005811p:plain

なんと!アサインもドラッグでいけます。アバター大活躍です。(機能自体はアバターがなくても使えます)

(弊社環境では他のプラグインが干渉してドラッグしたメンバーがカンバンの裏に行っていまいますがw ちゃんと使えます)

また、Lite版では以下の機能がないです。

  • チケットカンバン、ステータスなどの色分け
  • チケットステータスの2段(SubColumn)表示
  • カンバンのSwimLaneの設定
  • カスタムクエリの保存
  • チケットカンバン表示数(500制限)

弊社は最初に表示500制限にぶちあたり、PRO版を導入しましたが、期待以上に見易くなってgoodです。

なんとなくやっていたデイリースタンドアップの効果が大幅に上がっていることを実感できています。

体感3倍です。赤くなくても大丈夫です。

チケット全体の見通しも良くなるので漏れもなくなりました。

イテレーションが終わったら

振り返りと同時に持ち越しチケットを次のバージョンに移動しましょう

f:id:photosynth-inc:20171212005942p:plain

おわりに

Redmineでカンバンを活用するツールとして紹介しました。

僕的には先天的にとても字が汚いので、とてもとても重宝しています。

また、PRO版の本領は各種Agileに使えるチャート系の機能が拡充されていますが、そちらはまだ使いこなせていないので、またどこかで。

告知

弊社ではエンジニアを募集しています!

www.wantedly.com

治具に役立つ RPi3 x node x I2C電子部品 3選

回路設計・ファーム開発担当のす〜(@ksksue)です。

技術を磨くな、治具を作れ

ということで今回は基板検査治具で役立った電子部品の話題です。

治具はざっくり以下のような構成をよく取ります。

  • Raspberry Pi 3 : 汎用性が高い、Webと親和性が高い、nodeが使える
  • nodejsでドライバ設計 : C/C++等に比べ圧倒的に開発スピード早い、Webと親和性が高い
  • センサは秋月・ストリナ・スイッチサイエンスなど市販モジュールを使用 : 入手性が良い、壊れてもすぐ交換/購入できる
    • 通信方式はなるべくI2C : SW側でインタフェース統一できる

開発スピード早く、組合せでさっと環境が整うのが良いです。

よく使っているI2C電子部品は以下の3つ

  • IOエキスパンダ
  • ADC
  • 電流センサ

それぞれ1つずつ紹介していきます。

IOエキスパンダ

f:id:photosynth-inc:20171210032702j:plain

16bit I2C I/Oエキスパンダー MCP23017: 半導体 秋月電子通商 電子部品 ネット通販

RPiのIOを使えばOKといえばOKなのですが

  1. RPiのIOピンだけだと足りない
  2. 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にドライバがあります。

f:id:photosynth-inc:20171210040416j:plain

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

電流センサ

f:id:photosynth-inc:20171210044612j:plain

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で統一するとなにかとラクですね。

f:id:photosynth-inc:20171210044036p:plain

告知

フォトシンスではエンジニアを募集しています

www.wantedly.com

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()の後から復帰します。

f:id:photosynth-inc:20171206193722p:plain

ステートマシンのコード

割り込みのコンテキストでは、イベントを配送するだけで、処理はなるべくしません。 割り込み処理後、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);
}

ステートマシンの実装方法は過去にこちらの記事でも紹介しています。

qiita.com

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を使った省電力のテクニックは他にもいろいろあるので、またまとめてみたいと思います。

告知

弊社ではエンジニアを募集しています!

www.wantedly.com

イベントドリブンな設計 〜 Akerunつくったエンジニアのそこそこ大規模な組み込みプログラミング

こんにちは。Akerunエンジニアの @ishturk です。

Akerun Advent Calendar 6日目の記事です。よろしくお願いします。

今日は、AkerunのようなIoTデバイスの組み込み設計のお話です。

以前、このような記事を書きました。

qiita.com

今回は、もう少し具体的な内容を書いてみたいと思います。

イベントドリブンな設計

IoTガジェットや家電など、人が「操作」するものは、イベントドリブンで考えると、とても設計しやすくなります。Akerun Proも然り。結果、ステートマシンとの親和性が最高!

イベントドリブンとは

「Aという事象が発生したらαという処理をする」というものをイベント駆動とかイベントドリブンとか呼びます。ここで事象のことを「イベント」と呼びます。 これらを組み合わせてソフトウェアを組み上げていくことをイベントドリブンな設計と(僕は)呼んでます。

プログラマはイベントが発生することをしばしば「発火する」という表現を好んで使います。 代表的なイベントの種類としては、

  • ユーザー操作
  • センサ値などの値の変化
  • 時限(定期)イベント

があります。次項以降では、これらのイベントを扱うケースを具体的に考えてみます。

イベントドリブンな設計の利点

モジュールをカプセル化しやすい

直接的ではないですが、結果としてモジュールの責任を分離しやすいです。イベントを送る側と受ける側が独立(状態変数などを参照しないように)するのが重要です。

処理を追加しやすい

ユースケースが増えた場合、どう組込むか悩む場面が多いですね。この設計の場合、ユースケースの入り口を「イベントの発生」と捉えれば、一連の流れを実装しやすいです。また、処理を使いまわす場合は、同一のイベント(パラメータを変えることでその後の処理をフィットさせる)で実現できるのでコードの冗長化も回避し易いです。

コードの可読性

(慣れもありますね)

どう設計するのか [概要]

以前の記事で書いたように、ステートマシンで設計するのがベスプラです。 イベントが発火したとき、どのように振る舞うかが状態に依って決定され、処理を行います。

どう設計するのか [モジュール設計]

各モジュールが受け持つ範囲を明確にしておくことがポイントです。

たとえば次のような感じ

  • イベントを発生させるモジュール
  • イベントを配送するためのインターフェース
  • イベントを受け取るステートマシン
  • ステートマシンがアクションとして呼び出すアプリ

という責任分担をして、下のような関連にするとシンプルに実装できそうです。

f:id:photosynth-inc:20171206010419p:plain

どう実装するのか

サンプルコードを書いてみましょう。先述のモジュール構成とほぼ対応しています。

// アプリ処理
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);
}

最後に

ステートマシン信者の僕(ら)にはイベントドリブンな考えはとても強い武器になりますね。

明日も自分が担当です!

告知

弊社ではエンジニアを募集しています!

www.wantedly.com

CSVの文字コードをUTF-8に移行しようとした話

この記事はAkerun Advent Calendar 5日目です。

はじめまして! AkerunのAPIや管理画面を主に開発しているエンジニアのhirataです。

CSVファイルをWindowsMicrosoft Excelで読み込んだ時、日本語が文字化けする問題を解決しようとした時の話です。

f:id:photosynth-inc:20171205201757p:plain

よく見る解決方法は、読み込もうとしているCSVファイルの文字コードSJISに変換してからExcelで開く事です。

f:id:photosynth-inc:20171205201920p:plain

しかし、SJISにも読めない文字が存在します。 例えば「土」に「口」の「𠮷」という文字です。使用している変換プログラムによっては「吉」ではなく「𠮷」を使う場合があるのでExcelで開くと文字化けします。

f:id:photosynth-inc:20171205202019p:plain

ここでもう一つの可能性がある方法を紹介したいと思います。 それは、UTF-8文字コードです。

UTF-8CSVエンコードすると「𠮷」という字も読めます。 だが、CSVUTF-8エンコードしてもExcelで開いたらまだ文字化けをしています。

ExcelUTF-8を開く時はUTF-8のBOM付きというCSVではないと正しく読み込めない事がわかりました。 BOMとはByte Order Markの略で簡単に説明するとファイルの先頭に、このファイルはUTF-8ですよと数バイト付けてあげる事です。

これでExcelでも「𠮷」が文字化けしないで読めます。

f:id:photosynth-inc:20171205202100p:plain

文字化けしない事がわかったのでとりあえずCSVを「𠮷」で埋め尽くして保存しましょう。

f:id:photosynth-inc:20171205202148p:plain

少し寝かせて出来上がった物がこちらです。

f:id:photosynth-inc:20171205202224p:plain

え?

そうなんです、ExcelUTF-8のBOM付きのファイルを編集して保存をしたらBOMをはずしてしまうのです。 読める事はできるが編集ができないCSVファイル、、、、

読み込む事はできても編集ができないのでこれからもSJISで頑張りたいと思います。

今回は、皆様が同じような過程で時間を無駄にしないように記事にしてみました。

組込みCテストフレームワークUnityを導入してテストコード生産性をUp

回路設計・ファーム開発担当のす〜(@ksksue)です。

弊社のBLEモジュールnRF52(Cortex-M4F)のファームウェア開発環境にテストフレームワークUnity導入したところ カンタンな導入でテストコード生産性が飛躍的に上がりました。今回はそのUnityの紹介&解説です。

IoT開発においてハードウェアとソフトウェアを組合せたテストは品質を担保するために大事な工程ですが、 テスト項目が多くなってくると非常に煩雑になりがちです。Unity導入によってその煩雑さが少なくなりました。

オレオレテストコードをいじりまわす日々

f:id:photosynth-inc:20171201203239j:plain

Unity導入前、最初はカンタンなオレオレテスト環境で進めていたのですが、テスト項目数が増えるに従ってその環境自体を場当たり的に改善していくといった作業が発生しました。 正直なことを言うと、改善したいという欲求にあがらえず、必要以上の作業をしていた部分があったと思います。 それが進むと今度は過去のテストコードの再利用性が悪くなり、それをカバーする作業発生といった悪循環におちいりました。 テストコードを書いているのか、環境改善しているのかよくわからないというひどい状況でした。

Unityを導入することによって、前述したようなテスト環境自体に手を加える必要性がなくなり、本質的な作業に集中できるようになりました。 テストコードスタイルが統一されることでコードの保守性がダントツによくなります。 色々な邪念がなくなりテストコード生産性が上がったことが最大の導入メリットだと思います。

機能は少ないが充分な組込みCテストフレームワーク

f:id:photosynth-inc:20171201203031j:plain

Unityと検索するといろいろと別のフレームワークの結果が出てくるのですが、本記事で扱うUnityはC言語テストフレームワークのUnityです。

Unityは主に以下の機能を提供します

  • アサーション : 整数や配列などの期待値と実体値を比較しPASS/FAILを判定。FAILの場合そのアサートのファイル名と行数を出力
  • レポート:何件PASS/FAILあったかをレポート

例えばアサーションは以下のように期待値と実行値を指定します。

TEST_ASSERT_EQUAL_HEX8(10, func(i));

func(i)の結果が10でなかった場合は以下のようにファイル名、行数、関数名、期待値と実行値を出力します。

..\test\unity_test.c:232:test_drv_spi_write__should__Write:FAIL: Expected 10 Was 0

レポート結果はシンプルにいくつテストし、FAILが何回発生したかが出力されます。

-----------------------
10 Tests 0 Failures 0 Ignored 

他のテストフレームワークに比べると機能は少ないですが組込みCという環境を考えると充分と言えるでしょう。

さて、前置きが長くなりましたが以下導入方法の説明に入ります。

動作確認環境

  • MPU : nRF52832(Cortex-M4F)
  • 入出力 : J-Link Lite
  • 開発環境 : Nordic nRF5 SDK 11.0.0

※ Unityは非常に移植性が高いので一例と捉えてもらってOKです。基本的にはputやprintf などキャラクタ出力できる環境があればOKです。

Unity環境準備

ソースコードを下記ページから拝借します

https://github.com/ThrowTheSwitch/Unity

unity_config.h を編集します。環境依存の部分があるのでそこはよしなに設定してください。 ポイントは UNITY_OUTPUT_CHAR(a) の設定です。

#define UNITY_EXCLUDE_STDINT_H
#define UNITY_INCLUDE_DOUBLE
#define UNITY_OUTPUT_CHAR(a)    NRF_LOG_OUTPUT_CHAR(a)
#define UNITY_OUTPUT_START()    NRF_LOG_INIT()
#define UNITY_SUPPORT_WEAK weak __WEAK

編集前ファイル https://github.com/ThrowTheSwitch/Unity/blob/master/examples/unity_config.h

この NRF_LOG_OUTPUT_CHAR のput先に、今回はJLink RTTを指定します。JLink経由でキャラクタ出力できるので非常に便利です。 nRF52の場合は以下のようなファイルを用意しました。

unity_config.c

#include "nrf_log.h"

void NRF_LOG_OUTPUT_CHAR(int c) {
    NRF_LOG_PRINTF("%c",c);
}

これだけです。

あとはプロジェクトファイルに以下ファイルを追加

unity.c
unity_config.c

以下ファイルをincludeパス上に追加

unity_config.h
unity_internals.h

unity_config.hを有効にするためにコンパイルオプションのDefineに以下を追加

UNITY_INCLUDE_CONFIG_H

これでunity環境が整いました。

Unityテスト方法

あとは公式のドキュメントどおりにコーディングすればOKです。

引用: http://www.throwtheswitch.org/unity

以下のように、3つの引数の平均値を計算する関数をテストする場合を想定します。

テスト対象:DumpExample.c

#include "DumbExample.h"

int8_t AverageThreeBytes(int8_t a, int8_t b, int8_t c)
{
return (int8_t)(((int16_t)a + (int16_t)b + (int16_t)c) / 3);
}

テスト対象のヘッダファイル:DumpExample.h

#include <stdint.h>

int8_t AverageThreeBytes(int8_t a, int8_t b, int8_t c);

以下のように UNITY_BEGIN(); と UNITY_END(); の間に RUN_TEST(テスト関数); を入れ込み、 テスト関数内でチェックのためのアサートを書きます。

テストコード:main.c

#include "unity.h"
#include "DumbExample.h"

void test_AverageThreeBytes_should_AverageMidRangeValues(void)
{
TEST_ASSERT_EQUAL_HEX8(40, AverageThreeBytes(30, 40, 50));
TEST_ASSERT_EQUAL_HEX8(40, AverageThreeBytes(10, 70, 40));
TEST_ASSERT_EQUAL_HEX8(33, AverageThreeBytes(33, 33, 33));
}

void test_AverageThreeBytes_should_AverageHighValues(void)
{
TEST_ASSERT_EQUAL_HEX8(80, AverageThreeBytes(70, 80, 90));
TEST_ASSERT_EQUAL_HEX8(127, AverageThreeBytes(127, 127, 127));
TEST_ASSERT_EQUAL_HEX8(84, AverageThreeBytes(0, 126, 126));
}

int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_AverageThreeBytes_should_AverageMidRangeValues);
RUN_TEST(test_AverageThreeBytes_should_AverageHighValues);
return UNITY_END();
}

このコードをコンパイルし、JLinkRTTでウォッチすると以下のような結果が返ってきます。

testUnit1.c:21:test_AverageThreeBytes_should_AverageMidRangeValues:PASS
testUnit1.c:22:test_AverageThreeBytes_should_AverageHighValues:PASS
-----------------------
2 Tests 0 Failures 0 Ignored
OK

レポートはUNITY_BEGIN()からUNITY_END()までのテストがレポートされます。 テストシナリオを区切りたい場合には別のBEGIN/ENDで区切るのがよいでしょう。

まとめ

組込みCテストフレームワークUnityの導入方法、カンタンな使い方の解説をしました。

弊社ではIoT製品が商材ということもあって、ハードウェア/ソフトウェア共に何千万回とテストをしています。 例えばAkerunの開け閉め、Flashの書換、LEDの点灯消灯などを実環境で動かしつつ、一つ一つエラー率などを確認しています。 弊社の開発室にはRPiやArduino、センサ、ソレノイド、モーターなどで作った治具が所狭しと並べられていて、 テストシーズンには常にガチャガチャと治具が動く音が響き渡っています。

Unityはそいういったテスト環境においてカンタンに導入でき、テストコードの保守性、生産性をアップさせる非常に強力なツールです。 同じような環境の現場の方々、導入を検討してみてはいかがでしょうか?

参考

UnityのHP : http://www.throwtheswitch.org/

Unityのgithubリポジトリ : GitHub - ThrowTheSwitch/Unity: Simple Unit Testing for C

告知

弊社ではエンジニアを募集しています!

www.wantedly.com