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.

NuGet · GitHub

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.

Similar Posts