Darp.Fairino.Testing 0.9.0

dotnet add package Darp.Fairino.Testing --version 0.9.0
                    
NuGet\Install-Package Darp.Fairino.Testing -Version 0.9.0
                    
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="Darp.Fairino.Testing" Version="0.9.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Darp.Fairino.Testing" Version="0.9.0" />
                    
Directory.Packages.props
<PackageReference Include="Darp.Fairino.Testing" />
                    
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 Darp.Fairino.Testing --version 0.9.0
                    
#r "nuget: Darp.Fairino.Testing, 0.9.0"
                    
#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 Darp.Fairino.Testing@0.9.0
                    
#: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=Darp.Fairino.Testing&version=0.9.0
                    
Install as a Cake Addin
#tool nuget:?package=Darp.Fairino.Testing&version=0.9.0
                    
Install as a Cake Tool

Darp.Fairino.Testing

An in-memory Fairino robot controller for testing code written against Darp.Fairino. A real FairinoRobot connects to a FakeFairinoController instead of a network address, so the code under test runs unchanged.

The fake knows the channels of a robot, not its commands. It records what arrives on each channel, and the test says what comes back. It is no simulator: a command changes nothing, and there are no joints, no kinematics and no I/O behind it.

Install

dotnet add package Darp.Fairino.Testing

The package depends on exactly the Darp.Fairino version it was built with. Update both together.

Usage

using Darp.Fairino;
using Darp.Fairino.Testing;

var controller = new FakeFairinoController();
await using FairinoRobot robot = await controller.ConnectRobotAsync();

await robot.SetDOAsync(id: 1, status: 1, smooth: 0, block: 1);

controller.Commands.ShouldHaveSingleItem().ShouldBe(new FakeCommand("SetDO", [1, 1, 0, 1]));

Any command is recorded and answered with success and no values. That needs no setup, also for a command the SDK does not have and that you send with robot.SendCommandAsync(...).

ConnectRobotAsync returns with Commands empty: the two commands of its connect are not listed. CreateRobot returns a robot that is not connected yet; the commands of a connect the test does itself are listed. Both take an optional logger, a callback that configures the robot and FakeRecoveryTimings, which set how the robot recovers a lost state connection. With dependency injection, register the fake after the robot:

services.AddFairinoRobot().AddFakeFairinoController(controller);

Commands that return a value

A command that returns a value has to be told what comes back. Unscripted, its call fails with an SdkError that names the method and says so:

controller.AnswerNext("GetWeldingCurrent", FakeResponse.Values(182.5)); // once; the values of the controller
controller.AnswerNext("GetInverseKin", joints);                         // once; the result itself
controller.Handle("GetInverseKin", _ => joints);                        // every time
controller.Handle("GetSysVarValue", command => FakeResponse.Values(command.Arguments[0]));
controller.FailNext("MoveC", FairinoErrorCode.TheRobotIsNotEnabled);    // once

The answers and failures queued for a method are used first, in the order they were queued. Then its handler answers, and without one the command succeeds with no values.

Answer What the call returns
FakeResponse.Values(values…) What its decoder reads from the values, as from those of a controller.
A result, FakeResponse.Result(…) The result as it is. The call must read that type.
FakeResponse.Error(code, message) A RobotError with the code and the message the SDK creates for it.
FakeResponse.Failure(message) An SdkError with the message.

Values are in the units of the controller, so joints are six numbers in degrees: FakeResponse.Values([.. joints.ToControllerJointsInDegrees()]). A result is returned without decoding, in the units of the SDK.

The SDK asks the controller for kinematics before some moves: MoveLAsync(pose, …) sends GetInverseKin and MoveJAsync(joints, tool, user) sends GetForwardKin. Script those, or call the overloads that take both the joints and the pose.

An unscripted command is told from one without a result by the decoder of its call, which rejects an answer without values. A raw SendCommandAsync that returns the values as they are accepts it and returns no values.

A handler that takes the cancellation token of the call can wait, which keeps a move in progress:

var finished = new TaskCompletionSource();
controller.Handle("MoveJ", async (_, cancellationToken) =>
{
    await finished.Task.WaitAsync(cancellationToken);
    return FakeResponse.Values();
});

Task<Result<Success, FairinoError>> move = robot.MoveJAsync(joints, pose, tool: 0, user: 0); // pending
finished.SetResult();
await move;

The call also ends when it is cancelled, the robot disconnects or the controller becomes unreachable. A handler that throws or returns null fails its call with an SdkError that names the method.

What the robot sent

controller.Commands lists every XML-RPC call, oldest first, with the arguments as the SDK passed them: joints are a JointPos in radians, a pose is a Euclidean3d in metres, a whole number is an integer and a floating-point number a double. MoveL, MoveC and Circle have one argument, an RpcValue.Flat(...) list. The other channels are recorded the same way:

Record An entry
UdpFrames A UDP frame: its Text as it was sent, and its Count, CommandId and Content.
FileTransfers An upload or a download: Kind, the Content, and Succeeded.
StateConnections A realtime-state connection: the configured Fields and Period, IsStarted, IsOpen.
Links The link of one ConnectAsync: its Ip, IsDisposed and WasUsedAfterDisposal.

A state connection and a link stay listed after they were closed, so a recovery is one more entry in StateConnections; StateConnectionAttempts also counts the attempts that failed. ClearRecords() empties Commands, UdpFrames and FileTransfers.

controller.Records lists the commands, UDP frames and file transfers together, in the order they arrived. A FakeFileTransfer compares by value like a FakeCommand, so a test can say that a file went between two commands:

await robot.LuaUploadAsync("pick.lua", "-- pick");

controller.Records.ShouldBe([
    new FakeCommand("FileUpload", [0, "pick.lua"]),
    new FakeFileTransfer(FakeFileTransferKind.Upload, "-- pick"u8),
    new FakeCommand("LuaUpLoadUpdate", ["pick.lua"]),
]);

An upload is recorded with its bytes. The file channel carries no name: the name of a file is an argument of the FileUpload or FileDownload command in Commands. A download delivers the bytes the test queued, and fails with an SdkError when none are queued:

controller.QueueDownload("-- pick"u8);
await robot.LuaDownloadAsync("pick.lua", directory);

Realtime states

A state is what the test sets. The fake sends the states a robot configured once per configured period, and a field that was never set as zeros.

var time = new FakeTimeProvider();
var controller = new FakeFairinoController(time);
await using FairinoRobot robot = await controller.ConnectRobotAsync(
    new FairinoRobotStateConfiguration([FairinoRobotStates.MotionDone], TimeSpan.FromMilliseconds(8))
);
Task<Result<Success, FairinoError>> done = robot.WaitForMotionDoneAsync();

controller.SetState(FairinoRobotStates.MotionDone, 1);
await controller.PublishStateAsync();
await done;

SetState takes the value as the SDK returns it and sends it as the controller does, so a JointPos goes out in degrees. For anything that cannot express, such as a field with a parser of your own, SetStateBytes(field, bytes) takes the bytes on the wire. A field you define yourself is configured and sent like a field of FairinoRobotStates.

The stream is paced by the clock of the controller, which the robots it creates share; pass a FakeTimeProvider to control it. PublishState() sends a frame now without advancing the clock. PublishStateAsync() also waits until every robot has processed the frame: by then it is the robot's last state, and every wait that was started before, such as GetNextStateAsync or WaitForMotionDoneAsync, was offered it. The observers of WhenStateReceived may see it a moment later.

Faults

controller.IsReachable = false;                            // commands fail, the recovery keeps retrying
controller.DropStateConnection();                          // the robot recovers the connection
controller.RefuseStateConnections("The port is closed.");  // only that fails, until AcceptStateConnections()
controller.FailNextUdpSend("The socket is gone.");         // once; the frame does not arrive
controller.FailNextFileTransfer("The transfer broke.");    // once; listed in FileTransfers as failed
using (controller.HoldUdpSends()) { … }                    // UDP sends stay pending until the hold is disposed

The faults of a channel fail with an SdkError with the message given. While the controller is not reachable nothing reaches it: nothing is recorded, no queued answer, download or fault is used, and a UDP send succeeds into the void.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in 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
0.9.0 84 11/3/2026
0.8.0 96 10/31/2026