FC.SDK 3.0.771

There is a newer version of this package available.
See the version list below for details.
dotnet add package FC.SDK --version 3.0.771
                    
NuGet\Install-Package FC.SDK -Version 3.0.771
                    
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="FC.SDK" Version="3.0.771" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FC.SDK" Version="3.0.771" />
                    
Directory.Packages.props
<PackageReference Include="FC.SDK" />
                    
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 FC.SDK --version 3.0.771
                    
#r "nuget: FC.SDK, 3.0.771"
                    
#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 FC.SDK@3.0.771
                    
#: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=FC.SDK&version=3.0.771
                    
Install as a Cake Addin
#tool nuget:?package=FC.SDK&version=3.0.771
                    
Install as a Cake Tool

FC.SDK: Free Canon SDK

Canon EOS camera control via PTP over USB, WiFi and Windows MTP. Pure managed C#, no EDSDK binary required.

Why

Canon's EDSDK is closed-source, non-redistributable, and Windows-only. FC.SDK implements the same camera control capabilities using the PTP protocol (reverse-engineered by the libgphoto2 project), working over USB and WiFi on any platform.

Transports

Transport Platform Driver swap? Notes
WPD ioctl (ConnectWpdIoctl) Windows None (plug & play) Same path EDSDK itself uses. Preferred
WPD COM (ConnectWpd) Windows None (plug & play) Stock MTP driver via WPD COM API. Adds the WPD Content API
USB (LibUsbDotNet) Linux/macOS/Windows WinUSB on Windows Cross-platform, lowest latency
PTP/IP (WiFi) All None TCP port 15740, no cable needed

On Windows, prefer ConnectWpdAutoAsync: it probes the ioctl path with a real GetDeviceInfo read and falls back to COM if that fails, leaving the reason on CanonCamera.TransportFallbackReason.

Both Windows transports drive the same stock wpdmtp.sys, so neither needs a driver swap and both see the same camera. They differ in how they reach it. The COM path goes through the documented WPD COM API; the ioctl path talks to the driver with DeviceIoControl the way EDSDK does, which means hand-rolling Microsoft's undocumented property-bag serialization. Measured on a 450D, 300 live-view frames each:

WPD COM WPD ioctl
Frame rate 20.2 fps 20.3 fps
Private working set 73–119 MiB 38–47 MiB

Throughput is identical: the body is the limiter. The memory is the reason to prefer ioctl: over COM, an unfinished transfer poisons a device object while its end phase never returns, so every live-view frame has to create and release a whole new one. The same end phase completes in 0–22 ms over ioctl.

The COM transport is not strictly worse, though: the WPD Content API (EnumerateWpdObjects, DownloadWpdObjectAsync) is COM interfaces rather than MTP commands and has no equivalent below the COM layer. SupportsWpdContentApi reports which you have. The PTP equivalents work on every transport.

A session commits to one transport for its whole life; they are alternatives, not layers. See docs/canon-windows-transports.md for how EDSDK reaches the body and docs/wpd-ioctl-wire-format.md for the decoded buffer format.

Usage

using FC.SDK;
using FC.SDK.Canon;

// Connect via WPD (Windows, zero-install). Picks the ioctl transport when the body accepts it.
var (deviceId, _) = CanonCamera.EnumerateWpdCameras().First();
await using var camera = await CanonCamera.ConnectWpdAutoAsync(deviceId);
await camera.OpenSessionAsync();
Console.WriteLine($"{camera.Model}, serial {camera.SerialNumber}, battery {camera.BatteryLevelPercent}%");

// Typed settings, no magic uint32s
await camera.SetISOAsync(EdsISOSpeed.ISO_800);
await camera.SetShutterSpeedAsync(EdsTv.Tv_1_125);
await camera.SetApertureAsync(EdsAv.Av_2_8);

// Keep the event pump running for the whole session: on EOS bodies property values only ever
// arrive through GetEvent, and an undrained queue makes the camera reject property writes.
camera.StartEventPolling();

// Snap and download (auto-detects CR2, CR3, JPG)
await camera.SetSaveToAsync(EdsSaveTo.Host);
camera.ObjectAdded += async (s, e) =>
{
    var (_, fileName) = await camera.GetObjectFileNameAsync(e.ObjectHandle);
    await using var fs = File.Create(fileName ?? "capture.cr2");
    await camera.DownloadAsync(e.ObjectHandle, fs);
    await camera.TransferCompleteAsync(e.ObjectHandle);
};
await camera.TakePictureAsync();

// Bulb exposure. Either put the mode dial on B, or write the value on bodies that have no B
// position (a 450D flips its top LCD to "buLb" from this alone).
await camera.SetShutterSpeedAsync(EdsTv.Bulb);
await camera.BulbStartAsync();
await Task.Delay(TimeSpan.FromSeconds(120));
await camera.BulbEndAsync();

// Mirror lockup, which is not just a property write: lockup engages only in a self-timer drive,
// so the helper picks a timer off the body's own allowed list, releases, and restores. Do the
// restore after you have the image; see the note below.
await camera.TakePictureWithMirrorLockupAsync();
// ... wait for ObjectAdded, download, TransferCompleteAsync ...
await camera.ApplyPendingMirrorLockupRestoreAsync();

// Live view: frames, magnification, focus
await camera.StartLiveViewAsync();
var (error, jpeg) = await camera.GetLiveViewFrameAsync();

await camera.SetEvfZoomAsync(CanonEvfZoom.X5);        // verified against the real crop, not the ACK
var rect = await camera.GetEvfZoomRectAsync();        // the only honest read-out of magnification
await camera.SetEvfZoomPositionAsync(rect!.Value.X + 200, rect.Value.Y);   // pan, clamped by the body

var hist = await camera.GetEvfHistogramAsync();       // luma + R/G/B, 256 bins, linear in light
Console.WriteLine($"mean {hist?.MeanLevel:F1}, clipped {hist?.ClippedHighlights:P2}");

var (_, focus) = await camera.GetFocusStateAsync();   // can this body autofocus at all right now?
if (focus.AutoFocusAvailable) await camera.AutoFocusLiveViewAsync();
await camera.DriveLensAsync(EdsDriveLensStep.NearSmall);
await camera.StopLiveViewAsync();

Architecture

CanonCamera              (public async API)
  CanonPtpSession        (Canon vendor opcodes 0x9xxx)
    CanonPropertyCache   (property mirror fed by the GetEvent stream)
    PtpSession           (transaction management, half-duplex lock)
      IPtpTransport      (USB / PTP-IP: raw send/receive)
      IMtpExtTransport   (WPD COM / WPD ioctl: 3 MTP phases)

How EOS properties are read

EOS bodies expose no property-read operation: standard PTP GetDevicePropValue (0x1015) is absent from their supported-operations list, and Canon's 0x9127 is RequestDevicePropValue, which asks the camera to emit a value rather than returning one. Values and their selectable-value lists arrive only as PropValueChanged / AvailListChanged records in the GetEvent (0x9116) stream. CanonPropertyCache mirrors that stream and GetPropertyAsync answers out of the mirror, requesting a push when a code has not been seen yet. Same design as EDSDK and libgphoto2.

Two practical consequences: run StartEventPolling for the whole session, and expect DeviceBusy on property writes if you don't (the SDK drains and retries, but the camera really does gate writes on an empty queue).

A third: a response code proves almost nothing on these bodies. A 450D answers OK to writes of properties it does not have, and to releases it silently discards. So the SDK verifies against state the camera cannot fake (a read-back for property writes, the live-view crop for magnification, the drive mode for mirror lockup), and returns OperationRefused when the camera kept its old behaviour.

Mirror lockup

Not a property write, on either body measured. Lockup engages only in a self-timer drive, where the firmware owns raise → settle → expose; in single-shot drive a 6D just takes the picture, so the caller gets a frame and believes they had lockup. On a 450D, where lockup is a Custom Function rather than a property, every remote release is discarded outright while it is armed, including bulb. SupportsMirrorLockupCapture tells the two cases apart by where the body keeps the setting, and TakePictureAsync refuses rather than relaying an ACK it knows is empty.

TakePictureWithMirrorLockupAsync handles the working recipe, but its cleanup is the caller's job. The method returns when the release finishes, which is before the image arrives, and a frame still awaiting TransferComplete makes the body reject the property writes that would restore your settings. So it does a best effort, records the remainder on PendingMirrorLockupRestore, and you call ApplyPendingMirrorLockupRestoreAsync once you have the frame. Skip it and the camera is left on a self-timer, delaying every later exposure in a way that reads as a fault.

Diagnostic viewer

Prebuilt, self-contained binaries are attached to every release: fc-viewer-<rid>.zip for Windows, .tar.gz for Linux and macOS. No .NET install needed. The archive carries the SDL3 native; the Vulkan loader comes from the OS:

Platform Also needs
Windows (win-x64, win-arm64) nothing; vulkan-1.dll ships with any modern GPU driver
Linux (linux-x64, linux-arm64) libvulkan.so.1 + an ICD (mesa-vulkan-drivers), and a font package (fonts-dejavu-core)
macOS (osx-arm64, osx-x64) MoltenVK (Vulkan-on-Metal)

Or from source:

dotnet run --project src/FC.SDK.Viewer [output-directory]

An SDL3 + Vulkan GUI that exposes every control and action the SDK has, shows the raw event-stream property cache next to the typed values, previews live view and captures, and writes a full timestamped log of every PTP exchange (including which PTP operations your body advertises). If a body misbehaves, run this and attach the log from the output directory.

Shortcuts: F5 read all properties · Space capture · Ctrl+L live view · Ctrl+D dump properties · Ctrl+± text size.

There are two headless tools alongside it. FC.SDK.Sample is the fastest way to exercise a body at all, and writes a device report every run; --ioctl selects the raw transport, so the two Windows paths are directly comparable in one harness:

dotnet run --project src/FC.SDK.Sample -- [--ioctl] [--frames N] [--no-capture]

FC.SDK.Diagnostics is for questions the sample cannot answer: sequencing a body through an experiment and judging it on delivered bytes rather than on a response code. Every mode brackets its result with controls, because a run whose controls did not expose proves nothing:

dotnet run --project src/FC.SDK.Diagnostics -- --help

Feature Matrix

Feature FC.SDK Canon.EDSDK (.NET) Canon EDSDK.dll (native)
Take picture yes yes yes
Bulb exposure yes yes yes
Download CR2/CR3/JPEG yes yes yes
Live view (MJPEG) yes yes yes
Live view zoom + pan yes yes yes
Live view autofocus yes yes yes
Exposure histogram yes yes yes
Manual focus (DriveLens) yes yes yes
Read/write ISO, Tv, Av yes yes yes
Event polling (GetEvent) yes yes yes
Mirror lockup control yes yes yes
WPD (zero-install, Windows) yes no (wraps EDSDK.dll) internally, not exposed
USB (LibUsbDotNet, cross-plat) yes no Windows only
WiFi (PTP/IP) yes no yes
Linux / macOS yes (USB, WiFi) no no
NativeAOT compatible yes yes n/a
Redistributable MIT LGPL no (Canon license)
Requires vendor binary no yes (EDSDK.dll) yes (EDSDK.dll)
Requires driver swap (Zadig) WPD: no, USB: yes n/a no

Canon.EDSDK (SharpAstro/Canon.EDSDK) is a .NET binding around Canon's native EDSDK.dll. It requires the vendor binary and only runs on Windows. FC.SDK reimplements the PTP protocol directly and needs no vendor DLLs.

Supported Cameras

Verified on hardware: Canon EOS 6D (USB, WiFi, WPD) and EOS 450D (WPD). The 450D is where most of the WPD, live-view, Custom Function and raw-ioctl work was proven, being DIGIC III and the strictest of the two. Should work with any Canon EOS body that supports PTP, since the vendor opcodes are shared across the lineup. Reported on the EOS 200D II / 250D / Rebel SL3 (DIGIC 8) in issue #1; the protocol bugs behind that report are fixed but the fix is unverified on that body, so please run the viewer and attach its log if anything still misbehaves.

The two bodies disagreed on nearly every question asked of them, which is the single most useful thing to know before porting to a third. Mirror lockup is a Custom Function on the 450D and an ordinary property on the 6D. With it armed, the 450D discards every remote release including bulb, while the 6D honours it, but only in a self-timer drive. The 450D has no RemoteReleaseOn (0x9128) at all; the 6D has it, and it turns out not to autofocus there with either documented parameter. Where behaviour is model-specific the SDK infers it from what the body announces rather than from a model-name table, and the docs say which body a measurement came from rather than generalising from one.

If you have a body not listed, CreateDeviceReportAsync (or the viewer's device report) is the one artefact worth attaching to an issue. It dumps every advertised operation, every announced property with its allowed values, and the decoded Custom Function block in wire order with menu numbers, which is what makes it possible to add a model without owning it, since a reporter can read menu numbers off their own camera.

Docs

The write-ups behind the non-obvious parts, each recording what was measured and on what:

canon-windows-transports.md How EDSDK actually reaches an EOS body on Windows, and the dead hypotheses behind the live-view fix
wpd-ioctl-wire-format.md The ioctl buffer format decoded: Microsoft's undocumented property-bag serialization, not a Canon one
canon-live-view-zoom.md Magnification is an operation, not a property, and the zoom rect is the only honest read-out
canon-custom-functions.md The C.Fn wire layout, and why EDSDK is no help for per-model ids
edsdk-feature-gaps.md What is still missing against EDSDK. Read before concluding something is absent by design

AOT Compatible

Both Windows transports are NativeAOT compatible: no dynamic, no reflection, all COM interop via [GeneratedComInterface], and the ioctl path is plain P/Invoke. PTP/IP is sockets, so it is clean too.

One caveat, deliberately left visible rather than suppressed: LibUsbDotNet 2.2.75 is not trim or AOT annotated and emits IL2104 + IL3053 on every publish. The cross-platform USB transport may therefore fail at runtime in an AOT binary. Verify it against a real body before relying on it.

License

MIT

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 (1)

Showing the top 1 NuGet packages that depend on FC.SDK:

Package Downloads
TianWen.Lib

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.801 98 8/8/2026
3.0.771 184 8/3/2026
3.0.751 105 8/3/2026
2.0.671 119 8/2/2026
1.7.651 144 8/2/2026
1.7.631 124 8/1/2026
1.6.611 117 7/30/2026
1.5.601 113 7/30/2026
1.5.571 99 7/30/2026
1.5.531 95 7/29/2026
1.4.521 617 7/5/2026
1.4.511 104 7/5/2026
1.4.501 131 7/4/2026
1.4.491 104 7/4/2026
1.3.481 701 5/14/2026
1.3.471 119 5/13/2026
1.3.461 110 5/13/2026
1.3.451 112 5/13/2026
1.3.441 116 5/13/2026
1.3.431 112 5/13/2026
Loading failed