# Cloud → Charger requests
c2d/{cert_org}/{cert_ou}/{cert_cn}/{service}/req/{operation}
c2d/{cert_org}/{cert_ou}/{cert_cn}/{service}/res/{operation}
# Charger → Cloud requests (yes, these exist!)
d2c/{cert_org}/{cert_ou}/{cert_cn}/{service}/req/{operation}
d2c/{cert_org}/{cert_ou}/{cert_cn}/{service}/res/{operation}
# Charger → Cloud events (fire-and-forget)
d2c/{cert_org}/{cert_ou}/{cert_cn}/{service}/evt/{event_type}
# Charger → Cloud telemetry
d2c/{cert_org}/{cert_ou}/{cert_cn}/telemetry/{component}/{metric}// base_types.proto
syntax = "proto3";
package basecamp.api.v1;
// Standard request header
message RequestHeader {
string request_id = 1; // UUID for correlation
int64 timestamp_ms = 2; // Unix timestamp
map<string, string> metadata = 3; // Extensions
}
// Standard response header
message ResponseHeader {
string request_id = 1; // Echo from request
int64 timestamp_ms = 2;
Status status = 3;
}
// HTTP-compatible status
message Status {
int32 code = 1; // HTTP status codes:
// 200 = OK
// 400 = Bad Request
// 401 = Unauthorized
// 403 = Forbidden
// 404 = Not Found
// 408 = Request Timeout
// 409 = Conflict
// 500 = Internal Server Error
// 503 = Service Unavailable
// 1000-1999 = Custom business codes
string message = 2; // Human readable
string details = 3; // Technical details
}
// For events that don't need replies
message EventHeader {
string event_id = 1; // UUID for deduplication
int64 timestamp_ms = 2;
string source = 3; // Event source
string type = 4; // Event type
map<string, string> metadata = 5;
}// config_service.proto
syntax = "proto3";
package basecamp.api.v1.config;
import "base_types.proto";
message ConfigSwitchRequest {
basecamp.api.v1.RequestHeader header = 1;
string config_path = 2;
bool permanent = 3;
}
message ConfigSwitchResponse {
basecamp.api.v1.ResponseHeader header = 1;
// Only populated if status.code == 200
string previous_config = 2;
string active_config = 3;
bool restart_pending = 4;
}// session_service.proto
syntax = "proto3";
package basecamp.api.v1.session;
import "base_types.proto";
// Charger requests upload URL from cloud
message SessionUploadUrlRequest {
basecamp.api.v1.RequestHeader header = 1;
string session_id = 2;
repeated FileInfo files = 3;
message FileInfo {
string filename = 1;
int64 size_bytes = 2;
string content_type = 3;
}
}
message SessionUploadUrlResponse {
basecamp.api.v1.ResponseHeader header = 1;
repeated UploadUrl urls = 2;
message UploadUrl {
string filename = 1;
string upload_url = 2;
int64 expires_at_ms = 3;
}
}
// Event when session ends (no reply expected)
message SessionEndedEvent {
basecamp.api.v1.EventHeader header = 1;
string session_id = 2;
int64 energy_wh = 3;
int64 duration_ms = 4;
string end_reason = 5;
}asyncapi: 3.0.0
info:
title: Basecamp Cloud Connector API
version: 1.0.0
servers:
production:
host: mqtt.basecamp.pionix.com:8883
protocol: mqtt
protocolVersion: '5.0'
security:
- mtls: []
# Default for all messages
defaultContentType: application/x-protobuf
channels:
# Cloud → Charger config switch
configSwitchRequest:
address: 'c2d/{cert_org}/{cert_ou}/{cert_cn}/config/req/switch'
parameters:
cert_org:
$ref: '#/components/parameters/certOrg'
cert_ou:
$ref: '#/components/parameters/certOu'
cert_cn:
$ref: '#/components/parameters/certCn'
messages:
ConfigSwitchRequest:
payload:
schemaFormat: application/vnd.google.protobuf;version=3
schema:
$ref: './config_service.proto#/ConfigSwitchRequest'
configSwitchResponse:
address: 'c2d/{cert_org}/{cert_ou}/{cert_cn}/config/res/switch'
parameters:
cert_org:
$ref: '#/components/parameters/certOrg'
cert_ou:
$ref: '#/components/parameters/certOu'
cert_cn:
$ref: '#/components/parameters/certCn'
messages:
ConfigSwitchResponse:
payload:
schemaFormat: application/vnd.google.protobuf;version=3
schema:
$ref: './config_service.proto#/ConfigSwitchResponse'
# Charger → Cloud session upload
sessionUploadUrlRequest:
address: 'd2c/{cert_org}/{cert_ou}/{cert_cn}/session/req/upload-url'
parameters:
cert_org:
$ref: '#/components/parameters/certOrg'
cert_ou:
$ref: '#/components/parameters/certOu'
cert_cn:
$ref: '#/components/parameters/certCn'
messages:
SessionUploadUrlRequest:
payload:
schemaFormat: application/vnd.google.protobuf;version=3
schema:
$ref: './session_service.proto#/SessionUploadUrlRequest'
sessionUploadUrlResponse:
address: 'd2c/{cert_org}/{cert_ou}/{cert_cn}/session/res/upload-url'
parameters:
cert_org:
$ref: '#/components/parameters/certOrg'
cert_ou:
$ref: '#/components/parameters/certOu'
cert_cn:
$ref: '#/components/parameters/certCn'
messages:
SessionUploadUrlResponse:
payload:
schemaFormat: application/vnd.google.protobuf;version=3
schema:
$ref: './session_service.proto#/SessionUploadUrlResponse'
# Charger → Cloud events
sessionEndedEvent:
address: 'd2c/{cert_org}/{cert_ou}/{cert_cn}/session/evt/ended'
parameters:
cert_org:
$ref: '#/components/parameters/certOrg'
cert_ou:
$ref: '#/components/parameters/certOu'
cert_cn:
$ref: '#/components/parameters/certCn'
messages:
SessionEndedEvent:
payload:
schemaFormat: application/vnd.google.protobuf;version=3
schema:
$ref: './session_service.proto#/SessionEndedEvent'
operations:
# Cloud sends config switch
sendConfigSwitch:
action: send
channel:
$ref: '#/channels/configSwitchRequest'
reply:
channel:
$ref: '#/channels/configSwitchResponse'
bindings:
mqtt:
qos: 1
# MQTT 5 properties set at runtime:
# - responseTopic: from address
# - correlationData: request_id as bytes
# Charger handles config switch
handleConfigSwitch:
action: receive
channel:
$ref: '#/channels/configSwitchRequest'
reply:
channel:
$ref: '#/channels/configSwitchResponse'
# Charger requests upload URL
requestUploadUrl:
action: send
channel:
$ref: '#/channels/sessionUploadUrlRequest'
reply:
channel:
$ref: '#/channels/sessionUploadUrlResponse'
bindings:
mqtt:
qos: 1
# Cloud handles upload URL request
handleUploadUrlRequest:
action: receive
channel:
$ref: '#/channels/sessionUploadUrlRequest'
reply:
channel:
$ref: '#/channels/sessionUploadUrlResponse'
# Charger publishes session ended event
publishSessionEnded:
action: send
channel:
$ref: '#/channels/sessionEndedEvent'
bindings:
mqtt:
qos: 1 # At least once delivery
retain: false
# Cloud receives session ended event
receiveSessionEnded:
action: receive
channel:
$ref: '#/channels/sessionEndedEvent'
components:
parameters:
certOrg:
description: Certificate organization field
certOu:
description: Certificate organizational unit field
certCn:
description: Certificate common name field
securitySchemes:
mtls:
type: X509
description: Mutual TLS authenticationpublic async Task<ConfigSwitchResponse> SwitchConfig(
string certOrg, string certOu, string certCn,
string configPath, bool permanent)
{
var request = new ConfigSwitchRequest
{
Header = new RequestHeader
{
RequestId = Guid.NewGuid().ToString(),
TimestampMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
},
ConfigPath = configPath,
Permanent = permanent
};
var cmdTopic = $"c2d/{certOrg}/{certOu}/{certCn}/config/req/switch";
var resTopic = $"c2d/{certOrg}/{certOu}/{certCn}/config/res/switch";
// MQTT 5 properties
var message = new MqttApplicationMessageBuilder()
.WithTopic(cmdTopic)
.WithPayload(request.ToByteArray())
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
.WithResponseTopic(resTopic) // MQTT 5
.WithCorrelationData(Encoding.UTF8.GetBytes(request.Header.RequestId)) // MQTT 5
.Build();
// Subscribe and send...
}// Charger requests S3 upload URL
void request_session_upload_url(const char* session_id) {
SessionUploadUrlRequest request = SessionUploadUrlRequest_init_zero;
// Fill request
strncpy(request.header.request_id, generate_uuid(),
sizeof(request.header.request_id));
request.header.timestamp_ms = get_timestamp_ms();
strncpy(request.session_id, session_id, sizeof(request.session_id));
// Build topic
char req_topic[256], res_topic[256];
snprintf(req_topic, sizeof(req_topic),
"d2c/%s/%s/%s/session/req/upload-url",
cert_org, cert_ou, cert_cn);
snprintf(res_topic, sizeof(res_topic),
"d2c/%s/%s/%s/session/res/upload-url",
cert_org, cert_ou, cert_cn);
// Publish with MQTT 5 properties
mqtt5_publish_request(&request, req_topic, res_topic,
request.header.request_id);
}// No response expected
void publish_session_ended(const char* session_id, int64_t energy_wh) {
SessionEndedEvent event = SessionEndedEvent_init_zero;
// Fill event header
strncpy(event.header.event_id, generate_uuid(),
sizeof(event.header.event_id));
event.header.timestamp_ms = get_timestamp_ms();
strncpy(event.header.source, get_charger_id(),
sizeof(event.header.source));
strncpy(event.header.type, "session.ended",
sizeof(event.header.type));
// Fill event data
strncpy(event.session_id, session_id, sizeof(event.session_id));
event.energy_wh = energy_wh;
// Publish with QoS 1 for delivery guarantee
char topic[256];
snprintf(topic, sizeof(topic),
"d2c/%s/%s/%s/session/evt/ended",
cert_org, cert_ou, cert_cn);
mqtt_publish(topic, &event, SessionEndedEvent_fields, 1);
}