Building EV Integration with Greenflux CPMS
I built a .NET SDK for EV integration with Greenflux CPMS. A typical charging-app session on Charge Assist is: register an app token, attach a payment method, start, poll status, stop. That is an OCPI session lifecycle, not OCPP.
What EV integration with Greenflux CPMS looks like in code
Greenflux ships four OpenAPI surfaces. The SDK is one package with four clients. A driver app usually only needs Charge Assist.
| Client | For | Auth |
|---|---|---|
IChargeAssistClient |
App token, payment method, start / status / stop session | APIM key or OpenAPI ApiKey header |
IGreenfluxPlatformClient |
Locations, sessions, CDRs, smart charging | Platform token header |
IChargeLocationManagementClient |
Create or patch stations and locations | Platform token header |
IRemoteCommandsClient |
CPO-side start, stop, unlock | Platform token header |
Install
dotnet add package GreenfluxDotNet.Sdk
Typical charging session flow with the SDK:
using Greenflux.ChargeAssist;
using Microsoft.Extensions.DependencyInjection;
const string apiKey = "YOUR_CHARGE_ASSIST_KEY";
const string appToken = "demo-app-token";
const string externalPaymentMethodId = "demo-card";
const string locationId = "your-location-id";
const string evseUid = "your-evse-uid";
const string connectorId = "1";
const int chargingMinutes = 4;
var services = new ServiceCollection();
services.AddGreenfluxChargeAssist(options =>
{
options.ApiKey = apiKey;
options.UseAcceptanceGateway();
});
await using var provider = services.BuildServiceProvider();
var client = provider.GetRequiredService<IChargeAssistClient>();
// Register the app token (skip if it already exists).
try
{
await client.AppTokenV2_GetAsync(appToken);
}
catch (ChargeAssistApiException ex) when (ex.StatusCode is 400 or 404)
{
await client.AppTokenV2_CreateAsync(new CreateAppTokenRequest { AppToken = appToken });
}
// Register a payment method (skip if it is already on the wallet).
var wallet = await client.Payment_GetWalletAsync(appToken, locationId: null);
var existingPayment = wallet.Data?.FirstOrDefault(p =>
string.Equals(p.ExternalPaymentMethodId, externalPaymentMethodId, StringComparison.OrdinalIgnoreCase));
var paymentMethodId = existingPayment?.Id;
if (paymentMethodId is null)
{
var paymentResp = await client.Payment_AddExternalMethodAsync(appToken, new PutExternalPaymentMethodRequest
{
DisplayName = "Demo Card",
ExternalPaymentMethodId = externalPaymentMethodId,
});
paymentMethodId = paymentResp.Id
?? throw new InvalidOperationException("No payment method id returned.");
}
// Start a charging session on the chosen EVSE.
var startResp = await client.Session_StartAsync(appToken, new StartRequest
{
LocationId = locationId,
EvseUid = evseUid,
ConnectorId = connectorId,
PaymentMethodId = paymentMethodId,
});
var chargeSessionId = startResp.ChargeSessionId
?? throw new InvalidOperationException("No chargeSessionId returned.");
// Poll session status until it completes or the wait expires.
var deadline = DateTime.UtcNow.AddMinutes(chargingMinutes);
while (DateTime.UtcNow < deadline)
{
var status = await client.Session_GetStatusAsync(appToken, chargeSessionId, maxDataPoints: 15);
if (status.Status is StatusResponseStatus.COMPLETED
or StatusResponseStatus.CDR_AVAILABLE
or StatusResponseStatus.ERROR
or StatusResponseStatus.CANCELLED
or StatusResponseStatus.TIMEOUT)
break;
var delay = status.NextStatusCall.HasValue
? status.NextStatusCall.Value - DateTime.UtcNow
: TimeSpan.FromSeconds(15);
delay = TimeSpan.FromSeconds(Math.Clamp(delay.TotalSeconds, 5, 30));
await Task.Delay(delay);
}
// Stop the charging session.
await client.Session_StopAsync(appToken, new StopRequest { ChargeSessionId = chargeSessionId });
// Read the final status.
var finalStatus = await client.Session_GetStatusAsync(appToken, chargeSessionId, maxDataPoints: 15);
Charge Assist defaults to the acceptance gateway. Put retry on IHttpClientBuilder. Do not log exception bodies; they can contain session data.