Xfs351 1.0.5
dotnet add package Xfs351 --version 1.0.5
NuGet\Install-Package Xfs351 -Version 1.0.5
<PackageReference Include="Xfs351" Version="1.0.5" />
<PackageVersion Include="Xfs351" Version="1.0.5" />
<PackageReference Include="Xfs351" />
paket add Xfs351 --version 1.0.5
#r "nuget: Xfs351, 1.0.5"
#:package Xfs351@1.0.5
#addin nuget:?package=Xfs351&version=1.0.5
#tool nuget:?package=Xfs351&version=1.0.5
Xfs351
Xfs351 is a single managed .NET 10 library for the legacy CEN/XFS 3.50 Manager API. The assembly name follows the repository naming; the supplied CWA headers declare the wire-level API and service version as 3.50 (0x3203).
Current implementation slice
- One distributable assembly:
Xfs351.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.
- Typed CDM and CIM logical/physical cash-unit information, including CIM note counts and configured note IDs.
- 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
NoDevicestatus 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 Xfs351;
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();
Configured device registry
Applications that reference the DLL can load every logical service once and then use typed static lists:
using Xfs351.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:
Bootstrapfor logger initialization.Runtimefor XFS Manager startup, service open/close coordination, and cleanup.Interopfor 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.2 from nuget.org, so its client and abstraction assemblies are restored alongside Xfs351.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/Xfs351.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\Xfs351.Example -c Release -p:Platform=x86
Build
dotnet build Xfs351.slnx -c Release
dotnet build src\Xfs351\Xfs351.csproj -c Release -p:Platform=x86
dotnet build src\Xfs351\Xfs351.csproj -c Release -p:Platform=x64
dotnet pack src\Xfs351.Package\Xfs351.Package.csproj -c Release -o artifacts\packages
The distribution package contains the final Xfs351.dll, single-file Xfs351.Settings.exe,
and devices.json configuration under both tools/win-x86 and tools/win-x64.
Version 1.0.5 includes the current device monitoring and configuration changes, readable system
error and device status payloads, CIM/IPM event names and device position details, IPM toner
threshold details, and printer paper threshold details. It depends on CentralLogger.Client 2.0.2.
Learn more about Target Frameworks and .NET Standard.
-
net10.0
- CentralLogger.Client (>= 2.0.2)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Readable event payload details for system errors, device status and position, IPM toner thresholds, and printer paper thresholds; CIM/IPM event names; CentralLogger.Client updated to 2.0.2.