TachoInsight.CardReader.Core 8.0.1

TachoInsight.CardReader.Core

CI/CD Pipeline wakatime

A clean, dependency-free .NET 10 library that parses EU tachograph driver cards (Gen1, Gen2, Gen2 v2) into strongly-typed regions and provides normalized document models.

⚠️ Pre-Release Notice: This project is in active development. Public APIs may change between releases. Not recommended for production use without careful version pinning. See CHANGELOG.md for migration notes.

Features

  • Parses all card generations: Gen1, Gen2, and Gen2 v2 card formats automatically detected
  • Two consumption patterns:
    • Region-first API for fine-grained, direct access to elementary regions
    • Document-first API for flat, normalized snapshots of card data
  • Strongly-typed models: Immutable records and interfaces; no stringly-typed data
  • No external I/O: Accepts Stream only; consumer controls file operations
  • Comprehensive diagnostics: ParseContext collects non-fatal parsing warnings
  • Little-endian byte order: Locked by unit tests covering real card dumps
  • Zero dependencies: No EF Core, UI frameworks, or external libraries

Installation

Option 1: NuGet (when published)

dotnet add package TachoInsight.CardReader.Core --version x.y.z

Option 2: Local reference (development)

cd Core
dotnet pack -c Release -o ./nupkg

# Then in your project:
dotnet add package TachoInsight.CardReader.Core --source ./Core/nupkg

Option 3: Project reference

dotnet add reference ../TachoInsight.CardReader/Core/TachoInsight.CardReader.Core.csproj

Quick Start

Region-First API (Direct Access)

Parse a card and access elementary regions:

using TachoInsight.CardReader.Core;

var manager = new DriverCardReaderManager();
manager.ProcessFile("driver_card.bin");
var card = manager.GetDriverCard();

// Access regions directly
var driverId = card.DriverId;
var cardId = card.DriverCardId;
var identity = card.Identity;           // Identification region
var activities = card.DriverActivities; // All activity records
var events = card.Events;               // Event records (speeding, tampering, etc.)
var vehicles = card.Vehicles;           // Vehicle usage records

Parse from Stream

If you manage the stream yourself:

using var stream = File.OpenRead("card.bin");
var manager = new DriverCardReaderManager();
manager.Process(stream);
var card = manager.GetDriverCard();

Keep Stream Open

For embedded or wrapped streams:

var manager = new DriverCardReaderManager();
var memoryStream = new MemoryStream(cardBytes);
manager.Process(memoryStream, leaveOpen: true);
var card = manager.GetDriverCard();
// memoryStream remains open for your use

Document Snapshot

Convert to Flat Model

For simplified access, convert IDriverCard to a normalized document:

using TachoInsight.CardReader.Core.Documents;

var manager = new DriverCardReaderManager();
manager.ProcessFile("driver_card.bin");
var card = manager.GetDriverCard();

// Create a flat snapshot
var options = new DriverCardDocumentOptions
{
    IncludeRawDescriptors = false  // Set true to include region metadata
};
var document = card.ToDocument(options);

// Access as flat model
Console.WriteLine($"Driver: {document.DriverId}");
Console.WriteLine($"Card: {document.DriverCardId}");
Console.WriteLine($"Last Download: {document.LastDownloadUtc}");
Console.WriteLine($"Activities: {document.Activities.Count}");
Console.WriteLine($"Events: {document.Events.Count}");

Access Generation 2 Data

If the card is Gen2 or Gen2 v2, access additional regions:

var document = card.ToDocument();

if (document.Gen2 != null)
{
    var borderCrossings = document.Gen2.BorderCrossings;  // GPS-based border entries
    var loadUnloads = document.Gen2.LoadUnloads;          // Load/unload events
    var gnssPlaces = document.Gen2.GnssPlaces;            // GPS waypoint records
}
else
{
    // Card is Gen1 only
}

Supported Card Generations

Feature Gen1 Gen2 Gen2 v2
Driver identification
Activity records
Event records
Vehicle records
Border crossings
Load/unload entries
GPS places (waypoints)

Limitations:

  • Gen1 cards have no GPS or extended Gen2 regions; accessing document.Gen2 will return null
  • Partial cards (missing optional regions) are parsed gracefully; missing regions result in empty collections
  • Malformed or truncated cards may throw InvalidOperationException if mandatory regions cannot be read
  • Byte order is always little-endian and matches the EU tachograph specification

Troubleshooting

Invalid Operation: Card Stream Not Processed

Symptom:

InvalidOperationException: Driver card stream not processed yet.
Invoke Process(Stream) or ProcessFile(path) before calling GetDriverCard().

Solution: Ensure you call Process() or ProcessFile() before GetDriverCard():

var manager = new DriverCardReaderManager();
manager.ProcessFile("card.bin");  // ← Don't skip this
var card = manager.GetDriverCard();

Partial Card Parsing (Empty Collections)

Symptom: Some regions are empty even though the card appears valid.

Cause: The card dump may be truncated or missing optional regions (especially Gen2 regions on Gen1 cards).

Solution: Check the card generation and available regions:

var card = manager.GetDriverCard();

if (card.Identity == null)
    Console.WriteLine("ERROR: Card is missing mandatory identification region");

if (card.DriverActivities.Count == 0)
    Console.WriteLine("WARNING: No activity records found; card may be empty or truncated");

var document = card.ToDocument();
if (document.Gen2 == null)
    Console.WriteLine("INFO: This is a Gen1 card; Gen2 regions are not available");

Diagnostics (Non-Fatal Warnings)

The parser collects non-fatal warnings in ParseContext during parsing. By default, diagnostics are discarded after parsing completes. Fatal errors (missing mandatory regions, malformed data) throw exceptions immediately.

If you need to inspect diagnostics, access them through the internal DriverCardProcessor:

// Note: This requires internal API access; not recommended for production use
// Diagnostics are collected but not exposed in the public IDriverCard API

Unsupported or Corrupted FID (File ID)

Symptom: Parser skips unexpected regions without error.

Cause: The card contains regions (FIDs) that the parser doesn't recognize or handle.

Solution: This is non-fatal. The parser will skip unknown FIDs and continue parsing known regions. All standard EU tachograph FIDs are supported.

Recent Migration Notes

The library underwent significant refactoring in recent versions. If upgrading, note these breaking changes:

Diagnostics (ParseContext replaces static diagnostics)

Before: Diagnostics were collected statically and required manual access to internal types.

Now: All parsing uses ParseContext to collect non-fatal warnings. Diagnostics are discarded by default; fatal errors throw exceptions immediately.

Action: Remove any static diagnostic accessor calls. The public API does not expose diagnostics; use exception handling for error cases.

Region Registry (RegionFactory switched to reflection-based registry)

Before: RegionFactory used switch statements based on FID values.

Now: RegionFactory uses a reflection-based registry with RegionDescriptor metadata for automatic type discovery.

Action: No action required for consumers. Internal parsing is unchanged from user perspective.

Documents & Snapshots (New ToDocument extension)

Before: Direct access to elementary regions was the only API.

Now: New DriverCardDocument record provides a flat, normalized snapshot via card.ToDocument() extension method.

Action: Optional. Use IDriverCard directly for region-based access, or call ToDocument() for simplified access.

For detailed migration information, see CHANGELOG.md.

API Reference

Parsing & Management

DriverCardReaderManager (class)

High-level manager that coordinates parsing and exposes a clean API for building driver card models.

Key Methods:

  • void ProcessFile(string path) — Parse a card from file path
  • void Process(Stream stream) — Parse a card from a stream (closes stream after parsing)
  • void Process(Stream stream, bool leaveOpen) — Parse with control over stream ownership
  • IDriverCard GetDriverCard() — Return the parsed card model after processing

Example:

var manager = new DriverCardReaderManager();
manager.ProcessFile("card.bin");
var card = manager.GetDriverCard();

IDriverCardReaderManager (interface)

Public contract for the manager. Same methods as above.

Card Models

IDriverCard (interface)

Read-only view of parsed card data. Provides access to elementary regions.

Key Properties:

  • string DriverCardId — Unique card identifier
  • string DriverId — Driver identifier from card
  • Identification Identity — Driver identity region
  • IReadOnlyCollection<CardDriverActivity> DriverActivities — Activity records
  • IReadOnlyCollection<EventRecord> Events — Event records
  • IReadOnlyCollection<CardVehicleRecord> Vehicles — Vehicle usage records
  • DateTime LastCardDownload — Timestamp of last download
  • DrivingLicenseInfo LicenseInfo — License information (if present)

DriverCard (sealed class)

Concrete implementation of IDriverCard. Created via DriverCardReaderManager.GetDriverCard().

Document Model (Normalized Snapshot)

DriverCardDocument (record)

Flat, normalized representation of card data for simplified consumption. Create via extension method:

var document = card.ToDocument(new DriverCardDocumentOptions
{
    IncludeRawDescriptors = false
});

Key Properties:

  • string DriverCardId — Card identifier
  • string DriverId — Driver identifier
  • CardIdentity? Card — Card identity (issuer, number, dates)
  • CardHolderIdentity? Holder — Card holder personal information
  • DriverLicense? License — License information
  • DateTime LastDownloadUtc — Last download timestamp
  • IReadOnlyList<ActivityInterval> Activities — Flattened activity records
  • IReadOnlyList<CardEventRecord> Events — Normalized event records
  • IReadOnlyList<VehicleUseRecord> Vehicles — Vehicle usage records
  • Gen2Section? Gen2 — Generation 2 data (null for Gen1 cards)

DriverCardDocumentOptions (sealed class)

Configuration for document creation.

Properties:

  • bool IncludeRawDescriptors { get; init; } — Include raw region descriptors (default: false)

Extension Method: ToDocument()

public static DriverCardDocument? ToDocument(this IDriverCard? card,
    DriverCardDocumentOptions? options = null)

Converts IDriverCard to DriverCardDocument. Returns null if card is null.

Elementary Regions (Region-First API)

These are accessed via properties on IDriverCard. All implement ElementaryRegion base class.

Identification (class)

Driver identification block from the card.

Key Properties:

  • string? Surname — Driver surname
  • string? FirstNames — Driver first names
  • DateTime DateOfBirth — Driver date of birth
  • string? IssuingMemberState — Issuing country code
  • string? CardNumber — Card number/serial
  • DateTime CardIssuedDate — Card issue date
  • DateTime CardValidityEnd — Card expiration date

CardDriverActivity (class)

Activity records parsed from the card (daily activity log).

Key Properties:

  • int DayCounter — Day sequence number
  • List<CardActivityDailyRecord> Records — Daily activity entries with start/end times and activity types

EventsData (class)

Event records (speeding violations, tampering events, etc.).

Key Properties:

  • List<EventRecord> Records — List of recorded events with timestamps and event types

CardVehiclesUsed (class)

Vehicle usage records.

Key Properties:

  • List<CardVehicleRecord> Records — List of vehicles used with registration and usage details

Generation 2 Regions

Accessed via document.Gen2 (if not null) or via IDriverCard properties. Gen1 cards have no Gen2 regions.

BorderCrossing (class)

Border crossing entries with GPS coordinates.

LoadTypeEntries (class)

Load type change records (empty, partially loaded, fully loaded).

GNSSPlaces (class)

GPS waypoint records with coordinates and timestamps.

Gen2Section (record, in DriverCardDocument)

Container for normalized Gen2 data accessed via document model:

if (document.Gen2 != null)
{
    var borders = document.Gen2.BorderCrossings;     // List<BorderCrossingItem>
    var loads = document.Gen2.LoadUnloads;           // List<LoadUnloadItem>
    var places = document.Gen2.GnssPlaces;           // List<GnssPlaceItem>
}

Common Document Types

ActivityInterval (record)

Normalized activity entry in DriverCardDocument.Activities:

public record ActivityInterval(
    DateTime StartUtc,
    DateTime EndUtc,
    ActivityType ActivityType,
    string? Odometer = null
)

CardEventRecord (record)

Normalized event entry in DriverCardDocument.Events:

public record CardEventRecord(
    DateTime OccurredUtc,
    string EventType,
    string? Details = null
)

VehicleUseRecord (record)

Vehicle usage entry in DriverCardDocument.Vehicles:

public record VehicleUseRecord(
    string VehicleRegistration,
    DateTime FirstUseUtc,
    DateTime LastUseUtc
)

Contributing & Tests

Run Tests

cd Core
dotnet test

Test Coverage

  • Real card samples in driverCards/ folder — tests against actual EU tachograph dumps
  • Synthetic tests — programmatically built test data for edge cases
  • Document mapping tests — verify normalization and transformation logic
  • Region-specific tests — AtrInfo, BorderCrossing, LoadTypeEntries, GnssPlaces, etc.

Contributing Guidelines

  1. Code Quality:

    • Keep the library free of UI and external I/O dependencies
    • Use immutable types (records, sealed classes) for public APIs
    • Add XML documentation to all public members
  2. Testing:

    • Add unit tests for new region parsing logic
    • Use MemoryStream based tests; avoid file I/O in unit tests
    • Test against real card samples if available
  3. Documentation:

    • Update CHANGELOG.md for breaking changes
    • Keep README.md synchronized with public API changes
    • Document migrations and breaking changes clearly
  4. Byte Order & Compatibility:

    • Never modify CustomBinaryReader byte-order logic
    • All reads are little-endian and locked by tests
    • Changes must pass byte-order tests against real cards

License

Licensed under the terms specified in the LICENSE file in the repository root.

No packages depend on TachoInsight.CardReader.Core.

.NET 10.0

  • No dependencies.

Version Downloads Last updated
8.0.1 8 12/24/2025
8.0.0 1 12/24/2025
8.0.0-ci0004 1 12/24/2025
8.0.0-ci.3 1 12/24/2025
8.0.0-ci.1 1 12/24/2025
7.0.0 1 12/24/2025
4.0.0 1 12/21/2025
3.0.0 1 12/21/2025
2.0.0 1 12/21/2025
1.0.0 1 12/21/2025
0.1.0 1 12/24/2025