Grote.Daas.LiveFeedA managed gRPC stream that fans real-time trailer and telemetry updates out to your handlers, with authentication, heartbeat filtering, and reconnect handled for you.
Install the LiveFeed package for the real-time gRPC push stream. If you only need request/response queries, the REST client (documented on its own page) is a separate package you can take on its own, without gRPC and protobuf dependencies.
dotnet add package Grote.Daas.LiveFeedAuthentication is two-step: you hold a long-lived API key, and the SDK exchanges it for a short-lived JWT against the Grote auth server, then calls the data API with that token. The token is cached and refreshed automatically, so you never handle the JWT yourself.
Set Audience to match the key you were issued. Use Customer for a fleet-owner key (the default) or Partner for an integration-partner key. The wrong audience fails authentication at the auth server.
using Grote.Daas.Client;
// Resolve the key up front so a missing value fails with a clear message instead of
// silently overwriting the environment-variable fallback with null.
var apiKey = builder.Configuration["DAAS_API_KEY"];
if (string.IsNullOrWhiteSpace(apiKey))
throw new InvalidOperationException("DAAS_API_KEY is not configured.");
builder.Services.AddDaas(options =>
{
options.ApiKey = apiKey;
options.Audience = DaasAudience.Customer; // or DaasAudience.Partner
});Then inject IDaasClient anywhere. Registration goes through IHttpClientFactory and keeps the auth handler as a singleton, so the cached JWT is shared across requests.
public sealed class FleetReporter(IDaasClient daas)
{
public Task<Fleet> GetFleetAsync(int id, CancellationToken ct)
=> daas.Fleets.GetAsync(id, ct);
}For a console app, worker, or script, construct the client directly. It owns its HttpClient, so dispose it when you are done.
using Grote.Daas.Client;
using var client = new DaasClient(new DaasClientOptions
{
ApiKey = Environment.GetEnvironmentVariable("DAAS_API_KEY")!
});The Grote.Daas.LiveFeed package subscribes to the gRPC live feed and fans updates out to your handlers. Authentication, heartbeat filtering, and reconnect are handled for you, so there is no connection code or stream loop in your application. It uses the same API key and audience model as the REST client.
using Grote.Daas.LiveFeed;
builder.Services
.AddDaasLiveFeed(options =>
{
options.ApiKey = apiKey; // resolved as shown under Configure & authenticate
options.Audience = DaasAudience.Customer;
})
.AddHandler<TrailerFaultAlerter>()
.AddHandler<TelemetryArchiver>()
.OnUpdate((update, services, ct) =>
{
Console.WriteLine($"{update.Trailer.UnitId} @ {update.Ts}");
return Task.CompletedTask;
});A handler is a plain class, constructor-injected like any other service. Each update is dispatched to every registered handler inside a fresh DI scope, so handlers can depend on scoped services such as an EF Core DbContext. If one handler throws, the failure is logged, the other handlers still run, and the stream keeps going.
public sealed class TrailerFaultAlerter : ILiveFeedHandler
{
private readonly ILogger<TrailerFaultAlerter> _log;
public TrailerFaultAlerter(ILogger<TrailerFaultAlerter> log) => _log = log;
public Task HandleAsync(LiveUpdate update, CancellationToken ct)
{
if (update.Trailer.Status?.IsFault == true)
_log.LogWarning("Trailer {Unit} faulted", update.Trailer.UnitId);
return Task.CompletedTask;
}
}All handlers for a single update run inside one shared DI scope, so you can hang a per-update context off that scope: register it as scoped, populate it once, and every handler for that update reads the same instance. This keeps each handler from repeating the same lookups (for example fetching the trailer's devices) when they all work the same trailer.
Ordering is explicit. A pre-handler runs before the regular handlers and a post-handler runs after them. Every pre-handler completes before any regular handler starts, and every regular handler completes before any post-handler starts, so the phases are barriers even when DispatchMode is Parallel. Within a phase, handlers honor the configured DispatchMode. Pre- and post-handlers implement the same ILiveFeedHandler interface as a regular handler; only how you register them differs.
// A per-update context, populated once and shared by every handler for that update.
public sealed class TrailerContext
{
public IReadOnlyList<Device> Devices { get; set; } = [];
}
// Runs before the regular handlers: do the shared lookups once.
public sealed class TrailerContextLoader : ILiveFeedHandler
{
private readonly IDaasClient _client;
private readonly TrailerContext _ctx;
public TrailerContextLoader(IDaasClient client, TrailerContext ctx)
=> (_client, _ctx) = (client, ctx);
public async Task HandleAsync(LiveUpdate update, CancellationToken ct)
=> _ctx.Devices = await _client.Devices.ListAsync(update.Trailer.Id, ct: ct);
}builder.Services
.AddDaasLiveFeed(o => o.ApiKey = apiKey)
.AddScoped<TrailerContext>() // shared per-update, resolved in the same scope
.AddPreHandler<TrailerContextLoader>() // runs first, fills the context
.AddHandler<TrailerFaultAlerter>() // regular handlers read ctx.Devices
.AddHandler<TelemetryArchiver>()
.OnBeforeUpdate((update, sp, ct) => Task.CompletedTask) // inline pre, mirrors OnUpdate
.OnAfterUpdate((update, sp, ct) => Task.CompletedTask); // inline postAs with regular handlers, an exception thrown by a pre- or post-handler is logged and swallowed so the other handlers and the stream keep going. A failing pre-handler does not stop the regular handlers, so treat the context as possibly unpopulated if your loader can fail.
Reconnect itself is not optional, but the backoff is tunable, and you can observe the connection lifecycle to flip a health flag or reconcile through the REST client.
builder.Services
.AddDaasLiveFeed(o =>
{
o.ApiKey = apiKey;
o.InitialReconnectDelay = TimeSpan.FromSeconds(2);
o.ReconnectBackoffFactor = 2.0;
o.MaxReconnectDelay = TimeSpan.FromSeconds(60);
})
.OnConnected((ctx, sp, ct) => Task.CompletedTask)
.OnDisconnected((ctx, sp, ct) => Task.CompletedTask) // ctx: Error, Attempt, WillRetry, NextDelay
.OnReconnecting((ctx, sp, ct) => Task.CompletedTask); // ctx: Attempt, Delay, LastError| DaasLiveFeedOptions | Default |
|---|---|
ApiKey | DAAS_API_KEY environment variable |
BaseUrl | https://4see.groteintegrations.com/ |
AuthUrl | https://auth.groteintegrations.com/ |
Audience | Customer (or Partner) |
DispatchMode | Sequential (or Parallel for concurrent handlers) |
InitialReconnectDelay | 2 seconds |
ReconnectBackoffFactor | 2.0 |
MaxReconnectDelay | 60 seconds |
AuthTimeout | 30 seconds |
KeepAlivePingDelay / KeepAlivePingTimeout | 25s / 20s |
If you would rather own the loop, use the low-level client directly. Heartbeats are still filtered, but there is no automatic reconnect at this level.
using var client = new DaasLiveFeedClient(new DaasLiveFeedOptions { ApiKey = apiKey });
await foreach (LiveUpdate update in client.SubscribeAsync(ct))
{
// Your logic. Heartbeats are already filtered out.
}Release history for the LiveFeed package. Pin the version you install (see Install) so a later release cannot change behavior under you.
Questions about the SDK, onboarding, or requesting an API key?