openshuttercounter
Read the shutter actuation count of a camera, on Linux, macOS and Windows, for
free.
$ openshuttercounter
Canon EOS 5D Mark IV (USB 04a9:3281)
Shutter actuations 26,374 (MPU 0x1001)
Mirror actuations 26,148 (MPU 0x100b)
Total counter 28,054 (MPU 0x1015)
Rated life 150,000 actuations - 17.6% used
One static binary. No libgphoto2, no vendor SDK, no Windows-only tooling.
Why this exists
I wanted the shutter count on my own 5D Mark IV. It ought to be a solved
problem — the camera knows the number, the cable is right there — but every
open source tool I tried came back empty, and the ones that worked were paid,
closed and Windows only. For a number the camera will happily tell you, that
seemed a poor state of affairs, so I went looking for why.
It turns out the free tools are not at fault. Canon moved the counter, and
nobody had published the way to the new place. So I worked it out and wrote it
down, which is really what this repository is: the answer, in enough detail
that the next person does not have to go looking.
Up to roughly the DIGIC 5 generation, Canon exposed a shutter counter as PTP
device property 0xD1AC. gphoto2 reads it as /main/status/shuttercounter
and it still works on a 5D Mark III.
On DIGIC 6 and newer bodies — 5D Mark IV, 5DS, 5DS R, 1D X Mark II, 80D and
everything after — that property is gone. GetDevicePropValue on 0xD1AC
returns OperationNotSupported. This is why gphoto2, FreeShutterCounter, EOS
Digital Info and every other free tool comes up empty on a modern body: not an
oversight on their part, but a wall Canon put there.
The counters were never in the PTP property tree. They live in the memory of
the MPU, the separate microcontroller that drives the shutter and mirror, and
they are reachable through a text command channel Canon carries over two vendor
PTP opcodes. That channel, its framing and the MPU address map are documented
in full in docs/PROTOCOL.md.
Status
Canon is the only manufacturer implemented so far, and exactly one body has
been confirmed by somebody holding it.
|
State |
| Canon protocol and framing |
Confirmed on an EOS 5D Mark IV, firmware 1.2.1 |
| Canon MPU address map |
Confirmed on the same body, over an exact count of 20 frames |
| Cameras that answer the standard PTP property |
Should work on any make; tried first on every camera |
| Other Canon bodies |
Very likely the same channel, but the addresses are unconfirmed |
| Other manufacturers |
Not implemented yet |
| Linux backend (usbfs) |
Implemented |
| macOS backend (libusb via purego) |
Implemented, not yet run against a camera |
| Windows backend (still image device) |
Implemented, not yet run against a camera |
If your camera is not on the confirmed list, try it anyway. It costs
nothing, it cannot write to the camera, and whether it works or not the result
is worth reporting:
openshuttercounter --trace mycamera.json
That produces a file recording everything exchanged with the camera, which is
enough for anyone else to confirm the reading or work out what went wrong.
CONTRIBUTING.md explains what to do with it. This is the
single most useful contribution to the project.
Install
# macOS, via Homebrew
brew install mvisonneau/tap/openshuttercounter
# Windows, via Scoop
scoop bucket add https://github.com/mvisonneau/scoops
scoop install openshuttercounter
# Anywhere with a Go toolchain
go install github.com/mvisonneau/openshuttercounter/cmd/openshuttercounter@latest
Binaries, .deb, .rpm and .apk packages are attached to every
release.
Usage
openshuttercounter # read the connected camera
openshuttercounter --raw # also show the raw replies
openshuttercounter --json # machine readable output
openshuttercounter --trace session.json # record the whole conversation
openshuttercounter list # what is connected
openshuttercounter models # cameras this build knows
openshuttercounter decode session.json # print a recorded conversation
decode reads traces written by openshuttercounter, and USB captures in pcap
or pcapng format for anyone who already has one. Comparing two traces taken an
exact number of frames apart is how a camera gets confirmed.
Setting up the camera
Whatever the platform:
- Built-in Wi-Fi fully disabled. On many bodies enabling it disables the
USB port entirely, and the camera simply will not appear.
- A card in the camera.
- A data cable, plugged in directly rather than through a hub.
- USB 2 ports proved noticeably more reliable than USB 3 during this work.
Linux
The .deb and .rpm packages install a udev rule that lets you read a camera
without root. Installing another way, copy it yourself:
sudo cp helpers/udev/99-canon-ptp.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm trigger
Then replug the camera. If you get device is claimed by another process, that
is gvfs, which grabs cameras on sight:
gio mount -u gphoto2://
systemctl --user mask gvfs-gphoto2-volume-monitor
macOS
USB access goes through libusb, loaded at runtime:
brew install libusb
macOS hands every camera to ptpcamerad as soon as it appears.
openshuttercounter stops it and retries automatically when the device is busy;
launchd restarts it afterwards.
Windows
No driver replacement is needed: the tool opens the still image device
interface the stock driver already exposes, so the manufacturer's own software
keeps working. Close anything else that might have the camera open.
Safety
This tool only reads.
On Canon bodies it sends MonOpen, MonReadAndGetData and MonClose, and
nothing else. That restraint is not incidental: the same channel carries
FA_SetProperty, FA_SetCalendar and MonWrite, which write factory state —
calibration data, the camera's calendar, and the actuation counters themselves.
This code
refuses to build those commands at all,
and the refusal is covered by a test.
Malformed payloads to Canon's 0x9052 put the camera into Err 70. That
happened twice while the channel was being worked out, and cleared on a battery
pull with no lasting damage, but it is the reason the command framing here is
asserted byte-for-byte rather than written from a description. If those tests
ever fail, do not run the result against a camera.
Do not sweep opcodes blind in the 0x9050–0x905F range. It contains write
and call primitives, and these bodies also advertise UpdateFirmware,
FormatStore and DeleteObject.
How it works
Cameras that still answer the standard PTP shutter counter property are read
that way, whatever the make. Canon bodies that do not are read over the factory
command channel:
0x9052 send a command params (0,1) if a reply follows, else (0,0)
data-OUT: <name NUL> <u32 argc> <argc x 20-byte argument record>
0x9053 read the reply params (0,0,1)
Both opcodes require a data phase. Calling either with parameters alone
returns DeviceBusy — which is exactly what made this channel look closed for
seven years, including to libgphoto2's maintainer.
MonReadAndGetData(2, address, length) reads MPU memory:
| MPU address |
Contents |
Confirmed reading |
0x1001 |
Shutter actuations |
26,354 → 26,374 after 20 frames |
0x100B |
Mirror actuations |
26,128 → 26,148 |
0x1015 |
Larger total counter |
28,034 → 28,054 |
0x1198 |
Error log, not a counter |
unchanged |
Full details, including the reply encoding and the evidence for it, are in
docs/PROTOCOL.md.
Repository layout
Everything except pkg/manufacturers/<name> is manufacturer agnostic, so
supporting a second maker means adding a package rather than rearranging the
rest.
| Path |
Contents |
cmd/, internal/ |
The CLI |
pkg/manufacturers |
What a camera maker has to provide to be readable |
pkg/manufacturers/canon |
Canon's factory channel, counter decoding and model list |
pkg/ptp |
PTP over USB, enough of it to send vendor opcodes |
pkg/usb |
The three USB backends and the descriptor parsing they share |
pkg/trace |
Recording and reading back a conversation with a camera |
pkg/pcap |
Reading USB captures, for traces that came from elsewhere |
docs/ |
The protocol reference |
Credit
The trail starts on a SourceForge thread with a title that says it all —
There must be a way to read shutter actuations from Digi6/6+ —
opened by Garfield in April 2017.
DD4DA answered it in May 2018 with the first public mention of MonOpen,
MonReadAndGetData and MonClose, noting that the procedure appears nowhere
in Canon's SDK. Marcus Meissner, libgphoto2's maintainer, followed up in
February 2019 with the first hypothesis about the opcode — "It might be PTP
Command 0x905f with 1 argument, like 0x17 for mirror count and 0x0d for shutter
count?" — reported DeviceBusy on an EOS 750D, and committed a wrapper for
it the same day. ptp_canon_eos_905f() is still in the tree,
and has never been wired to anything.
The Magic Lantern community's Canon 40D thread carries the firmware
symbol dump listing the Mon* family against ../MpuMonitor.c, which is what
established that these are MPU monitor functions rather than anything to do
with the main processor.
Still open, and what this is ultimately for: gphoto2 #81 (EOS 5DS),
libgphoto2 #270 (EOS 750D) and libgphoto2 #308 (EOS 6D Mark II).
What this project adds: the opcodes are 0x9052 / 0x9053, not 0x905F; the
interface is a text command channel rather than a counter selector; the missing
precondition was simply the data phase; and an argument record's first word is
a type tag, which may be an ASCII code rather than the integer 2.
Licence
MIT. See LICENSE.
MIT rather than the Apache-2.0 used across the rest of these repositories,
deliberately: the natural home for this work is
libgphoto2, which is LGPL-2.1, and
Apache-2.0 cannot be merged into it. MIT can.