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

パソコンをロックせずに放置する人をしつける12個のいたずら

Akerun Advent Calendar 3日目 〜セキュリティ意識の改善〜。

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

みなさんはパソコンから離れるとき、ロックする習慣はついているでしょうか?

Photosynthでは「社内でもパソコンから離れる時はロックする」ということが社内規則で定められています。

こういったルールを規則として設けてる会社は多いのですが、守らない人というのは僕がこれまで努めたどの会社にもいました。

そういう人には離席した時にパソコンを操作して「実際に困る」という事を体験してもらえばいいのではないかと考え、今回は僕が考えた効果の高そうな方法を紹介します。

あくまでネタ記事として軽い気持ちで読んでいただけると幸いです。

今回の記事の対象OSはmacOSです(便宜上「パソコン」と表記します)。

1. ディスプレイの表示を回転させる

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

ディスプレイを回転させた状態で表示させます。 マウスカーソルの操作がとても大変になるので、もとに戻すのもイライラします。

作業時間

1分

手順

  1. 「システム環境設定」を開く
  2. [⌘command] キー + [⌥option] キーを押しながら 「ディスプレイ」 を選択
  3. 「回転」項目を「標準」から適当な角度のものに変更
  4. 表示されたダイアログの「確認」を選択

戻し方

  1. 「システム環境設定」を開く
  2. [⌘command] キー + [⌥option] キーを押しながら 「ディスプレイ」 を選択
  3. 「回転」項目を「標準」に変更

対策

パソコンから離れる時はロックしましょう。

2. ディスプレイの色を反転させる

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

表示する色を反転させます。 地味ですが、人によってはディスプレイが壊れたと勘違いするほどインパクトがあります。 あまり使うことがない機能なので戻し方がわからない人も多いと思います。

作業時間

1分

手順

  1. [⌘command] キー + [⌥オプション] キー + [⌃control] キー + [F5] キー でアクセシビリティオプションを開く
  2. 「ディスプレイの色を反転」 にチェックを入れる
  3. 「完了」を選択

一応ショートカットが用意されているので、こちらで動けば一発でいけます。

[⌘command] キー + [⌥オプション] キー + [⌃control] キー + [8] キー

戻し方

  1. [⌘command] キー + [⌥オプション] キー + [⌃control] キー + [F5] キー でアクセシビリティオプションを開く
  2. 「ディスプレイの色を反転」 のチェックをはずす
  3. 「完了」を選択

対策

パソコンから離れる時はロックしましょう。

3. デスクトップに紛らわしいファイルを沢山つくる

デスクトップに存在するファイルを沢山コピーして名前を似たものに変えます。 中身が同じなのでファイルを消す際にとても慎重になる悪質ないたずらです。 アイコンの並び順をシャッフルすると更に悪質ですね。

作業時間

1分

手順

  1. 既存のファイルを選択
  2. [⌘command] キー + [d] キー でファイルを複製
  3. ファイル名を変更
  4. 1 ~ 3を繰り返す

戻し方

手動で地道に不要なファイルを消すくらいでしょうか。

対策

パソコンから離れる時はロックしましょう。

4. デスクトップの壁紙をスクショに差し替える

デスクトップの壁紙を「デスクトップのスクリーンショット」に差し替えるいたずらです。 ファイルを消してもアイコンが残ったり、選択できない謎のファイルが存在するように見えます。 なかなか気づかないと思います。

作業時間

1分

手順

  1. Docを右クリック
  2. ["自動的に非表示"をオンにする]を選択
  3. Docが非表示になっている状態で [⌘command] キー + [⇧shift] キー + [3] キー でスクリーンショットを保存
  4. デスクトップを右クリック
  5. [デスクトップのバックグラウンドを変更]を選択
  6. 先程とったスクリーンショットに変更

戻し方

  1. デスクトップを右クリック
  2. [デスクトップのバックグラウンドを変更]を選択
  3. 元の壁紙ファイルを選択

対策

パソコンから離れる時はロックしましょう。

5. ログインシェルを変える

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

ターミナルのログインシェルはデフォルトではbashですが、普通のshに変えてしまういたずらです。 挙動はあまりかわりませんが、bash_profileやbashrcで追加しているパスやエイリアスなどが読み込まれないので、普段使ってるコマンドが使えないという事が起こります。

作業時間

1分

手順

  1. terminal.app を開く
  2. echo '/bin/sh' >> .bash_profile を実行

戻し方

.bash_profile から該当のコードを削除

対策

パソコンから離れる時はロックしましょう。

6. stressコマンドをバックグラウンドで走らせる

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

常にパソコンが重たくなり、本体は熱くなり、ファンがうなり始めます。 ハード・ウェアに負担がかかり寿命が短くなったりするので長時間やるのは避けたほうがいいと思います。

作業時間

1分

手順

  1. terminal.app を開く
  2. brew install stress && stress --cpu 4 --quiet & を実行

戻し方

  1. terminal.app を開く
  2. killall stress && brew uninstall stress を実行

対策

パソコンから離れる時はロックしましょう。

7. デフォルトのエディタをnanoに置き換える

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

普段 vimemacs などを使っている人をイラッとさせることができます。 コマンドを叩くとエディタが立ち上がってしまうので下手な操作ができなく、 普段 nano をつかっていない人は慎重に exit する操作をするはめになります。

作業時間

3分

手順

  1. terminal.app を開く
  2. 以下のコマンドを実行
# gitなどで立ち上がるエディターを変更
echo 'export EDITOR=nano' >> ~/.bash_profile

# nano用の関数を定義
echo 'hoge(){ nano "${$1}"}' >> ~/.bash_profile

# viをnanoに置き換え
echo 'alias vi="hoge"' >> ~/.bash_profile

# vimをnanoに置き換え
echo 'alias vim="hoge"' >> ~/.bash_profile

# emacsをnanoに置き換え
echo 'alias emacs="hoge"' >> ~/.bash_profile

# atomをnanoに置き換え
echo 'alias atom="hoge"' >> ~/.bash_profile

戻し方

.bash_profile から該当のコードを削除

対策

パソコンから離れる時はロックしましょう。

8. lsコマンドをslコマンドに置き換える

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

ファイル一覧を表示しようとすると蒸気機関車のSLが走ります。 個人的にこういうくだらないのが大好きです。

作業時間

3分

手順

  1. terminal.app を開く
  2. 以下のコマンドを実行
# slコマンドをインストール
brew install sl

# じゃまなエイリアスを貼っている場合があるので無効化
echo 'unalias sl' >> ~/.bash_profile
echo 'unalias ll' >> ~/.bash_profile
echo 'unalias tree' >> ~/.bash_profile

# lsをslに置き換え
echo 'alias ls="sl"' >> ~/.bash_profile

# llをslに置き換え
echo 'alias ll="sl"' >> ~/.bash_profile

# treeをslに置き換え
echo 'alias tree="sl"' >> ~/.bash_profile

戻し方

  1. .bash_profile から該当のコードを削除
  2. terminal.app を開く
  3. brew uninstall sl を実行

対策

パソコンから離れる時はロックしましょう。

9. 環境変数の$PATHを空にする

ほとんどのコマンドが叩けなくなります。 一歩間違うと元に戻せなくなる可能性があり、非常に危険ないたずらです。

作業時間

1分

手順

  1. terminal.app を開く
  2. 以下のコマンドを実行
echo 'export PATH=""' >> ~/.bash_profile

戻し方

.bash_profile から該当のコードを削除

対策

パソコンから離れる時はロックしましょう。

10. echoコマンドやcatコマンドを叩くと大音量で読み上げる

echoコマンドやcatコマンドを叩くと最大音量で出力を読み上げるいたずらです。 一瞬驚かせることができると思います。

作業時間

1分

手順

  1. terminal.app を開く
  2. 以下のコマンドを実行
# 引数を読み上げる関数を作成
echo 'hoge1(){ set Volume output volume 100; say "${$1}"}' >> ~/.bash_profile
echo 'alias echo="hoge"' >> ~/.bash_profile
echo 'alias print="hoge"' >> ~/.bash_profile

# 引数のファイルを読み上げる関数を作成
echo 'hoge2(){ set Volume output volume 100; say -f "${$1}"}' >> ~/.bash_profile
echo 'alias cat="hoge"' >> ~/.bash_profile
echo 'alias less="hoge"' >> ~/.bash_profile
echo 'alias tail="hoge"' >> ~/.bash_profile

戻し方

.bash_profile から該当のコードを削除

対策

パソコンから離れる時はロックしましょう。

11. ブラウザを差し替える

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

Firefoxを起動したはずなのに、Safariが起動するというものです。 ChromeOperaなども同じやり方で可能です。 人によってはデフォルトのブラウザが変わったと勘違いすると思います。

作業時間

1分

手順

  1. Finderで/Applicationsを開く
  2. Firefox.app の名前を _Firefox.app に変更
  3. Safari.app を選択して [⌘command] キー + [d] キー で複製して Firefox.app に変更

戻し方

  1. Finderで/Applicationsを開く
  2. Firefox.app を削除
  3. _Firefox.app の名前を Firefox.app に変更

対策

パソコンから離れる時はロックしましょう。

12. シェルログイン時に「うっ…」となるメッセージを吐かせる

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

ターミナルを開く度に「進捗どうですか?」など出力するようにして何とも言えない気持ちにさせることができます。

作業時間

1分

手順

  1. terminal.app を開く
  2. 以下のコマンドを実行
echo 'echo "\n\n\n\n\t進 捗 ど う で す か ?\n\n\n"' >> /.bash_profile

戻し方

.bash_profile から該当のコードを削除

対策

パソコンから離れる時はロックしましょう。

最後に

他にも、「パソコンを閉じた時にGetWildを大音量で流す」というのを仕込んだりするのも面白いですね。

毎回違ういたずらを仕掛けると効果的だと思います。

ただ、他人のパソコンを操作すること自体いいことではないですし、元に戻せなくなるとそれこそ仕事に支障をきたしてしまうので、実践される方は十分に注意してください。

CTOに話したら「alias emacs=viが鉄板やな」って言っていて、いたずらどころか宗教戦争になりかねないので、これもやめましょう。

それでは、以上のことは完全に自己責任でよろしくお願いします。


株式会社フォトシンスでは、セキュリティ意識の高いエンジニアを募集しています!

www.wantedly.com

Slack Emojiを支える技術

この記事はAkerun Advent Calendar の2日目の記事です。 今回は弊社のSlack Emojiを量産する技術についてです。

Emojiは全従業員共通の概念

f:id:photosynth-inc:20171202214637g:plain

最初にEmoji(当時はEmoticonと呼んでいた)がSlackに追加されたときには、弊社のエンジニアやデザイナーがちょっと盛り上がった程度で終わっていました。

ですが、社員も増えて来てタイミングで大量にEmojiを追加してみたら、なんと皆さんいい感じに食いついてきて、今では一部の社員だけでなく、アルバイト含めた全従業員がカジュアルにEmojiを使っています。

またコミュニケーションが円滑に進んだり、業務の完了やステータスを伝えるために使われていたりと、多種多様案使い方のために、各自でカスタムEmojiを追加したりもするようになってきたので、その辺のEmoji追加を支える技術について書きたいと思います。

代表的な二つのChrome Extensionを導入する

これがあるとないとでは、今後のEmoji生活が変わってきます。 絶対に入れましょう。

複数のEmoji画像を一度にアップロードする

chrome.google.com

まず、ネットにある沢山のEmoji画像を大量にぶち込みたいので、このChromeExtensionを導入します。 これを導入することで、SlackのEmojiの管理画面に、以下の用にEmojiをドラッグアンドドロップできる、Viewが追加されます。

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

アップロードするEmojiは世界中の有志の方が親切にgithubに固めて上げてくれているので、それを使いましょう。ぐぐるとたくさんあります。 弊社ではポケモンが人気ですね。

GitHub - Templarian/slack-emoji-pokemon: Slack Pokemon Emojis

Web上の画像を右クリックからアップロードする

chrome.google.com

こちらもめちゃくちゃ便利です。

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

何か突発的にEmojiを追加しないといけないタイミングが皆さんあると思うのですが、これでググってすぐに話題に対応できるタイミングでEmojiを追加することができます。慣れれば最速5秒程度で追加できます。

また、なぜかこのサイトと親和性があり、クリックすると本来画像がダウンロードされるサイトなのですが、すぐにアップロードしてくれます(名前も勝手に付けてくれます)。

なので、まずは、このサイトでいいやつを追加してもいいかもですね。

Slackmojis - The Best Custom Slack Emojis

コマンドラインからSlackEmojiをアップロード

ネット上にある様々なEmojiをこのツールを使ってCLIから自動アップロードできます。

github.com

見ずにアップロードが不安なので、確認はした方がいいと思いますが。

便利。

絵文字系のEmojiを量産する

emoji.pine.moe

よく使います。先程のExtensionを入れていれば作成後に右クリックして上げるだけでいいので便利です。

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

社員の顔写真を自動でEmojiにする

これは自作です。MSのVison APIを使ってカジュアルにつくって見ました。 弊社では、全員の顔が見えるように、Slackのプロフィール画像を顔社員にしているので、今回はそれを自動でダウンロードして、加工しています。

Gem

# frozen_string_literal: true
source "https://rubygems.org"

gem "microsoft_computer_vision"
gem "mini_magick"
gem 'rest-client'

MSのVison APIのgemあるの便利。また、画像加工は今回はmini_magickを使って見ました。

SourceCode

手順としては以下です。

  • SlackAPIを叩いて、プロフ画像のURL一覧を取得する
  • そのURLをそのままVision APIに渡して顔座標を得る
  • 画像URLと顔座標からMiniMagick自体のメソットで画像ダウンロード+画像加工を行う
#!/usr/bin/env ruby
# coding : utf-8

require 'microsoft_computer_vision'
require 'mini_magick'
require 'json'
require 'rest-client'

class SlackProfileImage2Emoji
  class << self
    def run
      members = slack_members
      members.each do |member|
        sleep 5
        puts member
        rect, err = detect_face member[:profile_image]
        unless err
          image = crop_face rect: rect, url: member[:profile_image]
          image.write "orgs/ps_#{member[:name]}.#{member[:profile_image].split('.').last}"

          # Slackにアップロードできるサイズに縮小する
          image.resize "128x128"
          image.write "icons/ps_#{member[:name]}.#{member[:profile_image].split('.').last}"
        end
      end
    end

    # 画像URLに対応する顔座標を返却する
    def detect_face image_url
      client = MicrosoftComputerVision::Client.new('<VISION_API_TOKEN>')
      options = {
        visual_features: 'Faces',
        details: 'Celebrities'
      }
      res = client.analyze(image_url, options)
      json = JSON.parse res.body
      puts json

      return {
        width:  json["faces"].first["faceRectangle"]["width"],
        height: json["faces"].first["faceRectangle"]["height"],
        left:   json["faces"].first["faceRectangle"]["left"],
        top:    json["faces"].first["faceRectangle"]["top"],
      }, nil
    rescue
      puts "------- Error: Can not get face info"
      return nil, true
    end

    # Slackの従業員のプロフィール画像のURL一覧を取得する
    def slack_members
      res = RestClient.get 'https://slack.com/api/users.list', {
        params: {
          token: "<YOUR_SLACK_TOKEN>",
        }
      }

      (JSON.parse res.body)["members"].select{|member|
        # 適当にフィルタ
        member["deleted"] == false &&
          member["is_restricted"] == true &&
          member["is_bot"] == false &&
          member["name"] != "slackbot"
      }.map{|member|
        # 名前と画像URLのハッシュにして返却
        {name: member["real_name"].downcase.gsub(/ /, '_'), profile_image: member["profile"]["image_original"]}
      }
    end

    # 顔座標とURLを受け取って加工する
    def crop_face rect:, url:
      image = MiniMagick::Image.open(url)
      image.crop "#{rect[:width]}x#{rect[:height]}+#{rect[:left]}+#{rect[:top]}"
      image
    end

  end
end

if __FILE__ == $0
  # 引数ありなら、単一のURLの画像についてだけ顔認識APIを叩く
  if ARGV[0]
    puts (SlackProfileImage2Emoji.detect_face ARGV[0])
  else
    SlackProfileImage2Emoji.run
  end
end

完成品

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

※わざと小さくしてます。

定期的に実行するだけでいいので、便利。

まとめ

皆様も素敵なEmojiライフを❗


株式会社フォトシンスでは、急成長するIoT製品「Akerun」を進化させるエンジニアを募集しております!

www.wantedly.com

IoT開発を加速する!!IoTツール開発、その後

はじめまして!

この記事はAkerun Advent Calendar の記念すべき1日目の記事です。 今回はAkerunをつくっている会社フォトシンスのCTOのkazuphが担当します!(`・ω・´)ゞ

IoTツール開発

弊社のメインプロダクトはAkerunというスマートロックですが、その開発や工場での生産の過程で様々なIoTツールを開発しています。

つまり、ネットワークに接続するツールを開発することで、開発生産性を高めたり工場での作業フローを改善して、品質と原価削減を行っています。

去年のbuildersconやAdvent Calendarではその一部を発表させていただきました。

kazuph.hateblo.jp

akerun.hateblo.jp

akerun.hateblo.jp

akerun.hateblo.jp

akerun.hateblo.jp

あれから1年

ちょこちょこ自分がつくったツールを晒してみます。詳しくは、別記事で掘るかも。

[mqtt-eater]MQTTの流れを保存し画面表示+Slackに通知する君

うちの開発だと登場人物が多くてテストをするときなどは、信号の発信役、中継役、受け取り役などパートごとに開発者が違うので、わざわざ動作を確認してもらうのが億劫なときがあります。そういうときに、Slackとかで見れると便利だろうということでつくりました。

今回は情報の発信元のログ(MQTTのPublish)をSlackに流し、中継役(ゲートウェイ)、受け取り役(スマートロック本体)の開発者がそれを見ます。

使用gemは以下(あ、rubyが前提になってる)。

ソースコードのイメージは以下のような感じです。

class Mqtt
  def self.run
    t = Thread.new do
      MQTT::Client.connect(
        remote_host: "<mqtt.broker.domain>",
        remote_port: <XXXX>,
        username:    "<user_name>",
        password:    "<password>",
        ssl:         true,
      ) do |client|
        client.subscribe('<your_topic>/#')
        client.get do |topic,message|
          Slack.post "#dev-mqtt-log , "[MQTT] pub #{topic}: #{message}"
          ary = topic.split('/')
          DB.create_or_update ary.last, ary.first, message
        end
      end
    end
  end
end

開発用にPOSTも付けるといいかなと思いましたが、社内向け管理画面に"Chamber"という名前のカッコいいツールがあるので、そっちと統合してもいいかなと思い一旦最低限までやってペンディング中。

Sinatraの部分は一旦僕がニヤニヤするためだけに、今後も開発を続けてもいいかなという感じです。

[auto-wifi-setting-changer]APの管理画面から自動でWi-Fi設定を更新する君

Wi-Fiの周波数・チャネルが切り替わるものが増えてきているので、それを連続で試験するためにつくりました。 APの管理画面はWebベースなことが多いので、スクレイピングすればほぼなんでもできます。

使用gem

  • capybara
  • selenium-webdriver
  • chromedriver-helper
  • pry

スクレイピングしたいと思う度にcapybaraは未だ顕在だし、調べれば調べるほどに簡単な書き方が出てきて本当に素晴らしいですね(無駄なsleepを徹底的に解除できる)。

最近はChromeDriverも安定していて、開発中はpryで止めてChromeのインスペクターでDOMを確認しながら、pryのconsoleにclick_onとか打ち込みながらどんどんつくれるので、一瞬でつくれていいです。

ソースコードのイメージは以下みたいな感じ。

#!/usr/bin/env ruby
# coding : utf-8

require 'capybara'
require 'capybara/dsl'
require 'selenium-webdriver'
require 'pry'
require 'logger'

class WifiSetting
  include Capybara::DSL

  def initialize()
    Capybara.run_server = false
    Capybara.default_driver = Capybara.javascript_driver = :selenium_chrome # or :rack_test, :selenium, :selenium_chrome, :selenium_chrome_headless, :mobile, :webkit
    Capybara.app_host = "https://ap.ip"
    @log = Logger.new(STDOUT)
    @log.info "initialize"
  end

  def login
    visit("/")
    fill_in('username', with: ENV['ID'])
    fill_in('password', with: ENV['PASS'])
    click_button('Login')
    @log.info "login"
  end

  def change_channel(number)
    select "Channel #{number}", from: 'channel'
    click_button('Update')
    @log.info "Update Channel #{number}"
  end

  def change_state(enable: nil)
    click_on "Wireless9"
    if enable
      choose "Enabled",  name: "wireless"
    else
      choose "Disabled",  name: "wireless"
    end
    click_button('Update')
    @log.info "Update Enable #{enable}"
  end

end

if __FILE__ == $0
  o = WifiSetting.new()
  o.login
  loop do
    o.change_state enable: true
    o.change_channel o.random_channel
    o.change_state enable: false
  end
end

これの派生で、サクッとAkerunのユーザー向け管理画面からユーザー追加や鍵発行をして、ハード側にその情報がちゃんと伝搬するかなどのテストとかも行っております。

[akerun-api]Rubyを使ってバイナリ書き換えーる君

これは、ツールというか、Akerun API本体についに組み込みました。

もともと、工場で動くツール(shellでperlワンライナーでバイナリ置換を行っていたのですが、Rails(Ruby)側で置換して単にダウンロードすればいいんじゃね?IoTっぽいしということで、クラウド側に置換処理を持っていきました。置換のくだりは、冒頭に紹介したリンク先の記事をご覧ください。

置換部分のソースコードはこんだけ。BINARY形式で読み込むと普通にsubで置換できる。知らなかった。。。shellでperlワンライナーしていたのはなんだったのか。

    # バイナリの文字列置換
    def binary_str_replace(path, org, new)
      binary = File.read(path).force_encoding('BINARY')
      binary.sub!(org, new)
      File.binwrite path, binary
    end

    # バイナリの数字置換
    def binary_num_replace(path, org, new)
      binary = File.read(path).force_encoding('BINARY')
      binary.sub!([org].pack("H*"), [new].pack("H*"))
      File.binwrite path, binary
    end

まとめ

僕がこの1年でつくったツールの一部を紹介してみました。 気分が乗ったら深掘りや別のツールの紹介をしたいと思います。

明日は非エンジニアも含め多くの従業員が使いこなしているSlack絵文字を量産する技術について書きたいと思います! お楽しみに!


株式会社フォトシンスでは、急成長するIoT製品「Akerun」を進化させるエンジニアを募集しております!

www.wantedly.com