Boost.Asio + WebSocket + JSON でシンプルな OCPP 2.0.1 ゲートウェイを 5〜10 分で理解する
本記事では、OCPP 対応充電器と CSMS の間に配置するゲートウェイを C++17 で開発する際の最小構成例を紹介します。小規模 POC から商用拡張の足がかりになることを目指します。
1. 全体アーキテクチャ
- WebSocket Listener: 複数の充電器からの接続を非同期で受け付ける
- Router: 受信したメッセージ種別 (
messageTypeId
) に応じてハンドラーへ振り分け - Handlers: ドメインロジック(検証・変換・DB 書込など)
- CSMS Client: CSMS との上り通信。TLS・署名処理を担当
本実装では Boost.Asio / Beast を基盤に据え、JSON 処理に nlohmann/json を利用します。
2. 主要ライブラリ選定
機能 | ライブラリ | 理由 |
---|---|---|
非同期 I/O | Boost.Asio | C++17 標準的、成熟度高い |
WebSocket | Boost.Beast | Asio と親和性、HTTP Upgrade 対応 |
JSON | nlohmann/json | ヘッダオンリー、直感的 API |
TLS | OpenSSL 1.1+ | 実運用で必須、Beast から直接利用可 |
ロギング | spdlog | 高速、シングルヘッダオプション有 |
3. セッションとルーティングの実装例
session.hpp
抜粋:
cpp
class Session : public std::enable_shared_from_this<Session> {
websocket::stream<tcp::socket> ws_;
flat_buffer buffer_;
public:
template <typename... Args>
Session(Args&&... args) : ws_(std::forward<Args>(args)...) {}
void start() { do_accept(); }
private:
void do_accept() {
ws_.async_accept(
beast::bind_front_handler(&Session::on_accept, shared_from_this()));
}
void on_accept(beast::error_code ec) {
if (ec) return fail(ec, "accept");
do_read();
}
void do_read() {
ws_.async_read(buffer_,
beast::bind_front_handler(&Session::on_read, shared_from_this()));
}
void on_read(beast::error_code ec, std::size_t) {
if (ec == websocket::error::closed) return;
if (ec) return fail(ec, "read");
auto msg = beast::buffers_to_string(buffer_.data());
route_message(msg);
buffer_.consume(buffer_.size());
do_read();
}
};
ルータ例 (router.cpp
):
cpp
void Router::route_message(const std::string& raw) {
auto json = nlohmann::json::parse(raw);
int frameType = json[0].get<int>();
if (frameType != 2) return; // CALL のみ処理
std::string action = json[2];
if (action == "BootNotification") {
handlers_.boot(raw);
} else if (action == "TransactionEvent") {
handlers_.tx(raw);
} else {
logger.warn("Unhandled action {}", action);
}
}
4. BootNotificationハンドラーの雛形
cpp
void BootHandler::operator()(const std::string& raw) {
auto j = nlohmann::json::parse(raw)[3]; // payload
std::string model = j["chargingStation"]["model"];
std::string vendor = j["chargingStation"]["vendorName"];
db_.upsert_station(vendor, model);
// 応答生成
nlohmann::json res = {
3, j["msgId"], {
{"status", "Accepted"},
{"interval", 300},
{"currentTime", now_iso()}
}
};
session_.write(res.dump());
}
5. CMake 設定のポイント
CMakeLists.txt
スニペット:
cmake
find_package(Boost 1.78 COMPONENTS system thread REQUIRED)
find_package(OpenSSL 1.1 REQUIRED)
add_executable(ocpp-gateway
main.cpp session.hpp router.cpp handlers.cpp)
target_link_libraries(ocpp-gateway
Boost::system Boost::thread OpenSSL::SSL OpenSSL::Crypto)
set_property(TARGET ocpp-gateway PROPERTY CXX_STANDARD 17)
6. テスト & シミュレーション
- Virtual Charge Point: https://github.com/solidstudiosh/ocpp-virtual-charge-point を利用し、BootNotification → Heartbeat → TransactionEvent の一連を送信。
- ローカルで
./ocpp-gateway
を起動し、Wireshark で WebSocket フレームを確認。 - 単体テスト: GoogleTest で Router のルーティングテーブルを検査。
7. 今後の拡張ポイント
- セキュリティ Whitepaper 準拠:JWS 署名検証・鍵ローテーション API の実装
- スマート充電:
SetChargingProfile
ハンドラーとスケジューラ追加 - メトリクス:Prometheus Exporter でレイテンシとメッセージ数を可視化
- マルチスレッド化:
boost::asio::thread_pool
でコア数に合わせてセッションを分散
各ポイントの補足
セキュリティ Whitepaper 準拠
OCPP 2.0.1 Security Whitepaper に沿って JWS 署名検証や鍵ローテーション API を実装し、改ざん検出と通信の完全性を担保します。
スマート充電
SetChargingProfile
/ ClearChargingProfile
を解釈し、ピークカットや最適料金に合わせて出力電流を動的制御するスケジューラを追加します。
メトリクス
Boost.Beast で /metrics
エンドポイントを公開し、Prometheus 形式でレイテンシやメッセージレートを可視化します。
マルチスレッド化
boost::asio::thread_pool
を導入し、I/O とドメインロジックを分離して CPU コア数に合わせてスケールさせます。
参考リポジトリ
サンプル実装を GitHub で公開しています。ビルド手順や詳細ドキュメントもあるので、ぜひご覧ください。
これで 「シングルバイナリで動く最小ゲートウェイ」 の骨格が完成します。次の記事では、OCPP 2.0.1 のセキュリティ対策を具体的に解説します。