OCPP 2.0.1 ハンドシェイクのC++17実装手順をコード付きで解説 (5〜10分)
OCPP 2.0.1 で CS (充電器) が CSMS と通信を開始する際、必ず発生するハンドシェイクが BootNotification → Heartbeat です。 本記事では C++17 (Boost.Asio/Beast) での実装例を通じて、以下を解説します。
- 接続確立 (WebSocket Upgrade)
- BootNotification 送受信と検証
- Heartbeat のスケジューリング
- タイムアウト & 再接続戦略
1. シーケンス概要
2. 接続確立
connect()
関数で TLS 接続 + WebSocket Upgrade を行います。
cpp
void Client::connect(const tcp::resolver::results_type& endpoints) {
// SSL handshake
beast::get_lowest_layer(ws_).connect(endpoints);
ws_.next_layer().handshake(ssl::stream_base::client);
// WebSocket handshake
ws_.handshake(host_, "/ocpp/v201");
}
3. BootNotification 実装
3.1 リクエスト生成
cpp
json make_boot_request() {
return json::array({
2, // CALL
uuid::to_string(uuid::random_generator()()),
"BootNotification",
{
{"chargingStation", {{"model", model_}, {"vendorName", vendor_}}},
{"reason", "PowerUp"},
{"reasonTimestamp", now_iso()}
}
});
}
3.2 応答ハンドラ
cpp
void on_boot_conf(const json& payload) {
if (payload["status"] != "Accepted") {
logger.error("BootNotification rejected");
return retry_later();
}
interval_ = payload["interval"].get<int>();
schedule_heartbeat();
}
4. Heartbeat スケジューリング
boost::asio::steady_timer
を利用して一定間隔で Heartbeat を送信します。
cpp
void schedule_heartbeat() {
timer_.expires_after(std::chrono::seconds(interval_));
timer_.async_wait([this](beast::error_code ec) {
if (ec) return; // cancelled
send_heartbeat();
schedule_heartbeat();
});
}
void send_heartbeat() {
json req = {
2, uuid::to_string(uuid::random_generator()()), "Heartbeat", json::object() }
ws_.async_write(net::buffer(req.dump()),
beast::bind_front_handler(&Client::on_write, shared_from_this()));
}
応答検証
cpp
void on_heartbeat_conf(const json& payload) {
auto srv_time = payload["currentTime"].get<std::string>();
sync_clock(srv_time); // NTP 不要の簡易同期
}
5. タイムアウト & 再接続
- BootNotification 未応答:
boost::asio::deadline_timer
で 10 秒を越えたら TCP を切断し、再接続を試行。 - Heartbeat 連続失敗: 3 回連続失敗で再接続。
- WebSocket
close
フレーム受信: 直ちに再接続、BootNotification から再開。
cpp
void Client::fail(beast::error_code ec, std::string_view what) {
logger.warn("{}: {}", what, ec.message());
reconnect();
}
6. ログとメトリクス
KPI | 収集方法 |
---|---|
Boot 成功率 | BootNotification.conf の Accepted カウント / 試行回数 |
Heartbeat RTT | 送信時刻と応答時刻の差分を spdlog で追跡 |
再接続回数 | fail ハンドラの発火数 |
Prometheus Exporter を埋め込み、boot_success_total
や heartbeat_rtt_seconds
を監視することで、フィールドテスト時の接続品質を可視化できます。
7. まとめ
- BootNotification の
interval
を厳守し、CSMS の期待する通信量に合わせる - Heartbeat タイマーは async_wait → send → 再スケジュール の再帰パターンがシンプル
- タイムアウトや WebSocket 切断を検知したら BootNotification からリトライ することで状態機械を簡単に保つ
次回は スマート充電 (SetChargingProfile) の実装 を取り上げる予定です。