Xorvyx.Xfs 1.0.6

dotnet add package Xorvyx.Xfs --version 1.0.6
                    
NuGet\Install-Package Xorvyx.Xfs -Version 1.0.6
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Xorvyx.Xfs" Version="1.0.6" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Xorvyx.Xfs" Version="1.0.6" />
                    
Directory.Packages.props
<PackageReference Include="Xorvyx.Xfs" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Xorvyx.Xfs --version 1.0.6
                    
#r "nuget: Xorvyx.Xfs, 1.0.6"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Xorvyx.Xfs@1.0.6
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Xorvyx.Xfs&version=1.0.6
                    
Install as a Cake Addin
#tool nuget:?package=Xorvyx.Xfs&version=1.0.6
                    
Install as a Cake Tool

Xorvyx.Xfs

The developer documentation includes editable Word guides for every device, shared API guides, Markdown editions and regeneration tools.

The WPF Device Workbench provides per-device connections, information-query buttons, result inspection, JSON export, and live events.

Xorvyx.Xfs is a single managed .NET 10 library for the legacy CEN/XFS 3.50 Manager API. The application and assembly are named Xorvyx.Xfs, with C# namespaces rooted at Xorvyx.Xfs; the supplied CWA headers declare the wire-level API and service version as 3.50 (0x3203).

Current implementation slice

  • One distributable assembly: Xorvyx.Xfs.dll.
  • Windows-only target: net10.0-windows.
  • Explicit x86 and x64 builds.
  • XFS startup/cleanup and logical-service open/close.
  • Service-version negotiation from 3.00 through 3.50 for every device class, with per-device configurable ranges.
  • All 17 service-class IDs and their standard status, capabilities, and reset command IDs.
  • Typed status and capability snapshots for every service class, with named service-specific fields.
  • Provider-default reset for every class that defines reset; VDM has no standard reset.
  • All 14 TTU Execute commands with synchronous/Begin APIs, negotiated 3.00-3.50 compatibility, five event mappings and version-aware workbench forms; see TTU compatibility and acceptance status.
  • Shared Execute transport for Reset and SIU event setup, plus 18 typed PTR execute commands across negotiated service versions 3.00–3.50, with per-command/feature availability checks, workbench parameter forms and event decoding; see the Execute implementation plan and PTR version matrix.
  • Typed CDM and CIM logical/physical cash-unit information, including CIM note counts and configured note IDs.
  • All 23 typed CDM execute commands with negotiated 3.00–3.50 compatibility, version-aware events and workbench forms, plus a locked exchange workflow; see the CDM matrix and acceptance status.
  • All 31 typed CIM execute commands with negotiated 3.00–3.50 layouts, feature guards, 23 event mappings, workbench forms and locked cash-in/exchange workflows; see the CIM version matrix and acceptance status.
  • All 20 typed IPM execute commands across negotiated 3.10–3.50, with feature guards, 18 event mappings, workbench forms and a locked media-in workflow; see the IPM version matrix and acceptance status.
  • All five VDM Execute commands with synchronous/Begin APIs, negotiated 3.00�3.50 compatibility, version-aware workbench forms and five event mappings; see VDM compatibility and acceptance status.
  • All 54 PIN execute commands and 15 typed encrypted-I/O operations, with synchronous/Begin APIs, negotiated 3.00–3.50 guards (including 3.02), 13 event mappings, masked workbench inputs, redacted results and locked authentication/key-loading sequences; see PIN compatibility and acceptance status.
  • All 19 typed IDC execute commands across negotiated 3.00–3.50, with feature guards, packed input/output codecs, 16 event mappings and workbench editors; see the IDC version matrix and acceptance status.
  • Typed CHK scanner status and capabilities (media, ink, imaging, MICR/OCR, feeder, encoder, and guidance lights).
  • Config-driven, process-wide typed device registry with one class/list per XFS service class.
  • Failed or disabled configured devices remain available with NoDevice status and unreported detail fields or empty unit collections.
  • Shared XFS event window with subscription-based device-status monitoring.
  • Device-level change notifications, debounced event-triggered status/service-data refresh, and configurable periodic reconciliation.
  • Packed native layouts, bounded native readers, deterministic WFSFreeResult, and runtime layout validation.
  • Process-wide diagnostics through CentralLogger.Client.

Additional nested vendor fields, typed reset destinations, and service-specific event payloads are future implementation slices.

Example

using Xorvyx.Xfs;

using var runtime = XfsRuntime.Start();
using var cdm = runtime.Open(
    logicalName: "CashDispenser1",
    serviceClass: XfsServiceClass.CashDispenser);

var status = cdm.GetStatus();
var capabilities = cdm.GetCapabilities();
var cashUnits = cdm.GetCashUnitInfo();

cdm.DeviceStatusChanged += (_, change) =>
    Console.WriteLine($"{change.LogicalName}: {change.DeviceState}");
cdm.StartMonitoring();

// Reset is deliberately explicit and is never retried automatically.
var reset = cdm.Reset();

Waitable commands and completion events

Every implemented typed Execute has an additive Begin... counterpart on the device and connection. Existing synchronous methods keep their signatures and behavior. Every successful waitable command produces one CommandCompletedEvent<T>, including commands with no native progress events or output. Its Name identifies reviewed completions such as CardEjectedEvent, DeviceResetEvent, CashDispensedEvent and CashPresentedEvent; other commands use CommandCompletedEvent.

using var eject = cardReader.BeginEjectCard(
    new IdcEjectCardRequest(), commandTimeout: TimeSpan.FromSeconds(20));

var evt = eject.WaitForEvent(TimeSpan.FromSeconds(25));
if (evt?.Name == "CardEjectedEvent" && evt.Succeeded)
{
    // Eject is done; independently observe the customer taking the card.
    evt = eject.WaitForEvent(TimeSpan.FromSeconds(30)); // CardTakenEvent, or null
}

WaitForEventAsync(timeout, cancellationToken) is available for UI/async code. device.WaitForEvent(timeout) reads a shared device inbox; the returned operation has its own independent inbox and is preferable when several components use one device. Progress events can precede the completion: check IsCommandTerminal and Succeeded before the next dependent command. Typed completion Data contains XfsExecuteResult<T> with Output, result code and separate unlock status. operation.Completion provides the same command outcome without consuming events.

A command deadline produces TimeoutEvent. An event-wait deadline returns null and leaves execution running. Cancelling a wait token affects only that wait; operation.RequestCancel() requests targeted native cancellation. A timeout/cancel does not establish that cash or media movement stopped. Late native completions are retained as LateCompletionEvent; unresolved requests keep their input memory and block competing execution. Reconcile uncertain outcomes before explicitly calling AcknowledgeCommandRecovery() or closing/reopening the connection.

Dispense completion authorizes the application to inspect the returned denomination and explicitly choose BeginPresent; the library does not automatically present. See the compiled, deferred waitable examples for eject/removal, dispense/present and reset. Async transaction callbacks and workbench adoption are separate follow-ups. Managed tests do not establish hardware support; installed provider testing is still required.

Configured device registry

Applications that reference the DLL can load every logical service once and then use typed static lists:

using Xorvyx.Xfs.Devices;

var load = XfsDevices.Load("devices.json");

CashDispenserDevice dispenser = XfsDevices.CashDispensers[0];
CashAcceptorDevice acceptor = XfsDevices.CashAcceptors[0];
CheckScannerDevice scanner = XfsDevices.CheckScanners[0];
ItemProcessorDevice itemProcessor = XfsDevices.ItemProcessors[0];
CardReaderDevice cardReader = XfsDevices.CardReaders[0];

Console.WriteLine(dispenser.Status.DeviceState);
Console.WriteLine(dispenser.CashUnits.Count);
Console.WriteLine(dispenser.Status.ShutterAt(CashDispenserPosition.Left));
Console.WriteLine(acceptor.CashUnits.Count);
Console.WriteLine(scanner.ScannerCapabilities?.HasMicr);

// Standard CHK services do not define storage bins. Self-service check storage is exposed by IPM.
itemProcessor.RefreshMediaBinInfo();
foreach (var bin in itemProcessor.MediaBins)
    Console.WriteLine($"{bin.Type}: items={bin.Count}, accepted={bin.MediaInCount}, retracts={bin.RetractOperations}");

// Motorized IDC status contains the persistent number of retained cards.
Console.WriteLine($"Retained cards: {cardReader.Status.CardsRetained}");

Console.WriteLine(cardReader.Capabilities.Type); // Motorized, LatchedDip, Contactless, etc.
Console.WriteLine(cardReader.Status.MediaState);

var printer = XfsDevices.Printers[0];
Console.WriteLine(printer.Status.Paper(PrinterPaperSupply.Upper)); // Full, Low, Out, etc.

// Capabilities are loaded during connection and normally do not need refreshing.
// Status and cash-unit data can be refreshed independently whenever current data is needed.
dispenser.RefreshStatus();
dispenser.RefreshCashUnitInfo();

// Uses timeoutSeconds from devices.json unless a per-call timeout is supplied.
XfsResetOutcome reset = dispenser.ResetAndWait(TimeSpan.FromSeconds(60));
Console.WriteLine(reset.Succeeded ? "Reset completed" : reset.Error);

// Human-readable status, capabilities, flags, guidance lights, and cash units.
string json = XfsDeviceDetailsJson.Serialize(dispenser);
XfsDeviceDetailsSnapshot snapshot = XfsDeviceDetailsJson.CreateSnapshot(dispenser);

XfsDevice? namedDevice = XfsDevices.Get("main-dispenser");
Console.WriteLine(namedDevice?.Name); // CashDispenser, even from the XfsDevice base class.
XfsDevices.Shutdown();

For a monitoring client, subscribe before Load so the initial observations are included:

XfsDevices.DeviceChanged += (_, change) =>
{
    // Queue this quickly for the monitoring server; do not perform network I/O here.
    string snapshotJson = XfsDeviceDetailsJson.Serialize(change.Snapshot, writeIndented: false);
    Console.WriteLine($"{change.DeviceId}: {change.Reason}: {snapshotJson}");
};
XfsDevices.Load("devices.json");

DeviceChanged is also available on each XfsDevice. Each notification contains a human-readable snapshot, an observation time, a reason (InitialLoad, ManualRefresh, XfsEvent, EventRefresh, Poll, or Error), and the original event ID/name/class/result when an XFS event triggered it. XfsEvent notifications preserve each raw event and carry the latest cached snapshot; EventRefresh is the subsequent current status and unit snapshot. A short debounce combines related events into one query, but does not discard their notifications. The periodic Poll is a reconciliation observation even when the device reports no changed values.

Monitoring information beyond status and capabilities

For a 3.50 service, the initial refresh also requests WFS_INF_API_SERVICE_INFO. The resulting device.ServiceInfo and JSON connection.serviceInfo contain reported model and serial information, firmware/software versions, Service Provider version, compound-device relationships, supported commands, GetInfo categories, events, and end-to-end authentication requirements. Providers that do not support an optional category leave that monitoring section absent instead of failing the complete device refresh.

Typed devices expose the monitoring data that is useful operationally:

  • CashDispenserDevice: cash-unit counts plus the most recent dispense/present status.
  • PinPadDevice.SecurityInfo: key metadata, PCI PTS identity, and logical-HSM inventory; secret key data is never returned.
  • CashAcceptorDevice.MonitoringInfo: banknote configuration, count accuracy, physical locks, and recent cash-in/present outcomes.
  • ItemProcessorDevice.MonitoringInfo: media-in outcome and media-bin sensor/capacity information.
  • CardReaderDevice.MonitoringInfo: IFM certification identifiers and supported contactless applications.
  • BiometricsDevice.MonitoringInfo: template format/count inventory and non-secret key metadata.
  • SIU automatic-startup configuration and the active VDM maintenance interface when supported.

Static inventory is cached for the lifetime of the connection. Dynamic cash, bin, lock, count-accuracy, and recent-operation information is updated by the normal event/poll refresh path.

Each JSON entry has a unique id, a descriptive type, and the XFS Manager logicalName:

{
  "loading": {
    "maximumParallelDeviceLoads": 4
  },
  "monitoring": {
    "statusPollIntervalSeconds": 60,
    "eventRefreshDebounceMilliseconds": 500,
    "maximumQueuedEvents": 256,
    "sendSnapshotOnConnect": true
  },
  "devices": [
    {
      "id": "main-dispenser",
      "type": "CashDispenser",
      "logicalName": "CashDispenser1",
      "serviceProviderName": "Vendor CDM SP",
      "applicationId": "MyAtmApp",
      "minimumServiceVersion": "3.00",
      "maximumServiceVersion": "3.50",
      "timeoutSeconds": 30,
      "monitorEvents": true,
      "enabled": true,
      "connectionProperties": {
        "terminal": "ATM-01"
      }
    }
  ]
}

maximumParallelDeviceLoads defaults to 4. During Load, distinct logical services are opened and initialized concurrently up to this limit, so one slow provider does not block every service behind it. Entries sharing the same logicalName remain sequential because XFS applications must synchronize access to the same service. Set the value to 1 to restore sequential loading, or raise it carefully up to 64 when the installed XFS Manager and Service Providers handle more concurrent initialization requests reliably.

statusPollIntervalSeconds defaults to 60; set it to 0 to disable polling. Polls and event refreshes query dynamic status and service-specific unit/bin information, but do not re-query capabilities. Use RefreshCapabilities() only when needed. eventRefreshDebounceMilliseconds defaults to 500 and may be set to 0 for immediate queries. maximumQueuedEvents defaults to 256 and bounds memory use during an event storm; overflow is dropped and reported through an aggregated log entry. sendSnapshotOnConnect defaults to true. Event monitoring still requires the device's monitorEvents setting; polling can run independently of it.

Some Service Providers require every XFS application ID to end with a four-byte suffix published in shared memory. Enable that compatibility behavior on the device that uses such an SP with CustomeAppName:

{
  "devices": [
    {
      "id": "main-dispenser",
      "type": "CashDispenser",
      "logicalName": "CashDispenser1",
      "applicationId": "MyAtmApp",
      "CustomeAppName": true
    }
  ]
}

CustomeAppName defaults to false. When any enabled device sets it to true, that device is opened first and the resulting suffix is applied to every configured XFS session in the process. The runtime tries the required Local\KaligniteAppName/Local\KaligniteAppNameSuffix object pair first, then the corresponding Global\ pair. If neither mapping exists yet, it creates and initializes the 12-byte Local mapping before the first WFSOpen, keeps that mapping alive for the complete XFS runtime, and passes the generated four-byte suffix on the first connection attempt. The suffix value is never written to diagnostics.

The mutex timeout remains configurable when needed by adding the advanced root option alongside CustomeAppName:

"runtimeOptions": {
  "applicationIdSuffix": {
    "mutexTimeout": "00:00:30"
  }
}

type supplies XfsDevice.Name and determines the XFS service class for recognized types such as CardReader, CashDispenser, CashAcceptor, and ReceiptPrinter. Multiple devices may share a type/name; id remains the unique lookup key. logicalName is the service name registered with the local XFS Manager (the value passed to WFSOpen). The optional serviceClass remains supported for older configurations or custom type names. If both type and serviceClass are supplied for a recognized type, they must agree. If startup or an individual open fails, Load returns the error and the configured object remains in XfsDevices.All with Status.DeviceState == XfsDeviceState.NoDevice. After WFSOpen succeeds, the device remains connected even when an initial status, capabilities, service-data, or event-registration operation fails. Those operations are attempted independently, successful responses are retained, and status is StatusUnavailable until an actual device-status response is received.

Split SIU logical services

SIU is one XFS service class whose status and capabilities contain separate sensor, door, indicator, auxiliary/audio, and guidance-light arrays. When the vendor registers separate logical service names for those groups, configure each logical service as its own typed device:

{
  "devices": [
    { "id": "siu-audio", "type": "Audio", "logicalName": "SIUAudio" },
    { "id": "siu-doors", "type": "Doors", "logicalName": "SIUDoors" },
    { "id": "siu-sensors", "type": "Sensors", "logicalName": "SIUSensors" },
    { "id": "siu-indicators", "type": "Indicators", "logicalName": "SIUIndicators" },
    { "id": "siu-guidance", "type": "GuidLights", "logicalName": "SIUGuidLights" }
  ]
}

The aliases SiuAudio, SiuDoors, SiuSensors, SiuIndicators, and SiuGuidanceLights are also accepted. All of these remain XfsServiceClass.SensorsAndIndicators, but are exposed through dedicated device classes and the corresponding XfsDevices.Siu*Devices collections. Named SIU port enums and Get*Status/Get*Capabilities methods avoid array-index magic numbers.

If the vendor registers only one SIU logical name, configure one SensorsAndIndicators device. A single WFS_INF_SIU_STATUS query already returns every SIU group; configuring the same logical name five times would create redundant XFS sessions, polling, and event registrations. AlarmDevice is the separate XFS ALM service class and is not the SIU audible-alarm auxiliary.

minimumServiceVersion and maximumServiceVersion control the service-interface range passed to WFSOpen. The default is 3.00-3.50 for every service class. WFSOpen selects the highest version shared with the provider, so no fallback retry is needed. Configuration outside this supported range is rejected before opening the service.

PIN status has a version-dependent tail: lpdwPasswordState exists in the 3.50 structure but not in 3.40. The PIN parser uses the negotiated service version and does not read that field from a 3.40 provider. For 3.50, an unreadable optional password-state pointer is reported as a protocol error rather than dereferenced directly. If a vendor reports 3.50 but returns a malformed status, check the WFSOpen.Receive service version and WFSGetInfo.Failed logs and contact the vendor; invalid native data cannot be made reliable by changing the configured version range alone.

When accessed through a typed device class, Status and Capabilities expose that device's own properties. When accessed as the common XfsDevice base class, they expose the shared device-state and service-class headers. StatusDetails and CapabilityDetails always name the typed snapshots explicitly. The snapshots are populated by Load and can be updated with Refresh(). A disconnected device has NoDevice status and null detail fields or empty collections.

The vendor XFS Manager, Service Providers, device drivers, and logical-service configuration must already be installed. The process architecture must match the installed XFS stack.

Diagnostics

The library initializes one shared CentralLogger.Client instance on first use. Both its application name and application instance are xfslayer; callers do not need to initialize a logger for each connection.

Logs are separated into components:

  • Bootstrap for logger initialization.
  • Runtime for XFS Manager startup, service open/close coordination, and cleanup.
  • Interop for ABI validation and native result release.
  • SP.<service-class>.<logical-name> for each Service Provider connection.

Every native request and response emitted by the current synchronous API is logged with an operation name and correlation ID. This includes WFSOpen, WFSGetInfo, status/capability/cash-unit parsing, WFSRegister, unsolicited event reception, WFSDeregister, WFSLock, reset through WFSExecute, WFSUnlock, and WFSClose. Pointer values are never logged; only null or present is recorded. Logger failures are isolated from device communication.

The NuGet package references CentralLogger.Client 2.0.3 from nuget.org, so its client and abstraction assemblies are restored alongside Xorvyx.Xfs.dll. The XFS API itself remains a single public library assembly.

Event monitoring

Subscribe to DeviceStatusChanged and call StartMonitoring() to receive typed WFS_SYSE_DEVICE_STATUS changes. EventReceived exposes common metadata for every registered service, user, system, or execute event. Monitoring uses one process-wide message-only window, while each device has an ordered managed dispatch queue so subscriber code does not run in the native window procedure. Devices loaded through XfsDevices.Load register all four event classes when MonitorEvents is enabled.

SIU port events need a session-specific command. After registering an SIU service, the library uses its capability snapshot (or queries capabilities for a standalone connection) and executes WFS_CMD_SIU_ENABLE_EVENTS for each advertised sensor, door, indicator, auxiliary, and guidance-light port. WFS_SRVE_SIU_PORT_STATUS then arrives through EventReceived and DeviceChanged; its SiuPort payload contains the group, named or vendor port index, WORD status, DWORD status, and vendor extension strings. WFS_EXEE_SIU_PORT_ERROR also carries the port error code. SiuPort.StateName identifies standard values such as Run, Supervisor, Open, Closed, HeadsetPresent, and HeadsetAbsent. EffectiveStatus prefers the DWORD status but falls back to the legacy WORD when the provider leaves the DWORD at zero; both original fields remain available. Configured split SIU devices enable and forward only their relevant ports, avoiding duplicate device-level changes and unnecessary refreshes when a provider exposes all SIU ports through several logical services. A standalone SIU connection still monitors every advertised port. The configured device must have monitorEvents and enabled set to true and use the vendor's actual logical name.

StartMonitoring() registers system events by default. Pass an XfsEventClass flags value to register more classes. Call StopMonitoring() to deregister explicitly; disposing a connection also deregisters and closes it. XFS can already have queued an event when deregistration occurs, so applications should tolerate a final in-flight notification.

Service, user, and most system events are broadcast to every registered application. Execute events are different: XFS sends them only to the application that issued the WFSExecute request that caused them, even if other applications registered for the Execute class. Consequently, an observer can see another application's broadcast events but cannot receive that application's execute events or command-completion messages through standard XFS registration.

The device-level observer runs refreshes off the XFS event dispatcher. It does not persist notifications or deliver them to a server; consumers should enqueue promptly and implement their own durable retry/outbox if delivery must survive restarts or network outages. A post-event snapshot may show the final state after several rapid transitions, so keep the individual XfsEvent notifications as well.

Example application

The examples/Xorvyx.Xfs.Example .NET 10 console project loads the shared registry from devices.json, prints a JSON status and capability snapshot for every configured device, and monitors status changes until Ctrl+C. Standard coded values for CDM, IDC, PTR, CIM, and CHK devices are rendered as descriptive enum or flag names; unknown and vendor-defined codes remain visible as Unknown (value). Replace the sample logical names with those configured by the vendor XFS installation, then run with the architecture matching that installation:

dotnet run --project examples\Xorvyx.Xfs.Example -c Release -p:Platform=x86

Build

dotnet build Xorvyx.Xfs.slnx -c Release
dotnet build src\Xorvyx.Xfs\Xorvyx.Xfs.csproj -c Release -p:Platform=x86
dotnet build src\Xorvyx.Xfs\Xorvyx.Xfs.csproj -c Release -p:Platform=x64
dotnet pack src\Xorvyx.Xfs.Package\Xorvyx.Xfs.Package.csproj -c Release -o artifacts\packages

The distribution package contains the final Xorvyx.Xfs.dll, single-file Xorvyx.Xfs.Settings.exe, and devices.json configuration under both tools/win-x86 and tools/win-x64.

Version 1.0.6 includes the current XFS core, strongly typed device properties, and waitable command execution. It depends on CentralLogger.Client 2.0.3. The package uses an explicit file list and does not bundle .NET runtime DLLs. The settings editor is a framework-dependent single-file application and requires the .NET 10 Windows Desktop Runtime matching its architecture on the destination machine. Applications using the core library must also deploy their restored logger dependencies; those dependencies are bundled into the settings editor.

SIU exposes all 11 typed execute commands through SensorsAndIndicatorsDevice (including split SIU devices) and XfsDeviceConnection. The workbench's SIU commands tab uses the same negotiated-version validation as the library: 7 commands at 3.00, 8 at 3.10, 9 at 3.20, 10 at 3.30 and all 11 at 3.40/3.50. SiuCommands.cs in the console example supplies deferred examples for every command. SET_PORTS zero entries clear transaction/general-output bitmaps; SetIndicator can control one bitmap independently. Newer features are rejected before native allocation; provider errors and physical outcomes are retained. See SIU compatibility and validation for the matrix and remaining hardware gate.

There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.6 124 10/21/2026

Current XFS core with strongly typed device properties and waitable command execution; CentralLogger.Client 2.0.3. Includes only the XFS library, framework-dependent settings editor, and device configuration for x86 and x64; the .NET runtime is not bundled.