# Daqifi Core

> The official Cross-Platform .NET SDK for DAQiFi wireless data acquisition devices. Contains the official MCP Server.

- **Type:** MCP server
- **Install:** `agentstack add mcp-daqifi-daqifi-core`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [daqifi](https://agentstack.voostack.com/s/daqifi)
- **Installs:** 0
- **Category:** [Integrations](https://agentstack.voostack.com/c/integrations)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [daqifi](https://github.com/daqifi)
- **Source:** https://github.com/daqifi/daqifi-core
- **Website:** https://daqifi.com/

## Install

```sh
agentstack add mcp-daqifi-daqifi-core
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# DAQiFi Core

> **Revolutionizing the data collection experience with convenient, portable device connectivity.**
>
> The official cross-platform .NET SDK for DAQiFi wireless data acquisition devices.

[](https://www.nuget.org/packages/Daqifi.Core)
[](https://www.nuget.org/packages/Daqifi.Core)
[](https://github.com/daqifi/daqifi-core/actions/workflows/ci.yml)
[](LICENSE)

**[daqifi.com](https://daqifi.com)** · **[DAQiFi Desktop](https://github.com/daqifi/daqifi-desktop)** · **[Report an issue](https://github.com/daqifi/daqifi-core/issues)**

---

## What is DAQiFi Core?

DAQiFi builds wireless data acquisition hardware designed to get out of the way so you can focus on the data, not the collection process.

**DAQiFi Core is how you integrate that hardware into your own .NET applications** — custom dashboards, automated test rigs, research pipelines, production-monitoring tools. Discover devices, connect over WiFi or USB, stream samples in real time, configure networks, push firmware updates — all from one async, strongly-typed .NET API.

Prefer a ready-made GUI? Check out [DAQiFi Desktop](https://github.com/daqifi/daqifi-desktop), which is built on top of this library.

Want to drive a device from an AI assistant? The repo also ships an **[MCP server](src/Daqifi.Mcp)** — point Claude, Cursor, Codex, or any MCP-aware client at it to discover, configure channels, drive digital I/O, PWM and analog outputs, set the sample rate, and run SD-card logging — then list, download, and CSV the recorded data back — through plain conversation.

## See it in 30 seconds

```shell
dotnet add package Daqifi.Core
```

```csharp
using Daqifi.Core.Device;
using Daqifi.Core.Channel;

// Connect — transport and device initialization handled for you.
await using var device = await DaqifiDeviceFactory.ConnectTcpAsync("192.168.1.100", 9760);

// Subscribe to decoded, per-channel samples
var ai0 = device.GetChannelsSnapshot().First(c => c.Type == ChannelType.Analog && c.ChannelNumber == 0);
ai0.SampleReceived += (_, e) => Console.WriteLine($"{e.Sample.Timestamp}: {e.Sample.Value} V");

// Enable channel 0, then stream at 100 Hz
device.EnableChannel(ai0);
device.StreamingFrequency = 100;
device.StartStreaming();
```

A real, working program — no GUI required. Prefer the raw protobuf frame instead? Subscribe to
`device.MessageReceived` — see [Streaming Data](docs/DEVICE_INTERFACES.md#streaming-data).

## Common applications

DAQiFi hardware is in the field for work like:

- **Research labs** — moon regolith testing and similar materials studies
- **Medical R&D** — prosthetic socket pressure testing
- **Industrial monitoring** — wireless multi-channel sensing
- **Engineering education** — SCPI command structure and LabVIEW compatibility
- **Test automation** — scripted benchtop measurements

More examples at [daqifi.com](https://daqifi.com).

## Where DAQiFi Core fits

| Layer | What it is |
|---|---|
| Hardware | Nyquist 1 / Nyquist 3 — wireless DAQ devices (and their on-device firmware) |
| **SDK** | **DAQiFi Core — this library** |
| App | [DAQiFi Desktop](https://github.com/daqifi/daqifi-desktop) — GUI built on this SDK |
| Agent | [MCP server](src/Daqifi.Mcp) — drive a device from Claude / Cursor / any MCP client: discover, configure channels, DIO/PWM/analog output, SD logging, and SD data retrieval |
| Your code | Custom apps, dashboards, pipelines, test rigs |

## What you can do

| Capability | What it gives you |
|---|---|
| **Auto-discovery** | Find any DAQiFi on WiFi or USB in seconds — no IP hunting, no config files |
| **One-line connect** | `DaqifiDeviceFactory.ConnectTcpAsync(...)` wraps transport setup and device init; retries are opt-in via `DeviceConnectionOptions` |
| **Real-time streaming** | Per-channel `IChannel.SampleReceived` events with decoded, scaled values — or subscribe to the raw protobuf frame directly; no polling loops to write |
| **Acquisition health** | Attach `AcquisitionStatistics` to a stream and read back the rate you are really getting, per-channel jitter, value range, and how far behind the device's clock the host is |
| **Record to CSV** | `device.RecordLiveSamplesToCsvAsync(writer)` writes a live stream to CSV as it arrives — no buffering the session in memory — and reports what reached the file and what was dropped |
| **Digital I/O** | Set any DIO pin as input or output and drive outputs high/low; inputs stream alongside analog data |
| **PWM outputs** | Drive PWM on capable DIO pins with per-channel duty cycle and a shared, device-wide frequency |
| **SD card operations** | List, download, delete, format, and start/stop SD logging over USB / serial |
| **Network configuration** | Push WiFi credentials and static LAN IPs from your app |
| **Firmware updates** | PIC32 and WiFi-module flashing with progress, cancellation, and automatic recovery to a clean re-flashable bootloader state on mid-flash failure |
| **Cross-platform** | .NET 9.0 and 10.0 on Windows, macOS, Linux |

## Quick recipes

### Connection options

Pick whichever transport fits your setup — each snippet is a standalone, copy-paste-ready starting point.

**TCP with a resilient retry preset** (5 retries, longer timeouts):

```csharp
await using var device = await DaqifiDeviceFactory.ConnectTcpAsync(
    "192.168.1.100", 9760, DeviceConnectionOptions.Resilient);
```

**Serial / USB:**

```csharp
// Replace with your OS-specific port:
//   Windows: "COM3"   •   macOS: "/dev/cu.usbmodem1"   •   Linux: "/dev/ttyACM0"
await using var device = await DaqifiDeviceFactory.ConnectSerialAsync("COM3");
```

**From a discovered device:**

```csharp
using var finder = new WiFiDeviceFinder();
var devices = await finder.DiscoverAsync(TimeSpan.FromSeconds(5));
await using var device = await DaqifiDeviceFactory.ConnectFromDeviceInfoAsync(devices.First());
```

### Custom retry options

```csharp
using Daqifi.Core.Communication.Transport;

var options = new DeviceConnectionOptions
{
    DeviceName = "My DAQiFi",
    ConnectionRetry = new ConnectionRetryOptions
    {
        MaxAttempts = 3,
        ConnectionTimeout = TimeSpan.FromSeconds(10)
    },
    InitializeDevice = true
};
await using var device = await DaqifiDeviceFactory.ConnectTcpAsync("192.168.1.100", 9760, options);
```

> **Connecting takes control of the device.** A DAQiFi unit has a single global acquisition, and the
> default connect sequence stops it — so connecting to a device another session is already streaming
> silently ends that session's data. Use `DeviceConnectionOptions.Observing` for a secondary session
> that only needs to look, and `DaqifiDeviceRegistry` to avoid opening the same unit twice in one
> process. See
> [Connecting stops any stream already running](docs/DEVICE_INTERFACES.md#connecting-stops-any-stream-already-running).

### Device discovery

```csharp
using Daqifi.Core.Device.Discovery;

// WiFi — UDP broadcast on port 30303 by default
using var wifiFinder = new WiFiDeviceFinder();
wifiFinder.DeviceDiscovered += (_, e) =>
    Console.WriteLine($"Found: {e.DeviceInfo.Name} at {e.DeviceInfo.IPAddress}");

var wifiDevices = await wifiFinder.DiscoverAsync(TimeSpan.FromSeconds(5));

// USB / Serial
using var serialFinder = new SerialDeviceFinder();
var serialDevices = await serialFinder.DiscoverAsync();
```

**On a home or multi-AP network, browse with mDNS as well.** UDP broadcast does not reliably
cross an access-point boundary — a device associated to a second AP is online and healthy, yet the
broadcast sweep returns nothing — so `MDnsDeviceFinder` browses the `_daqifi._tcp.local.` service
over multicast instead, which is the traffic consumer routers already reflect across APs, SSIDs and
VLANs. It produces the same `IDeviceInfo` shape, so anything that connects to a broadcast-discovered
device connects to an mDNS-discovered one unchanged.

```csharp
using var mdnsFinder = new MDnsDeviceFinder();
var mdnsDevices = await mdnsFinder.DiscoverAsync(TimeSpan.FromSeconds(5));
```

Run both — devices on firmware without an mDNS responder are still found over UDP broadcast, so the
two paths together cover more networks than either alone:

```csharp
using var finder = new AllTransportsDeviceFinder(
    [new WiFiDeviceFinder(), new MDnsDeviceFinder(), new SerialDeviceFinder()],
    identitySelector: device => device.SerialNumber);

var devices = await finder.DiscoverAsync(TimeSpan.FromSeconds(5));
```

The `identitySelector` is what collapses a board that answers on *both* network paths into a single
entry. Without one, the default per-transport identity prefers the MAC address, which the broadcast
reply carries and the mDNS advertisement does not, so the same board is reported twice — as two
entries that are both genuinely connectable, but still two.

Two caveats worth knowing: the device must be on firmware that advertises the service (see
daqifi-nyquist-firmware#345), and some hardened corporate or guest networks filter multicast
entirely — connect by IP address directly when they do.

Need fine-grained control? Pass a `CancellationToken` or override the discovery port:

```csharp
using var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(10));
var devices = await wifiFinder.DiscoverAsync(cts.Token);

using var customFinder = new WiFiDeviceFinder(discoveryPort: 12345);
```

### Acquisition statistics

"Am I actually getting 1 kHz?" — attach an `AcquisitionStatistics` for the duration of a stream and
read a snapshot whenever you want the answer. It observes the same per-channel sample events
streaming already raises, so nothing changes for consumers that do not attach one.

```csharp
using Daqifi.Core.Device;

using var stats = new AcquisitionStatistics(device);

device.StreamingFrequency = 1000;
device.StartStreaming();
await Task.Delay(TimeSpan.FromSeconds(5));
device.StopStreaming();

var snapshot = stats.Snapshot();
foreach (var channel in snapshot.Channels)
{
    Console.WriteLine(
        $"{channel.Name}: {channel.SampleCount} samples, " +
        $"{channel.MeasuredSampleRateHz:F1} Hz measured vs {channel.DeviceClockSampleRateHz:F1} Hz by the device clock, " +
        $"{channel.MinValue:F3}..{channel.MaxValue:F3} V, worst gap {channel.MaxSampleInterval.TotalMilliseconds:F2} ms");
}
```

The two rates are reported side by side on purpose. Both dropping below the commanded rate means
samples went missing; the two disagreeing means the device's own clock is not keeping real time, and
it is `MeasuredSampleRateHz` that describes what your application actually received. `Reset()` starts
a fresh window mid-session, and `stats.Record(sample)` feeds one by hand from `StreamSamplesAsync`
instead of attaching.

### Record a live stream to CSV

Streaming and exporting used to be two halves with nothing between them. `RecordLiveSamplesToCsvAsync`
joins them: it writes rows through `CsvExporter` as frames decode, so the recording's memory does not
grow with its length, and it hands back what reached the file and what did not.

```csharp
using Daqifi.Core.Logging.Export;

device.StreamingFrequency = 100;
device.StartStreaming();

await using var writer = new StreamWriter("run.csv");
var result = await device.RecordLiveSamplesToCsvAsync(writer, duration: TimeSpan.FromSeconds(30));

device.StopStreaming();

Console.WriteLine($"{result.RowCount} rows from {result.SampleCount} samples");
if (result.DroppedSampleCount > 0)
{
    Console.WriteLine($"{result.DroppedSampleCount} samples dropped — raise bufferCapacity or lower the rate");
}
```

The columns are the channels that were enabled when the call started, in device order. `duration`
elapsing is a clean finish — the last frame is written and the result comes back; cancelling the
`CancellationToken` is an abort and throws, so a recording cut short is never mistaken for a complete
one. Need the rows somewhere other than a `TextWriter`? Build a `LiveSampleSource` over
`StreamSamplesAsync` and hand it to `CsvExporter` (or any other `ISampleSource` consumer) yourself.

### Digital output

Digital channels default to inputs. Flip one to output and drive it — the level is applied
immediately, and flipping back to input releases the pin to high-impedance.

```csharp
using Daqifi.Core.Channel;

var channels = device.GetChannelsSnapshot();
var dio3 = channels.First(c => c.Type == ChannelType.Digital && c.ChannelNumber == 3);

device.SetDioDirection(dio3, ChannelDirection.Output);
device.SetDioValue(dio3, true);   // drive high
device.SetDioValue(dio3, false);  // drive low

device.SetDioDirection(dio3, ChannelDirection.Input); // back to a streamed input
```

Every `IStreamingDevice` method above (and the rest of the channel/PWM/analog-output/reboot surface)
has a cancellable `...Async` twin declared on the interface — see
[IStreamingDevice](docs/DEVICE_INTERFACES.md#istreamingdevice) for the full list.

### PWM output

PWM runs on capable DIO pins (`IDigitalChannel.IsPwmCapable` — channels 0, 3, 4, 5, 6 and 7 on
Nyquist hardware). Duty cycle is per channel; the frequency is shared by all PWM channels, since
one hardware timer drives them all.

```csharp
using Daqifi.Core.Channel;

var pwm = device.GetChannelsSnapshot()
    .OfType()
    .First(c => c.IsPwmCapable);

device.SetPwmDutyCycle(pwm, 25);  // 1-100 percent
device.SetPwmFrequency(1000);     // 6-50000 Hz, applies to every PWM channel
device.SetPwmEnabled(pwm, true);  // start

device.SetPwmDutyCycle(pwm, 75);  // duty changes take effect live

device.SetPwmEnabled(pwm, false); // stop — the pin is left high-impedance
```

### Network configuration

`DaqifiStreamingDevice` implements `INetworkConfigurable` for programmatic WiFi and LAN configuration. `Mode`, `Ssid`, and `Password` are always applied on every call; only `StaticIP`, `SubnetMask`, and `Gateway` honor `null` as "leave unchanged" — so DHCP-only callers can omit the static-IP fields without affecting their DHCP setup.

```csharp
using System.Net;
using Daqifi.Core.Device.Network;

var config = new NetworkConfiguration
{
    Ssid       = "MyNetwork",
    Password   = "secret",
    Mode       = WifiMode.ExistingNetwork,
    StaticIP   = IPAddress.Parse("192.168.1.42"),
    SubnetMask = IPAddress.Parse("255.255.255.0"),
    Gateway    = IPAddress.Parse("192.168.1.1"),
};
await device.UpdateNetworkConfigurationAsync(config);
```

### Firmware updates

`IFirmwareUpdateService` orchestrates both PIC32 and WiFi-module flashing with explicit state transitions and `IProgress` for UI / CLI reporting.

- `UpdateFirmwareAsync(...)` — PIC32 firmware flashing from a local Intel HEX file
- `UpdateWifiModuleAsync(...)` — WiFi module flashing via an external tool runner. Automatically checks the device's current WiFi-chip firmware against the latest GitHub release and skips the flash if already up to date.

**Safe failure cleanup (PIC32).** If a PIC32 update fails — or is canceled — after flash has been written (`ErasingFlash`, `Programming`, or `Verifying`) and the HID bootloader is still connected, the service automatically re-erases the application flash so the device is never abandoned half-flashed: a half-flashed image would otherwise boot into garbage on the next power cycle, recoverable only by the physical button-hold procedure. The flow surfaces two extra states:

- `CleaningUp` — the re-erase is running; progress percent stays frozen at the failure point (never 100) so a percent-only UI can't mistake cleanup for success
- `Recovered` — terminal: the update **failed** (the call still throws), but the device is in a clean bootloader state and safe to simply re-flash

`FirmwareUpdateException.RecoveryGuidance` tells the operator whether to just re-run the update (`Recovered`) or power-cycle into bootloader mode first (cleanup couldn't run — the device may be half-flashed). `FirmwareUpdateException.FailedState` always reports where the original failure occurred, independent of cleanup outcome.

> **Note:** The default WiFi flash tool config uses `winc_flash_tool.cmd` conventions. On m

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [daqifi](https://github.com/daqifi)
- **Source:** [daqifi/daqifi-core](https://github.com/daqifi/daqifi-core)
- **License:** MIT
- **Homepage:** https://daqifi.com/

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-daqifi-daqifi-core
- Seller: https://agentstack.voostack.com/s/daqifi
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
