FT891.Simulator 2.1.0

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

FT891.Simulator

A virtual Yaesu FT-891 exposed over TCP. It behaves like the real radio's serial port: it reads ;-terminated CAT frames, validates each one, keeps a full RadioState, updates that state on a valid set, replies with a correctly formatted, byte-exact frame on a valid read, and silently drops anything malformed — logging every frame (accepted or rejected) with the reason.

Use it to develop and test a CAT client with no hardware attached.

Target framework: net48 (console app) · References FT891.Core


Running it

# default port 4000
dotnet run --project FT891.Simulator

# custom port
dotnet run --project FT891.Simulator -- 4005

…or run the built executable directly:

FT891.Simulator\bin\Debug\net48\FT891.Simulator.exe 4000

On start it prints:

[SIM] FT-891 virtual radio listening on 127.0.0.1:4000
FT-891 CAT simulator running. Connect a client (default baud-equivalent TCP).
Press Ctrl+C or Enter to stop.

Connect with any TCP client — most naturally the TcpCatTransport from FT891.Core:

using var radio = new FT891Cat(new TcpCatTransport("127.0.0.1", 4000));
radio.Connect();

The virtual radio's state

RadioState is a plain object holding the entire radio: frequency (VFO-A/B), mode, memory channel, power & TX power, split, all audio levels, RF attenuator / preamp / tuner, the full DSP chain (AGC, NR, NB, notch, IF-shift, bandwidth, contour), CW settings (+ five keyer memories), VOX, speech processor, clarifier, CTCSS/repeater, scan, and the display/UI toggles.

Defaults are chosen so a read issued before any set still returns a well-formed, parseable frame — e.g. VFO-A powers up at 14.250 MHz, VFO-B at 7.100 MHz, mode USB, TX power 50 W.


How a frame is processed

bytes in ──► assemble until ';' ──► validate ──► classify ──► act ──► log

Validation (in order)

  1. Terminator / length — at least 3 bytes and ends with ;, else rejected.
  2. 2-char alpha prefix — both leading characters must be letters, else rejected.
  3. Known command — the prefix must exist in CatSpec, else rejected (this is what makes the simulator drop unknown commands like a real radio).

READ vs SET, decided by byte count

Condition Treated as
Prefix is read-only telemetry (MR IF OI ID SM RM BY RS TS MS UL NA) READ
AC with length 6 (tuner set is one byte shorter than its reply) SET
length == the CatSpec reply length SET
length < the reply length (the short query form) READ
length > the reply length rejected (wrong byte count)

Action

  • Valid SET → ApplySet parses the payload into RadioState. No reply.
  • Valid READ → BuildResponse formats the answer out of RadioState and sends back exactly the number of bytes CatSpec promises.
  • Invalid → dropped, no reply. A parse error during handling is caught and logged rather than crashing the server.

Example log

[RX] FA014250000; -> SET FA accepted
[RX] FA;          -> READ -> FA014250000;
[RX] MD0123;      -> REJECTED: byte count 7 exceeds 5 for 'MD'
[RX] ZQ;          -> REJECTED: unknown command 'ZQ'

Embedding it (used by the tests)

SimulatorServer is a self-contained, in-process server — the integration tests spin one up on an ephemeral port and drive a real FT891Cat against it.

// port 0 → the OS picks a free port; read it back from .Port
var server = new SimulatorServer(port: 0, log: _ => { });   // quiet
server.Start();

var radio = new FT891Cat(new TcpCatTransport("127.0.0.1", server.Port));
radio.Connect();
// ... drive the radio ...

server.Stop();   // also via Dispose()
Member Purpose
SimulatorServer(int port = 4000, RadioState state = null, Action<string> log = null) Construct; inject state and/or a log sink.
Start() Bind, begin accepting clients (each handled concurrently).
Stop() / Dispose() Cancel and stop the listener.
Port The actually-bound port (meaningful after Start, esp. with port 0).
RequestedPort The port asked for.
State The live RadioState.

A virtual band, and a CW beacon

By default the S-meter reads a static value. Set SimulatorServer.SignalSource (a Func<long,int> mapping the tuned VFO-A frequency to an S-meter level 0–255) and the meter follows it instead — so a client can tune around and actually find signals. BY (busy) then derives from BusyThreshold. It's opt-in: leave it null and nothing changes.

server.SignalSource = hz => (Math.Abs(hz - 14_060_000) < 500) ? 200 : 10; // a signal on 14.060

The Morse namespace builds on this to transmit CW:

Type What it does
MorseCode International Morse tables; sanitise text, encode to a keyed timeline, decode patterns.
MorseDecoder Real-time decoder — feed it key on/off samples (e.g. from the S-meter), get text.
MorseAbbreviations Expand Q-codes / prosigns / operating shorthand into plain English.
CwBeacon Keys a block of text into Morse on a loop; plug its SignalAt into SignalSource and the S-meter on that frequency rises and falls with the keying.
var beacon = new CwBeacon(14_058_000, "CQ DE M0XYZ ES 73 K", wpm: 16);
server.SignalSource = hz => beacon.SignalAt(hz, Environment.TickCount);
// A client tuned to 14.058 now reads decodable CW on its S-meter.

FT891.Demo's Morse screen is the receiver for exactly this.


READMEs written by Claude Code Opus 4.8m Context - From CodeBase - TSGBMPPT

Product Compatible and additional computed target framework versions.
.NET Framework net48 is compatible.  net481 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
2.1.0 441 6/7/2026
2.0.0 285 6/6/2026
1.2.1 479 6/5/2026 1.2.1 is deprecated because it is no longer maintained.
1.2.0 451 6/5/2026 1.2.0 is deprecated because it is no longer maintained.