Edge Messaging
The gateway uses Azure Service Bus as the transport for bidirectional communication between the cloud platform and each edge gateway instance. All messaging infrastructure is defined in Voltimax.Edge.Messaging and wired up in Voltimax.Edge.Gateway.Service.
Queue Topology
Five queues are declared in MessagingExtensions.CreateTopology:
| Queue | Message type | Direction | Sessions |
|---|---|---|---|
telemetry | TelemetryFrame | Outbound | No |
gateway-heartbeat | GatewayHeartbeat | Outbound | No |
pricing-commands | FetchPricingRequest | Inbound | No |
gateway-commands | Polymorphic (see below) | Inbound | Yes |
gateway-configuration | GetGatewayConfigurationRequest | Inbound | No |
The topology is exposed via ServiceBusTopology as a record of QueueBuilder instances. This allows the Voltimax.Iot.AppHost orchestrator to introspect all required entities when provisioning the Service Bus namespace for local development.
Session-Based Command Routing
gateway-commands is a polymorphic, session-enabled queue. Every message carries the target GatewayId as its session ID. During startup, ConfigureMessaging reads GatewayId from configuration and registers the session filter:
serviceBus
.GatewayCommands
.WithAcceptedSessionId(gatewayId)
.MapMessage<PingGatewayRequest, PingGatewayHandler>()
// ...This ensures each gateway instance only dequeues messages addressed to it. The platform sends all command types to the single gateway-commands queue and the Service Bus session broker routes each session to the correct receiver.
Authentication
MessagingExtensions.AddMessaging supports two authentication modes, selected by the format of the ServiceBus:ConnectionString configuration value:
- SAS connection string (contains
Endpoint=): used as-is. - Fully-qualified namespace (URL or bare hostname emitted by Aspire / managed identity): the hostname is extracted and
DefaultAzureCredentialis used.
This means no code changes are needed when moving from a local Service Bus emulator (SAS) to Azure Container Apps with a managed identity.
Configuration
// appsettings.json
{
"ServiceBus": {
"Enabled": true,
"ConnectionString": "<SAS connection string or FQNS URL>"
}
}When Enabled is false, no connection is established and outbound publishers silently skip sending. Inbound handlers are not registered in that case - calling AddMessaging will throw if Enabled is false.
Inbound Command Handlers
All command handlers live in Voltimax.Edge.Gateway.Service.Messaging and implement IMessageHandler<TRequest, TResponse>. Each returns a typed response that is sent back to the platform via the reply queue (cloud-replies).
Core handlers
| Handler | Request | Behaviour |
|---|---|---|
PingGatewayHandler | PingGatewayRequest | Echoes a pong response with receive/respond timestamps. |
GetGatewayModeRequestHandler | GetGatewayModeRequest | Returns IGatewayModeAccessor.CurrentMode. Read-only, no side effects. |
SetGatewayModeRequestHandler | SetGatewayModeRequest | Calls IGatewayModeAccessor.SetMode and returns the previous mode. |
GetStrategyRequestHandler | GetStrategyRequest | Returns IStrategyAccessor.CurrentStrategy. Read-only. |
SetStrategyRequestHandler | SetStrategyRequest | Calls IStrategyAccessor.SetStrategy; the new strategy takes effect on the next ApplyStrategyJob tick. |
GetBatteryStatusRequestHandler | GetBatteryStatusRequest | Dispatches GetBatteryStatusQuery via Mediator and maps the result to the response contract. Returns Success = false with an error message instead of throwing. |
SetBatterySetpointRequestHandler | SetBatterySetpointRequest | Dispatches SetBatterySetpointCommand via Mediator. Rejects (returns Success = false) when the gateway is in Auto mode. |
GetEnergyFlowRequestHandler | GetEnergyFlowRequest | Dispatches AggregateEnergyFlowQuery and maps the aggregate summary to the response. |
RefreshConfigurationRequestHandler | RefreshConfigurationRequest | Dispatches RefreshConfigurationCommand via Mediator. Returns Success = false on failure without re-throwing. |
Mode guard
Any handler that applies a physical or logical change to the system rejects incoming messages when the gateway is in Auto mode (GatewayMode.Auto):
{
"Success": false,
"ErrorMessage": "Gateway is in Auto mode; manual setpoint commands are not accepted. Switch to Manual mode first."
}This mutual-exclusion prevents race conditions between the strategy executor and manual overrides from the platform.
Simulation handlers
Simulation-specific command handlers live in Voltimax.Edge.Gateway.Service.Simulation.Messaging and override physical asset values for testing and commissioning scenarios. They all extend SimulationHandlerBase<TRequest, TCommand, TResponse>, which provides the shared mode guard, logging, and command dispatch pattern.
| Handler | Request | Effect |
|---|---|---|
SetChargeStationConnectionRequestHandler | SetChargeStationConnectionRequest | Simulates a charger connection state change. |
SetChargeStationSetpointRequestHandler | SetChargeStationSetpointRequest | Overrides the charger power setpoint. |
SetGridMeterPowerRequestHandler | SetGridMeterPowerRequest | Overrides the simulated grid import/export power. |
SetLoadScaleRequestHandler | SetLoadScaleRequest | Scales simulated building load by a factor. |
SetSolarIrradianceRequestHandler | SetSolarIrradianceRequest | Overrides solar irradiance (simulates cloud cover). Clear by sending null. |
SetSolarInverterStateRequestHandler | SetSolarInverterStateRequest | Simulates the solar inverter going online or offline. |
Simulation handlers also reject commands in Auto mode, consistent with the core handlers.
Outbound Publishing
Telemetry frames
TelemetryFrameServiceBusPublisherHandler listens for SnapshotReceivedNotification via the internal Mediator notification bus. On each snapshot it:
- Converts the metric snapshot to a
TelemetryFrame(viaTelemetryFrameExtensions.FromMetricValues). - Calls
IMessageSender<TelemetryFrame>.SendAsyncto publish to thetelemetryqueue.
Publishing is skipped when ServiceBus:Enabled is false, so telemetry data is never silently lost during local runs without a Service Bus connection.
Configuration-change restart
ConfigurationUpdatedNotificationHandler handles ConfigurationUpdatedNotification. When the updated configuration carries RequiresRestart = true it calls IHostApplicationLifetime.StopApplication(), allowing the process manager (systemd or Kubernetes) to restart the service with the new configuration applied.
Adding a New Command
Define
MyRequestandMyResponserecords inVoltimax.Iot.Contracts.Messaging.Register the message type on the polymorphic builder in
MessagingExtensions.CreateTopology:csharp.MapMessage<MyRequest>()Create
MyRequestHandler : IMessageHandler<MyRequest, MyResponse>inVoltimax.Edge.Gateway.Service.Messaging.Wire the handler in
Program.ConfigureMessaging:csharp.MapMessage<MyRequest, MyRequestHandler>()
If the command is simulation-specific, extend SimulationHandlerBase instead and place the handler in Simulation/Messaging/.