Skip to content

Add Coherent HOPS / Verdi-G (Genesis CX-Vis) laser driver, DLL and native pyftdi#113

Merged
dccote merged 4 commits into
masterfrom
verdi-laser
Jul 10, 2026
Merged

Add Coherent HOPS / Verdi-G (Genesis CX-Vis) laser driver, DLL and native pyftdi#113
dccote merged 4 commits into
masterfrom
verdi-laser

Conversation

@dccote

@dccote dccote commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Use at your own risk: reverse-engineered without Coherent

Adds computer control for the Coherent Vergi G laser (the 532 nm pump). It presents as a "Verdi-G" but is actually a Genesis CX-Vis (head G532) on a HOPS (High Output Power Supply) whose USB is not a serial port: the FT2232 is driven as bit-banged I2C, so no serial framing ever answers. Two working control paths are provided for the same laser.

All the work here was obtained by reverse engineering the windows CohrHOPS.dll provided by Coherent. I want to use macOS or Linux, so I created this pure-Python version (with the help of Claude and no help from Coherent). Thank you to https://github.com/AllenNeuralDynamics/coherent-lasers which gave me critical information to get started. Using Claude is a great way of getting things done, but Claude is not the greatest for architecture, therefore I iterated several hours to get clean code that was easy to use and followed the PyHardwareLibrary strategy.

Why a driver is needed

You can't talk to the laser directly and send text commands for the Verdi G series. The computer reaches an FTDI FT2232 over USB, and that device is connected (through the I2C protocol) to other chips/devices (identified with a number 0x29, 0x48, etc...) that provide services or information; its channel A is an I2C master, and all control lives on that one I2C bus — power DAC 0x29, ADC 0x48, and the shutter/enable/remote I/O expander 0x25 in the supply, plus the head's identity/calibration EEPROM 0x52 across the umbilical, inside the laser head. There is no serial/host path to the head, so every command must speak HOPS: through Coherent's CohrHOPS.dll, or natively over pyftdi I2C.

HOPS / Verdi-G I2C control bus

Strategy

For the details on how I reconstructed from the sniffed I2C traffic; see hardwarelibrary/manuals/Coherent-HOPS-3-I2C-Wire-Protocol.md.

Since I had a working Windows DLL, I used AllenNeuralDynamics code to talk to the Verdi, but I intercepted the calls and dumped the parameters of the functions that were called. This allowed me to decode the ASCII commands and the corresponding I2C devices/registers to create a Pure-Python version without the need for this Windows-only, binary, proprietary-for-no-reason DLL.

Since everything goes through the High Output Power Supply (which I suppose is a very general device that Coherent reuses on many systems and then connects various things through I2C to it), the trick is to create a "HOPS Interface" using either the coherent-provided DLL or pure-Python. Then the LaserSourceDevice:VerdiGDevice creates one or the other, and will use the appropriate one but will otherwise be unaware of the details.

What's added

  • sources/verdig.pyVerdiGDevice (+ DebugVerdiGDevice) and the abstract
    HOPSInterface. VerdiGDevice combines OnOff / Shutter / Power / Interlock and
    delegates every hook to whichever transport it holds;
    VerdiGDevice(interface="auto") tries native first, then the DLL (or force one
    with "native" / "dll" / an instance).
  • sources/hopsnative.pyHOPSNativeInterface (+ HOPSNativeI2C,
    MockHOPSBus): the pure-Python pyftdi I2C transport, no DLL, so the laser
    runs natively on macOS/Linux.
  • sources/hopsdll.pyHOPSDLLInterface (+ the CohrHOPS ctypes
    binding): the transport over Coherent's CohrHOPS.dll (Windows/Linux).
  • manuals/ — a HOPS USB-protocol reference; a reproducible runbook for the
    macOS-writes / Windows-runs / Parallels workflow used to reach the laser; a
    decode of the HOPS I2C protocol sniffed with a logging CohrFTCI2C.dll proxy;
    and the bus diagram (SVG + PNG).
  • TeststestVerdiG.py: 22 tests (the 3 hardware tests skip when no laser is attached).

Verification status

  • Native path (HOPSNativeInterface, the default): hardware-confirmed end to
    end on the lab Genesis CX-Vis via interface="auto" (identity, on/off,
    shutter, remote write+read, power setpoint, power=0, temperature).
  • DLL path (HOPSDLLInterface): hardware-confirmed for the read path and the
    REM/PCMD write path; the emission-enable (KSWCMD) and shutter (SHCMD)
    writes are per the DLL spec but not yet exercised.
  • Documented gaps (not yet reverse-engineered) on the native transport: the
    full ?FF fault/interlock decode, ?PLIM, and the ADC power-read scale — so
    VerdiGDevice keeps InterlockControl in its capabilities but interlock() /
    faults() raise HOPSInterface.NotSupported on native, and the temperature
    calibration is a two-point fit valid only ~32-40 C.

Notes

  • pyftdi claims the FT2232 on macOS/Ventura without unloading Apple's VCP driver. This is new: if you are running on an older OS, this will fail.
  • The vendor DLLs, capture logs, and reverse-engineering prototypes live in a gitignored scratch-hops/ and are intentionally not part of this PR.
  • The untested Verdi V-series (V-2/V-5/V-6) RS-232 driver was intentionally left out: no such hardware is available to validate it.

Generated with Claude Code
https://claude.ai/code/session_01841beMNdCw3zYf6fdBUoy7

@dccote dccote changed the title Add Coherent Verdi (V-series) and HOPS/Verdi-G (Genesis) laser drivers Add Coherent HOPS / Verdi-G (Genesis CX-Vis) laser driver, DLL and native pyftdi Jul 10, 2026
dccote and others added 2 commits July 10, 2026 01:15
VerdiGDevice is a LaserSourceDevice (OnOff / Shutter / Power / Interlock) for a
Coherent laser on a HOPS ("High Output Power Supply") supply; the lab head reports
as a Genesis CX-Vis (G532). The supply is not serial: its FTDI FT2232
(0x0403:0x6010) is driven as bit-banged I2C, with the power DAC, ADC,
shutter/enable GPIO, and the head identity/calibration EEPROM all on one I2C bus.

VerdiGDevice drives that bus through an interchangeable HOPSInterface, selected
with interface="auto" (native first, then the DLL; or "native"/"dll"/an instance):
- HOPSNativeInterface (hopsnative.py): pure-Python pyftdi I2C, no DLL
  (macOS/Linux). Hardware-confirmed end to end on the lab unit (identity, on/off,
  shutter, remote, power setpoint, temperature). interlock()/faults() raise
  HOPSInterface.NotSupported until the ?FF decode is reverse-engineered.
- HOPSDLLInterface (hopsdll.py): Coherent's CohrHOPS.dll (ASCII command set;
  Windows/Linux), with the CohrHOPS ctypes binding.

Tests in testVerdiG.py: debug, native-on-mock, and read-modify-write always run;
the hardware class skips when no laser is reachable. scratch-hops/ (vendor DLLs
and reverse-engineering prototypes) is gitignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01841beMNdCw3zYf6fdBUoy7
Reconstructed by intercepting CohrHOPS.dll and decoding its I2C traffic. Three
numbered docs to read in order, plus the bus diagram:
1. Overview & runbook -- the macOS-writes / Windows-runs / Parallels workflow used
   to reach the laser, and how to reproduce it.
2. USB / CohrHOPS.dll command protocol.
3. I2C wire protocol (device addresses, registers, calibration) and the bus
   diagram (SVG + PNG) -- what the native pyftdi transport replays.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01841beMNdCw3zYf6fdBUoy7
dccote and others added 2 commits July 10, 2026 01:26
Add a "Commands implemented by the driver" section to
Coherent-HOPS-2-USB-and-DLL-Protocol.md: the exact ASCII queries, diagnostics,
and settings HOPSDLLInterface exchanges through CohrHOPS.dll, each with its
description and return value, plus the ?FF fault-bit decode. Notes that the
native pyftdi transport maps the same operations onto I2C.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01841beMNdCw3zYf6fdBUoy7
Two read-only status monitors under examples/verdig/ (stream once per second,
Ctrl-C to stop; neither takes remote control, so the front panel stays usable):
- temperature_monitor_native.py: main temperature over native pyftdi I2C (no DLL).
- status_monitor_dll.py: full status (four servo temperatures, power, shutter,
  emission, interlock/faults) over Coherent's CohrHOPS.dll.
Plus a short README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01841beMNdCw3zYf6fdBUoy7
@dccote
dccote merged commit ce03dde into master Jul 10, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant