Electronで組み込み端末向けアプリケーションを開発し3年間運用してみて

10 min read読了の目安(約9500字 5

組み込み端末のアプリケーションに Electron を採用し開発・運用を行っています。
継続的な機能追加やトラブルサポートを行いながら 3 年が経過したので、Electron を採用した経緯や結果を経験を交えながら共有したいと思います。
組み込み案件自体が初めてでしたのでその話も入っています。 約 700 台ほど導入されており、なかなか大きな案件での採用と思いますので参考になれば幸いです。

※Electron の基本的な説明は割愛します。

システムについて

一般的に「受付精算機」などと呼ばれ、店頭に何台か並べて設置して自動で受付・精算を行うシステムです。
ホテルの受付端末のイメージです。ハード的にはセルフレジにも近いです。

筐体自体は他社が用意し、弊社はその上に載せる GUI アプリケーションのみを担当しました。

機能

主な機能として

  • 来店受付・整理券発行
  • 予約チェックイン
  • チェックアウト・精算
  • 物販購入

を行います。

各店舗の中には受付情報を管理するサーバーがあり、HTTP 経由の API で通信を行います。
WebSocket を使って店舗の中にある他のシステム(POS など)とメッセージを交換したりもします。

搭載ハードウェア

次のようなハードウェアを搭載しています。

  • QR コードリーダー
  • 釣銭機
  • クレジット・電子マネー決済端末
  • レシートプリンター
  • センサー(人感・筐体扉など)
  • UPS
  • タッチディスプレイ(静電容量方式)

ディスプレイが静電容量式の点に驚きました。
ATM とかで見るのってだいたい感圧式ですが、静電容量式だとタブレット PC の感覚で触れます。ある程度スワイプやスクロールをさせることができるので UI の幅が広がりました。

Electron の採用経緯

受注の経緯は省略しますが、今の会社は普段 Web サービスやソーシャルゲームの受託開発をしており、組み込みアプリケーションの開発経験はほぼありませんでした[1]

開発チームにアサインされたメンバーも私と Web デザイナーの 2 人で、[2]しかも当時私は新卒入社 1 年目でした。
まだ新しいことばかりの中、デスクトップアプリケーションの開発手段の中で Electron に関してはある程度の理解があったため[3]、採用する方向で進めました。

相談役の先輩や上司からは Visual C++ や Unity を候補として提案されていましたが、ハードウェアとの連携さえ確立できれば今回の案件には Electron が向いていると判断したので Electron で進めるように相談しました。

理由として

  • メインプログラマーを担う自分が慣れている言語、フレームワークで進めることができる
  • Web のように HTML/CSS で画面を実装できるので Web デザイナーにコーディングを担当してもらえる[4]
  • Electron・JavaScript は標準 API が豊富に用意されているので開発効率が良い
  • JavaScript であれば社内の他の Web プログラマーも扱える

などを挙げたと思います。

ノウハウがや知識が全くなく先行きが不安なスタートでした。
ハードウェア連携については後述します。

Electron を採用するにあたっての課題・検証

Electron で開発できることを証明するためにいくつか課題をクリアする必要がありました。
その頃には筐体の試供品がパーツ単位でポツポツと送られてきていましたので順に検証していきました。

遅い CPU でも快適に動作するか

筐体 PC のスペックは次の通りでした。

OS: Windows 10 Enterprise
CPU: Intel Celeron (2GHz 4core)
メモリ: 4GB
HDD: 512GB

メモリは 4GB ありましたのでまず安心できました。
CPU は事前にある程度の性能が必要と伝えており、 Celeron を採用して貰えました。(普通だと組み込み向け Atom かと思います)
組み込み端末にしてはなかなかの性能のものと認識していますが、これでもスペックが抑えられたラップトップで Chrome ブラウザーを動かすようなものをイメージしており不安でした。

動かしてみると意外と軽快に動作しました。グラフィックについてもハードウェアアクセラレーションが効いており、CSS アニメーションで画面全体を動かしても滑らかでした。

業務用アプリとして振る舞えるか

アプリの挙動や OS の設定を確認します。

全画面モードにする

キオスクモードといいます。
以下のオプションを付けておけば全画面で常に最前面に表示されると思います。

const window = new BrowserWindow({
  fullscreen: true,
  frame: false,
  kiosk: true,
  alwaysOnTop: true,
  autoHideMenuBar: true,

  // ...
});

OS や Electron のバージョンによって変わるので BrowserWindow のオプション を色々試してみると良いです。

自動起動させる

Windows であれば %AppData%\Microsoft\Windows\Start Menu\Programs\Startup の中にショートカットを置いておけば OS のログイン時に起動されるようになります。

PC を起動した時にログイン画面をスキップしてデスクトップに入る必要もあります。
これは自動ログインなどでググれば出てくると思います。

OS のタッチジェスチャーを無効化する

Windows 10 にはいくつかタッチジェスチャーがあります。(画面左右からスワイプするとタスクスイッチャーが起動するなど)
これらを無効化しておかないとお客様に OS へアクセスされてしまいます。

OS の通知機能を無効にする

プリンターの用紙切れの際などに Windows だと画面右下に通知が出現します。
無効化しておかないとお客様に OS にアクセスされてしまいます。

この辺りの設定に関して、筐体を調達した企業がノウハウを持っているはずですがあまり事前共有して貰えませんでした。
OS の挙動に詳しくないと見逃してしまうので特に注意する必要があると思います。

ハードウェア連携

釣銭機や QR コードリーダーなど各デバイスの SDK はほとんどが DLL 形式で提供されています。

渡された SDK の中の一つが ActiveX の形式で、「こんなん無理や(意訳)」と言ったら DLL でラップしたものを送ってもらえました。[5]

Electron(Node.js) から DLL を使う方法はいくつかあります。

  • ① FFI ライブラリを使用して DLL の関数を JavaScript から直接実行する
  • ② node-addon-api で DLL をラップしたバインディングを作成する
  • ③ DLL をラップした exe ファイルを用意し、Child Process.execFile で呼び出す

① FFI ライブラリを使用して DLL の関数を JavaScript から直接実行する

本案件ではこの方法を採用しました。
FFI を採用するメリットはラッパーコードを書かなくて良いことです。
node-ffi で検証したところ問題なく各ハードウェアを操作することができました。

ただ、node-ffi は開発が終了してしまいました。もっと慎重に選定すべきでした。
今 Node.js からネイティブライブラリを FFI で呼び出したい場合は N-API 対応版のnode-ffi-napi を使うことになるかと思います。

② node-addon-api で DLL をラップしたバインディングを作成する

ラッパーを用意するのが面倒ですが、標準の node-addon-api を用いるのが無難と思います。

③ DLL をラップした exe ファイルを用意し、ChildProcess で呼び出す

DLL がシンプルなインターフェースであれば DLL の機能をラップした exe ファイルを作って ChildProcess.execFile から呼び出すのもアリかと思います。
① や ② は環境構築やドキュメントの読解に時間を要しますが、こちらはシンプルな実装で済みます。
ただし DLL に関数が多かったりインターフェースが複雑になると対応し切れなくなります。

音声再生

ページ遷移した時にナビゲーションの音声を再生させる必要がありますが、Electron のレンダラープロセス上で JS を使って音声を再生してみると、処理性能の問題で再生されるまでの遅延が大きくて使い物になりませんでした。

Linux や Mac だと aplayafplay など引数に音源ファイルを渡すと再生してくれるシンプルなコマンドがあるのでそれを使えば解決できると考えましたが、Windows には見当たりませんでした。
同等の機能を持った exe ファイルを作成し、ChildProcess.execFile から呼び出して使用することで遅延の問題を解決できました。
先程説明した ③ に当たります。

32bit Node.js を使用する

提供される DLL には 32bit 版しか用意されていないものがあり、アプリ全体を 32bit にする必要がありました。
64bit 版 Windows で 32bit の Electron アプリを動かす格好です。
今時 32bit って…と思いつつも、現実ってこんなもんなんかなと思い対応しました。

アプリを 32bit にしたことで特に問題はありませんでした。

Electron の安定性

一部 24 時間営業の店舗もあるので連続稼働に耐える必要があります。
元がシェアトップのブラウザーの Chromium ですし、Electron を採用している Slack や Atom などのアプリケーションがクラッシュしたりするのを経験したことがなかったので Electron 自体の安定性についてはあまり心配していませんでした。

検証アプリを何日か稼働させ続けて問題なく、メモリリークなどの現象も見られなかったので問題なしとしました。

ただし、FFI で呼び出した DLL が応答なしになったりクラッシュした場合は、呼び出し元のメインプロセスも巻き込まれてしまいます。

管理メニューへの入り方

お客様が触れる端末なので受付画面上に管理メニューへの入り口ボタンを置くことはできません。
筐体に QR コードリーダーを搭載しているので、特別な QR コードをかざすと管理メニューへ遷移する仕様としました。
QR コードは固定値だと流出・悪用の恐れがありますので、スタッフのタブレット端末で一定期間有効なものを生成するようにしています。
また、特定の権限を持った社員の QR コードでのみ筐体内のお金を取り出せるようにしてあります。

クロスプラットフォームの利点

Electron を採用するメリットとしてクロスプラットフォームである点があります。
今回の案件ではターゲット OS が Windows のみですが、開発時にもその恩恵を受けることができました。

開発 OS の自由化

私は普段の開発機が Mac なので、ハードウェアなど DLL に依存する箇所以外は Mac で開発できました。

CI/CD

Linux でも動作するので CI/CD が容易に行える点がよかったです。
ビルドや Linter などは Linux コンテナで実行することでいつものレシピを使えました。

パッケージ作成はネイティブモジュールのビルドが必要なので Wine では不十分で、 Windows で実行する必要がありました。
Windows でネイティブビルドが必要な npm モジュールを使う場合、Visual Studio BuildTools などをインストールする必要がありますが、必要なパッケージは Chocolatey で公開されているので CLI でセットアップを完結することができました。

以下の手順で Visual Studio 2017 の開発環境を揃えることができるかと思います。

# https://community.chocolatey.org/packages/python
choco install -y python --version=2.7.11
# https://community.chocolatey.org/packages/microsoft-build-tools
choco install -y visualstudio2017buildtools
npm config --global set msvs_version 2017

キッティング用ハードディスク

筐体量産時のセットアップ(キッティング)に使用するクローン用のハードディスクを作成する必要があります。
筐体業者から筐体 PC が送られてくるので、ドライバーやアプリケーションをインストールして送り返します。

結構早い段階で対応する必要があります。
ソフトウェアがまだ完成していなくても最低限バージョンアップ機能を備えたものをインストールしておく必要があります。

いざ店舗への設置が始まった時にドライバーをインストールし忘れていたりバージョンアップ機能が動作しなかったりを想像すると恐ろしいです…
その際は店舗のスタッフに手順書を共有して 1 台ずつ対応してもらうことになるのかと思います。
対応漏れが出てきたりすると思うのでそういった事態はなるべく避けたいです。

電子マネーの検定対応

どこまで書いて良いのか分からないので控えめにしますが、電子マネーには各ブランドの検定があります。クレジットカードに比較してとても厳しかったです。
実装中のアプリを提出し、ブランドロゴがちゃんと表示されているかやエラー時に意図した挙動になるかなど詳細までチェックを受けました。

運用にあたって直面した問題

運用フェーズに入ってから直面した問題やその対処など。

遠隔地のサポート

このプロジェクトで一番不安があったところです。
日本全国で稼働する筐体で不具合が起こった場合にどのようにサポートするのかを検討しました。
結果的にインフラが整備され、データセンターにある踏み台サーバーからプライベートネットワーク経由で筐体 PC まで繋がるようになりました。
後は Microsoft Remote Desktop (以後 RDP) のポートを開通すればリモートでアクセスできます。

具体的には

ssh {踏み台サーバー} -L 3390:{筐体PCのIP}:3389

で踏み台サーバーへ SSH を繋いだ状態で localhost:3390 へリモートデスクトップを行うと筐体 PC へ接続できます。

RDP 接続をすると筐体 PC 側は Windows のロック画面になってしまいます。お客様やスタッフが操作している時にリモートすると驚かせてしまうので、WebSocket 経由で送信するメンテナンスコマンドを用意しています。
メンテナンスコマンドで現在操作中かの確認を行い、誰も触っていなければ接続するようにしています。
WebSocket も同じ方法で踏み台サーバーのポートフォワーディングを経由しています。

アプリのバージョンアップ

Electron のバージョンアップには標準 API の autoUpdater を使用しています。

店舗内のサーバーに更新ファイルをデプロイし、スタッフに管理メニューから再起動してもらいます。

初めは VSCode のようにバックグラウンドで更新ファイルをダウンロードして次回起動時に新バージョンで起動するようにしていたのですが、店舗から「バージョンアップのために再起動したら起動しなくなった」という報告がよく寄せられました。

再起動してから更新ファイルのダウンロード・インストールを行うように変更したところ報告が止みました。

復旧用バッチファイル

先述のように稀に起動しなくなったという報告が寄せられることがありました。
初めはこちらから店舗の筐体 PC へリモートデスクトップを行い復旧対応していましたが、対応がパターン化できたところでバッチスクリプトを作成しました。

Electron(Windows)のアプリが起動不能になった際は次の対応でほぼ復旧できます。

  • %AppData%\{アプリ名} のディレクトリを削除する
  • 場合によっては %LocalAppData%\{アプリ名} も削除する
    (LocalStorage やブラウザーキャッシュも消える)
  • アプリケーションをインストールし直す
  • 再起動する

上記を行うバッチスクリプトをデスクトップに配置し、調子が悪い場合はスタッフに実行してもらうようにしました。

ハードディスク障害

運用を続けるうちに、バッチファイルで直らなかったり、奇妙な症状が報告されるようになりました。

  • アプリのアイコンをダブルクリックしても何も起こらない
  • 動作が極端に重い・フリーズする
  • 黒い画面で止まる(BIOS のことと思う)

報告があった筐体 PC にリモートデスクトップを行うと驚くほど重くなっていました。
何分かかけてタスクマネージャーまで辿り着くと、ディスクの読み書きが 100% に張り付いていました。

イベントビューアーを起動して「Windows ログ → システム」を確認するとエラーログがびっしり並んでいます。
中を確認するとハードディスクのブロック不良という内容でした。
ハードウェア障害に関しては筐体業者の対応となるので引き継ぎを行いました。

リモート接続を行えるのが弊社だけなのでこういった原因不明の問い合わせはまずこちらに来てしまいます。今後そういった問い合わせを回避するために、調子が悪い場合にはイベントログを確認してもらうように手順書を作成して先方に共有しています。

他にも何かトラブルがあった場合はイベントログを確認すると解決の手がかりが見つかることがあります。

QR コード決済対応

QR コード決済ブームに乗っかることになりました。
既に QR コードリーダーを搭載していたため、追加の機器は不要で対応することができました。

その他よくあるトラブル

ネットワーク障害

ネットワーク障害のトラブルがそこそこの頻度で報告されますが、大体は LAN ケーブルが抜けたりハブがループしています。

ハードウェアが動作しない

プリンターなど一部のハードウェアが動作しないといった報告があります。
これもネットワーク障害と似ていますが、USB の接続不良のケースが多いです。

挿し直しても直らない場合もあります。こちらとしてはケーブルの断線かハードウェアの故障だろうと思いますが、筐体の提供企業から「ソフトウェアの問題では」と回されることがあります。
その際は筐体 PC に RDP で接続して、デバイスマネージャー上でデバイスが接続されていない証拠を撮影して送り返す必要があります。

この作業も稀に行う必要が出てくるので、デバイスが動作しない場合はデバイスマネージャーで認識されているか確認してもらうように手順書を作成しました。

最後に

ここまで長々とお読みいただきありがとうございました 🙇

Electron 採用の是非ですが、私は本案件においては正解だったと考えています。
DLL などのネイティブライブラリを呼び出そうとした時に多少周りくどいことをする必要はありますが、HTML/CSS でリッチな UI・UX を実装できるなどの大きなメリットもあります。

もし私のような Web 技術に浸かっているエンジニアがこういった案件に直面した場合、Electron はまず第一の選択肢として出てくると思います。
そういった方の参考になれば幸いです。

脚注
  1. テレビのヘルプ画面を HTML で作った経験があったくらいと聞いている ↩︎

  2. 途中からプログラマーがもう一人増えました。 ↩︎

  3. 学生時代の最後の 1 年間は JavaScript・Node.js・Electron 辺りにハマっていた。 ↩︎

  4. C++や Unity だと画面実装も自分が担当する必要があり、機能数・画面数から考えて溢れる予想がついていた。 ↩︎

  5. ActiveX に関しては古の技術というくらいで今もよく分かっていません。当時も C++ などから呼び出す方法を調べても全然見つからなかった記憶があります。 ↩︎