Blog

ブログ

Eccube4.3にXdebugいれてみた(PhpStorm/WLS2/Docker)

PHPでデバックするときに一番手っ取り早いのはver_dump関数ですが、見たい内容を毎回記述する必要があったり、画面表示しない処理(CSVダウンロードなど)だとデバックしにくい面もあります。

 

そこでXdebugです。
公式:https://xdebug.org/

IDEでブレークポイントをつけた箇所で処理が止まってくれて、変数の中身などを確認することが出来ます。 例えば次の画像のように$idの内容が確認できます。

 xdebugの仕組みの概要は以下です。

  1. IDEは特定のportでWebサーバーにインストールされたXdebugからの通信を待機する
  2. Xdebugは特定のportにコールバックする
  3. デバックセッションを開始して、DBGPプロトコル(デバッグ用の特別なプロトコル)を介してデータを転送

IDEによってWebサーバーが「呼び出される」のかと思っていましたが、実際は逆で Xdebug(Webサーバー)がIDE に接続しに行くようです。

この辺りは以下の動画が分かりやすかったので、気になる方はご視聴をお勧めします。
#03 – PHP Advanced Debugging With Xdebug- How Xdebug Works

とても便利なのですが、設定が少しややこしいので、今回は現時点で最新のEccube4.3をローカルに構築して、Xdebugの導入してみました!

手順1:ローカルにEccube4.3環境を立ち上げる

まずはEccubeのコードを用意します。
EccubeはOSS(オープンソースソフトウェア)で、以下のGitHubから取得できます。


https://github.com/EC-CUBE/ec-cube

コードを適当なディレクトリの配置後、初回インストールを実施するのですが、今回はXdebugの起動を確認すれば良いだけなので、初回インストール状態を残すべく、以下の変更を加えます

docker-compose.yml
volumes:
  html-app:  # ← ソースコードが含まれるボリューム(初回インストール後も残す)

services:
  ec-cube:
    volumes:
      - html-app:/var/www/html

この状態で初回インストールを行います。
参考:Docker Composeを使用してインストールする


#初回インストールコマンド
# コンテナの起動 (初回のみビルド処理あり)
docker-compose up -d

# 初回はインストールスクリプトを実行
docker-compose exec -u www-data ec-cube bin/console eccube:install -n

ローカルにアクセスできたらOK。
http://localhost:8080/

手順2:Xdeugをインストールする

ルート直下にあるDockerfile、docker-compose.yamlを変更します。

1.Dockerfileの修正
PECLでインストールします。以下を追記します。

RUN pecl install xdebug \
    && docker-php-ext-enable xdebug ## 追記

以下をコメントアウトします。(2度目立ち上げ時に失敗してしまうのでコメントアウト)
#COPY dockerbuild/docker-php-entrypoint /usr/local/bin/

2.php.iniにxdebugの設定を追加
ビルド時に「dockerbuild/php.ini」のファイルをphp.iniに読み込んでいるので、ここに以下の設定を追記します。

[xdebug]
xdebug.mode = debug
xdebug.start_with_request = yes
xdebug.client_port = 9003
xdebug.client_host = host.docker.internal
  • mode=debug:ステップデバッグが出来るようになります。 それ以外の値はこちら
  • start_with_request:いつデバッグを開始するかを指定する。yesを選択 それ以外の値はこちら
  • client_port:PhpStormのデバッグポートで指定する値を設定します。※後述
  • client_host:Xdebug接続するIP アドレスまたはホスト名

個人的にclient_hostが毎回ハマるポイントです。
Macでは上記の「host.docker.internal」で問題ないらしいのですが、 私の環境(WLS2)だとうまくいきませんでした。
※最近のバージョンではWLS2でも「host.docker.internal」で出来る、という記事もみたのですが、残念ながら私の環境では出来ませんでした。
色々試行錯誤した結果以下の値を設定することで成功しました。
・コマンドプロンプトや PowerShell でipconfigを実行した後の「イーサネット アダプター vEthernet (WSL):IPv4 アドレス 」の値
※ただしこの値は固定ではない(再起動などで変更される可能性がある)ので、他の方法が出来る場合は他の方法を採用した方が良いと思います。


> ipconfig
イーサネット アダプター vEthernet (WSL):
   IPv4 アドレス . . . . . . . . . . : 172.**.***.***

手順3:IDE(今回はPhpStorm)にXdebugの設定を行う

仕組みの概要で触れた通り、IDEはXdebugからの通信を待機して受け取る必要があるので、設定が必要です。

1.設定->PHP->サーバ でサーバーを追加する
プラスマークから追加します。
ここでは主にパスマッピングが重要です。
パスマッピングの設定によって「Xdebug 経由でやってくる“/var/www/html/はローカルではこのパスですよ」と伝えることが出来ます。

名前 : 何でも良い。
ホスト : localhostで設定しているがそれ以外の値でも成功する。
ポート : 80で設定しているがそれ以外の値でも成功する。
デバッガー : Xdebug を選択
パスマッピングを使用する : オンにして対応するパスを設定する

設定値を入れたら適用する

2.設定->PHP->デバッグ でXdebugの設定を確認する

デバッグポート: 9000、9003の2つがデフォルトで指定されていますが、これは
Xdebugのポートのデフォルトが、Xdebug2では9000だったのが、Xdebug3で9003に変更されたためのようです。
今回はPHP8でXdebug3になるので、9003のみにして、
あとは「外部接続を受け入れる」にチェックが入っていることを確認して、適用保存します。
※9000,9003の2つが設定されたままでも問題ありません。

3.実行 / デバッグ構成 を設定する
PhpStormヘッダ部に「現在のファイル」となっている部分をクリックすると
「実行構成の編集」というメニューが表示されるのでそれをクリック。
プラスマークから「PHPリモートデバッグ」を追加します。


名前に何でも良いので値を設定し、適用します。
※「IDEキーでデバッグ接続をフィルターする」にチェックを入れる記事が多いですが、今回はチェック無しで出来たのでOFFのまま進めます。

手順4:ビルドを実施して再度コンテナを立ち上げ

手順1で立ち上げた環境ですが、一度

  • docker-compose down で削除
  • docker build -t ec-cube –no-cache –pull –build-arg TAG=8.1-apache . でビルドして
  • docker tag ec-cube ghcr.io/ec-cube/ec-cube-php:8.1-apache でタグ付けし、
  • docker-compose up -d で再度立ち上げます。

ec-cube_1コンテナに入り、 php -v で表示される情報の中に「Xdebug」の文字があればインストールされています。

手順5:デバッグを実行

1.PhpStormのヘッダ部にある電話のようなマークをクリックしてリッスンを開始します。
2.PhpStormのヘッダ部にある虫のようなマークをクリックしてデバッグを開始します。
3.ステップさせたいところにブレークポイントを設定して、ブラウザからリクエストを実行。

ステップデバッグが出来るようになると、初回のダイアログが表示されます。

承認し、最初の画像のようにブレークポイントで停止してくれるようになれば設定完了です!

 

いかがだったでしょうか。
開発を助けてくれるXdebugですが、EccubeだけではなくてPHPであれば利用できますので、
PHPで開発されている方はローカル環境に入れてみてはいかがでしょうか。

※Xdebugはあくまで開発専用の機能なので、本番環境には絶対に入れてはいけません。

【shopify】初回のみ表示するポップアップ

個人情報同意などに使える、サイト内のどのページからでも初回だけ表示されるポップアップを作りたいと思います。
全ページなのでtheme.liquidに直接書く方法もあると思いますが、簡単に管理できるように新規セクションで作成してみます。

セクションを新規作成

カスタマイズボタンの横にある3点ボタンから、「コードを編集」を開きます。
sections>「新しいセクションを追加する」で空のセクションを作成します。

liquid編集

まず、jquaryを読み込ませます。CDNまたはassetsに必要なファイルを入れて読み込んでください。
次にCSSを追加します。このセクション内だけに適用されればいいので直接書いちゃいます。

{%- style -%}
  .popup {
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.6);
    position: fixed;
    z-index: 1001;
    top: 0;
    left: 0;
  }
  .popup-content {
    width: 75vw;
    max-width: 750px;
    height: 40vh;
    background: white;
    border-radius: 4px;
    padding: 3rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    margin: auto;
  }
  .close-btn {
    background-color: black;
    border-radius: 2px;
    color: white;
    text-align: center;
    cursor: pointer;
    font-size: 1.2rem;
    padding: 1rem 2rem;
    display: block;
    max-width: 10rem;
    margin: 1rem auto;
    width: 100%;
  }
  .close-btn:hover, 
  .close-btn:focus {
      text-decoration: none;
      cursor: pointer;
      opacity: 0.8;
  }
  @media screen and (max-width: 768px){
    .popup-content{
      width: 80vw;
    }
  }
{%- endstyle -%}

そしてポップアップ本体です。

<div id="popup" class="popup">
  <div class="popup-content">
    <div>{{ section.settings.popup_text }}</div>
    <span class="close-btn">はい</span>
  </div>
</div>

今回は、localStorageでユーザーが訪問済みなのか否か判断させます。さらに「はい」のボタンクリックによって、visitedtrueになるようにしました。

<script>
  const keyName = 'visited';
  const keyValue = true;
  if (!localStorage.getItem(keyName)) {
      $('#popup').css('display', 'block');
      $('.close-btn').click(function(){
          $('#popup').css('display', 'none');
          localStorage.setItem(keyName, keyValue);
      });
  } else {
      $('#popup').css('display', 'none');
  }
</script>

最後にスキーマです。表示されるテキストはカスタマイズ画面から編集できるようにリッチテキストにします。これで改行や太字、リンクもできるので便利です。

{% schema %}
{
  "name": "ポップアップ",
  "class": "pop-section",
  "settings": [
    {
      "type": "richtext",
      "id": "popup_text",
      "label": "テキスト"
    }
  ],
  "presets": [
    {
      "name": "ポップアップ"
    }
  ]
}
{% endschema %}

セクションを設置

あとはカスタマイズ画面でセクションを差し込めば完成です。
特定のページのみであればテンプレートごとに設置しますが、全ページに適用したいので共通のヘッダー内に入れました。

このようなセクションが1つできれば、Cookieにしたり、販促案内やクーポン用にカスタマイズの幅が広がりそうです。

参考:https://into-the-program.com/execution-firsttime-access/

使用テーマ:Dawn 15.1.0

【shopify】ヘッダーのハンバーガーメニューの配置を変更したい

テーマのデフォルトでは左側にハンバーガーメニューが配置されているけど、右側に置きたい。。
しかし、カスタマイズ画面ではロゴの配置くらいしか設定できない。。
ということで、今回はshopifyのDawnテーマ(15.1.0)でできるだけ簡単に実装したいと思います。

liquidで物理的に動かしてみる

そもそもヘッダーセクションではソースコードの兄弟要素が「ハンバーガーメニュー・ロゴ・アイコン」という順番で書かれているため、header.liquid内の該当箇所を入れ替えてみました。

ソースコード上は理想の順番になりました。
しかしブラウザを確認しても、あれ、変わりません。。

 

CSSをチェック

ヘッダーセクションの中身が大きく3つに分かれており、flexで並べられています。
base.cssで順番が指定されていました。navigationを一番右に移動させます。

CSSだけでハンバーガーメニューの位置を変更できました!
でも開閉が左のままで不自然なので調整が必要みたいです。

 

Base.css

.menu-drawerに以下を追加

transform: translateX(100%);
left: auto;
right: 0;

 

component-menu-drawer.css

.js details[open].menu-opening>.menu-drawerに以下を追加

transform: translateX(0%);

 

 

配置も挙動も理想の動きになりました。

Liquidファイルを編集しなくても、CSSで簡単にカスタマイズできることがわかりました。

 

参考:

・CSS: カスケーディングスタイルシート[justify-self]

https://developer.mozilla.org/ja/docs/Web/CSS/justify-self

・shopifyコミュニティ「Re: モバイル表示のハンバーガーメニュー位置を右に表示したい。(テーマ:Spotlight)」

https://community.shopify.com/c/%E6%8A%80%E8%A1%93%E7%9A%84%E3%81%AAq-a/%E3%83%A2%E3%83%90%E3%82%A4%E3%83%AB%E8%A1%A8%E7%A4%BA%E3%81%AE%E3%83%8F%E3%83%B3%E3%83%90%E3%83%BC%E3%82%AC%E3%83%BC%E3%83%A1%E3%83%8B%E3%83%A5%E3%83%BC%E4%BD%8D%E7%BD%AE%E3%82%92%E5%8F%B3%E3%81%AB%E8%A1%A8%E7%A4%BA%E3%81%97%E3%81%9F%E3%81%84-%E3%83%86%E3%83%BC%E3%83%9E-spotlight/m-p/2646855

LINQ の基本を理解しよう

LINQ とは

LINQ とは コレクション・XML・SQLなど様々なデータソースに対する検索・操作を行うもので、System.Linq を参照することにより提供される拡張メソッド群(標準クエリ演算子)ことを指します。
本稿では特に利用する機会の多いコレクション(IEnumeable)に対する LINQ to Object の解説を行います。

メソッド構文とクエリ構文

LINQ は拡張メソッドで提供されることから、当然コレクションのメソッドとして利用する(メソッド構文)ことが可能です。
また一部の標準クエリ演算子に対しては言語仕様としてキーワードが割り当てられており、それを用いることでSQLライクに記述する(クエリ構文)ことも可能です。

例)メソッド構文とクエリ構文
 ※本稿のサンプルコードではメソッドの引数としてラムダ式を使用します。

var source = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// いずれも結果で得られるコレクションの型はIEnumerable<int>となり、
// 列挙することで { 1, 2, 3, 4, 5 } の結果(要素)を得ることができます。

// メソッド構文
// 処理しない値を取り出す場合はSelectを書略可能
var methodResult = source.Where(x => x <= 5);

// クエリ構文
// クエリ構文ではselectを省略不可
var queryResult =
    from x in source
    where x <= 5
    select x;

またクエリ構文で記述したコードはコンパイルを通して標準クエリ演算子に変換されますので、最終的に得られる結果は同一になります。
前述のコードから得られる実行形式のファイルを逆コンパイルした結果は下記となります。

// Program
using System.Collections.Generic;
using System.Linq;

private static void <Main>$(string[] args)
{
	int[] source = new int[9] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	IEnumerable<int> methodResult = source.Where((int x) => x <= 5);
	IEnumerable<int> queryResult = source.Where((int x) => x <= 5);
}

なお全ての標準クエリ演算子をクエリ構文で記述することはできないため、それらの機能が必要な場合はメソッド構文で記述する必要があります。
※メソッド構文とクエリ構文を混ぜて使用することも可能ですが、式を分けるかどちらかに統一したほうが可読性の面からも無難です。

基本的な使い方

Where によるデータの抽出と Select によるデータの選択・処理

Where メソッドでは、コレクションから条件に合致する要素を抽出することが可能です。
Select メソッドでは、コレクションの全要素に対して処理を行った結果を取得することができます。
Where メソッドと Select メソッドを組み合わせてコレクションの条件に合致した要素に処理を行った結果を取得する、といった用途で利用することが多いようです。
ここでは、数値のコレクションから条件に合致する要素を抽出し二乗したコレクションを取得する例を示します。

var source = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

// メソッド構文
var methodResult = source.Where(x => x <= 5).Select(x => x * x);

// クエリ構文
var queryResult =
    from x in source
    where x <= 5
    select x * x;

// 結果は { 1, 4, 9, 16, 25 } となる。

また、数値型のコレクションで利用する以外にも、オブジェクトのコレクションに対して処理を行うことも可能です。
例えば人物を定義するPersonクラスのコレクションから20歳以上の人を抽出して氏名をつなげた文字列を取得するといった処理の場合、下記のように書くことができます。
※本稿のサンプルコードではnullチェック等を行っていませんが、実際に利用する場合は例外が発生しないように条件式に気を付けましょう。

/// <summary>
/// 人物を定義するPersonクラス
/// </summary>
class Person
{
    /// <summary>名</summary>
    public string FirstName { get; set; }
    /// <summary>姓</summary>
    public string LastName { get; set; }
    /// <summary>年齢</summary>
    public int Age { get; set; }

    public Person(string firstName, string lastName, int age)
    {
        FirstName = firstName;
        LastName = lastName;
        Age = age;
    }
}

/// <summary>
/// サンプルクラス
/// </summary>
class Sample
{
    static void Main()
    {
        var persons = new[] {
            new Person("Terrance", "Huff", 18),
            new Person("Deven", "Cyrus", 26),
            new Person("Dave", "Corbett", 53),
            new Person("Brion", "Shoebridge", 12),
            new Person("Terence", "Long", 31)
        };

        // メソッド構文
        var methodResult = persons.Where(x => x.Age >= 20).Select(x => $"{x.FirstName} {x.LastName}");

        // クエリ構文
        var queryResult =
            from x in persons
            where x.Age >= 20
            select $"{x.FirstName} {x.LastName}";

        // 結果は { "Deven Cyrus", "Dave Corbett", "Terence Long" } となる。
    }
}

GroupBy によるデータの組み分け

GroupBy メソッドでは、コレクションの要素から取得した値をもとにグルーピングを行い、グループごとにデータを抽出することが可能です。
下記のサンプルコードでは、前述のPersonクラスのコレクションを元にFirstNameの頭文字でグルーピングを行っています。

// メソッド構文
var methodResult = persons.GroupBy(x => x.FirstName[0], x => $"{x.FirstName} {x.LastName}");

// クエリ構文
var queryResult =
    from x in persons
    group $"{x.FirstName} {x.LastName}" by x.FirstName[0];

// 結果は、下記となる。
// Key 'T'
//   { "Terrance Huff", "Terence Long" }
// Key 'D'
//   { "Deven Cyrus", "Dave Corbett" }
// Key 'B'
//   { "Brion Shoebridge" }

OrderBy / OrderByDescending による並び替え

OrderBy メソッドは昇順、OrderByDescending メソッドは降順で、コレクションの要素の並び替えを行います。
クエリ構文の場合は orderby 句と ascending 句または descending 句の組み合わせで並び替える順序を指定します。
数値を降順で並び替える場合は、下記のようなコードになります。

var source = new int[] { 3, 5, 7, 1, 10, 8, 4, 6, 2, 9 };

// メソッド構文
var methodResult = source.OrderByDescending(x => x);

// クエリ構文
var queryResult =
    from x in source
    orderby x descending
    select x;

// 結果は { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 } となる。

最後に

今回は割と利用頻度が高いと思われるメソッドを簡単に説明しましたが、これらを組み合わせることで複雑なデータの抽出処理を可読性を上げつつ簡単に記述することができます。
ただし LINQ は気軽に使える反面、書き方によっては非常に大量のメモリを使うことがあるため、Web アプリケーションのように複数スレッドで同時実行されるようなプログラムで大量のデータを扱う場合は、問題がないか十分気を付ける必要があります。
適切な場所でうまく活用できるようにしましょう。

.NET コーディングのTips2選

初めに

ここ最近携わった案件でいくつか気になるコードを見かけましたので、多少なりとも改善できればとTips的なものを2つまとめてみました。
非常に初歩的な内容のため、今更言われなくても知ってるよ!という方はスルー推奨、初めて聞いた方は今後の参考にしていただければと思います。

foreachステートメント直前の要素数チェック

foreach の直前で要素数の判定を行っているコードを見かけますが、要素数が 0 の場合は何もせずブロックを抜けるため、要素の有無で処理分けが必要なければ判定は不要です。

例1:直前のif文で抜けるパターン

// このif文のブロックは不要
if (elements.Count == 0)
{
    return;
}
foreach (var element in elements)
{
    // 処理本体
}
return;

例2:if分のブロックとして処理するパターン

// このif文の判定は不要
if (elements.Count > 0)
{
    foreach (var element in elements)
    {
        // 処理本体
    }
}

いずれの場合も、 `foreach` のみ記述するだけで問題ありません。

foreach (var element in elements)
{
    // 処理本体
}

IEnumerableを実装するコレクションの要素存在チェック

IEnumerable を実装するコレクションに要素が存在するかどうかをチェックする際、 Count メソッドで取得した結果が 0 より大きいかで判定するコードを見かけますが、このメソッドは対象をカウントするために内部で全要素を列挙しているため、パフォーマンスがかなり悪くなります。
明確な要素数で判定する必要がない場合は Any メソッドを使うようにしましょう。

検証

0 から 999999999 までの数値を列挙したコレクションから 1000000 以上の要素が存在するかどうかを、Count と Any それぞれのメソッドで判定した場合の実行時間を比較します。

コード
using System.Diagnostics;

public class Example
{
    delegate bool CheckFunc(IEnumerable<int> list);

    /// <summary>
    /// 判定結果と処理時間を出力
    /// </summary>
    static void WriteResult(string name, CheckFunc func, IEnumerable<int> list)
    {
        Console.Write($"{name} : ");

        // 時間計測開始
        var sw = new Stopwatch();
        sw.Start();

        if (func(list))
        {
            Console.Write("要素が存在します");
        }
        else
        {
            Console.Write("要素が存在しません");
        }

        // 時間計測終了
        sw.Stop();

        Console.WriteLine($" : {sw.ElapsedMilliseconds} ミリ秒");
    }

    /// <summary>
    /// 0から999999999までの整数を列挙
    /// </summary>
    static IEnumerable<int> GetValues()
    {
        for (var i = 0; i < 1000000000; ++i)
        {
            yield return i;
        }
    }

    public static void Main()
    {
        // コレクションを取得
        var array = GetValues();

        // 拡張メソッドを利用して要素を抽出
        // このコードでは1000000以上の値を抽出
        var result = array.Where(x => x >= 1000000);
        // 次のようにクエリ式で書くことも可能(現場によってはNGかも)
        // var result = from x in array where x >= 1000000 select x;

        // 結果に要素が存在するかどうかを判定
        // Anyメソッドの場合
        WriteResult("Any", x => { return x.Any(); }, result);
        // Countメソッドの場合
        WriteResult("Count", x => { return x.Count() > 0; }, result);
    }
}
実行結果
Any : 要素が存在します : 23 ミリ秒
Count : 要素が存在します : 18756 ミリ秒

処理にかかる時間は環境によって変わりますが、同じ判定結果を得るのにかなりの差が出ることを確認できます。

また、抽出した要素を再利用しない場合はメソッドチェーンで簡潔に書くこともできます。

if (array.Where(x => x >= 1000000).Any()) {
    // 処理
}

簡潔にパフォーマンスを考慮したコードを書くように意識したいですね。

次回は

今回出てきた IEnumerable に関連して LINQ の解説やTipsをまとめてみたいと思います。

EC-CUBEからMysqlへのSSL接続を設定する

お世話になっております。株式会社Joolenの白井です。

いつもOSS開発部にてEC-CUBEのカスタマイズ開発とかをやっています。ときどき本体へのコントリビュ〜ションなどもやっています(もうちょっと増やしたいです)。

そんなに派手なことはやっておりませんので、ちまちました設定の話をします。

Continue reading “EC-CUBEからMysqlへのSSL接続を設定する”

ラズパイ で電子ロッカーを作る!(IoTネットワーク構成編)

今回の記事を担当するエンジニアの榎本です。
これまでの記事

電子ロッカー開発始めます → 電子ロッカー開発に至った経緯など
ラズパイ で電子ロッカーを作る!(物理構成編)→ 機器や配線など物理的な構成に関する記事

ラズパイ で電子ロッカーを作る!(ソフトウェア編)→プログラムについて

ここでは、AWSのEC2インスタンス上(EC-CUBE)で動いている「ご近所マルシェ Joolen」がどのようにして「電子ロッカーを開ける」という制御を行っているのかを説明していきます。

ネットワーク構成

IoT構成図

このようになっています。AWSとラズパイ 間の通信のプロトコルにはMQTTを採用しました。

MQTTは簡単にいうと、データを配信するためのプロトコルです。
「非力なデバイスやネットワークが不安定な場所でも動作しやすいように、メッセージ通信電文が軽量に設計されていることが特徴(Wikipediaより抜粋)」で、ラズパイ のような小さなコンピュータでも難なく扱えます。
Web socketにも似ていて、メッセージがPublish(送信)されると簡単に受けてはそのメッセージを拾うことができます。

AWSからラズパイ へのメッセージングに Web APIを使うことも検討しましたが、IoT機器にはセキュリテイ上、固定IPを持たせたくないという思いもありましたので今回の構成になりました。

今回のマルシェではEC2インスタンス上で、EC-CUBEは動いていますが「ロッカーの鍵を開けて!」というタイミングで メッセージを 送信(Publish)しています。流れとしては下記の通りで、仕組みとしては非常にシンプルなものです。
・解錠用のメッセージをMQTT Broker に対して 送信 (Publish)
・ロッカー(ラズパイ)は、MQTT Brokerのメッセージを監視(Subscribe)。MQTT BrokerへのPublishを検知したら後続の処理を行う。

AWSからラズパイ への通信

さて、ここからが本題です。
MQTTのやりとりをするためのサーバ(MQTT Broker)はAWS IoT Coreを採用しました。このIoT Coreへの接続は証明書を使う必要があります(ドキュメント)。
ラズパイ に 証明書を保存して接続、ということも当然可能ですが、小さなIoT機器にセキュリティ情報となる証明書を入れておくのは最善手とは言えません。

ということでIoT 専用のデータ転送サービスSORACOM Beamを使っての接続を行うことにしました。(SORACOM を使った双方向通信のデザインパターンはSORACOMさんによってまとまっています。)
このサービスは証明書の管理や脆弱性への対応等の煩雑な処理をクラウドにオフロードすることができるので、ラズパイ に証明書を保存する必要はありません。

 

下記の通り、ラズパイ 側ではAWS IoT Coreの認証情報を意識することなく接続することが可能です。

SORACOM Beamは非常に安価(1 リクエスト(*) あたり 0.00099 円)かつ、 1アカウントあたり月間 100,000 リクエストまでの無料枠もあるのでとても使いやすいサービスだと思います!

IoTは特にセキュアな接続を心がけていきたいですね。

ご近所マルシェ Joolen稼働中です!

※現在、アトレ松戸 3Fでご近所マルシェ ジョーレン で購入したものを受け取り可能です!(アトレ松戸での2021/5/23まで)

ありがとうございました。

ラズパイ で電子ロッカーを作る!(ソフトウェア編)

今回の記事を担当するエンジニアの榎本です。

これまでの記事

電子ロッカー開発始めます → 電子ロッカー開発に至った経緯など

ラズパイ で電子ロッカーを作る!(物理構成編)→ 機器や配線など物理的な構成に関する記事

ここからは Raspberry Pi上で動くプログラムについて書いていきます。

今回Raspberry Pi側で採用したプログラム言語について

Node-REDというビジュアルプログラミングツールを採用しました。Node-REDとは何かというと。。。

Node-REDはハードウェアデバイス、APIおよびオンラインサービスを新しく興味深い方法で接続するためのツールです。ブラウザベースのエディタによってパレットに並ぶ多種多様なノードを結びつけて用意にフローを作成でき、さらにシングルクリックで実行環境にデプロイすることができます。

Node-RED日本ユーザー会トップページより引用

とのこと。
つまり、ブラウザからノードと呼ばれるブロックを線でつなげて思い通りにデバイスを動かせる素敵な言語になります。名前から推察できる様に、Node.js上で構築されてますので、必要に応じて Javascriptでのコーディングまでできるので、編集が容易かつ柔軟性が高い言語となっています。

コードエディタはこんな感じ

画面上で「再起動」などと書かれているかボックスがノードとなります。各処理をつなげてやりたいことを実現できます。

電子ロッカーを構成する各種ノードの紹介

Injectノード
上記コードエディタ左側の「timestamp」と書かれているノードです。時間を指定して処理を起動することができます。

Execノード
上記コードエディタで「再起動」と書かれているノードです。このノードでコマンドの実行を行うことができます。この場合は下記のコマンドを実行することで再起動を実現しています。

sudo shutdown -r now

つまり上述2つのノードの組み合わせで、「毎朝7時に再起動をする」という命令が組み立てられます。非常に分かりやすくて便利ですね。

MQTTノード
今回の電子ロッカーは

ECサイトからの命令をAWS IoT CoreのMQTTへPublish

ラズパイ 側で該当のTopicをSubscribeしメッセージの受信をきっかけに鍵を操作

することで解錠・施錠の実行を実現しています。Node-REDはMQTT処理に必要なノードが用意されているので非常に簡単にこの処理を実行することができます。
入力側のmqttが Subscribeで 出力側がPublishになります。
ノードには MQTT Broker となるサーバの情報・トピック・QOS(=Quality Of Service)を設定することで簡単にPublish/Subscribeを実現できます。(見落としがちですが、QOSはPublish側と合わせる様にしましょう!私はここの数値が食い違っていたために数時間ハマりました。。。)

ノードの設定画面
↑↑MQTT Brokerの設定画面↑↑

余談ですがMQTT Brokerは AWS IoT Coreを SORACOM Beam経由で使っています。AWS IoT Coreの認証を肩代わりしてくれるので ラズパイ 側に証明書などを持つ必要がないことがメリットになります。(これはまた後日記事にします)

PCA9685ノード

こちらは標準のノードではないので、パレットの管理から自分で追加する必要があります。Node-REDのホームページにサンプルを含め説明が載っています。

ノードの追加はこの画面から可能です。
PCA9685ノードの編集画面

基本的には
1. PCA9685 Deviceで対象のデバイスを選択(デバイスの設定方法は下記に記述)
2. Channelはターゲットになる PCA 9685 の チャネルを選択 (直接指定でも良いし、msg.channel という変数に設定した数値でも良い)
3. Unitはサーボモータを動かしたいときは、「microseconds」 LEDを点灯させたりしたいときは「Percent」を選択しましょう。

デバイスの設定画面

PCA9685を指定する画面です。ラズパイ 側で下記の通りになっていれば特に変更する必要はありません。
※Addressは下記の画面で 0x40(=64)であることが確認できます。

例) 15個の扉の解錠を行い、操作がされた時にLEDを点滅させるためのフローは下記の様になります。
1. open_1_all へのメッセージの Publishをトリガーにしてフローが起動
2-1. サーボモータを動かす
  2-1-1. サーボモータを140度回転した位置にするためmsg.payloadに値を設定
  2-1-2. PCA9685 ノードを経由して1〜15チャネルに命令を出す
2-2. LEDを点滅させる
  2-2-1. 0番のチャネルに接続されたLEDを点滅させる
2-3. MQTTメッセージを受け取ったことを送信元のプログラムへ返却するため、受け取ったランダムな文字列をそのまま返却用のMQTTトピックにPublishする

他に大きなポイントとして、ラズパイ の死活監視に使っているlwm2ノードがあるのですが、これは監視についての記事で触れたいと思います。

次回は電子ロッカーのネットワークや、利用しているSORACOMのサービスについて触れていきたいと思います!

ラズパイ で電子ロッカーを作る!(物理構成編)

新規事業を担当している榎本です。
前回の記事で、私たちがなぜ電子ロッカーを作るという決定をしたのか記載しました。では早速、ロッカーを動かすための物理的な構成を書いていきます!

全体図

まずは全体図の写真です。かなりシンプルな構成ですね。基本的にECサイトから受け取った「ロッカーの解錠・施錠」に関する命令をラズパイ が受け取って複数のサーボモータに伝達する形で実現しています。

※見やすくするためにサーボモータ などは外した状態で撮影しています。

機器について一つずつ解説していきます。

機器名称役割
Raspberry Pi Zero WH電子ロッカーの心臓部。
内部のソフトウェアは誰でも扱いやすいようにビジュアルプログラミング言語である
Node-REDを採用しました。美味しいものマルシェ(ECサイト)からのロッカー解錠の
命令に従って周辺危機の操作を行っています。
あまり重たい処理は無いためスペックは重視せず、小さくて安価な Zero WHを採用。
AK-020 (Soracom Air)通信は Soracom Air for セルラーを使っています。通信に必要なモデムとしてAK-020を
利用しています。(このモデムの中に SIMが入っています)
Amazonで販売しているSORACOMスターターキットは通信量1,000円分のクーポンも
ついていてお得でした
PCA9685購入者1人につき、1つずつロッカーの扉を割り当てています。それぞれのロッカーの
扉を個別に操作するためにPCA9685を使ってサーボモータを制御しています。
電池ボックスサーボモータを動かすための電源です。プロトタイプでは電池ボックスを
使っていますが、USBからの給電になる予定です。
TowerPro
SG92R
ロッカーを実際に解錠するためのモーターです。扉1つにつき、1つのモーターが
使われます。写真には載せていませんが、PCA9685につなげています。
高輝度
赤色LEDランプ
ロッカーが実際に施錠・解錠されたことをユーザーが認識できるように、操作された
タイミングでLEDを点滅させます。これも写真には載せていませんが、PCA9685に
つなげてます。

配線について

ラズパイ とPCA 9685の配線図です。GPIOは 1,3,5,6 (青枠で囲った部分)を使っています。

ラズパイ のPIN番号ラズパイ側の名称PCA9685側のピンの名称
15V PowerVCC
3SDASDA
5SCLSCL
6GroundGND

実物の写真はこのようなイメージです。PCA9685は基盤にPINの役割が書いてあるので間違えにくいですね。

PCA9685

ちなみに、PCA9685を使うときにはラズパイ 側のコンフィグを変更してI2Cを有効にする必要があります。こちらの手順についての記事は多いので調べればすぐに分かると思いますが、こちらにも掲載しておきます。

sudo raspi-config

こちらを実行すると表示される画面がこちら。ここで5番の Interfacing Optionsを選択して

I2Cを選択

「はい」を選択して I2C interfaceを有効にしておきましょう!

これでラズパイ を通して複数のサーボモータの制御をするための準備は完了です。サーボモーター はこの様な形で扉に取り付けられています。サーボモータが約140°動くことでギアを経由して鍵が閉まり、ロッカーが施錠されている様子がご覧いただけます。

次回はラズパイ の操作に利用しているプログラム言語、Node-REDについてご紹介していきます!

電子ロッカー開発はじめます

今回の記事を担当するエンジニアの榎本です。
トップページにも記載されていますが、松戸の美味しいものマルシェを開催中です!(2021/3/31まで)
ありがたいことに多くのお客様にご利用いただき、めでたく4/23より第2回目の開催も決定しました。

さて、第1回目ではお客様への商品受け渡しを人の手で行っていました。しかし、下記の理由により次回のイベント以降の受け渡しは無人で行うことになっています。理由としては。。。

  1. 1. 購入いただいたお客様にできるだけ幅広い時間でのお受取りをしてもらうため
  2. 2. 受け渡しにかかるコストを削減するため
  3. 3. 受け渡し担当が長時間座ることにより発生する腰痛への対策

3つ目は冗談として…お客様の利便性と当社としての収益性を保つためにロッカーを使った受け渡しを行うことにしたのです。このロッカーの実現にあたっては下記の4案が検討されました。
大雑把に分けると、

  • ・アナログにダイヤル錠を使った運用をするか、電子キーを実装するか。
  • ・扉を共通化するか、受取人毎に別々にするか

です。これらのメリット・デメリットを大きく下記の様に分類しました。

方式メリットデメリット
1. ダイアル錠(共通扉)開発コストが小さい
運用が最も簡単
運用の負荷が大きい
セキュリティの不安が大きい
2. ダイアル錠(個別扉)開発コストが小さい
共有扉よりセキュリティが保たれ易い
運用負荷が大きい
ミスが発生しやすい
3. 電子キー(共通扉)後述の個別扉よりメンテナンスは容易開発コストが発生する
セキュリティの不安が大きい
4. 電子キー(個別扉)運用の
セキュリティ面で安全性が高い
物理面を含めると開発コストが最も大きい。
システムのメンテナンスコストも高い

私たちは上記を検討した結果、セキュリティ面での安全性と確実にお客様が商品を受け取れることを重視して案四の電子キー(個別扉)を採用することに決めました。
まぁ、開発者としては茨の道っぽいのですが… IoTは弊社としては初挑戦で、楽しそうなので良しとしましょう!構成はざっくりとこんな感じ。
ラズパイ 、SORACOM 、AWSを組み合わせた構成でラズパイ 上の言語は Node-REDを使っています。

次回からは、システム的な観点からどの様に私たちがこの電子ロッカーを構築しているのかを公開していきます!