diff --git a/README.md b/README.md index a6fcfe90..49a2a229 100644 --- a/README.md +++ b/README.md @@ -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 +[![Latest release](https://img.shields.io/github/v/release/daqifi/daqifi-desktop?style=flat-square&label=release&color=brightgreen)](https://github.com/daqifi/daqifi-desktop/releases/latest) +[![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 ` 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 `. +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 Desktop Runtime for Windows](https://dotnet.microsoft.com/download/dotnet/10.0) (install separately before running the MSI) +- **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` (``). 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`. + +--- + +
+ +Built by [DAQiFi](https://daqifi.com) · Licensed under [MIT](LICENSE) + +
diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 00000000..4d16eba2 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,116 @@ +# 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 +flowchart TB + user(["Operator
Person
Engineer, researcher,
or educator"]) + + desktop["DAQiFi Desktop
System
WPF app: discovery,
visualization, logging,
firmware updates"] + + nyquist["Nyquist Device
External
DAQiFi hardware
(Nyquist 1 or 3)"] + github["GitHub Releases
External
Update check"] + sentry["Sentry
External
Exception telemetry"] + firewall["Windows Firewall
External
UDP 30303 rule"] + + user -- "Connects devices,
logs data" --> desktop + desktop -- "Streaming
(WiFi UDP/TCP,
USB Serial)" --> nyquist + desktop -- "Firmware
(USB HID)" --> nyquist + desktop -- "Latest release
(HTTPS)" --> github + desktop -- "Exceptions
(HTTPS)" --> sentry + desktop -- "Adds rule
(first run, admin)" --> firewall + + classDef person fill:#08427b,stroke:#073b6f,color:#fff + classDef system fill:#1168bd,stroke:#0b4884,color:#fff + classDef external fill:#999,stroke:#6b6b6b,color:#fff + class user person + class desktop system + class nyquist,github,sentry,firewall external +``` + +## Containers + +What lives inside the DAQiFi Desktop process and on the user's machine. + +```mermaid +flowchart TB + user(["Operator"]) + nyquist["Nyquist Device
External"] + + subgraph app["DAQiFi Desktop (WPF process)"] + direction TB + ui["WPF UI
XAML + MVVM
Channels pane,
plot, profiles,
dialogs"] + domain["Device & Logging Domain
C# (.NET 10)
AbstractStreamingDevice,
Channel, LoggingManager,
DatabaseLogger"] + core["Daqifi.Core
NuGet package
Transport,
ProtobufProtocolHandler,
discovery, SCPI"] + end + + sqlite[("SQLite
EF Core, local file
Sessions, samples,
device metadata")] + config["App.config +
Profiles XML

CommonApplicationData
Settings,
named profiles"] + + user -- "Uses" --> ui + ui -- "Commands &
observable state" --> domain + domain -- "SCPI out,
MessageReceived in" --> core + core -- "Wire protocol" --> nyquist + domain -- "Bulk inserts" --> sqlite + ui -- "Reads / writes" --> config + + classDef person fill:#08427b,stroke:#073b6f,color:#fff + classDef container fill:#438dd5,stroke:#2e6295,color:#fff + classDef external fill:#999,stroke:#6b6b6b,color:#fff + class user person + class ui,domain,core container + class sqlite container + class nyquist,config external + style app stroke:#888,stroke-dasharray:5 5,fill:transparent +``` + +## 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
(DaqifiDevice + ProtobufProtocolHandler) + participant Dev as AbstractStreamingDevice
(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:
scale raw ADC (WiFi) or
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 + Note over DBL: Background Consumer thread
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` 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) |