Content is user-generated and unverified.

Corrected Cloud-Charger Protocol Design

1. Topic Structure

# 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}

2. Base Types with HTTP Status Codes

protobuf
// 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;
}

3. Service Examples

Config Service (Cloud → Charger)

protobuf
// 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 (Charger → Cloud)

protobuf
// 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;
}

4. Correct AsyncAPI 3.0 Specification

yaml
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 authentication

5. Implementation Examples

Cloud → Charger Request

csharp
public 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 → Cloud Request

c
// 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);
}

Charger → Cloud Event

c
// 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);
}

Key Improvements:

  1. ✅ Full certificate path for security
  2. ✅ Clear bidirectional request patterns (c2d and d2c)
  3. ✅ HTTP status codes (200, 400, 500, + custom 1000-1999)
  4. ✅ Proper AsyncAPI with protobuf references
  5. ✅ Events as fire-and-forget with QoS for reliability
Content is user-generated and unverified.
    Corrected Design with Your Feedback | Claude