diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 8a2322c..1a7970f 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -29,6 +29,26 @@ jobs:
- name: Run linting checks
run: pixi run lint
+ docs:
+ name: Build Docs
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ persist-credentials: false
+
+ - name: Setup pixi
+ uses: prefix-dev/setup-pixi@2253d9c9310442c09975d64615591b5b381da6e7 # v0.10.0
+ with:
+ pixi-version: v0.71.3
+ cache: false
+ frozen: true
+ environments: docs
+
+ - name: Build docs
+ run: pixi run -e docs build-docs
+
build-and-test:
name: Build and Test ADXSPD on EL${{ matrix.rhel_version }}
diff --git a/.gitignore b/.gitignore
index 1fa7ffd..cb10540 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,4 +29,6 @@ checkVar.sh
setVar.sh
coverage.info
coverage-report/
+# rendered docs from `pixi run build-docs`
+build/
__pycache__/
diff --git a/docs/ADXSPD/ADXSPD.rst b/docs/ADXSPD/ADXSPD.rst
new file mode 100644
index 0000000..d38c0b0
--- /dev/null
+++ b/docs/ADXSPD/ADXSPD.rst
@@ -0,0 +1,524 @@
+======================================
+ADXSPD
+======================================
+
+:author: Jakub Wlodek (Brookhaven National Laboratory)
+
+.. _ADDriver: https://areadetector.github.io/areaDetector/ADCore/ADDriver.html
+.. _areaDetector: https://areadetector.github.io/areaDetector/index.html
+.. _EPICS: https://epics-controls.org
+.. _X-Spectrum: https://x-spectrum.de
+.. _Lambda: https://x-spectrum.de/products/lambda/
+.. _ADDriverTemplate: https://github.com/NSLS2/ADDriverTemplate
+.. _cpr: https://github.com/libcpr/cpr
+.. _nlohmann/json: https://github.com/nlohmann/json
+.. _magic_enum: https://github.com/Neargye/magic_enum
+.. _ZeroMQ: https://zeromq.org
+.. _ADLambda: https://github.com/areaDetector/ADLambda
+
+.. contents:: Contents
+
+.. |br| raw:: html
+
+
+
+Introduction
+------------
+
+This is an `EPICS`_ `areaDetector`_ driver for the `Lambda`_ family of photon counting
+pixel detectors from `X-Spectrum`_. The driver communicates with the detector through
+XSPD, the X-Spectrum detector remote-control service, which exposes a REST API for control and a
+`ZeroMQ`_ publisher (the "data port") for streaming frames.
+
+.. note::
+
+ The `ADLambda`_ driver supports the same detector types, but must run on the control
+ computer provided by X-Spectrum, since it uses the C++ `libxsp` API directly.
+ Because ADXSPD talks to XSPD over the network (with XSPD handling the lower level `libxsp` interactions),
+ the IOC can instead run on any other host. This has several benefits:
+
+ * The IOC can run on a standard system configuration, managed the same way as your
+ other IOC hosts, rather than on the specific Debian-based server provided by X-Spectrum.
+ * The X-Spectrum control computer can be placed on a locked-down, dedicated detector
+ network, with only the IOC host able to reach it. This allows for a less strict policy
+ regarding software updates and patches on the host, which may improve stability and reduce maintenance overhead.
+ * Since all communication between the driver and XSPD is plain HTTP and ZeroMQ, it is
+ much simpler to simulate the detector. X-Spectrum provides a simulator for a
+ single-module detector as a container image, and this repository includes a
+ multi-module simulator in ``scripts/xspdSimulator.py``.
+
+ ADXSPD also currently supports some features that ADLambda does not, including beam
+ energy, flatfield correction, bit depth and ROI readout configuration. Adding these to
+ ADLambda is tracked in
+ `ADLambda issue #16 `__.
+
+The driver is made up of three layers:
+
+* ``XSPDAPI`` - a standalone C++ wrapper around the XSPD REST API. It uses `cpr`_ for HTTP,
+ `nlohmann/json`_ for parsing responses, and `magic_enum`_ to map XSPD string values to
+ C++ enumerations. It has no dependency on EPICS.
+* ``ADXSPD`` - the main areaDetector driver, which inherits from `ADDriver`_. It controls
+ detector-wide settings, runs an acquisition thread that reads frames from the ZeroMQ
+ data port into NDArrays, and runs a monitor thread that polls detector status.
+* ``ADXSPDModule`` - an ``asynPortDriver`` that is created automatically for each detector
+ module, and which exposes per-module information such as temperatures, humidity,
+ flatfield state and chip IDs.
+
+The driver requires XSPD version 1.6.0 or later. It was initially generated with
+`ADDriverTemplate`_.
+
+Acquisition overview
+~~~~~~~~~~~~~~~~~~~~
+
+When ``Acquire`` is set to 1, the driver sends the ``start`` command to XSPD. Each frame
+published on the data port is a 3-part ZeroMQ message; the driver copies the final part
+(the frame data) into an NDArray and passes it to any connected plugins. Acquisition stops
+automatically once ``NumImages`` frames have been received (or ``2 * NumImages`` when
+``CounterMode`` is ``Dual``). Setting ``Acquire`` to 0 sends the ``stop`` command.
+
+If XSPD is configured to compress frames, the driver can either pass the compressed data
+on as a compressed NDArray (with the ``codec`` field set so that ``NDPluginCodec`` or the
+HDF5 file plugin can handle it), or decompress the data itself. See
+:ref:`ADXSPD_compression` for details.
+
+Implementation of standard driver parameters
+--------------------------------------------
+
+The following table describes how the ADXSPD driver implements some of the standard
+driver parameters.
+
+.. cssclass:: table-bordered table-striped table-hover
+.. flat-table::
+ :header-rows: 2
+ :widths: 20 20 60
+
+ * - **Parameter Definitions in ADXSPD.cpp and EPICS Record Definitions in ADXSPD.template**
+ * - Parameter index variable
+ - EPICS record name
+ - Description
+ * - ADImageMode
+ - $(P)$(R)ImageMode
+ - Only ``Single`` and ``Multiple`` are supported. ``Continuous`` has been removed from the
+ record choices. Selecting ``Single`` sets the XSPD ``n_frames`` variable to 1.
+ The image mode is also updated automatically when ``NumImages`` is changed.
+ * - ADNumImages
+ - $(P)$(R)NumImages
+ - Sets the XSPD ``n_frames`` variable. The value must be between 1 and the smallest
+ ``MaxFrames_RBV`` reported by any of the detector modules.
+ * - ADTriggerMode
+ - $(P)$(R)TriggerMode
+ - Sets the XSPD ``trigger_mode`` variable. The choices are: |br|
+ ``Software`` - acquisition starts immediately. |br|
+ ``External Frames`` - each external trigger acquires a single frame. |br|
+ ``External Sequence`` - an external trigger starts the full sequence of frames. |br|
+ Cannot be changed while acquiring.
+ * - ADAcquireTime
+ - $(P)$(R)AcquireTime
+ - Sets the XSPD ``shutter_time`` variable. The record is in seconds; the driver converts
+ to milliseconds for XSPD. Cannot be changed while acquiring.
+ * - ADAcquire
+ - $(P)$(R)Acquire
+ - Sends the ``start`` or ``stop`` command to XSPD.
+ * - NDDataType
+ - $(P)$(R)DataType
+ - Disabled. The data type is set by the driver based on ``BitDepth``: 1 and 6 bit use
+ ``UInt8``, 12 bit uses ``UInt16``, and 24 bit uses ``UInt32``.
+ * - ADMaxSizeX, ADMaxSizeY, ADSizeX, ADSizeY
+ - $(P)$(R)MaxSizeX_RBV, $(P)$(R)MaxSizeY_RBV, $(P)$(R)SizeX_RBV, $(P)$(R)SizeY_RBV
+ - Read from the data port ``frame_width`` and ``frame_height`` variables at startup,
+ and whenever ``ROIRows`` is changed. The ``SizeX``, ``SizeY``, ``MinX``, ``MinY``,
+ ``BinX``, ``BinY``, ``ReverseX``, ``ReverseY`` and ``ColorMode`` output records are
+ disabled; use ``ROIRows`` to reduce the readout area.
+ * - ADSerialNumber, ADFirmwareVersion, ADSDKVersion, ADModel
+ - $(P)$(R)SerialNumber_RBV, $(P)$(R)FirmwareVersion_RBV, $(P)$(R)SDKVersion_RBV, $(P)$(R)Model_RBV
+ - Read from XSPD at startup. ``SDKVersion_RBV`` reports the libxsp version.
+ * - NDFilePath, NDFileName, NDFullFileName
+ - $(P)$(R)FilePath, $(P)$(R)FileName, $(P)$(R)FullFileName_RBV
+ - The driver loads ``NDFile.template``, but does not save image files itself. These
+ records are used to choose where HTTP request logs are written. See
+ :ref:`ADXSPD_request_logging`.
+
+ADXSPD specific parameters
+--------------------------
+
+The ADXSPD driver implements the following parameters in addition to those in
+``asynNDArrayDriver.h`` and ``ADDriver.h``. Parameters marked "Cannot be changed while
+acquiring" are rejected with an error if ``Acquire`` is 1.
+
+.. cssclass:: table-bordered table-striped table-hover
+.. flat-table::
+ :header-rows: 2
+ :widths: 55 15 20 10
+
+ * - **Parameter Definitions in ADXSPDParamDefs.h and EPICS Record Definitions in ADXSPD.template**
+ * - Description
+ - drvInfo string
+ - EPICS record name
+ - EPICS record type
+ * - :cspan:`3` **Version and system information**
+ * - XSPD REST API version in use.
+ - XSPD_API_VERSION
+ - $(P)$(R)APIVersion_RBV
+ - stringin
+ * - XSPD server version.
+ - XSPD_VERSION
+ - $(P)$(R)XSPDVersion_RBV
+ - stringin
+ * - Number of detector modules. One ``ADXSPDModule`` port is created for each module.
+ - XSPD_NUM_MODULES
+ - $(P)$(R)NumModules_RBV
+ - ai
+ * - Sensor material, read from the detector ``user_data/sensor_material`` variable. Empty,
+ with a warning printed at startup, if this has not been set in the XSPD user data.
+ - XSPD_SENSOR_MATERIAL
+ - $(P)$(R)SensorMaterial_RBV
+ - stringin
+ * - Sensor thickness, read from the detector ``user_data/sensor_thickness`` variable. 0 if
+ this has not been set in the XSPD user data.
+ - XSPD_SENSOR_THICKNESS
+ - $(P)$(R)SensorThickness_RBV
+ - ai
+ * - :cspan:`3` **Detector settings**
+ * - X-ray beam energy in keV (XSPD ``beam_energy``).
+ - XSPD_BEAM_ENERGY
+ - $(P)$(R)BeamEnergy, $(P)$(R)BeamEnergy_RBV
+ - ao, ai
+ * - Low energy threshold in keV. Written to XSPD as the first element of ``thresholds``.
+ - XSPD_LOW_THRESHOLD
+ - $(P)$(R)LowThreshold, $(P)$(R)LowThreshold_RBV
+ - ao, ai
+ * - High energy threshold in keV. Written to XSPD as the second element of ``thresholds``.
+ The low threshold must be set first.
+ - XSPD_HIGH_THRESHOLD
+ - $(P)$(R)HighThreshold, $(P)$(R)HighThreshold_RBV
+ - ao, ai
+ * - Counter bit depth. Choices are ``1 bit``, ``6 bit``, ``12 bit`` and ``24 bit``.
+ Changing this updates ``DataType`` and each module's ``MaxFrames_RBV``.
+ Cannot be changed while acquiring.
+ - XSPD_BIT_DEPTH
+ - $(P)$(R)BitDepth, $(P)$(R)BitDepth_RBV
+ - mbbo, mbbi
+ * - Counter mode. Choices are ``Single`` and ``Dual``. In ``Dual`` mode two frames are
+ read out per image. Changing this updates each module's ``MaxFrames_RBV`` and
+ flatfield state. Cannot be changed while acquiring.
+ - XSPD_COUNTER_MODE
+ - $(P)$(R)CounterMode, $(P)$(R)CounterMode_RBV
+ - bo, bi
+ * - Number of rows to read out from each module (XSPD ``roi_rows``). Choices are powers of
+ 2 from 1 to 256. Changing this updates the frame size and each module's
+ ``MaxFrames_RBV``.
+ - XSPD_ROI_ROWS
+ - $(P)$(R)ROIRows, $(P)$(R)ROIRows_RBV
+ - mbbo, mbbi
+ * - Number of frames to sum in the detector (XSPD ``summed_frames``).
+ - XSPD_SUMMED_FRAMES
+ - $(P)$(R)SummedFrames, $(P)$(R)SummedFrames_RBV
+ - ao, ai
+ * - Enable or disable gating mode (XSPD ``gating_mode``).
+ - XSPD_GATING_MODE
+ - $(P)$(R)GatingMode, $(P)$(R)GatingMode_RBV
+ - bo, bi
+ * - Enable or disable charge summing (XSPD ``charge_summing``).
+ - XSPD_CHARGE_SUMMING
+ - $(P)$(R)ChargeSumming, $(P)$(R)ChargeSumming_RBV
+ - bo, bi
+ * - Enable or disable flatfield correction (XSPD ``flatfield_correction``).
+ - XSPD_F_F_CORRECTION
+ - $(P)$(R)FlatFieldCorrection, $(P)$(R)FlatFieldCorrection_RBV
+ - bo, bi
+ * - Enable or disable count rate correction (XSPD ``countrate_correction``).
+ - XSPD_CR_CORR
+ - $(P)$(R)CountrateCorrection, $(P)$(R)CountrateCorrection_RBV
+ - bo, bi
+ * - Enable or disable the saturation flag (XSPD ``saturation_flag``).
+ - XSPD_SATURATION_FLAG
+ - $(P)$(R)SaturationFlag, $(P)$(R)SaturationFlag_RBV
+ - bo, bi
+ * - :cspan:`3` **Compression and data handling**
+ * - Compressor used by XSPD for data port frames. Choices are ``none``, ``zlib``,
+ ``blosc/blosclz``, ``blosc/lz4``, ``blosc/lz4hc``, ``blosc/snappy``, ``blosc/zlib``
+ and ``blosc/zstd``. Read-only; configure the compressor in XSPD.
+ - XSPD_COMPRESSOR
+ - $(P)$(R)Compressor_RBV
+ - mbbi
+ * - Compression level (0-9) used by XSPD. Read-only.
+ - XSPD_COMPRESS_LEVEL
+ - $(P)$(R)CompressLevel_RBV
+ - mbbi
+ * - Blosc shuffle mode. Choices are ``None``, ``Byte Shuffle``, ``Bit Shuffle`` and
+ ``Auto``. Cannot be changed while acquiring.
+ - XSPD_SHUFFLE_MODE
+ - $(P)$(R)ShuffleMode, $(P)$(R)ShuffleMode_RBV
+ - mbbo, mbbi
+ * - If enabled, compressed frames are decompressed in the driver. If disabled, compressed
+ frames are passed on as compressed NDArrays.
+ - XSPD_DECOMPRESS
+ - $(P)$(R)Decompress, $(P)$(R)Decompress_RBV
+ - bo, bi
+ * - Number of threads used for Blosc decompression when ``Decompress`` is enabled.
+ - XSPD_BLOSC_NUM_THREADS
+ - $(P)$(R)BloscNumThreads, $(P)$(R)BloscNumThreads_RBV
+ - ao, ai
+ * - Number of frames queued on the data port and not yet sent (XSPD ``frames_queued``).
+ - XSPD_FRAMES_QUEUED
+ - $(P)$(R)FramesQueued_RBV
+ - ai
+ * - :cspan:`3` **Status monitoring**
+ * - Enable or disable the monitor thread, which polls the detector ``status`` and data port
+ ``frames_queued`` variables and updates ``DetectorState_RBV`` and ``FramesQueued_RBV``.
+ - XSPD_MONITOR_MODE
+ - $(P)$(R)MonitorMode, $(P)$(R)MonitorMode_RBV
+ - bo, bi
+ * - Interval in seconds between monitor thread polls. The minimum is 0.5 seconds.
+ - XSPD_MONITOR_INTERVAL
+ - $(P)$(R)MonitorInterval, $(P)$(R)MonitorInterval_RBV
+ - ao, ai
+ * - :cspan:`3` **Diagnostics**
+ * - Enable or disable logging of every HTTP request sent to XSPD. See
+ :ref:`ADXSPD_request_logging`.
+ - XSPD_LOG_REQUESTS
+ - $(P)$(R)LogRequests, $(P)$(R)LogRequests_RBV
+ - bo, bi
+ * - :cspan:`3` **Reserved (records exist but are not yet implemented by the driver)**
+ * - Reset the detector.
+ - XSPD_RESET
+ - $(P)$(R)Reset
+ - bo
+ * - Generate a new flatfield.
+ - XSPD_GENERATE_FLATFIELD
+ - $(P)$(R)GenerateFlatfield
+ - bo
+ * - Flatfield status message.
+ - XSPD_FLATFIELD_STATUS
+ - $(P)$(R)FlatfieldStatus_RBV
+ - waveform
+ * - Average board, FPGA and sensor temperatures across all modules.
+ - XSPD_AVG_BOARD_TEMP, XSPD_AVG_FPGA_TEMP, XSPD_AVG_SENSOR_TEMP
+ - $(P)$(R)AvgBoardTemp_RBV, $(P)$(R)AvgFPGATemp_RBV, $(P)$(R)AvgSensorTemp_RBV
+ - ai
+
+ADXSPDModule parameters
+-----------------------
+
+One ``ADXSPDModule`` asyn port is created for each detector module, named
+``_MOD1``, ``_MOD2``, etc. Load ``ADXSPDModule.template`` once for each module.
+Values are read from XSPD when the IOC starts. Module values are not currently polled by
+the monitor thread, because reading module status takes too long.
+
+.. cssclass:: table-bordered table-striped table-hover
+.. flat-table::
+ :header-rows: 2
+ :widths: 55 15 20 10
+
+ * - **Parameter Definitions in ADXSPDModuleParamDefs.h and EPICS Record Definitions in ADXSPDModule.template**
+ * - Description
+ - drvInfo string
+ - EPICS record name
+ - EPICS record type
+ * - Board, FPGA and humidity sensor temperatures in degrees C.
+ - XSPD_BOARD_TEMP, XSPD_FPGA_TEMP, XSPD_HUM_TEMP
+ - $(P)$(R)BoardTemp_RBV, $(P)$(R)FPGATemp_RBV, $(P)$(R)HumiditySensorTemp_RBV
+ - ai
+ * - Relative humidity in %.
+ - XSPD_HUM
+ - $(P)$(R)Humidity_RBV
+ - ai
+ * - Sensor high voltage in V. Only the readback is currently implemented.
+ - XSPD_VOLTAGE
+ - $(P)$(R)Voltage, $(P)$(R)Voltage_RBV
+ - ao, ai
+ * - Sensor current in uA.
+ - XSPD_SENS_CURR
+ - $(P)$(R)SensorCurrent_RBV
+ - ai
+ * - Pixel saturation threshold.
+ - XSPD_SAT_THRESH
+ - $(P)$(R)SaturationThresh_RBV
+ - ai
+ * - Module rotation (yaw, pitch, roll) in degrees.
+ - XSPD_ROT_YAW, XSPD_ROT_PITCH, XSPD_ROT_ROLL
+ - $(P)$(R)RotationYaw_RBV, $(P)$(R)RotationPitch_RBV, $(P)$(R)RotationRoll_RBV
+ - ai
+ * - Module position (X, Y, Z).
+ - XSPD_POS_X, XSPD_POS_Y, XSPD_POS_Z
+ - $(P)$(R)PositionX_RBV, $(P)$(R)PositionY_RBV, $(P)$(R)PositionZ_RBV
+ - ai
+ * - Maximum number of frames the module can buffer with the current settings. This limits
+ ``NumImages``, and is refreshed when ``BitDepth``, ``ROIRows`` or ``CounterMode``
+ change.
+ - XSPD_MAX_FRAMES
+ - $(P)$(R)MaxFrames_RBV
+ - ai
+ * - Number of sub-frames.
+ - XSPD_NUM_SUBFRAMES
+ - $(P)$(R)NumSubFrames_RBV
+ - ai
+ * - Number of connectors.
+ - XSPD_NUM_CONS
+ - $(P)$(R)NumConnectors_RBV
+ - ai
+ * - Whether pixel interpolation is enabled.
+ - XSPD_INTERP_MODE
+ - $(P)$(R)InterpolationMode_RBV
+ - bi
+ * - Bitmask of features supported by the module: |br|
+ bit 0 - ``FEAT_HV`` |br|
+ bit 1 - ``FEAT_1_6_BIT`` |br|
+ bit 2 - ``FEAT_MEDIPIX_DAC_IO`` |br|
+ bit 3 - ``FEAT_EXTENDED_GATING`` |br|
+ bit 4 - ``FEAT_ROI``
+ - XSPD_FEAT_BITMASK
+ - $(P)$(R)Features_RBV
+ - mbbiDirect
+ * - Module compressor and compression level.
+ - XSPD_COMPRESSOR, XSPD_COMPRESS_LEVEL
+ - $(P)$(R)Compressor_RBV, $(P)$(R)CompressLevel_RBV
+ - mbbi
+ * - Whether RAM has been allocated on the module.
+ - XSPD_RAM_ALLOCATED
+ - $(P)$(R)RamAllocated_RBV
+ - bi
+ * - Whether flatfield correction is enabled on the module.
+ - XSPD_FF_ENABLED
+ - $(P)$(R)FlatfieldEnabled_RBV
+ - bi
+ * - Flatfield status message.
+ - XSPD_FF_STATUS
+ - $(P)$(R)FlatfieldStatus_RBV
+ - waveform
+ * - Author and date of the low and high threshold flatfields.
+ - XSPD_LOW_THRESH_FF_AUTHOR, XSPD_HIGH_THRESH_FF_AUTHOR, XSPD_LOW_THRESH_FF_DATE,
+ XSPD_HIGH_THRESH_FF_DATE
+ - $(P)$(R)LowThreshFfAuthor_RBV, $(P)$(R)HighThreshFfAuthor_RBV,
+ $(P)$(R)LowThreshFfDate_RBV, $(P)$(R)HighThreshFfDate_RBV
+ - stringin
+ * - Number of frames queued on the module.
+ - XSPD_FRAMES_QUEUED
+ - $(P)$(R)FramesQueued_RBV
+ - ai
+ * - Whether the pixel mask is applied. Only the readback is currently implemented.
+ - XSPD_PIXEL_MASK
+ - $(P)$(R)PixelMask, $(P)$(R)PixelMask_RBV
+ - bo, bi
+ * - Module shuffle mode. Not currently implemented.
+ - XSPD_SHUFFLE_MODE
+ - $(P)$(R)ShuffleMode, $(P)$(R)ShuffleMode_RBV
+ - mbbo, mbbi
+ * - Number of chips on the module, and the ID of each chip (up to 12).
+ - XSPD_NUM_CHIPS, XSPD_CHIP1_ID ... XSPD_CHIP12_ID
+ - $(P)$(R)NumChips_RBV, $(P)$(R)Chip1ID_RBV ... $(P)$(R)Chip12ID_RBV
+ - ai, stringin
+
+.. _ADXSPD_compression:
+
+Compressed data
+---------------
+
+XSPD can compress frames before publishing them on the data port. The compressor and
+level are shown in ``Compressor_RBV`` and ``CompressLevel_RBV``. The ``Decompress``
+record controls how the driver handles compressed frames:
+
+* ``Decompress = Enabled``: the driver decompresses each frame with zlib or Blosc before
+ passing it to plugins. Blosc decompression uses ``BloscNumThreads`` threads.
+* ``Decompress = Disabled``: the compressed bytes are copied into the NDArray as-is, and the
+ NDArray ``codec`` field is set to ``zlib`` or ``blosc`` (with the Blosc sub-compressor,
+ level and ``ShuffleMode``). Plugins such as ``NDPluginCodec`` or ``NDFileHDF5`` can then
+ decompress or write the data directly. zlib-compressed NDArrays require ADCore R3-15 or
+ later.
+
+.. _ADXSPD_request_logging:
+
+HTTP request logging
+--------------------
+
+For debugging communication with XSPD, the driver can log every HTTP request it sends.
+Each log entry contains a timestamp, the HTTP method, the full URI, the HTTP status code
+(0 if no response was received) and the time taken in milliseconds, for example::
+
+ 2026-09-30 12:00:00.123 | GET localhost:8008/api/v1/devices/lambda01/variables?path=lambda/status | status=200 | elapsed=1.842 ms
+
+Set ``LogRequests`` to ``Enabled`` to start logging. Where logs are written depends on the
+``NDFile.template`` records:
+
+* If ``FilePath`` is empty, logs are written to stdout.
+* Otherwise, logs are appended to ``FilePath`` + ``FileName``. If ``FileName`` is empty,
+ ``xspd_requests.log`` is used. ``FilePath`` must already exist; enabling logging fails
+ if it does not.
+* The full path of the log file is shown in ``FullFileName_RBV``.
+* Changing ``FilePath`` or ``FileName`` while logging is enabled switches to the new file.
+ If the new file cannot be opened, logging is disabled and an error is reported.
+
+Request logging can also be used directly from the ``XSPDAPI`` library with
+``XSPD::API::EnableRequestLogging(path)`` and ``XSPD::API::DisableRequestLogging()``.
+
+Configuration
+-------------
+
+The ADXSPD driver is created with the ``ADXSPDConfig`` command, either from C/C++ or from
+the EPICS IOC shell.
+
+::
+
+ int ADXSPDConfig(const char *portName, const char *ip, int portNum, const char *deviceId)
+
+.. cssclass:: table-bordered table-striped table-hover
+.. flat-table::
+ :header-rows: 1
+ :widths: 20 80
+
+ * - Argument
+ - Description
+ * - portName
+ - Name of the asyn port to create. Module ports are named ``_MOD1``,
+ ``_MOD2``, etc.
+ * - ip
+ - Hostname or IP address of the machine running XSPD.
+ * - portNum
+ - Port number of the XSPD REST API. If 0, the default of 8008 is used.
+ * - deviceId
+ - Optional. The XSPD device ID to connect to (e.g. ``lambda01``), or a single-digit index
+ into the list of devices. If omitted, the first device is used.
+
+Example st.cmd startup file
+---------------------------
+
+The following startup script is provided with ADXSPD.
+
+.. literalinclude:: ../../../ADXSPD/iocs/xspdIOC/iocBoot/iocXSPD/st_base.cmd
+
+Screens
+-------
+
+Phoebus screens are provided in ``xspdApp/op/bob``:
+
+* ``ADXSPD.bob`` - the main screen for controlling the detector.
+* ``ADXSPD_Module.bob`` - per-module status and information.
+
+Auto-generated screens covering every record in the database templates are in
+``xspdApp/op/bob/autogenerated``.
+
+Building
+--------
+
+ADXSPD depends on the following, in addition to EPICS base, asyn and ADCore:
+
+* `cpr`_, `nlohmann/json`_ and `magic_enum`_, which are included in ``xspdSupport``.
+ ``cpr`` requires libcurl; set ``CURL_EXTERNAL=YES`` in ``configure/CONFIG_SITE`` to use
+ the system libcurl.
+* `ZeroMQ`_ (``libzmq``). Set ``ZMQ_LIB`` to use a non-system build.
+* zlib and Blosc, which can be provided by ADSupport or the system.
+
+A C++17 compiler is required.
+
+Testing
+-------
+
+Unit tests for the API are in ``xspdApp/tests`` and use GoogleTest and GoogleMock. Set
+``BUILD_TESTS=YES`` in ``configure/CONFIG_SITE`` to build them, then run::
+
+ ./xspdApp/tests/O.linux-x86_64/TestADXSPD
+
+A simulator of the XSPD REST API and data port is provided in
+``scripts/xspdSimulator.py``, and can be started with::
+
+ pixi run simulator
diff --git a/pixi.lock b/pixi.lock
index 0b9e3a0..8742c2c 100644
--- a/pixi.lock
+++ b/pixi.lock
@@ -146,6 +146,76 @@ environments:
- pypi: git+https://github.com/jwlodek/epicsdbtools.git#9c1913b694cea222a7296662a9431400d9435e91
- pypi: https://files.pythonhosted.org/packages/c6/f3/ccadde3be7063fd2630908fe533ad8d9812d86c20996ba572196235ba78f/phoebusgen-3.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d0/fb/b39d311d90400166c73f91d2007704df0c08d8ee076f7ad63b2ef7e82c2d/rectangle_packer-2.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
+ docs:
+ channels:
+ - url: https://conda.anaconda.org/conda-forge/
+ indexes:
+ - https://pypi.org/simple
+ packages:
+ linux-64:
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.7.0-py312h3f22e6b_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py312he9c40d5_4.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/charset-normalizer-3.5.2-h1114479_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-py310h44b86e0_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.5-hd2095e1_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.7.0-h81df57d_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_7.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.2.0-he0feb66_7.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libpython-3.12.14-h0c77377_3_cpython.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-h13e7031_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_7.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.4-hcfc3c73_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.38-h280c20c_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py312h8a5da7c_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.4-h781a0a9_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.14-h5f976f7_3_cpython.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda
+ - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.14-py312hd8ed1ab_3.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/docutils-0.22.4-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.20-pyh5ded981_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/imagesize-2.0.1-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.14-hd8ed1ab_3.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-9_cp312.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-4.1.0-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-3.1.1-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-9.1.0-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx_rtd_theme-3.1.0-pyhcf101f3_2.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-2.0.0-pyhd8ed1ab_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8ed1ab_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jquery-4.1-pyhd8ed1ab_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-pyhd8ed1ab_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_1.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-2.0.0-pyhd8ed1ab_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda
+ - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.8.0-pyhd8ed1ab_0.conda
+ - pypi: https://files.pythonhosted.org/packages/55/39/c9136af840b1dce6e835d5a665b0cea00edf8b022375d99ed75c79df0203/fspath-20230629-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/95/ad/793c51946ac0a67d5ae10b4cf7ba1620aaa8488464be97d85ed2e9552eb7/linuxdoc-20260504-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl
packages:
- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda
build_number: 20
@@ -159,6 +229,9 @@ packages:
license: BSD-3-Clause
license_family: BSD
purls: []
+ run_exports:
+ strong:
+ - _openmp_mutex >=4.5
size: 28948
timestamp: 1770939786096
- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.3.0-py312h90b7ffd_0.conda
@@ -175,6 +248,19 @@ packages:
- pkg:pypi/backports-zstd?source=hash-mapping
size: 237970
timestamp: 1767045004512
+- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.7.0-py312h3f22e6b_1.conda
+ sha256: b377a4f053c4e184b031253c702984194d10c98228004d7cf07c1eca86247d62
+ md5: b0e9b44b494bb001c4d35b206022eba2
+ depends:
+ - python
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=15
+ - python_abi 3.12.* *_cp312
+ - zstd >=1.5.7,<1.6.0a0
+ license: BSD-3-Clause AND MIT AND EPL-2.0
+ run_exports: {}
+ size: 241113
+ timestamp: 1788491295568
- conda: https://conda.anaconda.org/conda-forge/linux-64/blosc-1.21.6-he440d0b_1.conda
sha256: e7af5d1183b06a206192ff440e08db1c4e8b2ca1f8376ee45fb2f3a85d4ee45d
md5: 2c2fae981fd2afd00812c92ac47d023d
@@ -208,6 +294,35 @@ packages:
- pkg:pypi/brotli?source=hash-mapping
size: 368300
timestamp: 1764017300621
+- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py312he9c40d5_4.conda
+ sha256: 6e4f440a7015d7d78120b3a3f90a4ec3d7bb6de7bc65458123c52433755db5f0
+ md5: edb667e7ce56106424e8afab2dc0f0cb
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=15
+ - libstdcxx >=15
+ - python >=3.12,<3.13.0a0
+ - python_abi 3.12.* *_cp312
+ constrains:
+ - libbrotlicommon 1.2.0 h39a168f_4
+ license: MIT
+ license_family: MIT
+ run_exports: {}
+ size: 367657
+ timestamp: 1788480501027
+- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda
+ sha256: 1a0d382c515ebf55f8ee1f38c8b81bc95af5c2acc42ad53b66bc5df932032f96
+ md5: e675fabcf81499adc7edf58124fb1e01
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=14
+ license: bzip2-1.0.6
+ license_family: BSD
+ run_exports:
+ weak:
+ - bzip2 >=1.0.8,<2.0a0
+ size: 257808
+ timestamp: 1785906269155
- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_9.conda
sha256: 0b75d45f0bba3e95dc693336fa51f40ea28c980131fec438afb7ce6118ed05f6
md5: d2ffd7602c02f2b316fd921d39876885
@@ -235,6 +350,20 @@ packages:
- pkg:pypi/cffi?source=hash-mapping
size: 295716
timestamp: 1761202958833
+- conda: https://conda.anaconda.org/conda-forge/linux-64/charset-normalizer-3.5.2-h1114479_0.conda
+ noarch: python
+ sha256: e4f04b45d68716fcc47c808aeacbf165f6f295fb91bd20b91aa38942b8d2cdc6
+ md5: 78d5c6112c9320a1a797935cecfec7c2
+ depends:
+ - python
+ - libgcc >=15
+ - __glibc >=2.17,<3.0.a0
+ - _python_abi3_support 1.*
+ - cpython >=3.9
+ license: MIT
+ run_exports: {}
+ size: 217200
+ timestamp: 1790766086110
- conda: https://conda.anaconda.org/conda-forge/linux-64/clang-format-21-21.1.8-default_h99862b1_3.conda
sha256: 10d5448a9fd8c9413d28ed328df4765646c122d24ba28fea70a25671a123d09b
md5: a6fcbe2c5df28f02d03a05faf532d908
@@ -307,6 +436,20 @@ packages:
purls: []
size: 12723451
timestamp: 1773822285671
+- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-py310h44b86e0_2.conda
+ sha256: 9f07834f0c546ab14d885ce0366285f61f44e326c0edd1fc63b8294e113ae432
+ md5: 72a381cbad04f24b1c2a43ef707f45b4
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libstdcxx >=14
+ - libgcc >=14
+ license: MIT
+ license_family: MIT
+ run_exports:
+ weak:
+ - icu >=78.3,<79.0a0
+ size: 14459115
+ timestamp: 1786545741408
- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda
sha256: 0960d06048a7185d3542d850986d807c6e37ca2e644342dd0c72feefcf26c2a4
md5: b38117a3c920364aff79f870c984b4a3
@@ -356,6 +499,19 @@ packages:
purls: []
size: 728002
timestamp: 1774197446916
+- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda
+ sha256: 27d83f1188cd19bcb7754a078b3fa7f4cfb8527f8eb2fde54dd01fc529d1adec
+ md5: 449500f2c089da11c40f5c21312e3e07
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - zstd >=1.5.7,<1.6.0a0
+ constrains:
+ - binutils_impl_linux-64 2.46.1
+ license: GPL-3.0-only
+ license_family: GPL
+ run_exports: {}
+ size: 745303
+ timestamp: 1784214507189
- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda
build_number: 6
sha256: 7bfe936dbb5db04820cf300a9cc1f5ee8d5302fc896c2d66e30f1ee2f20fbfd6
@@ -428,6 +584,19 @@ packages:
purls: []
size: 76624
timestamp: 1774719175983
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.5-hd2095e1_0.conda
+ sha256: 6de73033a7c5f712c9c5a54124e4581e025691029835e5bceaf10a333a7e7bc0
+ md5: 72cfd2b95b0540247220b42f58a148d7
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=15
+ constrains:
+ - expat 2.8.5.*
+ license: MIT
+ license_family: MIT
+ run_exports: {}
+ size: 77893
+ timestamp: 1790346247149
- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda
sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6
md5: a360c33a5abe61c07959e449fa1453eb
@@ -439,6 +608,19 @@ packages:
purls: []
size: 58592
timestamp: 1769456073053
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.7.0-h81df57d_1.conda
+ sha256: c8c7583ef063bc3c430f1d48298e5ad24f796a35c22ed5b14183325825a61106
+ md5: 6525a0b06aa4fd390795f0740636a9dd
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=15
+ license: MIT
+ license_family: MIT
+ run_exports:
+ weak:
+ - libffi >=3.7.0,<3.8.0a0
+ size: 68004
+ timestamp: 1787753412298
- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda
sha256: faf7d2017b4d718951e3a59d081eb09759152f93038479b768e3d612688f83f5
md5: 0aa00f03f9e39fb9876085dee11a85d4
@@ -453,6 +635,20 @@ packages:
purls: []
size: 1041788
timestamp: 1771378212382
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_7.conda
+ sha256: e031634c3a928f9594eba868bc46ef5230d6571a364a0c63215b06c4563bfcea
+ md5: 735629b400b70eeab21a2ba8f5b1c2ce
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - _openmp_mutex >=4.5
+ constrains:
+ - libgcc-ng ==16.2.0=*_7
+ - libgomp 16.2.0 he0feb66_7
+ license: GPL-3.0-only WITH GCC-exception-3.1
+ license_family: GPL
+ run_exports: {}
+ size: 1058444
+ timestamp: 1790303109576
- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda
sha256: e318a711400f536c81123e753d4c797a821021fb38970cebfb3f454126016893
md5: d5e96b1ed75ca01906b3d2469b4ce493
@@ -498,6 +694,18 @@ packages:
purls: []
size: 603262
timestamp: 1771378117851
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.2.0-he0feb66_7.conda
+ sha256: 92a0bb350c7be6a4eedcb4e111c60aefd176018e6b1c009a6e025dbe9bbcd758
+ md5: d904c7ddd95e026254b4b80808ee9e12
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ license: GPL-3.0-only WITH GCC-exception-3.1
+ license_family: GPL
+ run_exports:
+ strong:
+ - _openmp_mutex >=4.5
+ size: 640566
+ timestamp: 1790303046034
- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h3b78370_2.conda
sha256: c467851a7312765447155e071752d7bf9bf44d610a5687e32706f480aad2833f
md5: 915f5995e94f60e9a4826e0b0920ee88
@@ -551,6 +759,20 @@ packages:
purls: []
size: 113478
timestamp: 1775825492909
+- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda
+ sha256: 9787df8c22a59c9a70d3e5a10db9ad663485e75e9ccc3f09bd092cb7b95e0dab
+ md5: 1390b7c5ac0b1d8e447bc5efa6d3c8c2
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=14
+ constrains:
+ - xz 5.8.3.*
+ license: 0BSD
+ run_exports:
+ weak:
+ - liblzma >=5.8.3,<6.0a0
+ size: 112995
+ timestamp: 1786348617826
- conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda
sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5
md5: d864d34357c3b65a4b731f78c0801dc4
@@ -560,6 +782,9 @@ packages:
license: LGPL-2.1-only
license_family: GPL
purls: []
+ run_exports:
+ weak:
+ - libnsl >=2.0.1,<2.1.0a0
size: 33731
timestamp: 1750274110928
- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda
@@ -577,6 +802,18 @@ packages:
purls: []
size: 5928890
timestamp: 1774471724897
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libpython-3.12.14-h0c77377_3_cpython.conda
+ build_number: 3
+ sha256: 6be16a4906d83eb8e9e04375d524f339f426a847cffd294f1ceb0611988dc17a
+ md5: d247b7632f09324c11f24b5270361385
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=15
+ - libstdcxx >=15
+ license: Python-2.0
+ run_exports: {}
+ size: 8770625
+ timestamp: 1788392466381
- conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.21-h280c20c_3.conda
sha256: 64e5c80cbce4680a2d25179949739a6def695d72c40ca28f010711764e372d97
md5: 7af961ef4aa2c1136e11dd43ded245ab
@@ -599,6 +836,20 @@ packages:
purls: []
size: 958864
timestamp: 1775753750179
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-h13e7031_1.conda
+ sha256: f20d70da54e5b31dd4a51fb1efeaafafd5ab6dd8d7ac9d1438eaa2526ac4ed3d
+ md5: e72bbec309c2b0f37823ee7d4fabfcd3
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - icu >=78.3,<79.0a0
+ - libgcc >=15
+ - libzlib >=1.3.2,<2.0a0
+ license: blessing
+ run_exports:
+ weak:
+ - libsqlite >=3.53.4,<4.0a0
+ size: 974348
+ timestamp: 1787051145557
- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-15.2.0-h934c35e_18.conda
sha256: 78668020064fdaa27e9ab65cd2997e2c837b564ab26ce3bf0e58a2ce1a525c6e
md5: 1b08cd684f34175e4514474793d44bcb
@@ -612,6 +863,19 @@ packages:
purls: []
size: 5852330
timestamp: 1771378262446
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_7.conda
+ sha256: fa7018298629fc429971fffd6ecba3d27a7b02e4e890d6a93ea01ffebe8cb745
+ md5: 9b6707e0caca917fa6ea9ee473f37a5d
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc 16.2.0 ha9f2e26_7
+ constrains:
+ - libstdcxx-ng ==16.2.0=*_7
+ license: GPL-3.0-only WITH GCC-exception-3.1
+ license_family: GPL
+ run_exports: {}
+ size: 6615436
+ timestamp: 1790303132935
- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42-h5347b49_0.conda
sha256: bc1b08c92626c91500fd9f26f2c797f3eb153b627d53e9c13cd167f1e12b2829
md5: 38ffe67b78c9d4de527be8315e5ada2c
@@ -623,6 +887,19 @@ packages:
purls: []
size: 40297
timestamp: 1775052476770
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.4-hcfc3c73_0.conda
+ sha256: f34b8d1de9a2bf62476d8608057d9b8a918d97444236dc53a47128f2fae79041
+ md5: 54a948337e66706380ff24d6b5fde63f
+ depends:
+ - libgcc >=15
+ - __glibc >=2.17,<3.0.a0
+ license: BSD-3-Clause
+ license_family: BSD
+ run_exports:
+ weak:
+ - libuuid >=2.42.4,<3.0a0
+ size: 34442
+ timestamp: 1790121449161
- conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.51.0-hb03c661_1.conda
sha256: c180f4124a889ac343fc59d15558e93667d894a966ec6fdb61da1604481be26b
md5: 0f03292cc56bf91a077a134ea8747118
@@ -643,6 +920,18 @@ packages:
purls: []
size: 100393
timestamp: 1702724383534
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.38-h280c20c_0.conda
+ sha256: f7e9292dd219a6435bbb1223da9586c3e70d66d169c5a92f08db3f2127df04e9
+ md5: f7a7ff5a6ab331e037abd34f379a631d
+ depends:
+ - libgcc >=14
+ - __glibc >=2.17,<3.0.a0
+ license: LGPL-2.1-or-later
+ run_exports:
+ weak:
+ - libxcrypt >=4.4.38
+ size: 101957
+ timestamp: 1785887123445
- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.3-hca6bf5a_0.conda
sha256: 3d44f737c5ae52d5af32682cc1530df433f401f8e58a7533926536244127572a
md5: e79d2c2f24b027aa8d5ab1b1ba3061e7
@@ -688,6 +977,20 @@ packages:
purls: []
size: 63629
timestamp: 1774072609062
+- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda
+ sha256: eb8a0db0aa570124f7d2a93d7c7f596e3390df5e047818d873baad32985fc736
+ md5: 0de0122d9570a8ab637c6b73db268389
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ constrains:
+ - zlib 1.3.2 *_3
+ license: Zlib
+ license_family: Other
+ run_exports:
+ weak:
+ - libzlib >=1.3.2,<2.0a0
+ size: 63713
+ timestamp: 1785362952714
- conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda
sha256: 47326f811392a5fd3055f0f773036c392d26fdb32e4d8e7a8197eed951489346
md5: 9de5350a85c4a20c685259b889aa6393
@@ -714,6 +1017,7 @@ packages:
license_family: BSD
purls:
- pkg:pypi/markupsafe?source=hash-mapping
+ run_exports: {}
size: 26057
timestamp: 1772445297924
- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda
@@ -726,6 +1030,18 @@ packages:
purls: []
size: 891641
timestamp: 1738195959188
+- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda
+ sha256: 5d46557214ed184381dafe835b7c94a474a1c3b307a08a250b1ea4779b44ffb3
+ md5: ee6c0cd80a60961a1f48aa3e0b91f986
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=14
+ license: X11 AND BSD-3-Clause
+ run_exports:
+ weak:
+ - ncurses >=6.6,<7.0a0
+ size: 911196
+ timestamp: 1786355078102
- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py312h33ff503_0.conda
sha256: 1aab7ba963affa572956b1bd8d239df52a9c7bc799c560f98bc658ab70224e10
md5: 5930ee8a175a242b4f001b1e9e72024f
@@ -758,6 +1074,20 @@ packages:
purls: []
size: 3167099
timestamp: 1775587756857
+- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.4-h781a0a9_0.conda
+ sha256: 4747b2d6a8336f52343bceb8a1ebf41a9e6e665b9d2e3f989972de53a310599e
+ md5: 16e3034a330cc625f2c685edec60cf4e
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - ca-certificates
+ - libgcc >=15
+ license: Apache-2.0
+ license_family: Apache
+ run_exports:
+ weak:
+ - openssl >=3.6.4,<4.0a0
+ size: 3202955
+ timestamp: 1787698780103
- conda: https://conda.anaconda.org/conda-forge/linux-64/perl-5.32.1-7_hd590300_perl5.conda
build_number: 7
sha256: 9ec32b6936b0e37bcb0ed34f22ec3116e75b3c0964f9f50ecea5f58734ed6ce9
@@ -813,6 +1143,39 @@ packages:
purls: []
size: 31608571
timestamp: 1772730708989
+- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.12.14-h5f976f7_3_cpython.conda
+ build_number: 3
+ sha256: 14c579b1016da04e4c9f1c5c857272d83ec447317d8c4074a07d59de3cef70ef
+ md5: 98be3cf76eca2e8871f907a03aed3b84
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - bzip2 >=1.0.8,<2.0a0
+ - ld_impl_linux-64 >=2.36.1
+ - libexpat >=2.8.1,<3.0a0
+ - libffi >=3.7.0,<3.8.0a0
+ - libgcc >=15
+ - liblzma >=5.8.3,<6.0a0
+ - libnsl >=2.0.1,<2.1.0a0
+ - libpython 3.12.14 h0c77377_3_cpython
+ - libsqlite >=3.53.4,<4.0a0
+ - libuuid >=2.42.3,<3.0a0
+ - libxcrypt >=4.4.38
+ - libzlib >=1.3.2,<2.0a0
+ - ncurses >=6.6,<7.0a0
+ - openssl >=3.5.8,<4.0a0
+ - readline >=8.3,<9.0a0
+ - tk >=8.6.13,<8.7.0a0
+ - tzdata
+ constrains:
+ - python_abi 3.12.* *_cp312
+ license: Python-2.0
+ run_exports:
+ weak:
+ - python_abi 3.12.* *_cp312
+ noarch:
+ - python
+ size: 23044161
+ timestamp: 1788392496306
- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py312h8a5da7c_1.conda
sha256: cb142bfd92f6e55749365ddc244294fa7b64db6d08c45b018ff1c658907bfcbf
md5: 15878599a87992e44c059731771591cb
@@ -858,6 +1221,20 @@ packages:
purls: []
size: 345073
timestamp: 1765813471974
+- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda
+ sha256: 01b3fe073a66e321970d09e04b388708f8cbdca5cdfbfcb7c9eeb470ad10383d
+ md5: 69c01c781e7c8190bbdaf79e3848c5ca
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libgcc >=15
+ - ncurses >=6.6,<7.0a0
+ license: GPL-3.0-only
+ license_family: GPL
+ run_exports:
+ weak:
+ - readline >=8.3,<9.0a0
+ size: 348899
+ timestamp: 1787033801506
- conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.14.14-h40fa522_1.conda
noarch: python
sha256: 0c6c9825ff88195fd13936d63872213d6c88c1fe795d136881c0753c3037c5ff
@@ -887,6 +1264,22 @@ packages:
purls: []
size: 45829
timestamp: 1762948049098
+- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda
+ build_number: 104
+ sha256: a1a241d172c1ccab067ba245206dd048bc3c2d1b84504b53c9468e99adfc16a1
+ md5: 676a2f9b1c4fcf57b70aa5c65f6c60d0
+ depends:
+ - libgcc >=15
+ - __glibc >=2.17,<3.0.a0
+ - libzlib >=1.3.2,<2.0a0
+ constrains:
+ - xorg-libx11 >=1.8.13,<2.0a0
+ license: TCL
+ run_exports:
+ weak:
+ - tk >=8.6.13,<8.7.0a0
+ size: 3566806
+ timestamp: 1787272857910
- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda
sha256: cafeec44494f842ffeca27e9c8b0c27ed714f93ac77ddadc6aaf726b5554ebac
md5: cffd3bdd58090148f4cfcd831f4b26ab
@@ -998,6 +1391,19 @@ packages:
purls: []
size: 601375
timestamp: 1764777111296
+- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda
+ sha256: 47d682b9f6d6ec9eb1a6e6c3e75ea6273e899e78fb7fc59f81d39745009fbc60
+ md5: aa459086047c0e5e27023ab19f8cb86a
+ depends:
+ - __glibc >=2.17,<3.0.a0
+ - libzlib >=1.3.2,<2.0a0
+ license: BSD-3-Clause
+ license_family: BSD
+ run_exports:
+ weak:
+ - zstd >=1.5.7,<1.6.0a0
+ size: 601301
+ timestamp: 1786599621503
- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_2.conda
sha256: a3967b937b9abf0f2a99f3173fa4630293979bd1644709d89580e7c62a544661
md5: aaa2a381ccc56eac91d63b6c1240312f
@@ -1009,6 +1415,27 @@ packages:
purls: []
size: 8191
timestamp: 1744137672556
+- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda
+ sha256: 2a7204314663eeda5dec482a956f0e2eaf289bd5b9953eaaaad0e81aa64638f2
+ md5: 3845f3d75991bae0fb90884662f4327c
+ depends:
+ - cpython
+ - python-gil
+ license: MIT
+ license_family: MIT
+ run_exports: {}
+ size: 8144
+ timestamp: 1784221492234
+- conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda
+ sha256: 6c4456a138919dae9edd3ac1a74b6fbe5fd66c05675f54df2f8ab8c8d0cc6cea
+ md5: 1fd9696649f65fd6611fcdb4ffec738a
+ depends:
+ - python >=3.10
+ license: BSD-3-Clause
+ license_family: BSD
+ run_exports: {}
+ size: 18684
+ timestamp: 1733750512696
- conda: https://conda.anaconda.org/conda-forge/noarch/annotated-doc-0.0.4-pyhcf101f3_0.conda
sha256: cc9fbc50d4ee7ee04e49ee119243e6f1765750f0fd0b4d270d5ef35461b643b1
md5: 52be5139047efadaeeb19c6a5103f92a
@@ -1052,6 +1479,19 @@ packages:
- pkg:pypi/anyio?source=hash-mapping
size: 146764
timestamp: 1774359453364
+- conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda
+ sha256: a14a9ad02101aab25570543a59c5193043b73dc311a25650134ed9e6cb691770
+ md5: f1976ce927373500cc19d3c0b2c85177
+ depends:
+ - python >=3.10
+ - python
+ constrains:
+ - pytz >=2015.7
+ license: BSD-3-Clause
+ license_family: BSD
+ run_exports: {}
+ size: 7684321
+ timestamp: 1772555330347
- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda
sha256: 67cc7101b36421c5913a1687ef1b99f85b5d6868da3abbf6ec1a4181e79782fc
md5: 4492fd26db29495f0ba23f146cd5638d
@@ -1061,6 +1501,15 @@ packages:
purls: []
size: 147413
timestamp: 1772006283803
+- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda
+ sha256: 0a0544cf95f64394fe4959286f5c71f5444ad58feb0602e53becb27448d24da6
+ md5: 0f51e2391ade309db462a55611263e9c
+ depends:
+ - __unix
+ license: ISC
+ run_exports: {}
+ size: 131780
+ timestamp: 1784754889428
- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.2.25-pyhd8ed1ab_0.conda
sha256: a6b118fd1ed6099dc4fc03f9c492b88882a780fadaef4ed4f93dc70757713656
md5: 765c4d97e877cdbbb88ff33152b86125
@@ -1071,6 +1520,15 @@ packages:
- pkg:pypi/certifi?source=hash-mapping
size: 151445
timestamp: 1772001170301
+- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda
+ sha256: fb167de4388e64e52aa3907ed099afab944c1fa6e5f74b281a312dae1bcf7f3b
+ md5: 37e13edbe3b48f1095a9d085ef9cd83b
+ depends:
+ - python >=3.10
+ license: ISC
+ run_exports: {}
+ size: 137015
+ timestamp: 1784717699092
- conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda
sha256: aa589352e61bb221351a79e5946d56916e3c595783994884accdb3b97fe9d449
md5: 381bd45fb7aa032691f3063aff47e3a1
@@ -1106,6 +1564,16 @@ packages:
- pkg:pypi/click?source=hash-mapping
size: 98253
timestamp: 1775578217828
+- conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda
+ sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287
+ md5: 962b9857ee8e7018c22f2776ffa0b2d7
+ depends:
+ - python >=3.9
+ license: BSD-3-Clause
+ license_family: BSD
+ run_exports: {}
+ size: 27011
+ timestamp: 1733218222191
- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.13-py312hd8ed1ab_0.conda
noarch: generic
sha256: d3e9bbd7340199527f28bbacf947702368f31de60c433a16446767d3c6aaf6fe
@@ -1117,6 +1585,17 @@ packages:
purls: []
size: 46463
timestamp: 1772728929620
+- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.12.14-py312hd8ed1ab_3.conda
+ noarch: generic
+ sha256: 1052869d599008850098c033f9568a2e28bb31238705969719be69a87474cc93
+ md5: adee9213d9a98d7f8b5627209c8b4ae0
+ depends:
+ - python >=3.12,<3.13.0a0
+ - python_abi * *_cp312
+ license: Python-2.0
+ run_exports: {}
+ size: 47172
+ timestamp: 1788391292962
- conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.0-pyhd8ed1ab_0.conda
sha256: 6d977f0b2fc24fee21a9554389ab83070db341af6d6f09285360b2e09ef8b26e
md5: 003b8ba0a94e2f1e117d0bd46aebc901
@@ -1149,6 +1628,15 @@ packages:
- pkg:pypi/dnspython?source=hash-mapping
size: 196500
timestamp: 1757292856922
+- conda: https://conda.anaconda.org/conda-forge/noarch/docutils-0.22.4-pyhd8ed1ab_0.conda
+ sha256: 0d605569a77350fb681f9ed8d357cc71649b59a304099dc9d09fbeec5e84a65e
+ md5: d6bd3cd217e62bbd7efe67ff224cd667
+ depends:
+ - python >=3.10
+ license: CC-PDDC AND BSD-3-Clause AND BSD-2-Clause AND ZPL-2.1
+ run_exports: {}
+ size: 438002
+ timestamp: 1766092633160
- conda: https://conda.anaconda.org/conda-forge/noarch/email-validator-2.3.0-pyhd8ed1ab_0.conda
sha256: c37320864c35ef996b0e02e289df6ee89582d6c8e233e18dc9983375803c46bb
md5: 3bc0ac31178387e8ed34094d9481bfe8
@@ -1280,6 +1768,19 @@ packages:
- pkg:pypi/h2?source=hash-mapping
size: 95967
timestamp: 1756364871835
+- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda
+ sha256: 307dd6ec90140c3cf4171071b0e5e870abec314f4565c1edd5bc433e942cdcc0
+ md5: e652ac7756069c456d0da2a922cd7df5
+ depends:
+ - python >=3.10
+ - hyperframe >=6.1,<7
+ - hpack >=4.2,<5
+ - python
+ license: MIT
+ license_family: MIT
+ run_exports: {}
+ size: 100789
+ timestamp: 1785796355216
- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.1.0-pyhd8ed1ab_0.conda
sha256: 6ad78a180576c706aabeb5b4c8ceb97c0cb25f1e112d76495bff23e3779948ba
md5: 0a802cb9888dd14eeefc611f05c40b6e
@@ -1291,6 +1792,16 @@ packages:
- pkg:pypi/hpack?source=hash-mapping
size: 30731
timestamp: 1737618390337
+- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda
+ sha256: fdcea5d7cb314485d3907192ef024c704311548c5b0cbeb390cd1951051e29d2
+ md5: b395909221b9bd1df066e5930e18855b
+ depends:
+ - python >=3.10
+ license: MIT
+ license_family: MIT
+ run_exports: {}
+ size: 32884
+ timestamp: 1782283986153
- conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda
sha256: 04d49cb3c42714ce533a8553986e1642d0549a05dc5cc48e0d43ff5be6679a5b
md5: 4f14640d58e2cc0aa0819d9d8ba125bb
@@ -1332,6 +1843,7 @@ packages:
license_family: MIT
purls:
- pkg:pypi/hyperframe?source=hash-mapping
+ run_exports: {}
size: 17397
timestamp: 1737618427549
- conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.19-pyhd8ed1ab_0.conda
@@ -1357,6 +1869,27 @@ packages:
- pkg:pypi/idna?source=hash-mapping
size: 50721
timestamp: 1760286526795
+- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.20-pyh5ded981_0.conda
+ sha256: e044b1ec829e2e24f971fa3f3408eb26f1db836e521a58f08b0fd9a03fee47be
+ md5: ee73be987f9de9ea5c2fdaeefa7b6491
+ depends:
+ - python >=3.11
+ - python
+ license: BSD-3-Clause
+ license_family: BSD
+ run_exports: {}
+ size: 178572
+ timestamp: 1790020377150
+- conda: https://conda.anaconda.org/conda-forge/noarch/imagesize-2.0.1-pyhd8ed1ab_0.conda
+ sha256: 4e787f9ccc31053ccea56d98ecd284a4f788988a3f9c4627db72fdd0b127529b
+ md5: ebc56022e4ef7e74a829be94c53797ca
+ depends:
+ - python >=3.10
+ license: MIT
+ license_family: MIT
+ run_exports: {}
+ size: 21467
+ timestamp: 1787649248672
- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.8.0-pyhcf101f3_0.conda
sha256: 82ab2a0d91ca1e7e63ab6a4939356667ef683905dea631bc2121aa534d347b16
md5: 080594bf4493e6bae2607e65390c520a
@@ -1381,6 +1914,7 @@ packages:
license_family: BSD
purls:
- pkg:pypi/jinja2?source=hash-mapping
+ run_exports: {}
size: 120685
timestamp: 1764517220861
- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.0.0-pyhd8ed1ab_0.conda
@@ -1430,6 +1964,17 @@ packages:
- pkg:pypi/packaging?source=hash-mapping
size: 89360
timestamp: 1776209387231
+- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda
+ sha256: c432626b16768b8dab228bfb706f7060c2d462a21c516d240f68f2f902b5a044
+ md5: 936687ed80f295a1f5dbcf8bd34c252c
+ depends:
+ - python >=3.9
+ - python
+ license: Apache-2.0
+ license_family: APACHE
+ run_exports: {}
+ size: 116363
+ timestamp: 1785888127370
- conda: https://conda.anaconda.org/conda-forge/noarch/pip-25.3-pyh8b19718_0.conda
sha256: b67692da1c0084516ac1c9ada4d55eaf3c5891b54980f30f3f444541c2706f1e
md5: c55515ca43c6444d2572e0f0d93cb6b9
@@ -1545,6 +2090,17 @@ packages:
- pkg:pypi/pygments?source=hash-mapping
size: 893031
timestamp: 1774796815820
+- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda
+ sha256: f5f015ff1bc3e1b7fc08eee096b1865234189ae0b82bebdb86a5369116e42fa0
+ md5: 2882dee445dfa45b0c5afce3ffb7730a
+ depends:
+ - python >=3.10
+ - python
+ license: BSD-2-Clause
+ license_family: BSD
+ run_exports: {}
+ size: 959376
+ timestamp: 1786995678795
- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda
sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8
md5: 461219d1a5bd61342293efa2c0c90eac
@@ -1555,6 +2111,7 @@ packages:
license_family: BSD
purls:
- pkg:pypi/pysocks?source=hash-mapping
+ run_exports: {}
size: 21085
timestamp: 1733217331982
- conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.2.2-pyhcf101f3_0.conda
@@ -1592,6 +2149,16 @@ packages:
purls: []
size: 46449
timestamp: 1772728979370
+- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.12.14-hd8ed1ab_3.conda
+ sha256: 7096d98d7dd0cdb9256c5714d7228a8ce7aca3bd014304ea5c1673c259df72ef
+ md5: 7fafcd204183cfa7c1ae4ad9c2d8d414
+ depends:
+ - cpython 3.12.14.*
+ - python_abi * *_cp312
+ license: Python-2.0
+ run_exports: {}
+ size: 47142
+ timestamp: 1788391303150
- conda: https://conda.anaconda.org/conda-forge/noarch/python-multipart-0.0.26-pyhcf101f3_0.conda
sha256: 2c683f35fac0e6d5c314872bbe89f1ff9100e93a6ed3c2c249b5ba7f95baa139
md5: 8fe038d7eacf6aa4e0885f6ae9c560e5
@@ -1615,6 +2182,17 @@ packages:
purls: []
size: 6958
timestamp: 1752805918820
+- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.12-9_cp312.conda
+ build_number: 9
+ sha256: ee9c2922e07afc85fc82d5fa82c9ac2c79da3be3283a5e17bf2f2ae38dbd02fb
+ md5: 4c32076993e6270825441d059ab5c18b
+ constrains:
+ - python 3.12.* *_cpython
+ license: BSD-3-Clause
+ license_family: BSD
+ run_exports: {}
+ size: 6751
+ timestamp: 1788302823694
- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.33.1-pyhcf101f3_0.conda
sha256: c0249bc4bf4c0e8e06d0e7b4d117a5d593cc4ab2144d5006d6d47c83cb0af18e
md5: 10afbb4dbf06ff959ad25a92ccee6e59
@@ -1633,6 +2211,23 @@ packages:
- pkg:pypi/requests?source=hash-mapping
size: 63712
timestamp: 1774894783063
+- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda
+ sha256: 1715246b19c9f85ee022933b4845f2fc14ac9184981b7b7d9b728bec8e9588da
+ md5: 4a85203c1d80c1059086ae860836ffb9
+ depends:
+ - python >=3.10
+ - certifi >=2023.5.7
+ - charset-normalizer >=2,<4
+ - idna >=2.5,<4
+ - urllib3 >=1.26,<3
+ - python
+ constrains:
+ - chardet >=3.0.2,<8
+ license: Apache-2.0
+ license_family: APACHE
+ run_exports: {}
+ size: 68709
+ timestamp: 1778851103479
- conda: https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda
sha256: 3d6ba2c0fcdac3196ba2f0615b4104e532525ffa1335b50a2878be5ff488814a
md5: 0242025a3c804966bf71aa04eee82f66
@@ -1663,6 +2258,15 @@ packages:
- pkg:pypi/rich-toolkit?source=hash-mapping
size: 32484
timestamp: 1771977622605
+- conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-4.1.0-pyhd8ed1ab_0.conda
+ sha256: 30f3c04fcfb64c44d821d392a4a0b8915650dbd900c8befc20ade8fde8ec6aa2
+ md5: 0dc48b4b570931adc8641e55c6c17fe4
+ depends:
+ - python >=3.10
+ license: 0BSD OR CC0-1.0
+ run_exports: {}
+ size: 13814
+ timestamp: 1766003022813
- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-82.0.1-pyh332efcf_0.conda
sha256: 82088a6e4daa33329a30bc26dc19a98c7c1d3f05c0f73ce9845d4eab4924e9e1
md5: 8e194e7b992f99a5015edbd4ebd38efd
@@ -1696,6 +2300,132 @@ packages:
- pkg:pypi/sniffio?source=hash-mapping
size: 15698
timestamp: 1762941572482
+- conda: https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-3.1.1-pyhd8ed1ab_0.conda
+ sha256: ad89284ea94821c20ff87e64b948e4afc690cf5202d14c009355b0594cf23aea
+ md5: 46b6abe31482f6bca064b965696ae807
+ depends:
+ - python >=3.10
+ license: BSD-3-Clause
+ license_family: BSD
+ run_exports: {}
+ size: 74456
+ timestamp: 1780468201547
+- conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-9.1.0-pyhd8ed1ab_0.conda
+ sha256: 035ca4b17afca3d53650380dd94c564555b7ec2b4f8818111f98c15c7a991b7b
+ md5: aabfbc2813712b71ba8beb217a978498
+ depends:
+ - alabaster >=0.7.14
+ - babel >=2.13
+ - colorama >=0.4.6
+ - docutils >=0.21,<0.23
+ - imagesize >=1.3
+ - jinja2 >=3.1
+ - packaging >=23.0
+ - pygments >=2.17
+ - python >=3.12
+ - requests >=2.30.0
+ - roman-numerals >=1.0.0
+ - snowballstemmer >=2.2
+ - sphinxcontrib-applehelp >=1.0.7
+ - sphinxcontrib-devhelp >=1.0.6
+ - sphinxcontrib-htmlhelp >=2.0.6
+ - sphinxcontrib-jsmath >=1.0.1
+ - sphinxcontrib-qthelp >=1.0.6
+ - sphinxcontrib-serializinghtml >=1.1.9
+ license: BSD-2-Clause
+ license_family: BSD
+ run_exports: {}
+ size: 1584836
+ timestamp: 1767271941650
+- conda: https://conda.anaconda.org/conda-forge/noarch/sphinx_rtd_theme-3.1.0-pyhcf101f3_2.conda
+ sha256: 3581a8335a0da4de41f4344c4f1113a11fcbbb7933577ace813602e263842b02
+ md5: 787507b1d0f5c00c186b2f66adacc3ab
+ depends:
+ - python >=3.10
+ - sphinx >=6,<10
+ - docutils >0.18,<0.23
+ - sphinxcontrib-jquery >=4,<5
+ - python
+ license: MIT
+ license_family: MIT
+ run_exports: {}
+ size: 6616185
+ timestamp: 1785314437148
+- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_1.conda
+ sha256: d7433a344a9ad32a680b881c81b0034bc61618d12c39dd6e3309abeffa9577ba
+ md5: 16e3f039c0aa6446513e94ab18a8784b
+ depends:
+ - python >=3.9
+ - sphinx >=5
+ license: BSD-2-Clause
+ license_family: BSD
+ run_exports: {}
+ size: 29752
+ timestamp: 1733754216334
+- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-2.0.0-pyhd8ed1ab_1.conda
+ sha256: 55d5076005d20b84b20bee7844e686b7e60eb9f683af04492e598a622b12d53d
+ md5: 910f28a05c178feba832f842155cbfff
+ depends:
+ - python >=3.9
+ - sphinx >=5
+ license: BSD-2-Clause
+ license_family: BSD
+ run_exports: {}
+ size: 24536
+ timestamp: 1733754232002
+- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8ed1ab_1.conda
+ sha256: c1492c0262ccf16694bdcd3bb62aa4627878ea8782d5cd3876614ffeb62b3996
+ md5: e9fb3fe8a5b758b4aff187d434f94f03
+ depends:
+ - python >=3.9
+ - sphinx >=5
+ license: BSD-2-Clause
+ license_family: BSD
+ run_exports: {}
+ size: 32895
+ timestamp: 1733754385092
+- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jquery-4.1-pyhd8ed1ab_1.conda
+ sha256: 69c08d18663b57ebc8e4187c64c8d29b10996bb465a515cd288d87b6f2f52a5e
+ md5: 403185829255321ea427333f7773dd1f
+ depends:
+ - python >=3.9
+ - sphinx >=1.8
+ license: 0BSD AND MIT
+ run_exports: {}
+ size: 112964
+ timestamp: 1734344603903
+- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-pyhd8ed1ab_1.conda
+ sha256: 578bef5ec630e5b2b8810d898bbbf79b9ae66d49b7938bcc3efc364e679f2a62
+ md5: fa839b5ff59e192f411ccc7dae6588bb
+ depends:
+ - python >=3.9
+ license: BSD-2-Clause
+ license_family: BSD
+ run_exports: {}
+ size: 10462
+ timestamp: 1733753857224
+- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_1.conda
+ sha256: c664fefae4acdb5fae973bdde25836faf451f41d04342b64a358f9a7753c92ca
+ md5: 00534ebcc0375929b45c3039b5ba7636
+ depends:
+ - python >=3.9
+ - sphinx >=5
+ license: BSD-2-Clause
+ license_family: BSD
+ run_exports: {}
+ size: 26959
+ timestamp: 1733753505008
+- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-2.0.0-pyhd8ed1ab_0.conda
+ sha256: 20b49741065fd7d3fabf98caf6d19b6436badb06b6d41f66b58f1fc2b52f37a1
+ md5: f77df1fcf9af03b7287342638befca77
+ depends:
+ - python >=3.10
+ - sphinx >=5
+ license: BSD-2-Clause
+ license_family: BSD
+ run_exports: {}
+ size: 30640
+ timestamp: 1781260357443
- conda: https://conda.anaconda.org/conda-forge/noarch/starlette-1.0.0-pyhcf101f3_0.conda
sha256: 1a1dc376e95491f4b2003f4428e6f7caf4a3de0ef9869248b29dcc9704c34b39
md5: 8fbd5b6879350f1b5303c1a652d4b781
@@ -1779,6 +2509,13 @@ packages:
purls: []
size: 119135
timestamp: 1767016325805
+- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda
+ sha256: b928c30ddcb0e3f544c6eade8352737e6e610e263276b90232db6a578ef899d8
+ md5: fcb489df604d100968b737f2cb6076c6
+ license: LicenseRef-Public-Domain
+ run_exports: {}
+ size: 118849
+ timestamp: 1784250406640
- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.6.3-pyhd8ed1ab_0.conda
sha256: af641ca7ab0c64525a96fd9ad3081b0f5bcf5d1cbb091afb3f6ed5a9eee6111a
md5: 9272daa869e03efe68833e3dc7a02130
@@ -1794,6 +2531,20 @@ packages:
- pkg:pypi/urllib3?source=hash-mapping
size: 103172
timestamp: 1767817860341
+- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.8.0-pyhd8ed1ab_0.conda
+ sha256: c5511c190ab55168ca412f3c54592fbf677ae6250784cb72b227a7cf3f538f7d
+ md5: 407a3d3778570bae8e8fcf554e803e43
+ depends:
+ - backports.zstd >=1.0.0
+ - brotli-python >=1.2.0
+ - h2 >=4,<5
+ - pysocks >=1.5.6,<2.0,!=1.5.7
+ - python >=3.11
+ license: MIT
+ license_family: MIT
+ run_exports: {}
+ size: 107485
+ timestamp: 1789566710244
- conda: https://conda.anaconda.org/conda-forge/noarch/uvicorn-0.45.0-pyhc90fa1f_0.conda
sha256: 9e926875ad2c57d70cb09a9840e4976a9198526cd7cac94e4ee89d94c910f08e
md5: 2cdce4f4118633582c76dd2f596e8358
@@ -1900,6 +2651,83 @@ packages:
- types-mock ; extra == 'dev'
- import-linter ; extra == 'dev'
requires_python: '>=3.11'
+- pypi: https://files.pythonhosted.org/packages/55/39/c9136af840b1dce6e835d5a665b0cea00edf8b022375d99ed75c79df0203/fspath-20230629-py2.py3-none-any.whl
+ name: fspath
+ version: '20230629'
+ sha256: f64b560c7e3ed77da61b55f35764e03e2fb9dd8955c5c140535dbd07711693af
+ requires_dist:
+ - six
+- pypi: https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl
+ name: setuptools
+ version: 84.0.0
+ sha256: 51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670
+ requires_dist:
+ - pytest>=6,!=8.1.* ; extra == 'test'
+ - virtualenv>=13.0.0 ; extra == 'test'
+ - wheel>=0.44.0 ; extra == 'test'
+ - pip>=19.1 ; extra == 'test'
+ - packaging>=24.2 ; extra == 'test'
+ - jaraco-envs>=2.2 ; extra == 'test'
+ - pytest-xdist>=3 ; extra == 'test'
+ - jaraco-path>=3.7.2 ; extra == 'test'
+ - build[virtualenv]>=1.0.3 ; extra == 'test'
+ - filelock>=3.4.0 ; extra == 'test'
+ - ini2toml[lite]>=0.14 ; extra == 'test'
+ - tomli-w>=1.0.0 ; extra == 'test'
+ - pytest-timeout ; extra == 'test'
+ - pytest-perf ; sys_platform != 'cygwin' and extra == 'test'
+ - jaraco-develop>=7.21 ; python_full_version >= '3.9' and sys_platform != 'cygwin' and extra == 'test'
+ - pytest-home>=0.5 ; extra == 'test'
+ - pytest-subprocess ; extra == 'test'
+ - pyproject-hooks!=1.1 ; extra == 'test'
+ - jaraco-test>=5.5 ; extra == 'test'
+ - sphinx>=3.5 ; extra == 'doc'
+ - jaraco-packaging>=9.3 ; extra == 'doc'
+ - rst-linker>=1.9 ; extra == 'doc'
+ - furo ; extra == 'doc'
+ - sphinx-lint ; extra == 'doc'
+ - jaraco-tidelift>=1.4 ; extra == 'doc'
+ - pygments-github-lexers==0.0.5 ; extra == 'doc'
+ - sphinx-favicon ; extra == 'doc'
+ - sphinx-inline-tabs ; extra == 'doc'
+ - sphinx-reredirects ; extra == 'doc'
+ - sphinxcontrib-towncrier ; extra == 'doc'
+ - sphinx-notfound-page>=1,<2 ; extra == 'doc'
+ - pyproject-hooks!=1.1 ; extra == 'doc'
+ - towncrier<24.7 ; extra == 'doc'
+ - packaging>=24.2 ; extra == 'core'
+ - more-itertools>=8.8 ; extra == 'core'
+ - jaraco-text>=3.7 ; extra == 'core'
+ - importlib-metadata>=6 ; python_full_version < '3.10' and extra == 'core'
+ - tomli>=2.0.1 ; python_full_version < '3.11' and extra == 'core'
+ - wheel>=0.43.0 ; extra == 'core'
+ - jaraco-functools>=4 ; extra == 'core'
+ - more-itertools ; extra == 'core'
+ - pytest-checkdocs>=2.14 ; extra == 'check'
+ - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check'
+ - ruff>=0.13.0 ; sys_platform != 'cygwin' and extra == 'check'
+ - pytest-cov ; extra == 'cover'
+ - pytest-enabler>=3.4 ; extra == 'enabler'
+ - pytest-mypy>=1.0.1 ; platform_python_implementation != 'PyPy' and extra == 'type'
+ - mypy==1.18.* ; extra == 'type'
+ - importlib-metadata>=7.0.2 ; python_full_version < '3.10' and extra == 'type'
+ - jaraco-develop>=7.21 ; sys_platform != 'cygwin' and extra == 'type'
+ requires_python: '>=3.10'
+- pypi: https://files.pythonhosted.org/packages/95/ad/793c51946ac0a67d5ae10b4cf7ba1620aaa8488464be97d85ed2e9552eb7/linuxdoc-20260504-py3-none-any.whl
+ name: linuxdoc
+ version: '20260504'
+ sha256: cca33f1499a96ab30fdcab5296bfd28b0ae2371ae56e5dfe929f18b9a7dc7a2f
+ requires_dist:
+ - docutils
+ - fspath
+ - setuptools
+ - sphinx
+ requires_python: '>=3.10'
+- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl
+ name: six
+ version: 1.17.0
+ sha256: 4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274
+ requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*'
- pypi: https://files.pythonhosted.org/packages/c6/f3/ccadde3be7063fd2630908fe533ad8d9812d86c20996ba572196235ba78f/phoebusgen-3.2.0-py3-none-any.whl
name: phoebusgen
version: 3.2.0
diff --git a/pixi.toml b/pixi.toml
index bc2ebb6..2d9ff19 100644
--- a/pixi.toml
+++ b/pixi.toml
@@ -34,3 +34,17 @@ fastapi = ">=0.136.0,<0.137"
[pypi-dependencies]
epicsdbtools = { git = "https://github.com/jwlodek/epicsdbtools.git" }
epicsdb2bob = { git = "https://github.com/NSLS2/epicsdb2bob.git" }
+
+[feature.docs.dependencies]
+python = ">=3.12,<3.13"
+sphinx = ">=9.1.0,<10"
+sphinx_rtd_theme = ">=3.1.0,<4"
+
+[feature.docs.pypi-dependencies]
+linuxdoc = ">=20260504"
+
+[feature.docs.tasks]
+build-docs = "sphinx-build -W --keep-going -c scripts/sphinx -b html docs build/docs/html"
+
+[environments]
+docs = { features = ["docs"], no-default-feature = true }
diff --git a/scripts/sphinx/conf.py b/scripts/sphinx/conf.py
new file mode 100644
index 0000000..056b717
--- /dev/null
+++ b/scripts/sphinx/conf.py
@@ -0,0 +1,16 @@
+# Standalone Sphinx config for previewing the ADXSPD docs outside of the areaDetector docs build.
+# Kept out of docs/ so it isn't merged into the areaDetector docs tree.
+
+project = "ADXSPD"
+author = "Jakub Wlodek"
+copyright = "2026, Brookhaven National Laboratory"
+
+extensions = ["linuxdoc.rstFlatTable"]
+
+root_doc = "ADXSPD/ADXSPD"
+source_suffix = {".rst": "restructuredtext"}
+exclude_patterns = ["_build"]
+
+pygments_style = "sphinx"
+html_theme = "sphinx_rtd_theme"
+html_title = "ADXSPD"