Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 96 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,80 +1,120 @@
# DAQiFi Desktop

Windows desktop application (.NET) that is used to communicate with DAQiFi hardware.
> "Revolutionizing the data collection experience with convenient, portable device connectivity."
>
> The official Windows desktop application for DAQiFi hardware — real-time visualization, session logging, and firmware updates, all in one place.

## Tech Stack
[![Build](https://github.com/daqifi/daqifi-desktop/actions/workflows/build.yaml/badge.svg?branch=main)](https://github.com/daqifi/daqifi-desktop/actions/workflows/build.yaml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](LICENSE)
[![Platform](https://img.shields.io/badge/platform-Windows-blue?style=flat-square)](https://github.com/daqifi/daqifi-desktop/releases)
[![.NET](https://img.shields.io/badge/.NET-10.0-purple?style=flat-square)](https://dotnet.microsoft.com/)

- .NET 10.0
- WPF
- SQLite
[daqifi.com](https://daqifi.com) · [daqifi-core SDK](https://github.com/daqifi/daqifi-core) · [Issues](https://github.com/daqifi/daqifi-desktop/issues)

## Dependencies
---

- EntityFramework (ORM)
- Google Protocol Buffers (read incoming data from DAQiFi hardware)
- MahApps (UI components)
- Oxyplot (for graphing)
## What is DAQiFi Desktop?

## CI/CD
DAQiFi hardware is designed to get out of the way so you can focus on the data, not the collection process. DAQiFi Desktop is the application that makes that possible — connect a Nyquist device over WiFi or USB, configure your channels, start a logging session, and watch your data arrive in real time. No custom scripting required.

The project uses GitHub Actions for continuous integration and deployment:
If you are building automated pipelines or integrating DAQiFi devices into your own software, the [daqifi-core](https://github.com/daqifi/daqifi-core) .NET SDK gives you programmatic access to the same hardware.

- **Build & Test**: Automated build, testing, and code coverage on every pull request
- **Static Analysis**: .NET SDK analyzers and Roslynator for code quality enforcement
- **Code Coverage**: ReportGenerator creates HTML reports and posts summaries to PRs (80% minimum required)
- **MSI Installer**: Automated Windows installer builds using Wix Toolset
- **Release**: Automatic release asset publishing when GitHub releases are created
- **Dependency Updates**: Dependabot manages NuGet and GitHub Actions dependencies weekly
## Quick install / first run

All workflows run on .NET 10.0 with Windows runners for WPF compatibility.
1. Install the [.NET 10.0 Desktop Runtime for Windows](https://dotnet.microsoft.com/download/dotnet/10.0) if you don't already have it.
2. Download the latest `DAQifiDesktop_Setup.msi` from the [Releases page](https://github.com/daqifi/daqifi-desktop/releases).
3. Run the installer.
4. Launch **DAQiFi Desktop**.
5. Click **Connect** and let the app discover your Nyquist device on the local network, or enter its IP address manually.
6. Enable the channels you want to log and press **Start Logging**.

## Observability
## Common applications

Exceptions are sent to [Sentry](https://o4511134234640384.sentry.io/). The DSN is configured in `Daqifi.Desktop/App.config`.
- **Space research** — sensor data acquisition for moon regolith testing
- **Medical R&D** — prosthetic socket pressure monitoring
- **Industrial monitoring** — continuous analog and digital I/O logging
- **Engineering education** — hands-on data acquisition with SCPI-compliant hardware

## Documentation
## Where DAQiFi Desktop fits

How data goes from the device to the database.
| Layer | Repo | What it does |
|---|---|---|
| Hardware | Nyquist 1 / Nyquist 3 | Wireless DAQ devices (WiFi + USB, battery-powered) |
| SDK | [daqifi-core](https://github.com/daqifi/daqifi-core) | .NET library for device communication and data streaming |
| **App** | **daqifi-desktop** | **GUI for device connection, visualization, logging, and firmware updates** |
| User code | Your project | Custom dashboards, test rigs, or automated pipelines built on daqifi-core |

```mermaid
sequenceDiagram
DAQiFiHardware->>IStreamingDevice: Protobuf Message
IStreamingDevice->>StreamMessageConsumer: Protobuf Message
StreamMessageConsumer->>ProtobufMessageParser: Decode Message
StreamMessageConsumer->>IDevice:OnMessageReceived()
IDevice->>IChannel:Set Active Sample
IChannel->>IChannel:Scale Sample(Expression)
IChannel->>LoggingManager:OnChannelUpdated()
LoggingManager->>DatabaseLogger:HandleChannelUpdate()
DatabaseLogger->>DatabaseLogger:Add to Buffer
DatabaseLogger->>DatabaseLogger:ConsumerThread
DatabaseLogger->>Database:Bulk Insert Buffer
```
## What you can do

## Installer
| Capability | What it means for you |
|---|---|
| Auto-discovery over WiFi (UDP, port 30303) | Devices appear automatically when they are on the same network |
| Manual IP connection | Connect directly to a known device address when broadcast discovery is not available |
| USB / Serial connection | Use USB as an alternative to WiFi — same data, no network configuration needed |
| Multiple simultaneous devices | Connect and log from more than one Nyquist at the same time |
| Real-time channel visualization | Analog and digital channels plotted live with a viewport-aware minimap for large datasets |
| Start / stop logging sessions | Record data to a local SQLite database; sessions are preserved between runs |
| Per-channel formula scaling | Apply a custom NCalc expression (e.g. `x * 0.001 + 273.15`) to convert raw values before display and logging |
| CSV export with optional averaging | Export one or more sessions to `.csv`; optionally downsample by averaging N consecutive samples |
| Named profiles | Save and restore device connections, active channels, and sampling rate — switch between setups without reconfiguring |
| Firmware update via USB HID | Update Nyquist firmware from within the app — no separate tool needed |

- Uses [Wix Toolset](https://wixtoolset.org/)
- Separate solution `Daqifi.Desktop.Setup`
## Supported devices

## WiFi Device Connectivity
| Device | Analog inputs | Resolution | Input range | Digital I/O | Transport | Power |
|---|---|---|---|---|---|---|
| Nyquist 1 | 16 | 12-bit | 0 – 5 V | Yes | 802.11n WiFi + USB | Battery |
| Nyquist 3 | 8 | 18-bit | ±10 V | Yes | 802.11n WiFi + USB | Battery |

DAQiFi Desktop discovers and connects to DAQiFi devices over WiFi using UDP broadcasts and TCP connections.
Both devices are SCPI-compliant and compatible with LabVIEW.

### Network Requirements
- **Same Network**: Computer and DAQiFi device must be on the same network/subnet
- **Firewall**: UDP port 30303 must be allowed (configured automatically with admin privileges)
- **Virtual Machines**: Use bridged networking mode for VM environments
## WiFi connectivity

### Troubleshooting WiFi Discovery
1. **Run as Administrator** - Required for automatic firewall configuration
2. **Check Network Connection** - Ensure computer and device are on same WiFi network
3. **Verify Connectivity** - Test with `ping <device-ip>` from command prompt
4. **Manual Connection** - Use manual IP connection if discovery fails
**Requirements:**

### Port Configuration
- **UDP Discovery**: Port 30303 (device discovery broadcasts)
- **TCP Data**: Device-specific port (varies by device, typically 9760)
- Computer and DAQiFi device must be on the same subnet
- UDP port 30303 must be reachable (configured automatically when the app runs with administrator privileges)
- Virtual machines: use bridged networking, not NAT

## Contribution
**Troubleshooting:**

Please read [Contributing Guidelines](CONTRIBUTING.md) before contributing.
1. Run DAQiFi Desktop as administrator so it can configure the firewall rule automatically.
2. Confirm the computer and device are on the same WiFi network.
3. Test reachability with `ping <device-ip>`.
4. If discovery does not find the device, use **Manual Connection** and enter the IP address directly.

**Port reference:**

| Protocol | Port | Purpose |
|---|---|---|
| UDP | 30303 | Device discovery broadcasts |
| TCP | Device-specific (typically 9760) | Data streaming |

## Requirements

- **OS**: Windows 10 or later (x64)
- **Runtime**: .NET 10.0 for Windows (bundled in the MSI installer)
- **Privileges**: Administrator rights recommended for automatic firewall configuration during initial setup

## Community & support

- **Bug reports and feature requests**: [GitHub Issues](https://github.com/daqifi/daqifi-desktop/issues)
- **Commercial inquiries and custom hardware**: [daqifi.com](https://daqifi.com)

## Contributing

Please read the [Contributing Guidelines](CONTRIBUTING.md) before opening a pull request. See [docs/architecture.md](docs/architecture.md) for the streaming data pipeline and system overview, and [docs/design-philosophy.md](docs/design-philosophy.md) for UI/UX principles. All PRs require a conventional commit title (`feat:`, `fix:`, `docs:`, `deps:`, `chore:`) and at least one approving review from a DAQiFi core member.

## For maintainers

Releases are created by publishing a GitHub Release (via the GitHub web UI or API — the `release.yaml` workflow triggers on the `release: created` event, not on a tag push alone). Once a release is published, the workflow builds the MSI via WiX Toolset and attaches `DAQifiDesktop_Setup.msi` automatically. The app version is set in `Daqifi.Desktop/Daqifi.Desktop.csproj` (`<Version>`). Follow [semantic versioning](https://semver.org/); breaking changes should use the `feat!:` prefix in the PR title.

Unhandled exceptions are captured via Sentry. The DSN is in `Daqifi.Desktop/App.config`.

---

<div align="center">

Built by [DAQiFi](https://daqifi.com) · Licensed under [MIT](LICENSE)

</div>
102 changes: 102 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Architecture

This document is for contributors. It explains how DAQiFi Desktop is structured and how a single sample makes its way from a Nyquist device into the SQLite database. For visual/interaction principles, see [design-philosophy.md](design-philosophy.md). For specific design decisions, see [adr/](adr/).

## System context

How DAQiFi Desktop relates to the world outside it.

```mermaid
C4Context
title System Context — DAQiFi Desktop

Person(user, "Operator", "Engineer, researcher, or educator collecting data")

System(desktop, "DAQiFi Desktop", "WPF app: discovery, live visualization, session logging, firmware updates")

System_Ext(nyquist, "Nyquist Device", "DAQiFi hardware (Nyquist 1 or 3) over WiFi and/or USB")
System_Ext(github, "GitHub Releases API", "Source of update notifications")
System_Ext(sentry, "Sentry", "Unhandled-exception telemetry")
System_Ext(firewall, "Windows Firewall", "Host firewall — UDP 30303 rule for device discovery")

Rel(user, desktop, "Connects devices, configures channels, starts logging")
Rel(desktop, nyquist, "UDP discovery (30303), TCP streaming, USB-Serial, USB HID for firmware")
Rel(desktop, github, "Checks latest release", "HTTPS")
Rel(desktop, sentry, "Reports unhandled exceptions", "HTTPS")
Rel(desktop, firewall, "Creates inbound UDP 30303 rule on first run (requires admin)")
```

## Containers

What lives inside the DAQiFi Desktop process and on the user's machine.

```mermaid
C4Container
title Container View — DAQiFi Desktop

Person(user, "Operator")
System_Ext(nyquist, "Nyquist Device")

Container_Boundary(app, "DAQiFi Desktop (WPF process)") {
Container(ui, "WPF UI", "XAML + ViewModels (CommunityToolkit.Mvvm)", "Channels pane, plot, profiles, dialogs")
Container(domain, "Device & Logging Domain", "C# (.NET 10)", "AbstractStreamingDevice, Channel, LoggingManager, DatabaseLogger")
Container(core, "Daqifi.Core", "NuGet package", "Transport (TCP/Serial/HID), ProtobufProtocolHandler, discovery, SCPI")
}

ContainerDb(sqlite, "SQLite", "Local file (EF Core)", "Sessions, samples, device metadata")
Container_Ext(config, "App.config + Profiles XML", "Files in CommonApplicationData", "Settings, named profiles")

Rel(user, ui, "Uses")
Rel(ui, domain, "Commands and observable state")
Rel(domain, core, "Sends SCPI, subscribes to MessageReceived")
Rel(core, nyquist, "Wire protocol")
Rel(domain, sqlite, "Bulk inserts via EF Core")
Rel(ui, config, "Reads/writes")
```

## Streaming data flow

One sample's journey from the device wire to the database. Every step below was verified against the current code; the layer that owns each step is in parentheses.

```mermaid
sequenceDiagram
participant HW as Nyquist Device
participant Core as Daqifi.Core<br/>(DaqifiDevice + ProtobufProtocolHandler)
participant Dev as AbstractStreamingDevice<br/>(WiFi or Serial subclass)
participant Ch as AbstractChannel
participant LM as LoggingManager
participant DBL as DatabaseLogger
participant DB as SQLite

HW->>Core: Bytes (TCP or USB-Serial)
Core->>Core: Decode protobuf → DaqifiOutMessage
Core->>Dev: MessageReceived event
Dev->>Dev: HandleInboundMessage → ProtobufProtocolHandler.HandleAsync
Dev->>Dev: OnStreamMessageReceived(DaqifiOutMessage)
Note over Dev: For each active analog channel:<br/>scale raw ADC (WiFi) or<br/>use pre-scaled float (USB)
Dev->>Ch: channel.ActiveSample = new DataSample(...)
Ch->>Ch: Apply NCalc scale expression (if set)
Ch->>LM: OnChannelUpdated event
LM->>DBL: logger.Log(sample) // iterates all registered ILoggers
DBL->>DBL: _buffer.Add(sample) // BlockingCollection<DataSample>
Note over DBL: Background Consumer thread<br/>polls every 100 ms
DBL->>DB: context.BulkInsert(samples) in EF Core transaction
```

### Notes on the flow

- **WiFi vs USB scaling.** WiFi firmware sends raw ADC counts; USB firmware sends pre-scaled floats already in volts. `OnStreamMessageReceived` branches on this — see `AnalogInData` vs `AnalogInDataFloat` in [AbstractStreamingDevice.cs](../Daqifi.Desktop/Device/AbstractStreamingDevice.cs).
- **Two parallel paths per protobuf message.** The same `DaqifiOutMessage` produces per-channel `DataSample`s (the path above) *and* a single `DeviceMessage` carrying device-level state (battery, status, target frequency). The latter is dispatched via `LoggingManager.HandleDeviceMessage`.
- **Loggers are a list, not a single class.** `LoggingManager.Loggers` is an `ILogger` collection. `DatabaseLogger` is the persistent one, but `PlotLogger` and `SummaryLogger` also subscribe.
- **Backpressure.** `DatabaseLogger` uses a `BlockingCollection<DataSample>` producer/consumer split so the UI/device threads never wait on disk. The consumer drains in ~100 ms ticks and bulk-inserts via `EFCore.BulkExtensions.Sqlite`.
- **Timestamps.** `Daqifi.Core.TimestampProcessor` handles hardware-counter rollover and provides the firmware-measured inter-message delta (immune to TCP jitter).

## Key entry points

| Concern | Start here |
|---|---|
| New device transport | `Daqifi.Desktop/Device/AbstractStreamingDevice.cs` and the subclasses under `WiFiDevice/`, `SerialDevice/` |
| Channel behavior or scaling | `Daqifi.Desktop/Channel/AbstractChannel.cs` (`ActiveSample` setter) |
| Logging pipeline | `Daqifi.Desktop/Loggers/LoggingManager.cs` and `DatabaseLogger.cs` |
| Plot rendering and downsampling | See [adr/001-viewport-aware-downsampling.md](adr/001-viewport-aware-downsampling.md) |
| Visual and interaction design | See [design-philosophy.md](design-philosophy.md) |
Loading