chdkptp 0.1.0

Pure-Rust client for Canon cameras running CHDK firmware (PTP/USB)
Documentation
  • Coverage
  • 29.6%
    66 out of 223 items documented0 out of 69 items with examples
  • Size
  • Source code size: 107.44 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 1.95 MB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 5s Average build duration of successful builds.
  • all releases: 5s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • joseph-x-li/chdkptp_rs
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • joseph-x-li

chdkptp

Pure-Rust client for Canon cameras running CHDK firmware. Talks PTP/USB directly via nusb — no libusb C dependency, no host-side Lua interpreter.

Status

Early. The protocol surface is small and stable, but only a subset is implemented and only one camera model has been tested on the hardware bench.

Capability Status
USB enumeration done
PTP container framing + codec + sessions done
GetDeviceInfo parser done
CHDK opcode dispatch + chdk_version() done
Script execution + typed message decode (shoot(), raw Lua) done
Synchronized multi-camera shoot (barrier + clock-sync) done (examples)
File upload / download not yet
Live view (YUV decode) not yet
Remote-capture (USB-direct stills) not yet

The Lua caveat

The original chdkptp tool's UX is built around host-side Lua, which is what this crate replaces — you write Rust instead.

However: CHDK firmware itself embeds a Lua interpreter that runs on the camera, and any non-trivial camera operation requires composing Lua source and shipping it over PTP via ExecuteScript. The crate hides this behind typed APIs (session.shoot(), etc.), but if you want arbitrary camera control you write small Lua snippets that run inside the camera. There is no way around this — CHDK has no other scripting interface.

Raw access for power users:

let _id = session.execute_script_lua("return get_zoom()").await?;
while let msg = session.read_script_msg().await? {
    // ScriptMsg::Return / Error / User / None
}

Tested hardware

  • Canon PowerShot ELPH 180 (firmware 1.15.0.1.0, CHDK 2.9)

Other CHDK-supported PowerShots should work — the PTP wire format is identical across CHDK builds — but model-specific Lua quirks (button-press timing, available propcase numbers, available functions) exist and can surprise you. Bug reports with model + firmware + CHDK version welcome.

Tested platforms

  • macOS (Darwin 25). Linux and Windows should work because nusb is pure-Rust with platform backends for all three, but they are unverified.

Quickstart

use chdkptp::{list_cameras, Error, Result};
use pollster::block_on;

fn main() -> Result<()> {
    let cam = list_cameras()?
        .into_iter()
        .next()
        .ok_or(Error::NoDevicesFound)?;

    block_on(async {
        let mut s = cam.open_ptp().await?;
        let info = s.get_device_info().await?;
        println!("{} (CHDK {})", info.model, s.chdk_version().await?);
        s.shoot().await?;  // takes a picture (auto-switches to record mode)
        s.close().await?;
        Ok::<_, Error>(())
    })
}

Examples

Run with cargo run --example <name>:

Example What it does
list_cameras Enumerate Canon devices on USB
device_info Open a PTP session, print device descriptor + supported ops
chdk_version Query CHDK PTP protocol version
shoot Take a picture (auto-switches to record mode)
exec_lua Run arbitrary Lua: cargo run --example exec_lua -- 'return get_zoom()'
shoot_all Synchronized shoot across all connected cameras (barrier-based, ~10 ms host sync)
shoot_all_clocksync Same, via per-camera tick-offset measurement (~3 ms inter-camera precision)

Architecture

Single crate. Internal modules: usb, ptp/ (codec/container/session/device_info), chdk/ (opcodes, script messaging, extension methods on PtpSession). Built on nusb (pure-Rust USB) and thiserror. Async-first; examples wrap with pollster::block_on.

Limitations

  • Camera USB sleep. Canon PowerShots drop off the USB bus after ~30 s of idle PTP traffic. The crate surfaces this as Error::NoDevicesFound on the next enumeration. Long-running tools should plan for re-enumeration; setting "Override Auto Power Down" in the CHDK menu is the cleanest in-camera fix.
  • Camera-side Lua loader quirk. ExecuteScript source must be NUL-terminated (CHDK uses luaL_loadstring, not luaL_loadbuffer). The crate does this for you.
  • Per-model button-event timing. The low-level press('shoot_full') works on some camera firmwares and not others. The high-level CHDK shoot() Lua function is the portable choice; see the shoot() helper.

License

MIT — see LICENSE.