Initial commit: merge nuescan, pymso, pybbd202, and pypewpewhops into scanengine-3
- Merged four separate hardware control projects into unified platform - Created unified requirements.txt with all dependencies - Added comprehensive .gitignore - Added project overview README Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
# Coherent HOPS Laser I2C Protocol Documentation
|
||||
|
||||
This document describes the I2C communication protocol used to control Coherent HOPS laser systems, extracted from the CohrHopsDemo v2.0.7 codebase.
|
||||
|
||||
## Hardware Overview
|
||||
|
||||
### FTDI Interface
|
||||
- **Chip**: FT2232C (dual-channel USB)
|
||||
- **Protocol**: I2C via MPSSE (Multi-Protocol Synchronous Serial Engine)
|
||||
- **Library**: CohrFTCI2C.dll (Windows), use libftdi/libmpsse on Linux
|
||||
|
||||
### I2C Configuration
|
||||
| Parameter | Value/Range |
|
||||
|-----------|-------------|
|
||||
| Clock Divisor | 0 - 65535 |
|
||||
| Modes | STANDARD, FAST |
|
||||
| Control Bytes | 1 - 255 |
|
||||
| Data Bytes | 1 - 65535 |
|
||||
|
||||
### I2C Slave
|
||||
- **Device**: NXP microcontroller
|
||||
- **Role**: Intermediary between FTDI and laser hardware
|
||||
|
||||
---
|
||||
|
||||
## I2C Library Functions
|
||||
|
||||
These are the low-level FTDI I2C functions (from CohrFTCI2C.dll):
|
||||
|
||||
| Function | Purpose |
|
||||
|----------|---------|
|
||||
| `I2C_GetNumDevices` | Enumerate connected I2C devices |
|
||||
| `I2C_GetDeviceNameLocID` | Get device location identifier |
|
||||
| `I2C_GetDeviceNameSerialNumber` | Get device serial number |
|
||||
| `I2C_Open` | Open I2C device |
|
||||
| `I2C_OpenEx` | Extended open with options |
|
||||
| `I2C_OpenSerialNumber` | Open device by serial number |
|
||||
| `I2C_InitDevice` | Initialize MPSSE interface |
|
||||
| `I2C_SetMode` | Set STANDARD or FAST mode |
|
||||
| `I2C_GetClock` | Get current clock divisor |
|
||||
| `I2C_SetClock` | Set clock divisor |
|
||||
| `I2C_SetLoopback` | Enable/disable loopback testing |
|
||||
| `I2C_Write` | Write control + data bytes |
|
||||
| `I2C_Read` | Read data bytes |
|
||||
| `I2C_ReadAlt` | Alternative read function |
|
||||
| `I2C_Close` | Close I2C device |
|
||||
| `I2C_GetErrorCodeString` | Get error descriptions |
|
||||
|
||||
---
|
||||
|
||||
## NXP Slave Operations
|
||||
|
||||
The NXP microcontroller provides these I2C operations:
|
||||
|
||||
| Method | Purpose |
|
||||
|--------|---------|
|
||||
| `NXP::Write` | Write data to I2C slave |
|
||||
| `NXP::Read` | Read data from I2C slave |
|
||||
| `NXP::WriteRegister` | Write to internal registers |
|
||||
| `NXP::ReadRegister` | Read from internal registers |
|
||||
| `NXP::WriteGPIO` | Control GPIO outputs |
|
||||
| `NXP::ReadGPIO` | Read GPIO inputs |
|
||||
|
||||
---
|
||||
|
||||
## I2C Transaction Format
|
||||
|
||||
### Write Operation
|
||||
```
|
||||
1. WriteControlBuffer: I2C slave address + W bit (0)
|
||||
2. WriteDataBuffer: Register address + data
|
||||
- BYTE mode: Single byte writes
|
||||
- PAGE mode: Multi-byte writes
|
||||
```
|
||||
|
||||
### Read Operation
|
||||
```
|
||||
1. WriteControlBuffer: I2C slave address + R bit (1)
|
||||
2. ReadDataBuffer: Receive response
|
||||
- BYTE mode: Single byte reads
|
||||
- BLOCK mode: Multi-byte reads
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## High-Level Command Interface
|
||||
|
||||
Commands are sent via `CohrHOPS_SendCommand()` using the format `?COMMAND` for queries.
|
||||
|
||||
### System Information Commands
|
||||
|
||||
| Command | Purpose | Example Response |
|
||||
|---------|---------|------------------|
|
||||
| `?HID` | Query Hardware ID | Device identifier |
|
||||
| `?HTYPE` | Query Head Type | Head variant |
|
||||
| `?HBDREV` | Query Head Board Revision | PCB revision |
|
||||
| `?HEADDIO` | Query Head Digital I/O | DIO configuration |
|
||||
| `?LASERMODEL` | Query Laser Model | G532, Tina, Mini00, MiniX |
|
||||
| `?POWERUNITS` | Query Power Units | mW, W, etc. |
|
||||
| `?WAVELENGTH` | Query Wavelength | 532nm, etc. |
|
||||
|
||||
### Temperature Monitoring
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `?TMAIN` | Main Heatsink Temperature |
|
||||
| `?TBRF` | BRF (Birefringent Filter) Temperature |
|
||||
| `?TSHG` | SHG (Second Harmonic Generator) Temperature |
|
||||
| `?TTHG` | THG (Third Harmonic Generator) Temperature |
|
||||
| `?TETA` | ETA Temperature |
|
||||
|
||||
### Temperature Control (Setpoints)
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `?TMAINCMD` | Get/Set Main Temperature Setpoint |
|
||||
| `?TBRFCMD` | Get/Set BRF Temperature Setpoint |
|
||||
| `?TSHGCMD` | Get/Set SHG Temperature Setpoint |
|
||||
| `?TTHGCMD` | Get/Set THG Temperature Setpoint |
|
||||
| `?TETACMD` | Get/Set ETA Temperature Setpoint |
|
||||
|
||||
### Temperature Data
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `?MAIND` | Main Temperature Data |
|
||||
| `?BRFD` | BRF Temperature Data |
|
||||
| `?SHGD` | SHG Temperature Data |
|
||||
| `?THGD` | THG Temperature Data |
|
||||
| `?ETAD` | ETA Temperature Data |
|
||||
|
||||
### Power Control
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `?PCMD` | Get/Set Power Command |
|
||||
| `?PMEM` | Query Power Memory (stored settings) |
|
||||
| `?PLIM` | Query Power Limits |
|
||||
|
||||
### Current Control
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `?CCMD` | Get/Set Current Command |
|
||||
| `?CLIM` | Query Current Limits |
|
||||
| `?CMODE` | Get/Set Control Mode |
|
||||
| `?CMODECMD` | Get/Set Control Mode Command |
|
||||
|
||||
### Digital I/O
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `?PSDIO` | Power Supply Digital I/O |
|
||||
| `?PSGLUEIN` | Power Supply Glue Logic Input |
|
||||
| `?PSGLUEOUT` | Power Supply Glue Logic Output |
|
||||
|
||||
### Monitoring & Status
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `?ANA` | Query Analog Values |
|
||||
| `?ANACMD` | Get/Set Analog Command |
|
||||
| `?KSW` | Key Switch Status |
|
||||
| `?KSWCMD` | Get/Set Key Switch Command |
|
||||
| `?FAN` | Fan Status/Control |
|
||||
| `?INT` | Interlock Status |
|
||||
| `?REM` | Remote Control Status |
|
||||
| `?EEH` | EEPROM Header |
|
||||
|
||||
### Configuration Registers
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `?CFG0` | Configuration Register 0 |
|
||||
| `?CFG1` | Configuration Register 1 |
|
||||
| `?CFG2` | Configuration Register 2 |
|
||||
| `?CFG3` | Configuration Register 3 |
|
||||
|
||||
---
|
||||
|
||||
## Supported Laser Models
|
||||
|
||||
| Model | Description |
|
||||
|-------|-------------|
|
||||
| G532 | 532nm Green Laser |
|
||||
| Tina | Proprietary Model |
|
||||
| Mini00 | Compact Variant |
|
||||
| MiniX | Extended Mini Variant |
|
||||
| CommonLaser | Base Implementation |
|
||||
| DummyLaser | Test/Simulation |
|
||||
|
||||
---
|
||||
|
||||
## Linux Implementation Guide
|
||||
|
||||
### Required Libraries
|
||||
|
||||
For Linux implementation, use one of:
|
||||
- **libftdi** + **libmpsse** - Direct FTDI MPSSE control
|
||||
- **pylibftdi** - Python bindings for libftdi
|
||||
- Standard Linux I2C (`/dev/i2c-*`) if FTDI exposes as I2C adapter
|
||||
|
||||
### Installation (Debian/Ubuntu)
|
||||
|
||||
```bash
|
||||
sudo apt install libftdi-dev libmpsse-dev
|
||||
```
|
||||
|
||||
### Basic Implementation Steps
|
||||
|
||||
1. **Initialize FTDI Device**
|
||||
```c
|
||||
// Find and open FT2232C device
|
||||
ftdi_init(&ftdi);
|
||||
ftdi_usb_open(&ftdi, 0x0403, 0x6010); // FTDI VID/PID
|
||||
```
|
||||
|
||||
2. **Configure MPSSE for I2C**
|
||||
```c
|
||||
// Enable MPSSE mode
|
||||
ftdi_set_bitmode(&ftdi, 0, BITMODE_MPSSE);
|
||||
|
||||
// Set I2C clock speed
|
||||
// Clock = 60MHz / ((1 + divisor) * 2)
|
||||
```
|
||||
|
||||
3. **Send I2C Commands**
|
||||
```c
|
||||
// Write command to laser
|
||||
i2c_write(slave_addr, "?HID", 4);
|
||||
|
||||
// Read response
|
||||
i2c_read(slave_addr, buffer, sizeof(buffer));
|
||||
```
|
||||
|
||||
### Example: Query Laser Model
|
||||
|
||||
```c
|
||||
#include <ftdi.h>
|
||||
#include <mpsse.h>
|
||||
|
||||
int main() {
|
||||
struct mpsse_context *i2c;
|
||||
char response[256];
|
||||
|
||||
// Open I2C at 100kHz
|
||||
i2c = MPSSE(I2C, ONE_HUNDRED_KHZ, MSB);
|
||||
|
||||
if (i2c && i2c->open) {
|
||||
// Send query command
|
||||
Start(i2c);
|
||||
Write(i2c, "?LASERMODEL", 11);
|
||||
Stop(i2c);
|
||||
|
||||
// Read response
|
||||
Start(i2c);
|
||||
char *data = Read(i2c, 256);
|
||||
Stop(i2c);
|
||||
|
||||
printf("Laser Model: %s\n", data);
|
||||
free(data);
|
||||
}
|
||||
|
||||
Close(i2c);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Errors
|
||||
|
||||
| Error | Description |
|
||||
|-------|-------------|
|
||||
| Timeout after control byte | No ACK received after sending slave address |
|
||||
| Timeout after data byte | No ACK received after sending data |
|
||||
| MPSSE sync failure | Failed to synchronize FTDI MPSSE interface |
|
||||
|
||||
### Recovery
|
||||
|
||||
1. Reset MPSSE interface
|
||||
2. Re-initialize I2C
|
||||
3. Check physical connections
|
||||
4. Verify I2C slave address
|
||||
|
||||
---
|
||||
|
||||
## Protocol Notes
|
||||
|
||||
- Commands use ASCII text format
|
||||
- Query commands start with `?`
|
||||
- Set commands likely use `=` followed by value
|
||||
- Responses are ASCII strings
|
||||
- Temperature values likely in degrees Celsius
|
||||
- Power values use units from `?POWERUNITS` response
|
||||
|
||||
---
|
||||
|
||||
## Source Files Reference
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `CohrHOPS.dll` | Main laser control library |
|
||||
| `CohrFTCI2C.dll` | FTDI I2C bridge library |
|
||||
| `main.c` | Demo application |
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- FTDI MPSSE Documentation: https://ftdichip.com/software-examples/mpsse-projects/
|
||||
- libmpsse: https://github.com/devttys0/libmpsse
|
||||
- Linux I2C: https://www.kernel.org/doc/html/latest/i2c/
|
||||
@@ -0,0 +1,283 @@
|
||||
# PyPewPewHOPS - Coherent HOPS Laser Control Library
|
||||
|
||||
Python library for controlling Coherent HOPS laser systems via I2C protocol through an FTDI FT2232C USB interface.
|
||||
|
||||
## Features
|
||||
|
||||
- Complete implementation of all documented I2C commands
|
||||
- Support for system information queries
|
||||
- Temperature monitoring and control for all sensors
|
||||
- Power and current control
|
||||
- Digital I/O operations
|
||||
- Configuration register access
|
||||
- Context manager support for safe resource handling
|
||||
- Dummy laser simulator for development without hardware
|
||||
- Comprehensive error handling
|
||||
|
||||
## Installation
|
||||
|
||||
### Requirements
|
||||
|
||||
- Python 3.7+
|
||||
- FTDI FT2232C USB device
|
||||
- libftdi library (for Linux)
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Linux Setup
|
||||
|
||||
On Linux, you may need to install libftdi:
|
||||
|
||||
```bash
|
||||
# Debian/Ubuntu
|
||||
sudo apt install libftdi-dev
|
||||
|
||||
# Fedora
|
||||
sudo dnf install libftdi-devel
|
||||
```
|
||||
|
||||
You may also need to add your user to the appropriate group:
|
||||
|
||||
```bash
|
||||
sudo usermod -a -G dialout $USER
|
||||
sudo usermod -a -G plugdev $USER
|
||||
```
|
||||
|
||||
Then log out and log back in for the changes to take effect.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Using the Simulator (No Hardware)
|
||||
|
||||
```python
|
||||
from coherent_hops_laser import DummyLaser
|
||||
|
||||
with DummyLaser() as laser:
|
||||
# Query system information
|
||||
info = laser.get_system_info()
|
||||
print(f"Model: {info.laser_model}")
|
||||
print(f"Wavelength: {info.wavelength}")
|
||||
|
||||
# Monitor temperatures
|
||||
temps = laser.get_all_temperatures()
|
||||
print(f"Main temperature: {temps.main}°C")
|
||||
|
||||
# Control power
|
||||
laser.set_power_command(100.0)
|
||||
power = laser.get_power_command()
|
||||
print(f"Power set to: {power} mW")
|
||||
```
|
||||
|
||||
### Using Real Hardware
|
||||
|
||||
```python
|
||||
from coherent_hops_laser import CoherentHOPSLaser, I2CMode
|
||||
|
||||
# Initialize laser controller
|
||||
laser = CoherentHOPSLaser(
|
||||
slave_address=0x50, # I2C slave address
|
||||
i2c_mode=I2CMode.STANDARD # 100 kHz
|
||||
)
|
||||
|
||||
# Connect to FTDI device
|
||||
laser.connect('ftdi://ftdi:2232/1')
|
||||
|
||||
try:
|
||||
# Query laser model
|
||||
model = laser.get_laser_model()
|
||||
print(f"Laser Model: {model}")
|
||||
|
||||
# Get all temperatures
|
||||
temps = laser.get_all_temperatures()
|
||||
print(f"Temperatures: {temps}")
|
||||
|
||||
# Set power
|
||||
laser.set_power_command(50.0)
|
||||
|
||||
# Check control mode
|
||||
mode = laser.get_control_mode()
|
||||
print(f"Control Mode: {mode}")
|
||||
|
||||
finally:
|
||||
laser.disconnect()
|
||||
```
|
||||
|
||||
### Using Context Manager
|
||||
|
||||
```python
|
||||
from coherent_hops_laser import CoherentHOPSLaser
|
||||
|
||||
with CoherentHOPSLaser() as laser:
|
||||
laser.connect()
|
||||
|
||||
# Your laser control code here
|
||||
info = laser.get_system_info()
|
||||
print(info)
|
||||
|
||||
# Automatically disconnects
|
||||
```
|
||||
|
||||
## Available Commands
|
||||
|
||||
### System Information
|
||||
|
||||
- `get_hardware_id()` - Hardware ID
|
||||
- `get_head_type()` - Head type
|
||||
- `get_head_board_revision()` - PCB revision
|
||||
- `get_laser_model()` - Laser model (G532, Tina, Mini00, MiniX)
|
||||
- `get_power_units()` - Power units (mW, W)
|
||||
- `get_wavelength()` - Wavelength (e.g., 532nm)
|
||||
- `get_system_info()` - All system info at once
|
||||
|
||||
### Temperature Monitoring
|
||||
|
||||
- `get_temperature_main()` - Main heatsink temperature
|
||||
- `get_temperature_brf()` - BRF temperature
|
||||
- `get_temperature_shg()` - SHG temperature
|
||||
- `get_temperature_thg()` - THG temperature
|
||||
- `get_temperature_eta()` - ETA temperature
|
||||
- `get_all_temperatures()` - All temperatures at once
|
||||
|
||||
### Temperature Control
|
||||
|
||||
- `get_temperature_setpoint_main()` / `set_temperature_setpoint_main(temp)`
|
||||
- `get_temperature_setpoint_brf()` / `set_temperature_setpoint_brf(temp)`
|
||||
- `get_temperature_setpoint_shg()` / `set_temperature_setpoint_shg(temp)`
|
||||
- `get_temperature_setpoint_thg()` / `set_temperature_setpoint_thg(temp)`
|
||||
- `get_temperature_setpoint_eta()` / `set_temperature_setpoint_eta(temp)`
|
||||
|
||||
### Power Control
|
||||
|
||||
- `get_power_command()` / `set_power_command(power)` - Get/set power
|
||||
- `get_power_memory()` - Stored power settings
|
||||
- `get_power_limits()` - Power limits
|
||||
|
||||
### Current Control
|
||||
|
||||
- `get_current_command()` / `set_current_command(current)` - Get/set current
|
||||
- `get_current_limits()` - Current limits
|
||||
- `get_control_mode()` / `set_control_mode(mode)` - Control mode (POWER/CURRENT)
|
||||
|
||||
### Status Monitoring
|
||||
|
||||
- `get_key_switch_status()` - Key switch status
|
||||
- `get_fan_status()` / `set_fan_control(value)` - Fan control
|
||||
- `get_interlock_status()` - Interlock status
|
||||
- `get_remote_control_status()` - Remote control status
|
||||
- `get_analog_values()` - Analog sensor values
|
||||
|
||||
### Configuration
|
||||
|
||||
- `get_config_register_0()` / `set_config_register_0(value)`
|
||||
- `get_config_register_1()` / `set_config_register_1(value)`
|
||||
- `get_config_register_2()` / `set_config_register_2(value)`
|
||||
- `get_config_register_3()` / `set_config_register_3(value)`
|
||||
|
||||
See the [API documentation](LASER_I2C_PROTOCOL.md) for complete command reference.
|
||||
|
||||
## Examples
|
||||
|
||||
Run the example script:
|
||||
|
||||
```bash
|
||||
# Simulation mode (no hardware)
|
||||
python3 example_usage.py 1
|
||||
|
||||
# Real hardware mode
|
||||
python3 example_usage.py 2
|
||||
|
||||
# Continuous monitoring
|
||||
python3 example_usage.py 3
|
||||
```
|
||||
|
||||
Or run the built-in test:
|
||||
|
||||
```bash
|
||||
python3 coherent_hops_laser.py
|
||||
```
|
||||
|
||||
## Continuous Monitoring Example
|
||||
|
||||
```python
|
||||
from coherent_hops_laser import CoherentHOPSLaser
|
||||
import time
|
||||
|
||||
with CoherentHOPSLaser() as laser:
|
||||
laser.connect()
|
||||
|
||||
while True:
|
||||
temps = laser.get_all_temperatures()
|
||||
power = laser.get_power_command()
|
||||
|
||||
print(f"Main: {temps.main:.1f}°C Power: {power:.1f}mW")
|
||||
time.sleep(1)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Cannot find FTDI device
|
||||
|
||||
```bash
|
||||
# Check if device is connected
|
||||
lsusb | grep FTDI
|
||||
|
||||
# Should show something like:
|
||||
# Bus 001 Device 005: ID 0403:6010 Future Technology Devices International, Ltd FT2232C
|
||||
```
|
||||
|
||||
### Permission denied
|
||||
|
||||
Add your user to the dialout/plugdev group:
|
||||
|
||||
```bash
|
||||
sudo usermod -a -G dialout $USER
|
||||
sudo usermod -a -G plugdev $USER
|
||||
```
|
||||
|
||||
Then log out and back in.
|
||||
|
||||
### I2C communication errors
|
||||
|
||||
- Verify correct slave address (default: 0x50)
|
||||
- Check I2C speed (try I2CMode.STANDARD instead of FAST)
|
||||
- Verify physical connections
|
||||
- Check for other devices on the bus
|
||||
|
||||
### Import errors
|
||||
|
||||
```bash
|
||||
# Install pyftdi
|
||||
pip install pyftdi
|
||||
|
||||
# If that fails, try:
|
||||
pip install --user pyftdi
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
- **CoherentHOPSLaser**: Main class for real hardware control
|
||||
- **DummyLaser**: Simulator for development without hardware
|
||||
- **I2CController**: Low-level FTDI I2C communication (from pyftdi)
|
||||
- **LaserInfo / TemperatureStatus**: Data classes for structured responses
|
||||
|
||||
## Safety Notes
|
||||
|
||||
- Always verify power levels before enabling laser output
|
||||
- Monitor temperatures during operation
|
||||
- Check interlock status before operation
|
||||
- Use appropriate laser safety equipment
|
||||
- Follow all manufacturer safety guidelines
|
||||
|
||||
## License
|
||||
|
||||
This implementation is based on the Coherent HOPS Demo v2.0.7 protocol documentation.
|
||||
|
||||
## References
|
||||
|
||||
- FTDI MPSSE Documentation: https://ftdichip.com/software-examples/mpsse-projects/
|
||||
- PyFTDI: https://github.com/eblot/pyftdi
|
||||
- Original Protocol Documentation: [LASER_I2C_PROTOCOL.md](LASER_I2C_PROTOCOL.md)
|
||||
@@ -0,0 +1,773 @@
|
||||
"""
|
||||
Coherent HOPS Laser I2C Control Library
|
||||
|
||||
This module provides a Python interface to control Coherent HOPS laser systems
|
||||
via I2C protocol through an FTDI FT2232C USB interface.
|
||||
|
||||
Dependencies:
|
||||
pip install pyftdi
|
||||
|
||||
Usage:
|
||||
from coherent_hops_laser import CoherentHOPSLaser
|
||||
|
||||
laser = CoherentHOPSLaser()
|
||||
laser.connect()
|
||||
|
||||
# Query system information
|
||||
model = laser.get_laser_model()
|
||||
wavelength = laser.get_wavelength()
|
||||
|
||||
# Monitor temperatures
|
||||
main_temp = laser.get_temperature_main()
|
||||
|
||||
# Control power
|
||||
laser.set_power_command(100.0) # Set power in mW or W
|
||||
|
||||
laser.disconnect()
|
||||
"""
|
||||
|
||||
from typing import Optional, Union, List
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
import time
|
||||
import logging
|
||||
|
||||
try:
|
||||
from pyftdi.i2c import I2cController, I2cNackError
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"pyftdi library is required. Install with: pip install pyftdi"
|
||||
)
|
||||
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class I2CMode(Enum):
|
||||
"""I2C communication modes"""
|
||||
STANDARD = 100000 # 100 kHz
|
||||
FAST = 400000 # 400 kHz
|
||||
|
||||
|
||||
class ControlMode(Enum):
|
||||
"""Laser control modes"""
|
||||
POWER = "POWER"
|
||||
CURRENT = "CURRENT"
|
||||
|
||||
|
||||
@dataclass
|
||||
class LaserInfo:
|
||||
"""Laser system information"""
|
||||
hardware_id: Optional[str] = None
|
||||
head_type: Optional[str] = None
|
||||
head_board_revision: Optional[str] = None
|
||||
laser_model: Optional[str] = None
|
||||
power_units: Optional[str] = None
|
||||
wavelength: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class TemperatureStatus:
|
||||
"""Temperature monitoring data"""
|
||||
main: Optional[float] = None
|
||||
brf: Optional[float] = None
|
||||
shg: Optional[float] = None
|
||||
thg: Optional[float] = None
|
||||
eta: Optional[float] = None
|
||||
|
||||
|
||||
class CoherentHOPSLaser:
|
||||
"""
|
||||
Main interface class for Coherent HOPS laser control via I2C.
|
||||
|
||||
This class provides high-level methods for all documented laser commands
|
||||
including system queries, temperature control, power/current management,
|
||||
and digital I/O operations.
|
||||
"""
|
||||
|
||||
# Default I2C slave address for NXP microcontroller
|
||||
DEFAULT_SLAVE_ADDRESS = 0x50
|
||||
|
||||
# FTDI USB VID/PID for FT2232C
|
||||
FTDI_VID = 0x0403
|
||||
FTDI_PID = 0x6010
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
slave_address: int = DEFAULT_SLAVE_ADDRESS,
|
||||
i2c_mode: I2CMode = I2CMode.STANDARD,
|
||||
timeout: float = 1.0
|
||||
):
|
||||
"""
|
||||
Initialize the laser controller.
|
||||
|
||||
Args:
|
||||
slave_address: I2C slave address of the NXP microcontroller
|
||||
i2c_mode: I2C communication speed mode
|
||||
timeout: Command timeout in seconds
|
||||
"""
|
||||
self.slave_address = slave_address
|
||||
self.i2c_mode = i2c_mode
|
||||
self.timeout = timeout
|
||||
|
||||
self._i2c_controller = I2cController()
|
||||
self._i2c_slave = None
|
||||
self._connected = False
|
||||
|
||||
def connect(self, url: str = 'ftdi://ftdi:2232/1') -> None:
|
||||
"""
|
||||
Connect to the FTDI I2C device.
|
||||
|
||||
Args:
|
||||
url: FTDI device URL (default: first FT2232C device, channel 1)
|
||||
Examples:
|
||||
- 'ftdi://ftdi:2232/1' - First FT2232 device, channel 1
|
||||
- 'ftdi://ftdi:2232:SERIAL/1' - Device with specific serial number
|
||||
|
||||
Raises:
|
||||
IOError: If connection fails
|
||||
"""
|
||||
try:
|
||||
# Configure I2C controller
|
||||
self._i2c_controller.configure(url, frequency=self.i2c_mode.value)
|
||||
|
||||
# Get I2C slave interface
|
||||
self._i2c_slave = self._i2c_controller.get_port(self.slave_address)
|
||||
|
||||
self._connected = True
|
||||
logger.info(f"Connected to laser at I2C address 0x{self.slave_address:02X}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to connect to I2C device: {e}")
|
||||
raise IOError(f"I2C connection failed: {e}")
|
||||
|
||||
def disconnect(self) -> None:
|
||||
"""Disconnect from the I2C device."""
|
||||
if self._connected:
|
||||
self._i2c_controller.terminate()
|
||||
self._connected = False
|
||||
logger.info("Disconnected from laser")
|
||||
|
||||
def _ensure_connected(self) -> None:
|
||||
"""Verify device is connected before operations."""
|
||||
if not self._connected:
|
||||
raise RuntimeError("Not connected. Call connect() first.")
|
||||
|
||||
def _send_command(self, command: str, value: Optional[str] = None) -> str:
|
||||
"""
|
||||
Send a command to the laser and read response.
|
||||
|
||||
Args:
|
||||
command: Command string (e.g., 'HID', 'LASERMODEL')
|
||||
value: Optional value for set commands
|
||||
|
||||
Returns:
|
||||
Response string from the laser
|
||||
|
||||
Raises:
|
||||
I2cNackError: If I2C communication fails
|
||||
TimeoutError: If response timeout occurs
|
||||
"""
|
||||
self._ensure_connected()
|
||||
|
||||
# Format command: query = ?COMMAND, set = COMMAND=VALUE
|
||||
if value is not None:
|
||||
cmd_str = f"{command}={value}"
|
||||
else:
|
||||
cmd_str = f"?{command}"
|
||||
|
||||
cmd_bytes = cmd_str.encode('ascii')
|
||||
|
||||
try:
|
||||
# Write command
|
||||
self._i2c_slave.write(cmd_bytes)
|
||||
|
||||
# Small delay for laser to process
|
||||
time.sleep(0.01)
|
||||
|
||||
# Read response (max 256 bytes)
|
||||
response = self._i2c_slave.read(256)
|
||||
|
||||
# Decode and strip null bytes and whitespace
|
||||
result = response.decode('ascii', errors='ignore').rstrip('\x00').strip()
|
||||
|
||||
logger.debug(f"Command: {cmd_str} -> Response: {result}")
|
||||
return result
|
||||
|
||||
except I2cNackError as e:
|
||||
logger.error(f"I2C NACK error for command {cmd_str}: {e}")
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Communication error for command {cmd_str}: {e}")
|
||||
raise
|
||||
|
||||
# ==========================================
|
||||
# System Information Commands
|
||||
# ==========================================
|
||||
|
||||
def get_hardware_id(self) -> str:
|
||||
"""Query Hardware ID."""
|
||||
return self._send_command('HID')
|
||||
|
||||
def get_head_type(self) -> str:
|
||||
"""Query Head Type."""
|
||||
return self._send_command('HTYPE')
|
||||
|
||||
def get_head_board_revision(self) -> str:
|
||||
"""Query Head Board Revision."""
|
||||
return self._send_command('HBDREV')
|
||||
|
||||
def get_head_digital_io(self) -> str:
|
||||
"""Query Head Digital I/O configuration."""
|
||||
return self._send_command('HEADDIO')
|
||||
|
||||
def get_laser_model(self) -> str:
|
||||
"""
|
||||
Query Laser Model.
|
||||
|
||||
Returns:
|
||||
Model name (e.g., 'G532', 'Tina', 'Mini00', 'MiniX')
|
||||
"""
|
||||
return self._send_command('LASERMODEL')
|
||||
|
||||
def get_power_units(self) -> str:
|
||||
"""
|
||||
Query Power Units.
|
||||
|
||||
Returns:
|
||||
Power units (e.g., 'mW', 'W')
|
||||
"""
|
||||
return self._send_command('POWERUNITS')
|
||||
|
||||
def get_wavelength(self) -> str:
|
||||
"""
|
||||
Query Wavelength.
|
||||
|
||||
Returns:
|
||||
Wavelength (e.g., '532nm')
|
||||
"""
|
||||
return self._send_command('WAVELENGTH')
|
||||
|
||||
def get_system_info(self) -> LaserInfo:
|
||||
"""
|
||||
Query all system information.
|
||||
|
||||
Returns:
|
||||
LaserInfo dataclass with all system parameters
|
||||
"""
|
||||
return LaserInfo(
|
||||
hardware_id=self.get_hardware_id(),
|
||||
head_type=self.get_head_type(),
|
||||
head_board_revision=self.get_head_board_revision(),
|
||||
laser_model=self.get_laser_model(),
|
||||
power_units=self.get_power_units(),
|
||||
wavelength=self.get_wavelength()
|
||||
)
|
||||
|
||||
# ==========================================
|
||||
# Temperature Monitoring
|
||||
# ==========================================
|
||||
|
||||
def get_temperature_main(self) -> float:
|
||||
"""
|
||||
Get Main Heatsink Temperature.
|
||||
|
||||
Returns:
|
||||
Temperature in degrees Celsius
|
||||
"""
|
||||
response = self._send_command('TMAIN')
|
||||
return float(response)
|
||||
|
||||
def get_temperature_brf(self) -> float:
|
||||
"""
|
||||
Get BRF (Birefringent Filter) Temperature.
|
||||
|
||||
Returns:
|
||||
Temperature in degrees Celsius
|
||||
"""
|
||||
response = self._send_command('TBRF')
|
||||
return float(response)
|
||||
|
||||
def get_temperature_shg(self) -> float:
|
||||
"""
|
||||
Get SHG (Second Harmonic Generator) Temperature.
|
||||
|
||||
Returns:
|
||||
Temperature in degrees Celsius
|
||||
"""
|
||||
response = self._send_command('TSHG')
|
||||
return float(response)
|
||||
|
||||
def get_temperature_thg(self) -> float:
|
||||
"""
|
||||
Get THG (Third Harmonic Generator) Temperature.
|
||||
|
||||
Returns:
|
||||
Temperature in degrees Celsius
|
||||
"""
|
||||
response = self._send_command('TTHG')
|
||||
return float(response)
|
||||
|
||||
def get_temperature_eta(self) -> float:
|
||||
"""
|
||||
Get ETA Temperature.
|
||||
|
||||
Returns:
|
||||
Temperature in degrees Celsius
|
||||
"""
|
||||
response = self._send_command('TETA')
|
||||
return float(response)
|
||||
|
||||
def get_all_temperatures(self) -> TemperatureStatus:
|
||||
"""
|
||||
Query all temperature sensors.
|
||||
|
||||
Returns:
|
||||
TemperatureStatus dataclass with all temperature readings
|
||||
"""
|
||||
return TemperatureStatus(
|
||||
main=self.get_temperature_main(),
|
||||
brf=self.get_temperature_brf(),
|
||||
shg=self.get_temperature_shg(),
|
||||
thg=self.get_temperature_thg(),
|
||||
eta=self.get_temperature_eta()
|
||||
)
|
||||
|
||||
# ==========================================
|
||||
# Temperature Control (Setpoints)
|
||||
# ==========================================
|
||||
|
||||
def get_temperature_setpoint_main(self) -> float:
|
||||
"""Get Main Temperature Setpoint."""
|
||||
response = self._send_command('TMAINCMD')
|
||||
return float(response)
|
||||
|
||||
def set_temperature_setpoint_main(self, temperature: float) -> None:
|
||||
"""Set Main Temperature Setpoint."""
|
||||
self._send_command('TMAINCMD', str(temperature))
|
||||
|
||||
def get_temperature_setpoint_brf(self) -> float:
|
||||
"""Get BRF Temperature Setpoint."""
|
||||
response = self._send_command('TBRFCMD')
|
||||
return float(response)
|
||||
|
||||
def set_temperature_setpoint_brf(self, temperature: float) -> None:
|
||||
"""Set BRF Temperature Setpoint."""
|
||||
self._send_command('TBRFCMD', str(temperature))
|
||||
|
||||
def get_temperature_setpoint_shg(self) -> float:
|
||||
"""Get SHG Temperature Setpoint."""
|
||||
response = self._send_command('TSHGCMD')
|
||||
return float(response)
|
||||
|
||||
def set_temperature_setpoint_shg(self, temperature: float) -> None:
|
||||
"""Set SHG Temperature Setpoint."""
|
||||
self._send_command('TSHGCMD', str(temperature))
|
||||
|
||||
def get_temperature_setpoint_thg(self) -> float:
|
||||
"""Get THG Temperature Setpoint."""
|
||||
response = self._send_command('TTHGCMD')
|
||||
return float(response)
|
||||
|
||||
def set_temperature_setpoint_thg(self, temperature: float) -> None:
|
||||
"""Set THG Temperature Setpoint."""
|
||||
self._send_command('TTHGCMD', str(temperature))
|
||||
|
||||
def get_temperature_setpoint_eta(self) -> float:
|
||||
"""Get ETA Temperature Setpoint."""
|
||||
response = self._send_command('TETACMD')
|
||||
return float(response)
|
||||
|
||||
def set_temperature_setpoint_eta(self, temperature: float) -> None:
|
||||
"""Set ETA Temperature Setpoint."""
|
||||
self._send_command('TETACMD', str(temperature))
|
||||
|
||||
# ==========================================
|
||||
# Temperature Data
|
||||
# ==========================================
|
||||
|
||||
def get_temperature_data_main(self) -> str:
|
||||
"""Get Main Temperature Data."""
|
||||
return self._send_command('MAIND')
|
||||
|
||||
def get_temperature_data_brf(self) -> str:
|
||||
"""Get BRF Temperature Data."""
|
||||
return self._send_command('BRFD')
|
||||
|
||||
def get_temperature_data_shg(self) -> str:
|
||||
"""Get SHG Temperature Data."""
|
||||
return self._send_command('SHGD')
|
||||
|
||||
def get_temperature_data_thg(self) -> str:
|
||||
"""Get THG Temperature Data."""
|
||||
return self._send_command('THGD')
|
||||
|
||||
def get_temperature_data_eta(self) -> str:
|
||||
"""Get ETA Temperature Data."""
|
||||
return self._send_command('ETAD')
|
||||
|
||||
# ==========================================
|
||||
# Power Control
|
||||
# ==========================================
|
||||
|
||||
def get_power_command(self) -> float:
|
||||
"""
|
||||
Get Power Command value.
|
||||
|
||||
Returns:
|
||||
Power value in units from get_power_units()
|
||||
"""
|
||||
response = self._send_command('PCMD')
|
||||
return float(response)
|
||||
|
||||
def set_power_command(self, power: float) -> None:
|
||||
"""
|
||||
Set Power Command value.
|
||||
|
||||
Args:
|
||||
power: Power value in units from get_power_units()
|
||||
"""
|
||||
self._send_command('PCMD', str(power))
|
||||
|
||||
def get_power_memory(self) -> str:
|
||||
"""Query Power Memory (stored settings)."""
|
||||
return self._send_command('PMEM')
|
||||
|
||||
def get_power_limits(self) -> str:
|
||||
"""Query Power Limits."""
|
||||
return self._send_command('PLIM')
|
||||
|
||||
# ==========================================
|
||||
# Current Control
|
||||
# ==========================================
|
||||
|
||||
def get_current_command(self) -> float:
|
||||
"""
|
||||
Get Current Command value.
|
||||
|
||||
Returns:
|
||||
Current value in Amperes
|
||||
"""
|
||||
response = self._send_command('CCMD')
|
||||
return float(response)
|
||||
|
||||
def set_current_command(self, current: float) -> None:
|
||||
"""
|
||||
Set Current Command value.
|
||||
|
||||
Args:
|
||||
current: Current value in Amperes
|
||||
"""
|
||||
self._send_command('CCMD', str(current))
|
||||
|
||||
def get_current_limits(self) -> str:
|
||||
"""Query Current Limits."""
|
||||
return self._send_command('CLIM')
|
||||
|
||||
def get_control_mode(self) -> str:
|
||||
"""
|
||||
Get Control Mode.
|
||||
|
||||
Returns:
|
||||
Control mode (e.g., 'POWER' or 'CURRENT')
|
||||
"""
|
||||
return self._send_command('CMODE')
|
||||
|
||||
def set_control_mode(self, mode: Union[str, ControlMode]) -> None:
|
||||
"""
|
||||
Set Control Mode.
|
||||
|
||||
Args:
|
||||
mode: Control mode ('POWER' or 'CURRENT', or ControlMode enum)
|
||||
"""
|
||||
if isinstance(mode, ControlMode):
|
||||
mode = mode.value
|
||||
self._send_command('CMODE', mode)
|
||||
|
||||
def get_control_mode_command(self) -> str:
|
||||
"""Get Control Mode Command."""
|
||||
return self._send_command('CMODECMD')
|
||||
|
||||
def set_control_mode_command(self, mode: str) -> None:
|
||||
"""Set Control Mode Command."""
|
||||
self._send_command('CMODECMD', mode)
|
||||
|
||||
# ==========================================
|
||||
# Digital I/O
|
||||
# ==========================================
|
||||
|
||||
def get_ps_digital_io(self) -> str:
|
||||
"""Get Power Supply Digital I/O status."""
|
||||
return self._send_command('PSDIO')
|
||||
|
||||
def get_ps_glue_input(self) -> str:
|
||||
"""Get Power Supply Glue Logic Input status."""
|
||||
return self._send_command('PSGLUEIN')
|
||||
|
||||
def get_ps_glue_output(self) -> str:
|
||||
"""Get Power Supply Glue Logic Output status."""
|
||||
return self._send_command('PSGLUEOUT')
|
||||
|
||||
def set_ps_glue_output(self, value: str) -> None:
|
||||
"""Set Power Supply Glue Logic Output."""
|
||||
self._send_command('PSGLUEOUT', value)
|
||||
|
||||
# ==========================================
|
||||
# Monitoring & Status
|
||||
# ==========================================
|
||||
|
||||
def get_analog_values(self) -> str:
|
||||
"""Query Analog Values."""
|
||||
return self._send_command('ANA')
|
||||
|
||||
def get_analog_command(self) -> str:
|
||||
"""Get Analog Command."""
|
||||
return self._send_command('ANACMD')
|
||||
|
||||
def set_analog_command(self, value: str) -> None:
|
||||
"""Set Analog Command."""
|
||||
self._send_command('ANACMD', value)
|
||||
|
||||
def get_key_switch_status(self) -> str:
|
||||
"""Get Key Switch Status."""
|
||||
return self._send_command('KSW')
|
||||
|
||||
def get_key_switch_command(self) -> str:
|
||||
"""Get Key Switch Command."""
|
||||
return self._send_command('KSWCMD')
|
||||
|
||||
def set_key_switch_command(self, value: str) -> None:
|
||||
"""Set Key Switch Command."""
|
||||
self._send_command('KSWCMD', value)
|
||||
|
||||
def get_fan_status(self) -> str:
|
||||
"""Get Fan Status/Control."""
|
||||
return self._send_command('FAN')
|
||||
|
||||
def set_fan_control(self, value: str) -> None:
|
||||
"""Set Fan Control."""
|
||||
self._send_command('FAN', value)
|
||||
|
||||
def get_interlock_status(self) -> str:
|
||||
"""Get Interlock Status."""
|
||||
return self._send_command('INT')
|
||||
|
||||
def get_remote_control_status(self) -> str:
|
||||
"""Get Remote Control Status."""
|
||||
return self._send_command('REM')
|
||||
|
||||
def get_eeprom_header(self) -> str:
|
||||
"""Get EEPROM Header."""
|
||||
return self._send_command('EEH')
|
||||
|
||||
# ==========================================
|
||||
# Configuration Registers
|
||||
# ==========================================
|
||||
|
||||
def get_config_register_0(self) -> str:
|
||||
"""Get Configuration Register 0."""
|
||||
return self._send_command('CFG0')
|
||||
|
||||
def set_config_register_0(self, value: str) -> None:
|
||||
"""Set Configuration Register 0."""
|
||||
self._send_command('CFG0', value)
|
||||
|
||||
def get_config_register_1(self) -> str:
|
||||
"""Get Configuration Register 1."""
|
||||
return self._send_command('CFG1')
|
||||
|
||||
def set_config_register_1(self, value: str) -> None:
|
||||
"""Set Configuration Register 1."""
|
||||
self._send_command('CFG1', value)
|
||||
|
||||
def get_config_register_2(self) -> str:
|
||||
"""Get Configuration Register 2."""
|
||||
return self._send_command('CFG2')
|
||||
|
||||
def set_config_register_2(self, value: str) -> None:
|
||||
"""Set Configuration Register 2."""
|
||||
self._send_command('CFG2', value)
|
||||
|
||||
def get_config_register_3(self) -> str:
|
||||
"""Get Configuration Register 3."""
|
||||
return self._send_command('CFG3')
|
||||
|
||||
def set_config_register_3(self, value: str) -> None:
|
||||
"""Set Configuration Register 3."""
|
||||
self._send_command('CFG3', value)
|
||||
|
||||
# ==========================================
|
||||
# Context Manager Support
|
||||
# ==========================================
|
||||
|
||||
def __enter__(self):
|
||||
"""Context manager entry."""
|
||||
if not self._connected:
|
||||
self.connect()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Context manager exit."""
|
||||
self.disconnect()
|
||||
return False
|
||||
|
||||
|
||||
class DummyLaser(CoherentHOPSLaser):
|
||||
"""
|
||||
Simulated laser for testing without hardware.
|
||||
|
||||
This class provides dummy responses for all commands to enable
|
||||
software development and testing without physical hardware.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize dummy laser (no I2C connection needed)."""
|
||||
super().__init__()
|
||||
self._connected = True # Simulate connection
|
||||
|
||||
# Simulated state
|
||||
self._power_cmd = 50.0
|
||||
self._current_cmd = 1.5
|
||||
self._control_mode = 'POWER'
|
||||
self._temps = {
|
||||
'main': 25.0,
|
||||
'brf': 30.0,
|
||||
'shg': 35.0,
|
||||
'thg': 32.0,
|
||||
'eta': 28.0
|
||||
}
|
||||
self._temp_setpoints = {
|
||||
'main': 25.0,
|
||||
'brf': 30.0,
|
||||
'shg': 35.0,
|
||||
'thg': 32.0,
|
||||
'eta': 28.0
|
||||
}
|
||||
|
||||
def connect(self, url: str = 'dummy') -> None:
|
||||
"""Dummy connection (always succeeds)."""
|
||||
self._connected = True
|
||||
logger.info("Connected to DummyLaser (simulation mode)")
|
||||
|
||||
def _send_command(self, command: str, value: Optional[str] = None) -> str:
|
||||
"""Simulate command responses."""
|
||||
logger.debug(f"DummyLaser command: {command}, value: {value}")
|
||||
|
||||
# Handle set commands
|
||||
if value is not None:
|
||||
if command == 'PCMD':
|
||||
self._power_cmd = float(value)
|
||||
return value
|
||||
elif command == 'CCMD':
|
||||
self._current_cmd = float(value)
|
||||
return value
|
||||
elif command == 'CMODE':
|
||||
self._control_mode = value
|
||||
return value
|
||||
elif 'CMD' in command and 'T' in command:
|
||||
# Temperature setpoint
|
||||
key = command.replace('CMD', '').replace('T', '').lower()
|
||||
if key in self._temp_setpoints:
|
||||
self._temp_setpoints[key] = float(value)
|
||||
return value
|
||||
return 'OK'
|
||||
|
||||
# Handle query commands
|
||||
responses = {
|
||||
'HID': 'HOPS-12345',
|
||||
'HTYPE': 'Standard',
|
||||
'HBDREV': 'Rev 2.1',
|
||||
'HEADDIO': '0xFF',
|
||||
'LASERMODEL': 'G532',
|
||||
'POWERUNITS': 'mW',
|
||||
'WAVELENGTH': '532nm',
|
||||
'TMAIN': str(self._temps['main']),
|
||||
'TBRF': str(self._temps['brf']),
|
||||
'TSHG': str(self._temps['shg']),
|
||||
'TTHG': str(self._temps['thg']),
|
||||
'TETA': str(self._temps['eta']),
|
||||
'TMAINCMD': str(self._temp_setpoints['main']),
|
||||
'TBRFCMD': str(self._temp_setpoints['brf']),
|
||||
'TSHGCMD': str(self._temp_setpoints['shg']),
|
||||
'TTHGCMD': str(self._temp_setpoints['thg']),
|
||||
'TETACMD': str(self._temp_setpoints['eta']),
|
||||
'MAIND': 'MainTempData',
|
||||
'BRFD': 'BRFTempData',
|
||||
'SHGD': 'SHGTempData',
|
||||
'THGD': 'THGTempData',
|
||||
'ETAD': 'ETATempData',
|
||||
'PCMD': str(self._power_cmd),
|
||||
'PMEM': '100',
|
||||
'PLIM': '0-200',
|
||||
'CCMD': str(self._current_cmd),
|
||||
'CLIM': '0-5',
|
||||
'CMODE': self._control_mode,
|
||||
'CMODECMD': self._control_mode,
|
||||
'PSDIO': '0x00',
|
||||
'PSGLUEIN': '0x00',
|
||||
'PSGLUEOUT': '0x00',
|
||||
'ANA': '0,0,0,0',
|
||||
'ANACMD': '0',
|
||||
'KSW': 'ON',
|
||||
'KSWCMD': 'ON',
|
||||
'FAN': 'AUTO',
|
||||
'INT': 'OK',
|
||||
'REM': 'ENABLED',
|
||||
'EEH': 'EEPROM_V1',
|
||||
'CFG0': '0x00',
|
||||
'CFG1': '0x00',
|
||||
'CFG2': '0x00',
|
||||
'CFG3': '0x00',
|
||||
}
|
||||
|
||||
return responses.get(command, 'UNKNOWN')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""Example usage and testing."""
|
||||
|
||||
# Test with dummy laser
|
||||
print("=== Testing with DummyLaser ===\n")
|
||||
|
||||
with DummyLaser() as laser:
|
||||
# System info
|
||||
print("System Information:")
|
||||
info = laser.get_system_info()
|
||||
print(f" Model: {info.laser_model}")
|
||||
print(f" Wavelength: {info.wavelength}")
|
||||
print(f" Power Units: {info.power_units}")
|
||||
print(f" Hardware ID: {info.hardware_id}")
|
||||
print()
|
||||
|
||||
# Temperature monitoring
|
||||
print("Temperature Status:")
|
||||
temps = laser.get_all_temperatures()
|
||||
print(f" Main: {temps.main}°C")
|
||||
print(f" BRF: {temps.brf}°C")
|
||||
print(f" SHG: {temps.shg}°C")
|
||||
print(f" THG: {temps.thg}°C")
|
||||
print(f" ETA: {temps.eta}°C")
|
||||
print()
|
||||
|
||||
# Power control
|
||||
print("Power Control:")
|
||||
current_power = laser.get_power_command()
|
||||
print(f" Current Power: {current_power} {info.power_units}")
|
||||
laser.set_power_command(75.0)
|
||||
new_power = laser.get_power_command()
|
||||
print(f" New Power: {new_power} {info.power_units}")
|
||||
print()
|
||||
|
||||
# Control mode
|
||||
print("Control Mode:")
|
||||
mode = laser.get_control_mode()
|
||||
print(f" Current Mode: {mode}")
|
||||
print()
|
||||
|
||||
print("\n=== For real hardware, use: ===")
|
||||
print("laser = CoherentHOPSLaser()")
|
||||
print("laser.connect('ftdi://ftdi:2232/1')")
|
||||
print("# ... perform operations ...")
|
||||
print("laser.disconnect()")
|
||||
@@ -0,0 +1,256 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Example usage of the Coherent HOPS Laser control library.
|
||||
|
||||
This script demonstrates how to use the library to control a real laser system.
|
||||
"""
|
||||
|
||||
from coherent_hops_laser import CoherentHOPSLaser, DummyLaser, I2CMode
|
||||
import time
|
||||
|
||||
|
||||
def demo_system_info(laser):
|
||||
"""Demonstrate system information queries."""
|
||||
print("=" * 60)
|
||||
print("SYSTEM INFORMATION")
|
||||
print("=" * 60)
|
||||
|
||||
info = laser.get_system_info()
|
||||
print(f"Hardware ID: {info.hardware_id}")
|
||||
print(f"Laser Model: {info.laser_model}")
|
||||
print(f"Wavelength: {info.wavelength}")
|
||||
print(f"Power Units: {info.power_units}")
|
||||
print(f"Head Type: {info.head_type}")
|
||||
print(f"Board Revision: {info.head_board_revision}")
|
||||
print()
|
||||
|
||||
|
||||
def demo_temperature_monitoring(laser):
|
||||
"""Demonstrate temperature monitoring."""
|
||||
print("=" * 60)
|
||||
print("TEMPERATURE MONITORING")
|
||||
print("=" * 60)
|
||||
|
||||
temps = laser.get_all_temperatures()
|
||||
print(f"Main Heatsink: {temps.main:.2f}°C")
|
||||
print(f"BRF (Birefringent): {temps.brf:.2f}°C")
|
||||
print(f"SHG (2nd Harmonic): {temps.shg:.2f}°C")
|
||||
print(f"THG (3rd Harmonic): {temps.thg:.2f}°C")
|
||||
print(f"ETA: {temps.eta:.2f}°C")
|
||||
print()
|
||||
|
||||
|
||||
def demo_temperature_control(laser):
|
||||
"""Demonstrate temperature setpoint control."""
|
||||
print("=" * 60)
|
||||
print("TEMPERATURE CONTROL")
|
||||
print("=" * 60)
|
||||
|
||||
# Read current setpoints
|
||||
print("Current Setpoints:")
|
||||
print(f" Main: {laser.get_temperature_setpoint_main():.2f}°C")
|
||||
print(f" BRF: {laser.get_temperature_setpoint_brf():.2f}°C")
|
||||
print(f" SHG: {laser.get_temperature_setpoint_shg():.2f}°C")
|
||||
print()
|
||||
|
||||
# Example: Set a new setpoint (commented out for safety)
|
||||
# print("Setting Main temperature setpoint to 26.0°C...")
|
||||
# laser.set_temperature_setpoint_main(26.0)
|
||||
# print(f" New setpoint: {laser.get_temperature_setpoint_main():.2f}°C")
|
||||
print("(Temperature setpoint modification disabled in demo)")
|
||||
print()
|
||||
|
||||
|
||||
def demo_power_control(laser):
|
||||
"""Demonstrate power control."""
|
||||
print("=" * 60)
|
||||
print("POWER CONTROL")
|
||||
print("=" * 60)
|
||||
|
||||
units = laser.get_power_units()
|
||||
current_power = laser.get_power_command()
|
||||
print(f"Current Power: {current_power} {units}")
|
||||
|
||||
power_limits = laser.get_power_limits()
|
||||
print(f"Power Limits: {power_limits}")
|
||||
|
||||
power_memory = laser.get_power_memory()
|
||||
print(f"Power Memory: {power_memory}")
|
||||
print()
|
||||
|
||||
# Example: Set power (commented out for safety)
|
||||
# print("Setting power to 100.0 mW...")
|
||||
# laser.set_power_command(100.0)
|
||||
# print(f" New power: {laser.get_power_command()} {units}")
|
||||
print("(Power modification disabled in demo)")
|
||||
print()
|
||||
|
||||
|
||||
def demo_current_control(laser):
|
||||
"""Demonstrate current control."""
|
||||
print("=" * 60)
|
||||
print("CURRENT CONTROL")
|
||||
print("=" * 60)
|
||||
|
||||
current = laser.get_current_command()
|
||||
print(f"Current Command: {current} A")
|
||||
|
||||
limits = laser.get_current_limits()
|
||||
print(f"Current Limits: {limits}")
|
||||
|
||||
mode = laser.get_control_mode()
|
||||
print(f"Control Mode: {mode}")
|
||||
print()
|
||||
|
||||
|
||||
def demo_status_monitoring(laser):
|
||||
"""Demonstrate status monitoring."""
|
||||
print("=" * 60)
|
||||
print("STATUS MONITORING")
|
||||
print("=" * 60)
|
||||
|
||||
print(f"Key Switch: {laser.get_key_switch_status()}")
|
||||
print(f"Fan Status: {laser.get_fan_status()}")
|
||||
print(f"Interlock: {laser.get_interlock_status()}")
|
||||
print(f"Remote Control: {laser.get_remote_control_status()}")
|
||||
print(f"Analog Values: {laser.get_analog_values()}")
|
||||
print()
|
||||
|
||||
|
||||
def demo_configuration(laser):
|
||||
"""Demonstrate configuration register access."""
|
||||
print("=" * 60)
|
||||
print("CONFIGURATION REGISTERS")
|
||||
print("=" * 60)
|
||||
|
||||
print(f"Config Register 0: {laser.get_config_register_0()}")
|
||||
print(f"Config Register 1: {laser.get_config_register_1()}")
|
||||
print(f"Config Register 2: {laser.get_config_register_2()}")
|
||||
print(f"Config Register 3: {laser.get_config_register_3()}")
|
||||
print()
|
||||
|
||||
|
||||
def main_dummy_demo():
|
||||
"""Run demo with simulated hardware."""
|
||||
print("\n" + "=" * 60)
|
||||
print("COHERENT HOPS LASER CONTROL - SIMULATION MODE")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
with DummyLaser() as laser:
|
||||
demo_system_info(laser)
|
||||
demo_temperature_monitoring(laser)
|
||||
demo_temperature_control(laser)
|
||||
demo_power_control(laser)
|
||||
demo_current_control(laser)
|
||||
demo_status_monitoring(laser)
|
||||
demo_configuration(laser)
|
||||
|
||||
# Demonstrate power control
|
||||
print("=" * 60)
|
||||
print("POWER CONTROL DEMONSTRATION (Simulation)")
|
||||
print("=" * 60)
|
||||
print(f"Initial power: {laser.get_power_command()} mW")
|
||||
laser.set_power_command(125.0)
|
||||
print(f"After setting to 125.0 mW: {laser.get_power_command()} mW")
|
||||
print()
|
||||
|
||||
|
||||
def main_real_hardware():
|
||||
"""Run demo with real hardware."""
|
||||
print("\n" + "=" * 60)
|
||||
print("COHERENT HOPS LASER CONTROL - REAL HARDWARE")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
# Configure for your specific setup
|
||||
FTDI_URL = 'ftdi://ftdi:2232/1' # Adjust if needed
|
||||
SLAVE_ADDRESS = 0x50 # Default NXP slave address
|
||||
I2C_FREQUENCY = I2CMode.STANDARD # or I2CMode.FAST
|
||||
|
||||
try:
|
||||
# Connect to laser
|
||||
laser = CoherentHOPSLaser(
|
||||
slave_address=SLAVE_ADDRESS,
|
||||
i2c_mode=I2C_FREQUENCY
|
||||
)
|
||||
|
||||
print(f"Connecting to FTDI device at {FTDI_URL}...")
|
||||
laser.connect(FTDI_URL)
|
||||
print("Connected successfully!\n")
|
||||
|
||||
# Run demos
|
||||
demo_system_info(laser)
|
||||
demo_temperature_monitoring(laser)
|
||||
demo_status_monitoring(laser)
|
||||
demo_power_control(laser)
|
||||
demo_current_control(laser)
|
||||
|
||||
# Clean disconnect
|
||||
laser.disconnect()
|
||||
print("Disconnected successfully.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
print("\nTroubleshooting:")
|
||||
print("1. Check FTDI device is connected (lsusb | grep FTDI)")
|
||||
print("2. Verify user permissions (add user to 'dialout' or 'plugdev' group)")
|
||||
print("3. Check FTDI URL matches your device")
|
||||
print("4. Try: sudo python3 example_usage.py (not recommended long-term)")
|
||||
|
||||
|
||||
def continuous_monitoring_example():
|
||||
"""Example of continuous temperature and power monitoring."""
|
||||
print("\n" + "=" * 60)
|
||||
print("CONTINUOUS MONITORING EXAMPLE")
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
with DummyLaser() as laser:
|
||||
print("Monitoring laser parameters (5 iterations)...")
|
||||
print("Press Ctrl+C to stop\n")
|
||||
|
||||
try:
|
||||
for i in range(5):
|
||||
temps = laser.get_all_temperatures()
|
||||
power = laser.get_power_command()
|
||||
mode = laser.get_control_mode()
|
||||
|
||||
print(f"[{i+1}] T_main={temps.main:.1f}°C "
|
||||
f"T_shg={temps.shg:.1f}°C "
|
||||
f"Power={power:.1f}mW "
|
||||
f"Mode={mode}")
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\nMonitoring stopped.")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
print("Coherent HOPS Laser Control - Example Usage\n")
|
||||
print("Available demos:")
|
||||
print(" 1. Simulation mode (no hardware required)")
|
||||
print(" 2. Real hardware mode")
|
||||
print(" 3. Continuous monitoring example")
|
||||
print()
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
choice = sys.argv[1]
|
||||
else:
|
||||
choice = input("Select demo (1/2/3) [default: 1]: ").strip() or "1"
|
||||
|
||||
if choice == "1":
|
||||
main_dummy_demo()
|
||||
print("\nContinuous monitoring demo:")
|
||||
continuous_monitoring_example()
|
||||
elif choice == "2":
|
||||
main_real_hardware()
|
||||
elif choice == "3":
|
||||
continuous_monitoring_example()
|
||||
else:
|
||||
print("Invalid choice. Use 1, 2, or 3.")
|
||||
sys.exit(1)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Demo complete!")
|
||||
print("=" * 60)
|
||||
@@ -0,0 +1 @@
|
||||
pyftdi>=0.54.0
|
||||
Reference in New Issue
Block a user