More clauding

This commit is contained in:
2026-03-05 13:19:12 -06:00
parent 5a21f38dab
commit 29e80e74cd
29 changed files with 2380 additions and 1573 deletions

274
README.md
View File

@@ -2,22 +2,24 @@
Python control library and server for the TEAM1k X-ray detector.
Communicates directly with the detector FPGA via UDP, replacing the previous `k_test` C++ program. Uses EPICS PV Access (PVA) for command/status and ZMQ for bulk data transfer.
Communicates directly with the detector FPGA via UDP. Uses EPICS PV Access (PVA) for the image stream and basic controls (areaDetector-style), and a TCP protocol for the Python client.
## Architecture
```
Client machine Detector machine
+--------------+ +-------------------------------+
| | PVA (commands/status) | team1k-server |
| Client |<---------------------->| Register I/O (UDP:42000) |
| | | PVA server |
| | ZMQ (frame data) | PVA streamer thread |
| |<---------------------->| Frame capture thread |
| | | ZMQ data transfer thread |
+--------------+ | |
| Acquisition subprocess |
| UDP data recv (UDP:41000) |
| | TCP (control+data) | team1k-server |
| Client |<--------------------->| Register I/O (UDP:42000) |
| (Python) | | TCP client server |
| | | Buffered frame capture |
+--------------+ | PVA server (areaDetector) |
| PVA streamer (IMAGE) |
EPICS clients | Power supply monitoring |
+--------------+ | Auto-reconnect |
| pvget/CSS/ | PVA (IMAGE stream) | |
| other tools |<--------------------->| Acquisition subprocess |
+--------------+ | UDP data recv (UDP:41000) |
| Frame assembly |
| -> locking_shmring |
+-------------------------------+
@@ -29,154 +31,202 @@ Client machine Detector machine
pip install .
```
For HDF5 support (optional):
For notebook progress bars (optional):
```bash
pip install ".[hdf5]"
pip install ".[client]"
```
### Dependencies
- `locking_shmring` — shared memory ring buffer (must be installed separately)
- `p4p` — EPICS PV Access for Python
- `pyzmq`ZeroMQ for bulk data transfer
- `pyyaml`YAML config parsing
- `pyvisa`, `pyvisa-py` — power supply control
- `numpy`, `pyserial`
## Configuration
Create a YAML config file:
```yaml
detector:
ip: "10.0.0.32"
register_port: 42000
data_port: 41000
auto_reconnect: true
reconnect_interval: 5.0
adc:
clock_frequency_mhz: 50
data_delay: 0x180
order_7to0: 0x89abcdef
order_15to8: 0x01234567
digital_signals:
order_7to0: 0x24FFFFFF
order_15to8: 0xF5F310FF
polarity: 0x0014
defaults:
exposure_mode: 3
trigger_mode: external
trigger_polarity: rising
integration_time_ms: 4.0
server:
pv_prefix: "TEAM1K:"
client_port: 42010
peripherals:
bellow_port: "/dev/CameraBellowStage"
detector_power:
port: "/dev/DetectorPowerSupply"
voltage_step: 0.2
channels:
1: { voltage: 5.0, current: 2.0, ovp: 6.0 }
2: { voltage: 3.3, current: 1.0, ovp: 4.0 }
tec:
port: "/dev/TECsPowerSupply"
voltage_step: 0.2
channels:
1: { voltage: 12.0, current: 3.0, ovp: 14.0 }
```
## Running the server
```bash
team1k-server --detector-ip 10.0.0.32 --log-level INFO
team1k-server --config config.yaml --log-level INFO
```
All options:
Options:
```
--detector-ip Detector IP address (default: 10.0.0.32)
--register-port Detector register port (default: 42000)
--data-port Detector data port (default: 41000)
--pv-prefix PVA prefix (default: TEAM1K:)
--zmq-port ZMQ data transfer port (default: 42005)
--config Parameter file to apply on startup
--bellow-port Bellow stage serial port (default: /dev/CameraBellowStage)
--config YAML config file path
--detector-ip Override detector IP address
--pv-prefix Override PVA prefix
--client-port Override TCP client port
--log-level DEBUG, INFO, WARNING, ERROR (default: INFO)
```
The server starts DAQ automatically after initialization (continuous mode). The PVA IMAGE stream is always live.
## Client usage
### Acquire frames
The Python client connects via TCP. No EPICS/p4p installation needed.
### Basic usage
```python
from team1k import Client, ExposureModes
from team1k import Client
client = Client(data_host="detector-machine")
client = Client("detector-machine")
# Configure
client.set_exposure_mode(ExposureModes.GLOBAL_SHUTTER_CDS)
client.set_integration_time(6.0) # ms
client.exposure_mode = 3 # GlobalCDS
client.trigger_mode = 1 # External
client.integration_time = 4.0 # ms
# One-shot: start DAQ, capture 100 frames, stop DAQ
frames = client.acquire_frames(100)
# Status
print(client.state) # "Acquiring"
print(client.frame_rate) # 500.0
# Capture frames (shows progress bar)
frames = client.capture(100)
print(frames.shape) # (100, 1024, 1024)
print(frames.dtype) # uint16
client.close()
```
### Manual DAQ control
### Capture modes
```python
client = Client(data_host="detector-machine")
# Fixed number of frames
frames = client.capture(num_frames=100)
client.start_daq()
# Capture for a duration
frames = client.capture(duration=5.0)
# Grab frames from the running stream
batch1 = client.get_frames(50)
batch2 = client.get_frames(50)
# Open-ended capture (start/stop)
stream = client.start_capture()
# ... do something ...
frames = stream.stop()
client.stop_daq()
client.close()
# Disable progress bar
frames = client.capture(100, progress=False)
```
### Live image monitoring
### Peripherals
```python
client = Client()
def on_frame(image):
print(f"Frame received: {image.shape}")
client.monitor_image(on_frame)
# ... later
client.stop_monitor("IMAGE")
client.close()
```
### Status
```python
client = Client()
print(client.get_status())
print(client.is_acquiring())
print(client.get_frame_rate())
print(client.get_exposure_mode())
print(client.get_integration_time())
client.close()
```
### Other commands
```python
client.set_trigger_mode(TriggerModes.EXTERNAL)
client.set_adc_clock_freq(60.0) # MHz
client.set_adc_data_delay(0x1A7)
client.load_parameter_file("/path/to/params.txt")
client.set_test_mode(True) # FPGA test pattern
client.reset_connection()
# Peripherals
# Bellow stage
client.insert_detector()
client.retract_detector()
# Detector power
client.power_on()
client.power_off()
print(client.power_status) # {1: {"voltage": 5.01, "current": 1.2}, ...}
# TEC
client.tec_on()
client.tec_off()
print(client.tec_status)
```
### Advanced
```python
# Direct register access
value = client.read_register(22)
client.write_register(22, 3)
# Reconnect after power cycle
client.power_on()
client.reconnect()
# Full status
print(client.status())
# Context manager
with Client("detector-machine") as c:
frames = c.capture(100)
```
## PV Access interface
All PVs are prefixed with `TEAM1K:` by default.
All PVs are prefixed with `TEAM1K:` by default. Follows areaDetector conventions with RW + `_RBV` readback pattern.
### Status PVs (read-only)
### Acquisition PVs
| PV | Type | Description |
|----|------|-------------|
| `STATUS` | string[] | Server status |
| `ACQUIRING` | bool | DAQ running |
| `FRAME_RATE` | float | Current frame rate (Hz) |
| `FRAME_COUNT` | int | Total frames acquired |
| `EXPOSURE_MODE` | int | Current exposure mode (0-3) |
| `TRIGGER_MODE` | int | Current trigger mode |
| `INTEGRATION_TIME` | float | Integration time (ms) |
| `CAPTURE:STATUS` | string | IDLE / CAPTURING / READY / ERROR |
| `CAPTURE:PROGRESS` | int | Frames captured so far |
| `CAPTURE:TOTAL` | int | Frames requested |
| `IMAGE` | NTNDArray | Live image stream |
| PV | Type | RW | Description |
|----|------|----|-------------|
| `Acquire` / `_RBV` | bool | RW | Start/stop DAQ |
| `AcquireTime` / `_RBV` | float | RW | Integration time (seconds) |
| `ExposureMode` / `_RBV` | enum | RW | Rolling/Rolling with Pause/Global/Global with CDS |
| `TriggerMode` / `_RBV` | enum | RW | Internal/External |
| `ArrayCounter_RBV` | int | RO | Total frames acquired |
| `FrameRate_RBV` | float | RO | Current frame rate (Hz) |
| `DetectorState_RBV` | enum | RO | Disconnected/Initializing/Idle/Acquiring/Error |
| `MaxSizeX_RBV` / `MaxSizeY_RBV` | int | RO | 1024, 1024 |
| `IMAGE` | NTNDArray | RO | Live image stream |
### Command PVs (writable)
### Peripheral PVs
| PV | Type | Description |
|----|------|-------------|
| `CMD:EXPOSURE_MODE` | int | Set exposure mode |
| `CMD:TRIGGER_MODE` | int | Set trigger mode |
| `CMD:INTEGRATION_TIME` | float | Set integration time (ms) |
| `CMD:START_DAQ` | int | 1=start, 0=stop |
| `CMD:CAPTURE` | int | Capture N frames |
| `CMD:ADC_CLOCK_FREQ` | float | Set ADC clock (MHz) |
| `CMD:ADC_DATA_DELAY` | int | Set ADC data delay |
| `CMD:PARAMETER_FILE` | string | Apply parameter file |
| `CMD:RESET` | int | 1=reset connection |
| `CMD:TEST_MODE` | int | 1=enable FPGA test data |
| PV | Type | RW | Description |
|----|------|----|-------------|
| `Bellow:Insert` | bool | RW | Write true to insert |
| `Bellow:Retract` | bool | RW | Write true to retract |
| `Bellow:Position_RBV` | int | RO | Current position |
| `Power:Enable` / `_RBV` | bool | RW | Detector power on/off |
| `Power:ChN:Voltage_RBV` | float | RO | Channel N voltage |
| `Power:ChN:Current_RBV` | float | RO | Channel N current |
| `TEC:Enable` / `_RBV` | bool | RW | TEC power on/off |
| `TEC:ChN:Voltage_RBV` | float | RO | Channel N voltage |
| `TEC:ChN:Current_RBV` | float | RO | Channel N current |
## Package structure
@@ -189,17 +239,21 @@ src/team1k/
chip_config.py # Chip constants (1024x1024, packet layout)
commands.py # Detector commands (exposure, trigger, etc.)
adc.py # Si570 I2C clock programming
parameter_file.py # Parameter file parser
acquisition/ # High-throughput data path
receiver.py # Acquisition subprocess (UDP -> shmring)
filewriter/ # On-demand frame capture
capture.py # FrameCapture + ZMQ DataTransferServer
pva/ # EPICS PV Access
interface.py # PVA server, command/status PVs
pva/ # EPICS PV Access (areaDetector-style)
interface.py # PVA server, RW PVs with _RBV readbacks
streamer.py # shmring -> NTNDArray live stream
peripherals/ # Hardware peripherals
power_base.py # VISA power supply base class
power_supply.py # Detector power supply
tec.py # TEC controller
bellow_stage.py # Camera bellow stage (serial)
power_supply.py # Power supply control
config.py # YAML configuration loader
state.py # DetectorState enum
server.py # Main server entry point
Client.py # PVA + ZMQ client library
tcp_server.py # TCP client handler
tcp_protocol.py # Shared TCP message framing
capture.py # Buffered frame capture (shmring -> TCP)
client.py # Pure-TCP client library
```