はじめに
Elixir の Port を使うと、C 言語などで書かれた外部プログラムと連携できます。
ただ、どこから手をつければいいのか、全体の構成が掴みにくいことがありますよね。
この記事では、Port を使ったプロジェクトの基本構成を学べるように、次の 2 つのプロジェクトを例に解説します。
- Blinkchain: LED 制御ライブラリで、Port を活用したベストプラクティスの学びが得られます。
- elixir-sensors/sgp40: Blinkchain の構成を参考にして実装した空気質センサーライブラリ。
Port を用いたプロジェクトの全体像を理解し、自分のアイデアに応用できる内容を目指します。
Blinkchain とは
Blinkchain は、Elixir を使って LED ストリップ(WS2812B など)を制御するライブラリです。
内部では Port を利用して C 言語コードを呼び出し、高速でハードウェア制御を行っています。
Blinkchain の学びが役立った点
-
ディレクトリ構成の整理:
-
c_src/
ディレクトリで C プログラムを管理し、Makefile を活用したビルドフローを導入。
-
-
Port 通信の抽象化:
- Port を専用モジュールで管理し、Elixir 側からは API を通じて簡単に操作できる構造を採用。
-
明確な公開 API の設計:
- ユーザー向けの操作をシンプルにし、利用者が内部の複雑さを意識せずに使えるように設計。
Blinkchain のディレクトリ構成
blinkchain/
├── c_src/ # **C プログラムのソースコードを格納**
│ └── port_interface.c # **LED 制御ロジック**
├── lib/
│ ├── blinkchain.ex # **公開 API**
│ └── blinkchain/ # **内部ロジックをモジュール化**
│ └── port.ex # **Port 通信の管理**
├── test/
│ └── blinkchain_test.exs # **テストコード**
├── Makefile # **C プログラムのビルド設定**
├── mix.exs # プロジェクト設定
└── README.md # プロジェクトのドキュメント
Blinkchain の各ファイルの役割と実装例
C プログラム(c_src/port_interface.c
)
以下は、標準入力で受け取ったコマンドを元に LED を制御する C プログラムの簡略化版です。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char command[256];
while (fgets(command, sizeof(command), stdin)) {
if (strcmp(command, "turn_on") == 0) {
printf("LED turned on");
} else if (strcmp(command, "turn_off") == 0) {
printf("LED turned off");
} else {
printf("Unknown command");
}
fflush(stdout);
}
return 0;
}
Makefile(Makefile
)
C プログラムを簡単にコンパイルできるように Makefile を用意します。
all:
gcc -o port_interface c_src/port_interface.c
make
コマンドを実行することで、port_interface
バイナリが生成されます。
Port 管理モジュール(lib/blinkchain/port.ex
)
Port を管理する Elixir モジュールです。
defmodule Blinkchain.Port do
def send_command(command) do
port = Port.open({:spawn, "./port_interface"}, [:binary])
send(port, {self(), {:command, "#{command}"}})
receive do
{^port, {:data, response}} -> String.trim(response)
after
5000 -> "Timeout"
end
end
end
公開 API(lib/blinkchain.ex
)
ライブラリ利用者向けのシンプルな API を提供します。
defmodule Blinkchain do
alias Blinkchain.Port
def turn_on do
Port.send_command("turn_on")
end
def turn_off do
Port.send_command("turn_off")
end
end
テストコード(test/blinkchain_test.exs
)
defmodule BlinkchainTest do
use ExUnit.Case
alias Blinkchain
test "turns on the LED" do
assert Blinkchain.turn_on() == "LED turned on"
end
test "turns off the LED" do
assert Blinkchain.turn_off() == "LED turned off"
end
end
elixir-sensors/sgp40 プロジェクトでの応用
Blinkchain で学んだ構成や実装パターンを元に、私のプロジェクト elixir-sensors/sgp40 では、空気質センサー SGP40 のデータを Elixir で取得する仕組みを構築しました。
sgp40/
├── c_src/ # **C プログラムのソースコードを格納**
│ └── sgp40.c # **センサー制御ロジック**
├── lib/
│ ├── sgp40.ex # **公開 API**
│ └── sgp40/ # **内部ロジックをモジュール化**
│ └── port.ex # **Port 通信の管理**
├── test/
│ └── sgp40_test.exs # **テストコード**
├── Makefile # **C プログラムのビルド設定**
├── mix.exs # プロジェクト設定
└── README.md # プロジェクトのドキュメント
おわりに
この記事では、Elixir の Port を使って C 言語プログラムと連携する方法を解説しました。
Port を活用した連携は、ハードウェアや外部プログラムを操作する際に非常に強力です。ぜひ、この記事を参考に、自分のプロジェクトに取り入れてみてください!
何か氣づいた点や改善提案があれば、コメントで共有していただけると嬉しいです!