diff --git a/.ci/assemble_synApps.sh b/.ci/assemble_synApps.sh deleted file mode 100755 index 2713cbb..0000000 --- a/.ci/assemble_synApps.sh +++ /dev/null @@ -1,172 +0,0 @@ -#!/bin/bash -shopt -s expand_aliases -set -x - -# This file is intended to gather everything in or used in synApps. -# The version numbers in this file are not guaranteed to be up to date, -# and the modules are not guaranteed to work or even build together. - -shallow_repo() -{ - PROJECT=$1 - MODULE_NAME=$2 - RELEASE_NAME=$3 - TAG=$4 - - FOLDER_NAME=$MODULE_NAME-${TAG//./-} - - echo - echo "Grabbing $MODULE_NAME at tag: $TAG" - echo - - if [ ! -d "$FOLDER_NAME" ] - then - git clone --branch $TAG --depth 1 git://github.com/$PROJECT/$MODULE_NAME.git $FOLDER_NAME - fi - - echo "$RELEASE_NAME=\$(SUPPORT)/$FOLDER_NAME" >> ./configure/RELEASE - - echo -} - -shallow_support() -{ - if [ ! -d "$1" ] - then - git clone --branch $2 --depth 1 git://github.com/EPICS-synApps/$1.git - fi -} - -cd $HOME/.cache - -EPICS_BASE=$HOME/.cache/base-$BASE - -if [ ! -d "$EPICS_BASE" ] -then - git clone --branch $BASE --depth 1 git://github.com/epics-base/epics-base.git base-$BASE - - EPICS_HOST_ARCH=`sh $EPICS_BASE/startup/EpicsHostArch` - - case "$STATIC" in - static) - cat << EOF >> "$EPICS_BASE/configure/CONFIG_SITE" -SHARED_LIBRARIES=NO -STATIC_BUILD=YES -EOF - ;; - *) ;; - esac - - case "$CMPLR" in - clang) - echo "Host compiler is clang" - - cat << EOF >> "$EPICS_BASE/configure/os/CONFIG_SITE.Common.$EPICS_HOST_ARCH" -GNU = NO -CMPLR_CLASS = clang -CC = clang -CCC = clang++ -EOF - ;; - *) echo "Host compiler is default";; - esac - - # requires wine and g++-mingw-w64-i686 - if [ "$WINE" = "32" ] - then - echo "Cross mingw32" - sed -i -e '/CMPLR_PREFIX/d' $EPICS_BASE/configure/os/CONFIG_SITE.linux-x86.win32-x86-mingw - cat << EOF >> "$EPICS_BASE/configure/os/CONFIG_SITE.linux-x86.win32-x86-mingw" -CMPLR_PREFIX=i686-w64-mingw32- -EOF - cat << EOF >> "$EPICS_BASE/configure/CONFIG_SITE" -CROSS_COMPILER_TARGET_ARCHS+=win32-x86-mingw -EOF - fi - - # set RTEMS to eg. "4.9" or "4.10" - if [ -n "$RTEMS" ] - then - echo "Cross RTEMS${RTEMS} for pc386" - install -d /home/travis/.cache - curl -L "https://github.com/mdavidsaver/rsb/releases/download/travis-20160306-2/rtems${RTEMS}-i386-trusty-20190306-2.tar.gz" \ - | tar -C /home/travis/.cache -xj - - sed -i -e '/^RTEMS_VERSION/d' -e '/^RTEMS_BASE/d' $EPICS_BASE/configure/os/CONFIG_SITE.Common.RTEMS - cat << EOF >> "$EPICS_BASE/configure/os/CONFIG_SITE.Common.RTEMS" -RTEMS_VERSION=$RTEMS -RTEMS_BASE=/home/travis/.cache/rtems${RTEMS}-i386 -EOF - cat << EOF >> $EPICS_BASE/configure/CONFIG_SITE -CROSS_COMPILER_TARGET_ARCHS+=RTEMS-pc386 -EOF - - fi - - make -C "$EPICS_BASE" -j2 - - - # get MSI for 3.14 - case "$BASE" in - 3.14*) - if [ ! -d "$HOME/msi/extensions/src" ] - then - echo "Build MSI" - install -d "$HOME/msi/extensions/src" - curl https://epics.anl.gov/download/extensions/extensionsTop_20120904.tar.gz | tar -C "$HOME/msi" -xvz - curl https://epics.anl.gov/download/extensions/msi1-7.tar.gz | tar -C "$HOME/msi/extensions/src" -xvz - mv "$HOME/msi/extensions/src/msi1-7" "$HOME/msi/extensions/src/msi" - - cat << EOF > "$HOME/msi/extensions/configure/RELEASE" -EPICS_BASE=$EPICS_BASE -EPICS_EXTENSIONS=\$(TOP) -EOF - - fi - - make -C "$HOME/msi/extensions" - cp "$HOME/msi/extensions/bin/$EPICS_HOST_ARCH/msi" "$EPICS_BASE/bin/$EPICS_HOST_ARCH/" - echo 'MSI:=$(EPICS_BASE)/bin/$(EPICS_HOST_ARCH)/msi' >> "$EPICS_BASE/configure/CONFIG_SITE" - - cat <> configure/CONFIG_SITE -MSI = \$(EPICS_BASE)/bin/\$(EPICS_HOST_ARCH)/msi -EOF - - ;; - *) echo "Use MSI from Base" - ;; - esac -fi - -alias get_support='shallow_support' -alias get_repo='shallow_repo' - -get_support support synApps_5_8 -cd support - -get_support configure synApps_5_8 - -echo "SUPPORT=$HOME/.cache/support" > configure/RELEASE -echo "EPICS_BASE=$EPICS_BASE" >> configure/RELEASE - -# modules ################################################################## - -#get_repo Git Project Git Repo RELEASE Name Tag - -if [[ $SNCSEQ ]] -then - -# seq -wget http://www-csr.bessy.de/control/SoftDist/sequencer/releases/seq-$SNCSEQ.tar.gz -tar zxf seq-$SNCSEQ.tar.gz -# The synApps build can't handle '.' -mv seq-$SNCSEQ seq-${SNCSEQ//./-} -rm -f seq-$SNCSEQ.tar.gz -echo "SNCSEQ=\$(SUPPORT)/seq-${SNCSEQ//./-}" >> ./configure/RELEASE - -fi - -make release -make - -cp -f configure/RELEASE $TRAVIS_BUILD_DIR/configure/RELEASE diff --git a/.github/workflows/3_16.set b/.github/workflows/3_16.set deleted file mode 100644 index b95bf9d..0000000 --- a/.github/workflows/3_16.set +++ /dev/null @@ -1,5 +0,0 @@ -BASE="3.16" - -MODULES="sncseq" - -SNCSEQ="R2-2-9" diff --git a/.github/workflows/7_0.set b/.github/workflows/7_0.set index a7a09ba..b397cfb 100644 --- a/.github/workflows/7_0.set +++ b/.github/workflows/7_0.set @@ -1,4 +1,4 @@ -BASE="R7.0.4.1" +BASE="7.0" MODULES="sncseq" diff --git a/.github/workflows/ci-scripts-build.yml b/.github/workflows/ci-scripts-build.yml index 39e3f3a..f3e9bee 100644 --- a/.github/workflows/ci-scripts-build.yml +++ b/.github/workflows/ci-scripts-build.yml @@ -44,19 +44,13 @@ jobs: matrix: # Job names also name artifacts, character limitations apply include: - - os: ubuntu-20.04 + - os: ubuntu-24.04 cmp: gcc configuration: default set: 7_0 name: "Linux 7.0" - - - os: ubuntu-20.04 - cmp: gcc - configuration: default - set: 3_16 - name: "Linux 3.16" - - os: ubuntu-20.04 + - os: ubuntu-24.04 cmp: gcc configuration: default set: 3_15 @@ -75,7 +69,7 @@ jobs: name: "Windows 7.0" steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: submodules: true - name: Automatic core dumper analysis @@ -99,7 +93,7 @@ jobs: - name: Run main module tests run: python .ci/cue.py -T 15M test - name: Upload tapfiles Artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: tapfiles ${{ matrix.name }} path: '**/O.*/*.tap' diff --git a/.gitignore b/.gitignore index dd00cd7..79d35a3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,13 +2,15 @@ O.*/ *.swp *BAK.adl -/db -/bin -/dbd -/include -/lib -/templates -/html +bin/ +/db/ +dbd/ +html/ +include/ +lib/ +iocsh/ +templates/ +cdCommands envPaths cdCommands dllPath.bat diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index f3e1b7c..0000000 --- a/.travis.yml +++ /dev/null @@ -1,61 +0,0 @@ -sudo: false -dist: trusty -language: c -compiler: - - gcc -cache: - directories: - - $HOME/.cache -notifications: - email: false -addons: - apt: - packages: - - libreadline6-dev - - libncurses5-dev - - perl - - clang - - g++-mingw-w64-i686 - - re2c -matrix: - include: - - name: "3.16 master with Sequencer" - env: BASE=3.16 STATIC=shared SNCSEQ=2.2.6 - - - name: "3.16 master without Sequencer" - env: BASE=3.16 STATIC=shared - - - name: "3.15 master with Sequencer" - env: BASE=3.15 STATIC=shared SNCSEQ=2.2.6 - - - name: "3.15 master without Sequencer" - env: BASE=3.15 STATIC=shared - - - name: "3.14 master with Sequencer" - env: BASE=3.14 STATIC=shared SNCSEQ=2.2.6 - - - name: "3.14 master without Sequencer" - env: BASE=3.14 STATIC=shared - - - name: "Windows Shared with Sequencer" - env: BASE=3.16 STATIC=shared CMPLR=clang WINE=32 SNCSEQ=2.2.6 - - - name: "Windows Shared without Sequencer" - env: BASE=3.16 STATIC=shared CMPLR=clang WINE=32 - - - name: "Windows Static with Sequencer" - env: BASE=3.16 STATIC=static CMPLR=clang WINE=32 SNCSEQ=2.2.6 - - - name: "Windows Static without Sequencer" - env: BASE=3.16 STATIC=static CMPLR=clang WINE=32 - - - -before_install: chmod +x ./.ci/assemble_synApps.sh - -install: ./.ci/assemble_synApps.sh - -script: - # Build the module - - make - diff --git a/docs/MDAFormat.md b/docs/MDAFormat.md new file mode 100644 index 0000000..8ea22ac --- /dev/null +++ b/docs/MDAFormat.md @@ -0,0 +1,176 @@ +--- +layout: default +title: MDA File Format +nav_order: 7 +--- + +# MDA File Format + +MDA (Multi-Dimensional Archive) is the binary file format used by the sscan module's `saveData` component to write scan data to disk. MDA files use the `.mda` file extension. + +Data in MDA files is encoded using XDR (External Data Representation, RFC 1014), a platform-independent big-endian binary encoding. This ensures that MDA files written on one architecture (e.g., vxWorks/PowerPC) can be read on another (e.g., Linux/x86\_64) without conversion. + +The current file format version is **1.4**. + +## Reading MDA files + +The sscan module includes utilities for reading MDA files: + +- **Python**: `mda.py` (in `sscanApp/op/python/`) provides functions for reading, writing, and performing arithmetic on MDA files up to 4 dimensions. `mdaAscii.py` can render 1D MDA files as ASCII text. +- **C**: `mdautils-src.tar.gz` (in `sscanApp/src/`) contains Dohn Arms' C library for reading MDA files into C structures, converting them to ASCII, and printing file metadata. + +## XDR conventions + +Note that an `xdr_counted_string` is not part of the XDR standard, but a definition added on top of the standard. It consists of an `xdr_short` containing the number of characters in the string, possibly followed by an `xdr_string`. Only if the `xdr_short` value is nonzero will an `xdr_string` follow it. Thus an empty string looks like this: + +`` + +and a nonempty string looks like this: + +`` + +A counted string looks like this: + +``` + int number of characters + # if number of characters>0: + int number of characters + char[number_of_characters] +``` + +## File structure + +``` +FILE HEADER + xdr_float: VERSION (1.4 == 3FB33333) + xdr_long: scan number + xdr_short data's rank + xdr_vector(rank, xdr_int) dims; + xdr_int isRegular (true=1, false=0) + xdr_long: pointer to the extra pvs + + +SCAN + HEADER: + xdr_short: this scan's rank + xdr_long: number of requested points (NPTS) + xdr_long: current point (CPT) + if the scan rank is > 1 + xdr_vector(NPTS, xdr_long) pointer to the lower scans + + INFO: + xdr_counted_string: scan name + xdr_counted_string: time stamp + + + xdr_int: number of positioners + xdr_int: number of detectors + xdr_int: number of triggers + + for each positioner + xdr_int: positioner number + xdr_counted_string: positioner name + xdr_counted_string: positioner desc + xdr_counted_string: positioner step mode + xdr_counted_string: positioner unit + xdr_counted_string: readback name + xdr_counted_string: readback description + xdr_counted_string: readback unit + + for each detector + xdr_int: detector number + xdr_counted_string: detector name + xdr_counted_string: detector desc + xdr_counted_string: detector unit + + for each trigger + xdr_int: trigger number + xdr_counted_string: trigger name + xdr_float: trigger command + + DATA: + for each positioner + xdr_vector(NPTS, xdr_double): readback array + + for each detector + xdr_vector(NPTS, xdr_float): detector array + +[SCAN] +... +... +... +[SCAN] + +EXTRA PVs + xdr_int: number of extra pvs + + for each pv + xdr_counted_string: name + xdr_counted_string: desc + xdr_int: type + if type != DBR_STRING + xdr_long: count + xdr_counted_string: unit + + depending on the type: + DBR_STRING: + xdr_counted_string: value + DBR_CTRL_CHAR: + xdr_vector(count, xdr_char): value + DBR_CTRL_SHORT: + xdr_vector(count, xdr_short): value + DBR_CTRL_LONG: + xdr_vector(count, xdr_long): value + DBR_CTRL_FLOAT: + xdr_vector(count, xdr_float): value + DBR_CTRL_DOUBLE: + xdr_vector(count, xdr_double): value + +``` + +### Notes on specific fields + +**VERSION**: The file format version, written as an XDR float. Version 1.4 (`0x3FB33333`) is written by the current `saveData_writeXDR.c`. Version 1.3 (`0x3FA66666`) was written by the older `saveData.c`. Most readers should accept both. + +**isRegular**: Indicates whether the scan data forms a regular rectangular grid. When `isRegular` is 1 (true), all inner-dimension scans completed with the same number of points (e.g., every row of a 2D scan has the same number of columns). When `isRegular` is 0 (false), inner scans may have different numbers of points, which can occur if some inner scans were aborted or had their parameters changed during the outer scan. Readers should use each scan's CPT (current point) field to determine how many data points are valid, rather than relying on NPTS alone. + +**Pointer fields**: The `pointer to the extra pvs` and `pointer to the lower scans` fields are byte offsets from the beginning of the file. They are used with `fseek()` to navigate directly to those sections. + +**CPT vs NPTS**: Data arrays always contain NPTS elements, but only the first CPT elements contain valid data. If a scan was aborted, CPT will be less than NPTS. + +## Scan layout examples + +A 1D scan looks like this: + +``` + header + scan1 + extra PV's +``` + +A 2D scan looks like this + +``` + header + scan2 + scan1 + scan1 + ... + extra PV's +``` + +A 3D scan looks like this +``` + header + scan3 + scan2 + scan1 + scan1 + ... + scan2 + scan1 + scan1 + ... + ... + extra PV's +``` diff --git a/docs/index.md b/docs/index.md index 618b478..e85398a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,11 +4,84 @@ title: Home nav_order: 1 --- +# The sscan module -# HTML documentation +## Documentation -* [Known Problems](known_problems.md) -* [scanparmRecord](scanparmRecord.md) -* [sscanDoc](sscanDoc.md) -* [sscanRecord](sscanRecord.md) -* [sscanReleaseNotes](sscanReleaseNotes.md) +The following documentation is available: + +- [sscanRecord](sscanRecord.md) -- The sscan record documentation. +- [scanparmRecord](scanparmRecord.md) -- The scanparm record documentation. +- [saveData](saveData.md) -- Configuration and usage of the saveData data-storage client, which writes scan data to disk in MDA format. +- [MDA Format](MDAFormat.md) -- Description of the MDA (multidimensional archive) file format written by saveData. +- [saveData.req](saveData.req) -- Sample saveData configuration file. +- [XDR_RFC1014.txt](XDR_RFC1014.txt) -- A description of the XDR (External Data Representation) standard. +- [Scans.ppt](Scans.ppt) -- Powerpoint presentation that describes the sscan module; shows how to use the sscan record; describes saveData's MDA file format; and explains how EPICS putNotify/ca_put_callback() completion behaves, and how to handle processing chains that don't satisfy EPICS' execution-tracing requirements. + +## Installation and use of the sscan module + +### Installation and Building + +After obtaining a copy of the distribution, it must be installed and built for use at your site. Usually, these steps only need to be performed once. + +1. Clone or download the sscan module source, e.g.: + ``` + git clone https://github.com/epics-modules/sscan.git + ``` + Usually this is done in an EPICS 'support' directory. + +2. Edit sscan's `configure/RELEASE` file and set the paths to your installation of EPICS base and to your versions of other dependent modules. + +3. Run `make` in the top level directory and check for any compilation errors. + +### Use of the sscan module in an EPICS IOC + +The **sscan** module is not intended to run an IOC application directly, but rather to contribute code libraries, databases, MEDM displays, etc., to an IOC application. SynApps contains an example IOC application (the **xxx** module), which pulls software from the **sscan** module and deploys it in an IOC. + +The essential steps in applying sscan-module code in an IOC application ("example") are the following: + +1. Include the following line in `example/configure/RELEASE`: + ``` + SSCAN= + ``` + +2. Include the following lines in `example/exampleApp/src/exampleInclude.dbd`: + ``` + include "sscanSupport.dbd" + include "scanProgressSupport.dbd" + ``` + +3. Include the following line in `example/exampleApp/src/Makefile`: + ``` + example_LIBS += sscan scanProgress + ``` + +4. Load the databases by including the following lines in `st.cmd`: + ``` + dbLoadRecords("$(SSCAN)/sscanApp/Db/standardScans.db","P=xxx:,MAXPTS1=2000,MAXPTS2=1000,MAXPTS3=1000,MAXPTS4=10,MAXPTSH=2000") + dbLoadRecords("$(SSCAN)/sscanApp/Db/saveData.db","P=xxx:") + dbLoadRecords("$(SSCAN)/sscanApp/Db/scanProgress.db","P=xxx:scanProgress:") + ``` + + Alternatively, you can use the provided iocsh script: + ``` + iocshLoad("$(SSCAN)/iocsh/sscan.iocsh", "PREFIX=xxx:,SSCAN=$(SSCAN)") + ``` + +5. If you use autosave, include `standardScans_settings.req` and `saveData_settings.req` in your autosave request file: + ``` + file standardScans_settings.req P=$(P) + file saveData_settings.req P=$(P) + ``` + +6. Tell saveData how to initialize by editing the file `saveData.req`, and placing it in the IOC's startup directory. Usually, the only part of this file that you modify is the section marked `[extraPV]`. In this section, enter the names of the PVs you want saveData to include in every scan-data file. If a PV is not well described by its record's `.DESC` field, you can append your own description. + +7. Initialize saveData by including the following line in `st.cmd`, after `iocInit`: + ``` + saveData_Init("saveData.req", "P=xxx:") + ``` + +8. Before running any scans, specify where saveData is to write scan-data files. Bring up the medm display `scan_saveData.adl` and fill in the "File system" and "Subdirectory" fields (i.e., the PVs `$(P)saveData_fileSystem` and `$(P)saveData_subDir`). + +Suggestions and Comments to: +[Keenan Lang](mailto:klang@anl.gov) : (klang@anl.gov) diff --git a/docs/known_problems.md b/docs/known_problems.md index 692f79f..59d6b52 100644 --- a/docs/known_problems.md +++ b/docs/known_problems.md @@ -1,102 +1,79 @@ --- layout: default title: Known Issues -nav_order: 6 +nav_order: 3 --- +# Known Problems -sscan\_known\_problems -====================== - -R2-10-2 and earlier -------------------- +## R2-10-2 and earlier * On Windows, built with Visual Studio 2010, scanProg.st crashes because it uses a strftime() format character that is not implemented by Visual Studio 2010. -sscan 2-7 ---------- +## sscan 2-7 * saveData is not ready for 64-bit architectures. It writes 1D files correctly, but not 2D or higher. -sscan 2-6-6 ------------ +## sscan 2-6-6 * On Linux, Solaris, and probably other non-real-time operating systems, saveData can write corrupted data files for 2D and higher scans, because monitored DATA fields from sscan records can be received out of order. This bug affects 2.6.6 and all earlier releases of the sscan module. * scans with any fly-mode positioners and no detector triggers fail to launch fly-mode positioners to the end point. * saveData is not ready for 64-bit architectures -sscan 2-6-4 ------------ +## sscan 2-6-4 * saveData did not check all chids before using them, leading to the task crashing when PV's did not connect. -sscan 2-6-3 ------------ +## sscan 2-6-3 * saveData crashed under tornado 2.2.* -sscan 2-6-2 ------------ +## sscan 2-6-2 * saveData occassionally writes garbage to the file-version number (the first four bytes of an .mda file). -sscan 2-6-1 ------------ +## sscan 2-6-1 * Callback counters are susceptible to a race condition that leads the sscan record to think detector values have been received over channel access when they might not have been received, and to hang waiting for values that have, in fact, been received. * The sscan record renews positioner links unnecessarily during multidimensional scans. -sscan 2-6 ---------- +## sscan 2-6 * Callback counters are susceptible to a race condition that leads the sscan record to think detector values have been received over channel access when they might not have been received, and to hang waiting for values that have, in fact, been received. * The sscan record renews positioner links unnecessarily during multidimensional scans. -sscan 2-5-7 ------------ +## sscan 2-5-7 * Callback counters are susceptible to a race condition that leads the sscan record to think detector values have been received over channel access when they might not have been received, and to hang waiting for values that have, in fact, been received. * The sscan record renews positioner links unnecessarily during multidimensional scans. * The sscan record doesn't correctly handle writes to PnPA (the arrays used for table scans). -* saveData aborts its initialization when its initialization file lacks a \[basename\] section, and fails to connect to sscan records. +* saveData aborts its initialization when its initialization file lacks a [basename] section, and fails to connect to sscan records. -sscan 2-5-6 ------------ +## sscan 2-5-6 * Callback counters are susceptible to a race condition that leads the sscan record to think detector values have been received over channel access when they might not have been received, and to hang waiting for values that have, in fact, been received. * The sscan record renews positioner links unnecessarily during multidimensional scans. * saveData's init file cannot usefully specify PV names containing several characters that are legal for a PV name. -sscan 2-5-5 ------------ - -* saveData was writing scan dimensions to the wrong offset in the data file header, under some circumstances, corrupting the header so that some readers could not understand the file. -* Prefix length was inconsistently enforced, with the result that the maximum number of characters (exclusing the trailing null) was actually eight, instead of nine. - -sscan 2-5-5 ------------ +## sscan 2-5-5 * saveData was writing scan dimensions to the wrong offset in the data file header, under some circumstances, corrupting the header so that some readers could not understand the file. -* Prefix length was inconsistently enforced, with the result that the maximum number of characters (exclusing the trailing null) was actually eight, instead of nine. +* Prefix length was inconsistently enforced, with the result that the maximum number of characters (excluding the trailing null) was actually eight, instead of nine. -sscan 2-5-4 ------------ +## sscan 2-5-4 * scanSee (a display program not included in the sscan module) gets confused because saveData's messages to users have been reformatted. * the sscan record does not behave correctly if autosave restores an illegal value for NPTS. -sscan 2-5-3 ------------ +## sscan 2-5-3 * Scans hang if the data-file fails. -sscan 2-5-2 ------------ +## sscan 2-5-2 * Scans can hang because of a race condition in doPuts() and a call to recDynLinkPutCallback() that failed for a reason other than notifyInProgress. -sscan 2-4 ---------- +## sscan 2-4 * The before-scan link (.BSPV, .BSCD) is broken. * If a PV link is changed while the previous value is still connecting, the software will hang. diff --git a/docs/saveData.md b/docs/saveData.md new file mode 100644 index 0000000..e614225 --- /dev/null +++ b/docs/saveData.md @@ -0,0 +1,217 @@ +--- +layout: default +title: saveData +nav_order: 6 +--- + +# saveData + +## Overview + +saveData is a data-storage client included with the sscan module. It monitors sscan records via Channel Access, and writes scan data to disk in the [MDA (Multi-Dimensional Archive)](MDAFormat.md) binary file format. saveData runs as a dedicated high-priority thread within the IOC and handles 1D through 4D scans automatically, including nested multidimensional scans where multiple sscan records are chained together. + +saveData coordinates with sscan records using the AWAIT/AAWAIT handshake mechanism to ensure that scan data arrays are not overwritten before they have been written to disk. For details on how the sscan record implements this handshake, see [section 1.3.5 of the sscan record documentation](sscanRecord.md#135-handshaking-with-data-storage-clients). + +> **Important**: Once saveData is initialized, scan records that it monitors will *always* write data files. There is no runtime on/off switch for data storage. Data storage is configured at boot time by specifying which sscan records to monitor in the `saveData.req` file. The only way to disable data storage is to remove saveData from the startup script and reboot. + +## Setup + +### Loading the database + +Load `saveData.db` to create the PVs that saveData uses for status, file paths, and user configuration: + +``` +dbLoadRecords("$(SSCAN)/sscanApp/Db/saveData.db", "P=xxx:") +``` + +Alternatively, the `sscan.iocsh` script loads this database (along with `standardScans.db` and `scanProgress.db`) automatically: + +``` +iocshLoad("$(SSCAN)/iocsh/sscan.iocsh", "PREFIX=xxx:,SSCAN=$(SSCAN)") +``` + +### Configuration file (saveData.req) + +saveData is configured by a request file (typically named `saveData.req`) that must be placed in the IOC's startup directory. The file uses an INI-style format with `[section]` headers, `#` comments, and macro substitution using `$(MACRO)` syntax. Macros are defined by the second argument to `saveData_Init()`. + +The following sections are recognized: + +| Section | Required | Purpose | +|---------|----------|---------| +| `[prefix]` | No | IOC prefix, used in data file names. Punctuation characters are replaced with underscores. | +| `[status]` | **Yes** | PV name for the saveData status indicator (mbbi). | +| `[message]` | No | PV name for status messages (stringout). | +| `[filename]` | No | PV name where saveData writes the current MDA filename. | +| `[fullPathName]` | No | PV name where saveData writes the full file path. Accessed as a long string (appends `.$`). | +| `[counter]` | **Yes** | PV name for the scan number counter (longout). | +| `[fileSystem]` | **Yes** | PV name for the file system / mount point path. Accessed as a long string. | +| `[subdir]` | **Yes** | PV name for the subdirectory under the file system. Accessed as a long string. | +| `[basename]` | No | PV name for a user-specified file base name. If not provided or not connected, the IOC prefix is used. Accessed as a long string. | +| `[realTime1D]` | No | PV name for the real-time 1D writing toggle (bo). | +| `[scanRecord]` | No | List of sscan record names to monitor (one per line). saveData will set AAWAIT=1 on each. | +| `[extraPV]` | No | List of additional PVs to include in every data file. An optional quoted description string can follow each PV name. | + +Example `saveData.req`: + +``` +[prefix] +$(P) + +[status] +$(P)saveData_status + +[message] +$(P)saveData_message + +[filename] +$(P)saveData_fileName + +[fullPathName] +$(P)saveData_fullPathName + +[counter] +$(P)saveData_scanNumber + +[fileSystem] +$(P)saveData_fileSystem + +[subdir] +$(P)saveData_subDir + +[basename] +$(P)saveData_baseName + +[realTime1D] +$(P)saveData_realTime1D + +[scanRecord] +$(P)scanH +$(P)scan1 +$(P)scan2 +$(P)scan3 +$(P)scan4 + +[extraPV] +$(P)saveData_comment1 +$(P)saveData_comment2 +$(P)scaler1.TP +$(P)scan1.P1SM "scan mode" +``` + +In the `[extraPV]` section, each line contains a PV name optionally followed by a quoted description string. If no description is provided, saveData will fetch the PV's `.DESC` field. Extra PVs are read at the beginning of each scan and their values are written into the MDA file's extra-PV section. + +### Initialization + +After `iocInit`, initialize saveData with: + +``` +saveData_Init("saveData.req", "P=xxx:") +``` + +Arguments: +- **fname** -- Name of the configuration file (searched in the IOC's startup directory). +- **macros** -- Macro substitution string (e.g., `"P=xxx:"`). Macros are substituted in PV names throughout the configuration file. + +`saveData_Init` can only be called once. It spawns the saveData thread and suspends the calling thread (the IOC shell) until initialization is complete and all Channel Access connections have been attempted. + +### Autosave + +If you use autosave, include `saveData_settings.req` in your autosave request file: + +``` +file saveData_settings.req P=$(P) +``` + +This saves and restores the following fields across IOC reboots: + +| PV | Description | +|----|-------------| +| `$(P)saveData_fileSystem.$` | File system path (long string) | +| `$(P)saveData_subDir.$` | Subdirectory (long string) | +| `$(P)saveData_baseName.$` | Base file name (long string) | +| `$(P)saveData_scanNumber` | Scan number counter | +| `$(P)saveData_realTime1D` | Real-time 1D writing toggle | +| `$(P)saveData_maxAllowedRetries` | Maximum allowed retries | +| `$(P)saveData_retryWaitInSecs` | Wait time between retries (seconds) | + +## Runtime PVs + +All PVs use the macro `$(P)` as a prefix. Load these by loading `saveData.db`. + +### Status and messaging + +| PV Suffix | Type | Description | +|-----------|------|-------------| +| `saveData_status` | mbbi | Status: 0="Inactive", 1="Active", 2="Mount err", 3="I/O err" | +| `saveData_message` | stringout | Status/error message from saveData | + +### File path configuration + +| PV Suffix | Type | Max Length | Description | +|-----------|------|-----------|-------------| +| `saveData_fileSystem` | lso | 256 | File system or mount point path (e.g., `/data/`) | +| `saveData_subDir` | lso | 256 | Subdirectory under the file system | +| `saveData_baseName` | lso | 256 | User-specified base name for data files | +| `saveData_fileName` | lso | 256 | Current MDA filename (written by saveData) | +| `saveData_fullPathName` | lso | 512 | Full path of the current data file (written by saveData) | + +### Scan control + +| PV Suffix | Type | Description | +|-----------|------|-------------| +| `saveData_scanNumber` | longout | Current scan file number (0-9999, auto-incremented) | +| `saveData_realTime1D` | bo | If "Yes", write 1D scan data point-by-point as it is acquired, rather than only at the end of the scan | +| `saveData_comment1` | stringout | User comment 1 (included in data files as an extra PV) | +| `saveData_comment2` | stringout | User comment 2 (included in data files as an extra PV) | + +### Retry mechanism + +If a file write fails (e.g., due to a network filesystem error), saveData will retry up to `maxAllowedRetries` times, waiting `retryWaitInSecs` seconds between each attempt. If all retries are exhausted, the write is abandoned. + +| PV Suffix | Type | Default | Description | +|-----------|------|---------|-------------| +| `saveData_maxAllowedRetries` | longout | 10 | Maximum number of write retries | +| `saveData_retryWaitInSecs` | longout | 15 | Seconds to wait between retries | +| `saveData_currRetries` | longout | 0 | Current retry count (for the current write attempt) | +| `saveData_totalRetries` | longout | 0 | Cumulative retry count across all scans | +| `saveData_abandonedWrites` | longout | 0 | Number of writes that were abandoned after exhausting retries | + +## File naming + +Data files are named using the pattern `NNNN.mda`, where `NNNN` is a four-digit scan number from the `saveData_scanNumber` PV. The scan number auto-increments after each file is written. + +- If `saveData_baseName` is set, its value is used as the base name. +- If `saveData_baseName` is empty, the IOC prefix (from the `[prefix]` section) is used, with punctuation characters replaced by underscores (e.g., prefix `xxx:` becomes `xxx_`). +- If a file with the computed name already exists, saveData appends a duplicate counter: `NNNN_MM.mda` (where `MM` increments until a unique name is found). + +## Data file output + +saveData writes scan data in the MDA binary format. See the [MDA File Format](MDAFormat.md) documentation for the complete format specification. + +For multidimensional scans, saveData detects the scan hierarchy by examining the trigger PV fields (T1PV-T4PV) of each monitored sscan record. When a trigger PV points to another monitored sscan record's EXSC field (with trigger command value 1.0), saveData links the two scans as outer and inner dimensions. + +### Real-time 1D writing + +When `saveData_realTime1D` is set to "Yes", saveData writes 1D scan data point-by-point as each data point is acquired, rather than waiting for the entire scan to complete. This allows partial data recovery if a scan is aborted or the IOC crashes mid-scan. The file is updated in-place by seeking to the appropriate position and overwriting the current-point counter and data values. + +## Diagnostic commands + +The following iocsh commands are available for troubleshooting: + +| Command | Arguments | Description | +|---------|-----------|-------------| +| `saveData_PrintScanInfo` | `name` | Print connection status and configuration for the named sscan record | +| `saveData_Priority` | `priority` | Change the saveData thread priority | +| `saveData_SetCptWait_ms` | `ms` | Set the minimum wait time (in milliseconds) between current-point monitor postings | +| `saveData_Version` | (none) | Print the saveData version string | +| `saveData_Info` | (none) | Print overall saveData status and configuration | + +## Debug variables + +The following variables can be set from the iocsh to control debug output: + +| Variable | Default | Description | +|----------|---------|-------------| +| `debug_saveData` | 0 | General debug verbosity level. Higher values produce more output. | +| `debug_saveDataMsg` | 0 | Message queue debug verbosity level. | +| `saveData_MessagePolicy` | 0 | Controls how current-point monitor messages are queued. 0=wait for space; 1=drop if full; 2=drop if full and time hasn't elapsed; 3=wait if time has elapsed. | diff --git a/docs/saveData_fileFormat.txt b/docs/saveData_fileFormat.txt deleted file mode 100644 index e83e9e5..0000000 --- a/docs/saveData_fileFormat.txt +++ /dev/null @@ -1,137 +0,0 @@ -Note, in the following, that an "xdr_counted_string" is not part of the XDR -standard, but a definition added on top of the standard. It consists of an -xdr_short, containing the number of characters in the string, possibly followed -by an xdr_string. Only if the xdr_short value is nonzero will an xdr_string -follow it. Thus an empty string looks like this: - - - -and a nonempty string looks like this: - - - ------------------------------------------------------------------------ - -scan file format - -FILE HEADER - xdr_float: VERSION (1.3 == 3FA66666) - xdr_long: scan number - xdr_short data's rank - xdr_vector(rank, xdr_int) dims; - xdr_int isRegular (true=1, false=0) - xdr_long: pointer to the extra pvs - - -SCAN - HEADER: - xdr_short: this scan's rank - xdr_long: number of requested points (NPTS) - xdr_long: current point (CPT) - if the scan rank is > 1 - xdr_vector(NPTS, xdr_long) pointer to the lower scans - - INFO: - xdr_counted_string: scan name - xdr_counted_string: time stamp - - - xdr_int: number of positioners - xdr_int: number of detectors - xdr_int: number of triggers - - for each positioner - xdr_int: positioner number - xdr_counted_string: positioner name - xdr_counted_string: positioner desc - xdr_counted_string: positioner step mode - xdr_counted_string: positioner unit - xdr_counted_string: readback name - xdr_counted_string: readback description - xdr_counted_string: readback unit - - for each detector - xdr_int: detector number - xdr_counted_string: detector name - xdr_counted_string: detector desc - xdr_counted_string: detector unit - - for each trigger - xdr_int: trigger number - xdr_counted_string: trigger name - xdr_float: trigger command - - DATA: - for each positioner - xdr_vector(NPTS, xdr_double): readback array - - for each detector - xdr_vector(NPTS, xdr_float): detector array - -[SCAN] -... -... -... -[SCAN] - -EXTRA PVs - xdr_int: number of extra pvs - - for each pv - xdr_counted_string: name - xdr_counted_string: desc - xdr_int: type - if type != DBR_STRING - xdr_long: count - xdr_counted_string: unit - - depending on the type: - DBR_STRING: - xdr_counted_string: value - DBR_CTRL_CHAR: - xdr_vector(count, xdr_char): value - DBR_CTRL_SHORT: - xdr_vector(count, xdr_short): value - DBR_CTRL_LONG: - xdr_vector(count, xdr_long): value - DBR_CTRL_FLOAT: - xdr_vector(count, xdr_float): value - DBR_CTRL_DOUBLE: - xdr_vector(count, xdr_double): value ------------------------------------------------------------------------ - -A 1D scan looks like this: - - header - scan1 - extra PV's - -A 2D scan looks like this - - header - scan2 - scan1 - scan1 - ... - extra PV's - -A 3D scan looks like this - - header - scan3 - scan2 - scan1 - scan1 - ... - scan2 - scan1 - scan1 - ... - ... - extra PV's - -A counted string looks like this: - int number of characters - # if number of characters>0: - int number of characters - char[number_of_characters] diff --git a/docs/scanparmRecord.md b/docs/scanparmRecord.md index effec8b..aa2ca02 100644 --- a/docs/scanparmRecord.md +++ b/docs/scanparmRecord.md @@ -4,26 +4,26 @@ title: Scanparm Record nav_order: 5 --- +# Scanparm Record and Related Software -Scanparm Record and Related Software -==================================== +Original Author: Tim Mooney -Tim Mooney +## Contents -- - - - - - +- [Overview](#overview) +- [Field Descriptions](#field-descriptions) + - [Alphabetical listing of record-specific fields](#alphabetical-listing-of-record-specific-fields) + - [Fields involved in sending information out](#fields-involved-in-sending-information-out) + - [Fields involved in collecting information](#fields-involved-in-collecting-information) + - [Fields involved in managing execution](#fields-involved-in-managing-execution) + - [Miscellaneous fields](#miscellaneous-fields) + - [Private fields](#private-fields) +- [Files](#files) +- [Restrictions](#restrictions) -Contents --------- +## Overview -- [Overview](#Overview) -- [Field Descriptions](#Fields) -- [Files](#Files) -- [Restrictions](#Restrictions) - -Overview --------- - -This documentation describes the EPICS scanparm record, and related EPICS software required to build and use it. This version of the record is compatible with EPICS 3.14.8.2, and is incompatible with any 3.13.x version of EPICS. +This documentation describes the EPICS scanparm record, and related EPICS software required to build and use it. This version of the record is compatible with EPICS 3.14.8.2 and onward, and is incompatible with any 3.13.x version of EPICS. The scanparm record stores parameters intended to be written to the EPICS sscan record, and provides the EPICS end user with a convenient way to load those parameters into the sscan record and cause the sscan record to perform a scan. The idea is to allow the user to configure and execute a predefined scan with a single mouse click. @@ -38,180 +38,175 @@ The scanparm record stores parameters intended to be written to the EPICS sscan In the simplest and most common use, a scanparm record is associated at boot time with a particular positioner (e.g., a motor) and targeted to configure and run a particular sscan record. At run time, the user typically will write start and end positions, and the number of data points to be acquired, to a scanparm record, and from then on can run that scan with a single write to the scanparm record. It is possible to have more than one scanparm record associated with a positioner, and it is possible to gang scanparm records together into a database that stores parameters for scans involving more than one positioner, and more than one sscan record. -The scanparm record contains sets of paired fields for parameters it writes, for parameters it reads, and for commands it receives from the user and may forward to another record. - +The scanparm record contains sets of paired fields for parameters it writes, for parameters it reads, and for commands it receives from the user and may forward to another record. - -Field Descriptions ------------------- +## Field Descriptions In addition to fields common to all record types (see the EPICS Record Reference Manual for these) the scanparm record has the fields described below. -- [Alphabetical listing of record-specific fields](#Fields_alphabetical) -- [Fields involved in sending information out](#Fields_write) -- [Fields involved in collecting information](#Fields_read) -- [Fields involved in managing execution](#Fields_command) -- [Miscellaneous fields](#Fields_misc) -- [Private fields](#Fields_private) - -- - - - - - +- [Alphabetical listing of record-specific fields](#alphabetical-listing-of-record-specific-fields) +- [Fields involved in sending information out](#fields-involved-in-sending-information-out) +- [Fields involved in collecting information](#fields-involved-in-collecting-information) +- [Fields involved in managing execution](#fields-involved-in-managing-execution) +- [Miscellaneous fields](#miscellaneous-fields) +- [Private fields](#private-fields) - +### Alphabetical listing of record-specific fields | Name | Type | DCT prompt | Access | DCT | |---|---|---|---|---| -| [ ACT ](#Fields_read) | DBF\_SHORT | ScanActive | R | No | -| [ AFT ](#Fields_write) | DBF\_MENU(sscanPASM) | After | R/W | Yes | -| [ AR ](#Fields_write) | DBF\_MENU(sscanP1AR) | absRel | R/W | Yes | -| [ AQT ](#Fields_write) | DBF\_DOUBLE | Acquire time | R/W\* | Yes | -| [ DPV ](#Fields_write) | DBF\_STRING | DetPVName | R/W | Yes | -| [ EP ](#Fields_write) | DBF\_DOUBLE | EndPos | R/W\* | Yes | -| [ GO ](#Fields_command) | DBF\_SHORT | Go | R/W\* | Yes | -| [ IACT ](#Fields_read) | DBF\_INLINK | InLink | R | Yes | -| [ IMP ](#Fields_read) | DBF\_INLINK | MP InLink | R | Yes | -| [ LOAD ](#Fields_command) | DBF\_SHORT | Load | R/W\* | Yes | -| [ LSTP ](#Fields_misc) | DBF\_DOUBLE | Last stepSize | R | No | -| [ MP ](#Fields_read) | DBF\_LONG | MaxPts | R | No | -| [ NP ](#Fields_write) | DBF\_LONG | nPts | R/W\* | Yes | -| [ OAFT ](#Fields_write) | DBF\_OUTLINK | AFT OutLink | R | Yes | -| [ OAQT ](#Fields_write) | DBF\_OUTLINK | AQT OutLink | R | Yes | -| [ OAR ](#Fields_write) | DBF\_OUTLINK | AR OutLink | R | Yes | -| [ ODPV ](#Fields_write) | DBF\_OUTLINK | D1PV OutLink | R | Yes | -| [ OEP ](#Fields_write) | DBF\_OUTLINK | EP OutLink | R | Yes | -| [ OGO ](#Fields_command) | DBF\_OUTLINK | GO OutLink | R | Yes | -| [ OLOAD ](#Fields_command) | DBF\_OUTLINK | LOAD OutLink | R | Yes | -| [ ONP ](#Fields_write) | DBF\_OUTLINK | NP OutLink | R | Yes | -| [ OPPV ](#Fields_write) | DBF\_OUTLINK | P1PV OutLink | R | Yes | -| [ OPRE ](#Fields_write) | DBF\_OUTLINK | PRE-write OutLink | R | Yes | -| [ ORPV ](#Fields_write) | DBF\_OUTLINK | R1PV OutLink | R | Yes | -| [ OSC ](#Fields_write) | DBF\_OUTLINK | SC OutLink | R | Yes | -| [ OSM ](#Fields_write) | DBF\_OUTLINK | SM OutLink | R | Yes | -| [ OSP ](#Fields_write) | DBF\_OUTLINK | SP OutLink | R | Yes | -| [ OTPV ](#Fields_write) | DBF\_OUTLINK | T1PV OutLink | R | Yes | -| [ PPV ](#Fields_write) | DBF\_STRING | PositionerPVName | R/W | Yes | -| [ PRE ](#Fields_write) | DBF\_SHORT | PRE-write command | R/W\* | Yes | -| [ PREC ](#Fields_misc) | DBF\_SHORT | Display Precision | R/W | Yes | -| [ RPV ](#Fields_write) | DBF\_STRING | ReadbackPVName | R/W | Yes | -| [ SC ](#Fields_write) | DBF\_SHORT | StartCmd | R/W | Yes | -| [ SM ](#Fields_write) | DBF\_MENU(sscanP1SM) | StepMode | R/W | Yes | -| [ SP ](#Fields_write) | DBF\_DOUBLE | StartPos | R/W\* | Yes | -| [ STEP ](#Fields_misc) | DBF\_DOUBLE | StepSize | R | No | -| [ TPV ](#Fields_write) | DBF\_STRING | TrigPVName | R/W | Yes | -| [ VAL ](#Fields_misc) | DBF\_DOUBLE | Result | R | No | -| [ VERS ](#Fields_misc) | DBF\_FLOAT | Code Version | R | No | - -NOTE: Hot links in this table take you only to the *section* in which the linked item is described in detail. You'll probably have to scroll down to find the actual item. +| [ACT](#fields-involved-in-collecting-information) | DBF\_SHORT | ScanActive | R | No | +| [AFT](#fields-involved-in-sending-information-out) | DBF\_MENU(sscanPASM) | After | R/W | Yes | +| [AR](#fields-involved-in-sending-information-out) | DBF\_MENU(sscanP1AR) | absRel | R/W | Yes | +| [AQT](#fields-involved-in-sending-information-out) | DBF\_DOUBLE | Acquire time | R/W\* | Yes | +| [DPV](#fields-involved-in-sending-information-out) | DBF\_STRING | DetPVName | R/W | Yes | +| [EP](#fields-involved-in-sending-information-out) | DBF\_DOUBLE | EndPos | R/W\* | Yes | +| [GO](#fields-involved-in-managing-execution) | DBF\_SHORT | Go | R/W\* | Yes | +| [IACT](#fields-involved-in-collecting-information) | DBF\_INLINK | InLink | R | Yes | +| [IMP](#fields-involved-in-collecting-information) | DBF\_INLINK | MP InLink | R | Yes | +| [LOAD](#fields-involved-in-managing-execution) | DBF\_SHORT | Load | R/W\* | Yes | +| [LSTP](#miscellaneous-fields) | DBF\_DOUBLE | Last stepSize | R | No | +| [MP](#fields-involved-in-collecting-information) | DBF\_LONG | MaxPts | R | No | +| [NP](#fields-involved-in-sending-information-out) | DBF\_LONG | nPts | R/W\* | Yes | +| [OAFT](#fields-involved-in-sending-information-out) | DBF\_OUTLINK | AFT OutLink | R | Yes | +| [OAQT](#fields-involved-in-sending-information-out) | DBF\_OUTLINK | AQT OutLink | R | Yes | +| [OAR](#fields-involved-in-sending-information-out) | DBF\_OUTLINK | AR OutLink | R | Yes | +| [ODPV](#fields-involved-in-sending-information-out) | DBF\_OUTLINK | D1PV OutLink | R | Yes | +| [OEP](#fields-involved-in-sending-information-out) | DBF\_OUTLINK | EP OutLink | R | Yes | +| [OGO](#fields-involved-in-managing-execution) | DBF\_OUTLINK | GO OutLink | R | Yes | +| [OLOAD](#fields-involved-in-managing-execution) | DBF\_OUTLINK | LOAD OutLink | R | Yes | +| [ONP](#fields-involved-in-sending-information-out) | DBF\_OUTLINK | NP OutLink | R | Yes | +| [OPPV](#fields-involved-in-sending-information-out) | DBF\_OUTLINK | P1PV OutLink | R | Yes | +| [OPRE](#fields-involved-in-sending-information-out) | DBF\_OUTLINK | PRE-write OutLink | R | Yes | +| [ORPV](#fields-involved-in-sending-information-out) | DBF\_OUTLINK | R1PV OutLink | R | Yes | +| [OSC](#fields-involved-in-sending-information-out) | DBF\_OUTLINK | SC OutLink | R | Yes | +| [OSM](#fields-involved-in-sending-information-out) | DBF\_OUTLINK | SM OutLink | R | Yes | +| [OSP](#fields-involved-in-sending-information-out) | DBF\_OUTLINK | SP OutLink | R | Yes | +| [OTPV](#fields-involved-in-sending-information-out) | DBF\_OUTLINK | T1PV OutLink | R | Yes | +| [PPV](#fields-involved-in-sending-information-out) | DBF\_STRING | PositionerPVName | R/W | Yes | +| [PRE](#fields-involved-in-sending-information-out) | DBF\_SHORT | PRE-write command | R/W\* | Yes | +| [PREC](#miscellaneous-fields) | DBF\_SHORT | Display Precision | R/W | Yes | +| [RPV](#fields-involved-in-sending-information-out) | DBF\_STRING | ReadbackPVName | R/W | Yes | +| [SC](#fields-involved-in-sending-information-out) | DBF\_SHORT | StartCmd | R/W | Yes | +| [SM](#fields-involved-in-sending-information-out) | DBF\_MENU(sscanP1SM) | StepMode | R/W | Yes | +| [SP](#fields-involved-in-sending-information-out) | DBF\_DOUBLE | StartPos | R/W\* | Yes | +| [STEP](#miscellaneous-fields) | DBF\_DOUBLE | StepSize | R | No | +| [TPV](#fields-involved-in-sending-information-out) | DBF\_STRING | TrigPVName | R/W | Yes | +| [VAL](#miscellaneous-fields) | DBF\_DOUBLE | Result | R | No | +| [VERS](#miscellaneous-fields) | DBF\_FLOAT | Code Version | R | No | + +NOTE: Links in this table take you only to the *section* in which the linked item is described in detail. You may have to scroll down to find the actual item. Note: In the __Access__ column above: -* R \| Read only \| -* R/W \| Read and write are allowed \| -* N \| No access allowed \| -a channel-access write triggers record processing if the record's SCAN field is set to "Passive." - - -- - - - - - - - +| Code | Meaning | +|---|---| +| R | Read only | +| R/W | Read and write are allowed | +| R/W\* | Read and write; a channel-access write triggers record processing if the record's SCAN field is set to "Passive." | +| N | No access allowed | ### Fields involved in sending information out | Value Field | Type | Output Link | Typical Target Field | Purpose | |---|---|---|---|---| -| PRE | DBF\_SHORT | OPRE | .CMND | clear old positioner configuration | -| SM | DBF\_MENU(sscanP1SM) | OSM | .P1SM | positioner scan mode (e.g., linear, table, fly) | -| AR | DBF\_MENU(sscanP1AR) | OAR | .P1AR | positioner absolute/relative | -| AFT | DBF\_MENU(sscanPASM) | OAFT | .PASM | positioner after-scan mode (e.g., stay, go to start pos,...) | -| PPV | DBF\_STRING | OPPV | .P1PV | positioner drive PV name | -| RPV | DBF\_STRING | ORPV | .R1PV | positioner readback PV name | -| TPV | DBF\_STRING | OTPV | .T1PV | detector-trigger PV name | -| DPV | DBF\_STRING | ODPV | .D01PV | detector PV name | -| SP | DBF\_DOUBLE | OSP | .P1SP | positioner start point | -| EP | DBF\_DOUBLE | OEP | .P1EP | positioner end point | -| NP | DBF\_LONG | ONP | .NPTS | number of data points to acquire | -| SC | DBF\_SHORT | OSC | .EXSC | start the scan | -| AQT | DBF\_DOUBLE | OAQT | .TP | acquire time | - -- - - - - - - - +| PRE | DBF\_SHORT | OPRE | \.CMND | clear old positioner configuration | +| SM | DBF\_MENU(sscanP1SM) | OSM | \.P1SM | positioner scan mode (e.g., linear, table, fly) | +| AR | DBF\_MENU(sscanP1AR) | OAR | \.P1AR | positioner absolute/relative | +| AFT | DBF\_MENU(sscanPASM) | OAFT | \.PASM | positioner after-scan mode (e.g., stay, go to start pos,...) | +| PPV | DBF\_STRING | OPPV | \.P1PV | positioner drive PV name | +| RPV | DBF\_STRING | ORPV | \.R1PV | positioner readback PV name | +| TPV | DBF\_STRING | OTPV | \.T1PV | detector-trigger PV name | +| DPV | DBF\_STRING | ODPV | \.D01PV | detector PV name | +| SP | DBF\_DOUBLE | OSP | \.P1SP | positioner start point | +| EP | DBF\_DOUBLE | OEP | \.P1EP | positioner end point | +| NP | DBF\_LONG | ONP | \.NPTS | number of data points to acquire | +| SC | DBF\_SHORT | OSC | \.EXSC | start the scan | +| AQT | DBF\_DOUBLE | OAQT | \.TP | acquire time | ### Fields involved in collecting information | Input Link | Value Field | Typical Target Field | Purpose | |---|---|---|---| -| IMP | MP | .MPTS | get the maximum permitted number of data points | -| IACT | ACT | .BUSY | determine whether the target sscan record is active | +| IMP | MP | \.MPTS | get the maximum permitted number of data points | +| IACT | ACT | \.BUSY | determine whether the target sscan record is active | -- - - - - - +### Fields involved in managing execution - +| Value field | Output Link | Typical Target Field | Purpose | +|---|---|---|---| +| LOAD | OLOAD | \.LOAD | Cause the scanparm record to write parameters to the sscan record. If more than one scanparm record is needed to define a scan (e.g., for a multi-positioner scan, or a multi-dimensional scan), the OLOAD field should link to the next scanparm record. | +| GO | OGO | \.GO | Cause the scanparm record to write parameters to the sscan record and also cause the sscan record to begin the scan. If more than one scanparm record is needed to define a scan (e.g., for a multi-positioner scan, or a multi-dimensional scan), the OGO field should link to the next scanparm record, and the last scanparm record to execute should use its OGO link to cause its sscan record to start scanning. | -### Fields involved in managing execution. +### Miscellaneous fields -| Value field | Output Link | Typical Target Field | Purpose | -| LOAD | OLOAD | .LOAD | cause the scanparm record to write parameters to the sscan record. If more than one scanparm record is needed to define a scan (e.g., for a multi-positioner scan, or a multi-dimensional scan), the OLOAD field should link to the next scanparm record. | -| GO | OGO | .GO | Cause the scanparm record to write parameters to the sscan record and also cause the sscan record to begin the scan. If more than one scanparm record is needed to define a scan (e.g., for a multi-positioner scan, or a multi-dimensional scan), the OGO field should link to the next scanparm record, and the last scanparm record to execute should use its OGO link to cause its sscan record to start scanning. | +| Field | Type | Purpose | +|---|---|---| +| STEP | DBF\_DOUBLE | Step size, calculated from SP, EP, and NP | +| LSTP | DBF\_DOUBLE | Last step size (previous value of STEP) | +| PREC | DBF\_SHORT | Display precision | +| VAL | DBF\_DOUBLE | Result | +| VERS | DBF\_FLOAT | Code version | -- - - - - - +### Private fields -Files ------ +| Field | Type | Purpose | +|---|---|---| +| RPVT | DBF\_NOACCESS | Record private | + +## Files The following table briefly describes the files required to implement and use the scanparm record. -### SOURCE CODE +### Source Code +| File | Description | +|---|---| | scanparmRecord.c | Record support for the scanparm record | | scanparmRecord.dbd | This file defines all of the fields menus, etc. for the scanparm record. | -### DATABASE and AUTOSAVE-REQUEST FILES +### Database and Autosave-Request Files -| scanParms.db | database used for one-dimensional, one-positioner scans, when the sscan record and the scanparm record have the same prefix. | -| scanParmsRemote.db | database used for one-dimensional, one-positioner scans, when the sscan record and the scanparm record have different prefixes. | -| scanParms2Pos.db | database used for one-dimensional, two-positioner scans. | -| scanParms2D.db | database used for two-dimensional scans. | +| File | Description | +|---|---| +| scanParms.db | Database used for one-dimensional, one-positioner scans, when the sscan record and the scanparm record have the same prefix. | +| scanParmsRemote.db | Database used for one-dimensional, one-positioner scans, when the sscan record and the scanparm record have different prefixes. | +| scanParms2Pos.db | Database used for one-dimensional, two-positioner scans. | +| scanParms2D.db | Database used for two-dimensional scans. | -### MEDM DISPLAY FILES +### MEDM Display Files -| scanParms.adl | | -| scanParmsRemote.adl | | -| scanParmsCustom.adl | | -| scanParms2Pos.adl | | -| scanParms2D.adl | | +| File | Description | +|---|---| +| scanParms.adl | Single-positioner scan parameters | +| scanParmsRemote.adl | Remote single-positioner scan parameters | +| scanParmsCustom.adl | Custom scan parameters | +| scanParms2Pos.adl | Two-positioner scan parameters | +| scanParms2D.adl | Two-dimensional scan parameters | -These files build `medm` screens to access the scanparm record and related process variables. To use one of them from the command line, type, for example -``` -medm -x -macro "P=xxx:,Q=m1,PV=m1" scanParms.adl -medm -x -macro "P=xxx:,Q=yyy:m1,PV=yyy:m1" scanParmsRemote.adl -medm -x -macro "P=xxx:,Q=m1,EGU=,NAME=,DESC=" scanParmsCustom.adl -medm -x -macro "P=xxx:,Q=device,PV1=xxx:m1,PV2=xxx:m2,SCAN=yyy:scan1" scanParms2Pos.adl -medm -x -macro "P=xxx:,Q=device,DESC=,EGU1=,NAME1=,EGU2=,NAME2=" scanParms2D.adl +These files build `medm` screens to access the scanparm record and related process variables. To use one of them from the command line, type, for example +``` +medm -x -macro "P=xxx:,Q=m1,PV=m1" scanParms.adl +medm -x -macro "P=xxx:,Q=yyy:m1,PV=yyy:m1" scanParmsRemote.adl +medm -x -macro "P=xxx:,Q=m1,EGU=,NAME=,DESC=" scanParmsCustom.adl +medm -x -macro "P=xxx:,Q=device,PV1=xxx:m1,PV2=xxx:m2,SCAN=yyy:scan1" scanParms2Pos.adl +medm -x -macro "P=xxx:,Q=device,DESC=,EGU1=,NAME1=,EGU2=,NAME2=" scanParms2D.adl ``` -### EPICS STARTUP FILE - -| st.cmd | Startup script | +### EPICS Startup File -This file is not included in the distribution. Here are annotated excerpts from a startup file that supports scanparms: +This file is not included in the distribution. Here are annotated excerpts from a startup file that supports scanparms: ``` -####################################################################### -# vxWorks startup script to load and execute system (iocCore) software. -# Tell EPICS all about the record types, device-support modules, drivers, -# etc. in the software we just loaded (xxxApp) -dbLoadDatabase("dbd/xxxApp.dbd") -dbLoadTemplate("scanParms.substitutions") +####################################################################### +# vxWorks startup script to load and execute system (iocCore) software. +# Tell EPICS all about the record types, device-support modules, drivers, +# etc. in the software we just loaded (xxxApp) +dbLoadDatabase("dbd/xxxApp.dbd") +dbLoadTemplate("scanParms.substitutions") ``` -### AUTOSAVE REQUEST FILE - -| scanParms\_Settings.req | sample request file to be included in auto\_settings.req to save the user modifiable settings of one scanParms.db database. To use this, add a line of the following form in auto\_settings.req for each scanParms database: `file scanParms_settings.req P=xxx: M=m1` | - -- - - - - - - -Restrictions ------------- +### Autosave Request File -Suggestions and comments to: -[Tim Mooney](mailto:mooney@aps.anl.gov) : (mooney@aps.anl.gov) -Last modified: December 11, 2007 +| File | Description | +|---|---| +| scanParms\_Settings.req | Sample request file to be included in auto\_settings.req to save the user modifiable settings of one scanParms.db database. To use this, add a line of the following form in auto\_settings.req for each scanParms database: `file scanParms_settings.req P=xxx: M=m1` | diff --git a/docs/sscanDoc.md b/docs/sscanDoc.md deleted file mode 100644 index 6925466..0000000 --- a/docs/sscanDoc.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -layout: default -title: Overview -nav_order: 2 ---- - - - -The sscan module -================ - -Documentation -------------- - -The following documentation is available: - -[sscanRecord](sscanRecord.md) - -The sscan record documentation. This also includes some information on saveData, the synApps client program that writes scan data to disk. - -[scanparmRecord](scanparmRecord.md) - -The scanparm record documentation. - -[saveData\_fileFormat.txt](saveData_fileFormat.txt) - -description of the 'MDA' (multidimensional archive) file format written by saveData. This file format uses the XDR standard for cross-platform compatibility. - -[saveData](saveData.req) - -Sample copy of the file that tells saveData what to do. - -[XDR\_RFC1014.txt](XDR_RFC1014.txt) - -A description of the XDR (External Data Representation) standard - -[Scans.ppt](Scans.ppt) - -Powerpoint presentation that describes the sscan module; shows how to use the sscan record; describes saveData's MDA file format; and explains how EPICS putNotify/ca\_put\_callback() completion behaves, and how to handle processing chains that don't satisfy EPICS' execution-tracing requirements. - -Installation and use of the sscan module ----------------------------------------- - -### Installation and Building - -After obtaining a copy of the distribution, it must be installed and built for use at your site. Usually, these steps only need to be performed once. - -1. Unzip and untar the distribution, e.g. on Unix: - - gunzip sscan\_R2-3.tar.gz - tar xf sscan\_R2-3.tar - - Usually this is done in an EPICS 'support' directory. It will produce the subdirectory - - sscan/2-3 - - > _in synApps 5.5 and higher, it will produce `sscan-2-6-6` -- i.e., no subdirectory._ - -2. Edit **sscan**'s configure/RELEASE file and set the paths to your installation of EPICS base and to your versions of other dependent modules. -3. Run gnumake in the top level directory and check for any compilation errors. - -### Use of the sscan module in an EPICS IOC - -The **sscan** module is not intended to run an IOC application directly, but rather to contribute code libraries, databases, MEDM displays, etc., to an IOC application. SynApps contains an example IOC application (the **xxx** module), which pulls software from the **sscan** module and deploys it in an IOC. - -The essential steps in applying **sscan**\-module code in an IOC application ("example") are the following: - -1. Include the following line in example/configure/RELEASE. - ``` - SSCAN= - ``` - -2. Include the following line in example/exampleApp/src/iocexampleInclude.dbd. - ``` - include "sscanSupport.dbd" - include "scanProgressSupport.dbd" - ``` - -3. Include the following line in example/exampleApp/src/Makefile. - ``` - example\_LIBS += sscan scanProgress - ``` - -4. Load the databases standardScans, and saveData, by including the following lines in st.cmd: - ``` - dbLoadRecords("$(SSCAN)/sscanApp/Db/standardScans.db","P=xxx:,MAXPTS1=2000,MAXPTS2=1000,MAXPTS3=1000,MAXPTS4=10,MAXPTSH=2000") - dbLoadRecords("$(SSCAN)/sscanApp/Db/saveData.db","P=xxx:") - dbLoadRecords("$(SSCAN)/sscanApp/Db/scanProgress.db","P=xxx:scanProgress:") - ``` - -5. If you use autosave, include standardScans\_settings.req, and saveData\_settings.req in your autosave request file: - ``` - file standardScans\_settings.req P=$(P) - file saveData\_settings.req P=$(P) - ``` - -6. Tell saveData how to initialize by editing the file `saveData.req`, and placing it in the ioc's startup directory. Usually, the only part of this file that you modify is the section marked `[extraPV]`. In this section, enter the names of the PV's you want saveData to include in every scan-data file. If a PV is not well described by it's record's `.DESC` field, you can append your own description. - -7. Initialize saveData by including the following line in st.cmd, after iocInit: - ``` - saveData\_Init("saveData.req", "P=xxx:") - ``` - -8. Before running any scans, specify where saveData is to write scan-data files. Bring up the medm display, `scan_saveData.adl` and fill in the "File system" and "Subdirectory" fields (i.e., the PV's `$(P)saveData_fileSystem` and `$(P)saveData_subDir`). - -Suggestions and Comments to: -[Tim Mooney](mailto:mooney@aps.anl.gov) : (mooney@aps.anl.gov) diff --git a/docs/sscanRecord.md b/docs/sscanRecord.md index adfafd2..f39fd37 100644 --- a/docs/sscanRecord.md +++ b/docs/sscanRecord.md @@ -5,57 +5,50 @@ nav_order: 4 --- -The sscan record -================ +# The sscan record Author: Tim M. Mooney Based on the scan record, written by Ned D. Arnold. - Advanced Photon Source - Argonne National Laboratory - -Contents: -[__1.__ Introduction](#HEADING_1) -[__1.1.__ A Simple One Dimensional Scan](#HEADING_1-1) -[__1.2.__ Multidimensional Scans](#HEADING_1-2) -[__1.3.__ Interaction with clients](#HEADING_1-3) -[__1.3.1__ Starting a scan](#HEADING_1-3-1) -[__1.3.2__ Stopping a scan](#HEADING_1-3-2) -[__1.3.3__ Pausing a scan](#HEADING_1-3-3) -[__1.3.4__ Displaying scan data](#HEADING_1-3-4) -[__1.3.5__ Handshaking with data-storage clients](#HEADING_1-3-5) -[__1.3.6__ Handshaking with CA clients that implement positioners or detectors](#HEADING_1-3-6) -[__1.4.__ Completion of positioner and detector-trigger operations](#HEADING_1-4) -[__1.5.__ Fly scans](#HEADING_1-5) -[__1.5.1.__ Scalar-mode fly scans](#HEADING_1-5-1) -[__1.5.2.__ Array-mode fly scans](#HEADING_1-5-2) -[__2.__ sscan-Record Fields](#HEADING_2) -[__2.1.__ Control Fields](#HEADING_2-1) -[__2.2.__ Positioner Fields](#HEADING_2-2) -[__2.2.1__ LINEAR Mode](#HEADING_2-2-1) -[__2.2.2.__ TABLE Mode](#HEADING_2-2-2) -[__2.2.3.__ FLY Mode](#HEADING_2-2-3) -[__2.3.__ Detector-Trigger Fields](#HEADING_2-3) -[__2.4.__ Delay Fields](#HEADING_2-4) -[__2.5.__ Client Handshaking Fields](#HEADING_2-5) -[__2.6.__ Detector Fields](#HEADING_2-6) -[__2.7.__ Execution Fields](#HEADING_2-7) -[__2.8.__ Status/Progress Fields](#HEADING_2-8) -[__2.10.__ Miscellaneous Fields](#HEADING_2-10) - -- - - - - - - - - -1. Introduction -=============== - -- - - - - - - -The purpose of the sscan record is to move *positioners* through a series of positions and record *detector* data at each of the positions. This series of operations is commonly referred to as a *scan*, or as one loop of a multi-dimensional scan. After parameters defining the scan have been initialized and the scan has been launched, the sscan record begins a possibly long and involved sequence of operations normally without further input, and notifies any interested clients as the scan progresses. The data are collected into arrays within the record so that clients needn't handle them point by point. A separate piece of software ("saveData", which is included with the sscan record in the synApps sscan module) can coordinate with the sscan record to write scan data to disk. +Advanced Photon Source +Argonne National Laboratory + +Contents: +- [__1.__ Introduction](#1-introduction) +- [__1.1.__ A Simple One Dimensional Scan](#11-a-simple-one-dimensional-scan) +- [__1.2.__ Multidimensional Scans](#12-multidimensional-scans) +- [__1.3.__ Interaction with clients](#13-interaction-with-clients) +- [__1.3.1__ Starting a scan](#131-starting-a-scan) +- [__1.3.2__ Stopping a scan](#132-stopping-a-scan) +- [__1.3.3__ Pausing a scan](#133-pausing-a-scan) +- [__1.3.4__ Displaying scan data](#134-displaying-scan-data) +- [__1.3.5__ Handshaking with data-storage clients](#135-handshaking-with-data-storage-clients) +- [__1.3.6__ Handshaking with CA clients that implement positioners or detectors](#136-handshaking-with-ca-clients-that-implement-positioners-or-detectors) +- [__1.4.__ Completion of positioner and detector-trigger operations](#14-completion-of-positioner-and-detector-trigger-operations) +- [__1.5.__ Fly scans](#15-fly-scans) +- [__1.5.1.__ Scalar-mode fly scans](#151-scalar-mode-fly-scans) +- [__1.5.2.__ Array-mode fly scans](#152-array-mode-fly-scans) +- [__2.__ sscan-Record Fields](#2-sscan-record-fields) +- [__2.1.__ Control Fields](#21-control-fields) +- [__2.2.__ Positioner Fields](#22-positioner-fields) +- [__2.2.1__ LINEAR Mode](#221-linear-mode) +- [__2.2.2.__ TABLE Mode](#222-table-mode) +- [__2.2.3.__ FLY Mode](#223-fly-mode) +- [__2.3.__ Detector-Trigger Fields](#23-detector-trigger-fields) +- [__2.4.__ Delay Fields](#24-delay-fields) +- [__2.5.__ Client Handshaking Fields](#25-client-handshaking-fields) +- [__2.6.__ Detector Fields](#26-detector-fields) +- [__2.7.__ Execution Fields](#27-execution-fields) +- [__2.8.__ Status/Progress Fields](#28-statusprogress-fields) +- [__2.10.__ Miscellaneous Fields](#210-miscellaneous-fields) + + +# 1. Introduction + +The purpose of the sscan record is to move *positioners* through a series of positions and record *detector* data at each of the positions. This series of operations is commonly referred to as a *scan*, or as one loop of a multi-dimensional scan. After parameters defining the scan have been initialized and the scan has been launched, the sscan record begins a possibly long and involved sequence of operations normally without further input, and notifies any interested clients as the scan progresses. The data are collected into arrays within the record so that clients needn't handle them point by point. A separate piece of software ("saveData", which is included with the sscan record in the synApps sscan module) can coordinate with the sscan record to write scan data to disk. > Note that the word "scan" is used frequently in other EPICS documentation to mean something quite different from what is meant here. In the *EPICS Application Developers Guide*, "scan" connotes record processing or execution, as in "Database scanning is the mechanism for deciding when to process a record." Also, periodic record processing is performed by "scan tasks", and the field that controls when a record will be processed is named "SCAN". None of these uses of "scan" have anything to do with the sscan record, and the word will not have the EPICS meaning in the rest of this documentation. -A single sscan record supports a one dimensional scan. Several sscan records can be linked together to perform a multi-dimensional scan. Each sscan record can control up to four positioners, trigger up to four detectors, and acquire data from up to 74 process variables (70 detector values of type floatand four positioner readbacks of type double). +A single sscan record supports a one dimensional scan. Several sscan records can be linked together to perform a multi-dimensional scan. Each sscan record can control up to four positioners, trigger up to four detectors, and acquire data from up to 74 process variables (70 detector values of type float and four positioner readbacks of type double). In the most common use, the sscan record moves motors and acquires scaler (pulse counter) data at each motor position, but obviously it can also be used for other purposes. Any writable EPICS PV (process variable) can be scanned through a set of values while data are recorded from any other PVs. For example, one of the positioner PVs could be used to vary the gain or dwell time of a detector during a scan. Therefore, throughout this document the term *positioner* should be taken to mean "any PV to which you can write a number". Similarly, the term *detector trigger* will typically refer to a PV that will cause data acquisition to begin when it is written to, but it could be taken to mean "any PV to which you can write a number". Finally, the term *detector* refers to any readable numeric PV. ("Signal" might be a better word for this.) @@ -65,19 +58,28 @@ All of the process variable names used to identify positioners, detectors, and d Before a scan can start, all of the links that aren't blank must connect with the named PVs. Links that will write must, in addition, have write permission granted by EPICS' access security. The sscan record doesn't check continuously for link connection and write permission; it checks when a link is defined, and at the beginning of every scan. - +## 1.1. A Simple One Dimensional Scan -1.1. A Simple One Dimensional Scan ----------------------------------- +In the simplest reasonably complete configuration for a one-dimensional scan, the following fields are used: -In the simplest reasonably complete configuration for a one-dimensional scan, the following fields are used: P1PV the name of a positioner (e.g., "myMotor.VAL") P1SP start position -- the first position at which data will be acquired P1EP end position -- the last position at which data will be acquired NPTS the total number of positions to visit T1PV the name of a detector-trigger PV. This PV will be written to after the positioner has arrived at each position, and it is expected to initiate some data-acquisition operation. D01PV the name of a detector (signal) PV. The value of this PV will be recorded after the detector trigger has finished acquiring data. When a scan is started (by writing a 1 to the EXSC field) the sscan record commands the positioner to move to its starting position. The sscan record uses recDynLinkPutCallback() to tell the positioner to move, and waits for the resulting callback, indicating that the positioner is finished, before moving on to the next phase of the scan, which is to trigger the detector. The detector is also triggered using recDynLinkPutCallback(), and the sscan record waits for it to finish before reading detectors and going on to perform another (move, trigger, read) sequence to acquire the next data point. This algorithm continues until the sscan record has completed NPTS steps, or the scan is aborted (by a client writing 0 to the EXSC field). At the end of the scan, the sscan record has filled in an array of the positions visited (P1RA), and an array of detector values acquired (D01DA). Let's run through that again, this time more generally, with more detail, and including more of the available options. +* P1PV - the name of a positioner (e.g., "myMotor.VAL") +* P1SP - start position -- the first position at which data will be acquired +* P1EP - end position -- the last position at which data will be acquired +* NPTS - the total number of positions to visit +* T1PV - the name of a detector-trigger PV. This PV will be written to after the positioner has arrived at each position, and it is expected to initiate some data-acquisition operation. +* D01PV - the name of a detector (signal) PV. The value of this PV will be recorded after the detector trigger has finished acquiring data. -Positioners You can specify zero to four positioners. Positioners are expected to tell the sscan record when they're done moving (more about this later). After all positioners have declared themselves done, the sscan record waits for a user-specified settling time (PDLY, normally zero) before writing to detector triggers. (If no positioners, then no positioner settling time.) Positions to visit There are lots of possibilities here. You can specify any combination of the set \[*start, end, center, width, step-size*\] for each positioner; you can load a table of positions for each positioner; or you can specify that positioners are to be moved continuously during a scan. You can specify that positions be regarded as absolute, or as relative to the pre-scan position. Detector triggers Detector triggers act much like positioners, in that they write a value and wait for any ensuing processing to finish, but they send the same value at every data point (T*n*CD). After all triggered detectors have declared themselves done, the sscan record waits for a user-specified settling time (DDLY, normally zero) before reading data from detector-signal PVs. (If no detector triggers, then no detector settling time.) Detector signals Typically, detector signals are scalar PVs, but they can be array-valued PVs. If so, the sscan record will read NPTS values from them at the end of the scan. If array-valued PVs require processing to acquire their values, the sscan record can write to a special array trigger (A1PVA1CD, exactly analogous to detector triggers), and wait for any ensuing processing to finish before reading the arrays. If all detector signals are array valued, it's probably better to use the array acquisition type. (See ACQT.) Detector-signal values can be accumulated from scan to scan, so you can sweep over a set of positions, building up statistical precision and averaging over any positioning errors or variable external conditions. (See ACQM.) +When a scan is started (by writing a 1 to the EXSC field) the sscan record commands the positioner to move to its starting position. The sscan record uses recDynLinkPutCallback() to tell the positioner to move, and waits for the resulting callback, indicating that the positioner is finished, before moving on to the next phase of the scan, which is to trigger the detector. The detector is also triggered using recDynLinkPutCallback(), and the sscan record waits for it to finish before reading detectors and going on to perform another (move, trigger, read) sequence to acquire the next data point. This algorithm continues until the sscan record has completed NPTS steps, or the scan is aborted (by a client writing 0 to the EXSC field). At the end of the scan, the sscan record has filled in an array of the positions visited (P1RA), and an array of detector values acquired (D01DA). -After the scan You can tell positioners what to do after the scan is finished, using the PASM field. The default behavior is simply to remain where the scan left them, but you could tell them to return to their pre-scan positions, go to their start positions, or go to positions calculated from acquired data (e.g., the position at which a specified detector signal REFD reached its peak value during the scan). +Let's run through that again, this time more generally, with more detail, and including more of the available options. -1.2. Multidimensional Scans ---------------------------- +* Positioners - You can specify zero to four positioners. Positioners are expected to tell the sscan record when they're done moving (more about this later). After all positioners have declared themselves done, the sscan record waits for a user-specified settling time (PDLY, normally zero) before writing to detector triggers. (If no positioners, then no positioner settling time.) +* Positions to visit - There are lots of possibilities here. You can specify any combination of the set \[*start, end, center, width, step-size*\] for each positioner; you can load a table of positions for each positioner; or you can specify that positioners are to be moved continuously during a scan. You can specify that positions be regarded as absolute, or as relative to the pre-scan position. +* Detector triggers - Detector triggers act much like positioners, in that they write a value and wait for any ensuing processing to finish, but they send the same value at every data point (T*n*CD). After all triggered detectors have declared themselves done, the sscan record waits for a user-specified settling time (DDLY, normally zero) before reading data from detector-signal PVs. (If no detector triggers, then no detector settling time.) +* Detector signals - Typically, detector signals are scalar PVs, but they can be array-valued PVs. If so, the sscan record will read NPTS values from them at the end of the scan. If array-valued PVs require processing to acquire their values, the sscan record can write to a special array trigger (A1PVA1CD, exactly analogous to detector triggers), and wait for any ensuing processing to finish before reading the arrays. If all detector signals are array valued, it's probably better to use the array acquisition type. (See ACQT.) Detector-signal values can be accumulated from scan to scan, so you can sweep over a set of positions, building up statistical precision and averaging over any positioning errors or variable external conditions. (See ACQM.) +* After the scan You can tell positioners what to do after the scan is finished, using the PASM field. The default behavior is simply to remain where the scan left them, but you could tell them to return to their pre-scan positions, go to their start positions, or go to positions calculated from acquired data (e.g., the position at which a specified detector signal REFD reached its peak value during the scan). + +## 1.2. Multidimensional Scans Multidimensional scans are easy: an outer-loop sscan record (which we'll call "scan2") regards an inner-loop sscan record ("scan1") as a detector to be triggered, and each sscan record acquires its own data. Thus, scan2.T1PV, is set to scan1.EXSC, and scan2.T1CD is set to 1. In words, scan2 writes a 1 to the "execute scan" field (EXSC) of scan1. To initiate the scan, the scan2 record is commanded to begin (scan2.EXSC is set to 1). scan2 sends its *positioners* to their starting points, and waits for their callbacks. Then scan2 writes to its *detector trigger*(s), (one of) which in this case causes scan1 to begin its own scan. The scan1 record will now go through its entire programmed scan, acquiring data from its detectors at each point. @@ -89,22 +91,22 @@ An outer sscan record involved in a multidimensional scan doesn't know or care t Clearly, this calls for some handshaking between the client and the sscan records involved in a multidimensional scan. The next section describes the handshake mechanisms implemented by the sscan record. - - -1.3. Interaction with clients ------------------------------ +## 1.3. Interaction with clients Clients of the sscan record include the software that starts, stops, or pauses a scan; software that displays data acquired by a scan; software that writes scan data to disk; and software that participates in the a scan by implementing positioner or detector operation. A single client may do any or all of these things, of course, but it seems best to discuss them separately. - - ### 1.3.1 Starting a scan The client writes the number 1 to the sscan record's EXSC field to start a scan. If the sscan record is able to start a new scan, it sets its BUSY field to 1 while the new scan is in progress, and it sets BUSY to 0 when the scan is done. If the sscan record is not able to start a new scan, the client will receive an error indication, and the command may be ignored. The sscan record will set its SMSG field to a string describing the reason why it cannot start a new scan. Possible reasons include the following: - "Waiting for PV's to connect"One or more of the PVs specified as positioners, triggers, readbacks, etc. has not yet connected. If the offending PV must be written to, it's possible that the PV has connected but the ioc doesn't have permission to write to it, because of an EPICS access-security setting or condition. "Scan is paused ..."The sscan-record field PAUS has a nonzero value, indicating that some client has told the sscan record to stand by until PAUS is set back to 0. In this case, the sscan record will set FAZE to "SCAN\_PENDING", and wait until PAUS is rescinded before setting BUSY to 1 and starting the scan. "waiting for client ..."The sscan-record field WTNG has a nonzero value, indicating that some client has told the sscan record to wait. (See "Handshaking with data-storage clients", below, for details.) In this case the command will be ignored. "Already scanning"A scan is already in progress. Setting EXSC to 1 while scan is in progress has no effect on that scan. In this case, the command will be ignored. "Waiting for saveData"A new scan cannot be started because the data-storage client, "saveData", is still using one of the two sets of data arrays, and the other set is full of scan data also waiting for service by saveData. In this case, the sscan record will wait until saveData has finished using the arrays (which saveData indicates by writing 0 to the sscan record's AWAIT field) before setting BUSY to 1 and starting the scan. "Waiting for callback"The previous scan is essentially complete, but one of the commands the sscan record issued has not yet completed. In this case, the command to start a new scan will be ignored. In all but the "Waiting for PV's to connect" and "Scan is paused" cases, the start command will be ignored, and the scan will not automatically start when the condition that prevented it from starting is removed. A new start command must be issued. + * "Waiting for PV's to connect" - One or more of the PVs specified as positioners, triggers, readbacks, etc. has not yet connected. If the offending PV must be written to, it's possible that the PV has connected but the ioc doesn't have permission to write to it, because of an EPICS access-security setting or condition. + * "Scan is paused ..." - The sscan-record field PAUS has a nonzero value, indicating that some client has told the sscan record to stand by until PAUS is set back to 0. In this case, the sscan record will set FAZE to "SCAN\_PENDING", and wait until PAUS is rescinded before setting BUSY to 1 and starting the scan. + * "waiting for client ..." - The sscan-record field WTNG has a nonzero value, indicating that some client has told the sscan record to wait. (See "Handshaking with data-storage clients", below, for details.) In this case the command will be ignored. + * "Already scanning" - A scan is already in progress. Setting EXSC to 1 while scan is in progress has no effect on that scan. In this case, the command will be ignored. + * "Waiting for saveData" - A new scan cannot be started because the data-storage client, "saveData", is still using one of the two sets of data arrays, and the other set is full of scan data also waiting for service by saveData. In this case, the sscan record will wait until saveData has finished using the arrays (which saveData indicates by writing 0 to the sscan record's AWAIT field) before setting BUSY to 1 and starting the scan. + * "Waiting for callback" - The previous scan is essentially complete, but one of the commands the sscan record issued has not yet completed. In this case, the command to start a new scan will be ignored. - +In all but the "Waiting for PV's to connect" and "Scan is paused" cases, the start command will be ignored, and the scan will not automatically start when the condition that prevented it from starting is removed. A new start command must be issued. ### 1.3.2 Stopping a scan @@ -120,23 +122,19 @@ If the sscan record is waiting for both outstanding callbacks and the data-stora When a scan is aborted, and more than one write to EXSC was required, the *next* scan may inherit the problem. If the problem was an outstanding callback, and that callback *still* has not come in by the next time the sscan record is told to start, the scan will not be permitted to write to the PV whose callback is still outstanding. This may indicate that a PV is imperfectly implemented, and cannot be scanned; or that some error prevented the operation from completing; or that the sscan record missed the completion message; or simply that the operation is taking a long time to finish. If the operation cannot be manually stopped, the only recourse is to erase the PV name and rewrite it. This closes and reopens the channel-access connection to that PV, and frequently will resolve the immediate problem. - - ### 1.3.3 Pausing a scan A scan can be paused by writing "PAUSE", or the number 1, to the PAUS field. While a sscan record is paused, it will do nothing to further the progress of the scan, but it will remain receptive to outstanding callbacks. A paused scan is continued by writing "GO", or the number 0, to the PAUS field. A multidimensional scan can be paused by writing "PAUSE" to the PAUS fields of all involved sscan records. (The database standardScans.db does this by implementing a single "PAUSE" pv, and writing its value to all of the sscan records in the database.) - - ### 1.3.4 Displaying scan data -Scan data is published by the sscan record using EPICS Channel Access, just as any other EPICS record would publish the values of its fields. The act of publishing data via Channel Access is referred to here as *posting*, because the EPICS function that performs this function is db\_post\_events(). After a field has been posted, a client can get the new value by issuing the Channel Access call ca\_get(). A client can also arrange, in advance, to receive posted data from a particular field, whenever it is posted, by *monitoring* -- also called *subscribing to*-- the field. See the Channel Access Reference Manual (specifically, ca\_add\_event() or ca\_create\_subscription()) for the details of how this is done. The purpose here is simply to introduce the terms *post*and *monitor*, so that I can use them in this documentation. The sscan record maintains two sets of array PV's for scan data: data from a completed scan are posted as P*n*RA and D*nn*DA (e.g., P1RA, D01DA); data from a scan in progress are posted as P*n*CA and D*nn*CA. During a scan, arrays are posted only if the user requests this by setting the array-posting period, ATIME, to a value greater than or equal to 0.1 (seconds). After a scan has completed, all data arrays are posted, marked with the mask DBE\_LOG, and the completed-scan postings (P*n*RA and D*nn*DA) remain available to clients until the next scan completes. +Scan data is published by the sscan record using EPICS Channel Access, just as any other EPICS record would publish the values of its fields. The act of publishing data via Channel Access is referred to here as *posting*, because the EPICS function that performs this function is db\_post\_events(). After a field has been posted, a client can get the new value by issuing the Channel Access call ca\_get(). A client can also arrange, in advance, to receive posted data from a particular field, whenever it is posted, by *monitoring* -- also called *subscribing to*-- the field. See the Channel Access Reference Manual (specifically, ca\_add\_event() or ca\_create\_subscription()) for the details of how this is done. The purpose here is simply to introduce the terms *post* and *monitor*, so that I can use them in this documentation. The sscan record maintains two sets of array PV's for scan data: data from a completed scan are posted as P*n*RA and D*nn*DA (e.g., P1RA, D01DA); data from a scan in progress are posted as P*n*CA and D*nn*CA. During a scan, arrays are posted only if the user requests this by setting the array-posting period, ATIME, to a value greater than or equal to 0.1 (seconds). After a scan has completed, all data arrays are posted, marked with the mask DBE\_LOG, and the completed-scan postings (P*n*RA and D*nn*DA) remain available to clients until the next scan completes. > Because the sscan record implements double-buffered data arrays, and because of the way in which posting is accomplished in EPICS, the posting of scan-in-progress data arrays results unavoidably in useless reposting of completed-scan data arrays. If this presents a problem for a data-display or data-storage client, there are two ways to avoid the problem: 1) Tell the sscan record not to post arrays during scans by leaving the array-post period, ATIME, at its default value of zero; 2) modify the client so that it monitors only postings flagged with the DBE\_LOG mask. A more efficient, but more difficult, way for a client to get data from a scan in progress is to monitor the scalar current-value PVs, such as R1CV, D01CV, etc., and collect their values into arrays. Positioners actually have two fields that might be suitable for display while a scan is in progress: the positioner's desired value (P*n*DV) and the readback's current value (R*n*CV). (If there is no readback PV, the posted readback value will be a copy of the desired value.) -Not all data points of a scan are guaranteed to be posted as scalar values, because the sscan record *throttles* it's posting, so that it doesn't exceed 20 data points per second. ("data point" means "all positioners and detectors".) This throttling is intended to limit the network activity caused by a scan, and it's necessary because displaying scan data is not more important that acquiring it, and because the sscan record also uses the network to acquire data. +Not all data points of a scan are guaranteed to be posted as scalar values, because the sscan record *throttles* its posting, so that it doesn't exceed 20 data points per second. ("data point" means "all positioners and detectors".) This throttling is intended to limit the network activity caused by a scan, and it's necessary because displaying scan data is not more important that acquiring it, and because the sscan record also uses the network to acquire data. The task of accumulating posted scalar values into data arrays is complicated by the standard EPICS behavior of declining to post a field whose value has not changed since the last time the field was posted. If a client were simply to append each new posting to the data arrays it is accumulating, it would not be including those repeated values. The following algorithm will accumulate data correctly: @@ -149,8 +147,6 @@ The task of accumulating posted scalar values into data arrays is complicated by 7. When VAL is received, append to all data arrays from cached scalar values, and increment numPoints. 8. When DATA==1 is received, clear all data arrays and replace with cached array values; set numPoints to the cached value received from CPT. - - ### 1.3.5 Handshaking with data-storage clients 1\) __The new way, using the AWAIT and AAWAIT fields:__ @@ -161,11 +157,11 @@ For *very* fast scans, or a very slow data-storage client, there might not be su NOTE: Because saveData sets AAWAIT for the sscan records it monitors, a scan cannot execute to completion until saveData has written the previous scan's data to disk (or has tried and failed a preset number of times to do this). The __sscan__ module currently does not provide a mechanism by which the end user can turn data storage off and on. Data storage is turned on at boot time, for each sscan record, by telling saveData to monitor that sscan record. The only way to turn data storage off is to edit the startup file and reboot. - Only one data-storage client can use AWAIT. If you have more than one data-storage client, you must arrange for them to pool their use of the AWAITfield, so that it gets reset to zero only when all have finished. (It's OK if AWAIT gets set to one more than once. Only the first AWAIT==1 write has any effect.) +Only one data-storage client can use AWAIT. If you have more than one data-storage client, you must arrange for them to pool their use of the AWAIT field, so that it gets reset to zero only when all have finished. (It's OK if AWAIT gets set to one more than once. Only the first AWAIT==1 write has any effect.) Note that this AWAIT handshake protects scan data no matter how the sscan record gets executed, unlike the old method described next. -2\) __The old way, using the WAIT, WCNT, and AWCTfields:__ +2\) __The old way, using the WAIT, WCNT, and AWCT fields:__ Before the AWAIT field was introduced, the only means of handshaking was an extension of the mechanism by which the sscan record waits for detector triggers to signal completion. In this extension, the sscan record waits until all detector triggers have signalled completion, *and* the field WAIT is equal to zero. This extension's intended purpose is to support detectors that can't signal completion with a callback, but that can write to a PV -- for example, a detector that's implemented as a channel-access client -- and it can still be used for that purpose, while a data-storage client is using it to protect data acquired from an inner-loop scan. @@ -181,11 +177,11 @@ The advantage of the autoWaitCount==0 method is that scans can be performed whet Note that this form of handshaking doesn't do a very thorough job of data protection, because it does not directly prevent a sscan record from overwriting its own arrays; it only prevents an outer-loop sscan record from *telling* an inner-loop record to start a new scan line. If the sscan record is executed by some other agent, the WAIT handshake doesn't protect data at all. - +For details on configuring and using saveData itself, see the [saveData documentation](saveData.md). ### 1.3.6 Handshaking with CA clients that implement positioners or detectors -A channel-access client can participate in scans driven by the sscan record if two criteria are met: +A channel-access client can participate in scans driven by the sscan record if two criteria are met: 1. The client is driven by a PV to which one of the sscan record's positioner or detector-trigger links writes. 2. The client can signal completion in a way that the sscan record understands. @@ -221,14 +217,11 @@ Many of the databases in synApps contain *busy* records for this purpose, partic - The client performs its data-acquisition task. - The client writes 0 to <scanrecord>.WAIT, indicating that it's done. -Several clients can use the WAIT field, each write of 1increments a wait count WCNT; each write of 0 decrements WCNT; the sscan record stops waiting when WCNT is decremented to zero. The sscan record doesn't care, by the way, who writes what to WAIT; it simply waits until the number of WAIT==0writes equals the number of WAIT==1 writes. +Several clients can use the WAIT field, each write of 1 increments a wait count WCNT; each write of 0 decrements WCNT; the sscan record stops waiting when WCNT is decremented to zero. The sscan record doesn't care, by the way, who writes what to WAIT; it simply waits until the number of WAIT==0 writes equals the number of WAIT==1 writes. But what if clients are a little slow to react, and the sscan record checks its wait-count WCNT before the clients have had time to write 1's to it? If this is a problem, you can cause the sscan record to set WCNT at the appropriate time, by setting <scanrecord>.AWCT to the number of slow clients. (But now those slow clients must NOT write 1 to WAIT.) - - - 1.4. Completion of positioner and detector-trigger operations --------------------------------------------------------------- +## 1.4. Completion of positioner and detector-trigger operations As was mentioned previously, all of the process variable names used to identify positioners, detectors, and detector triggers are specified using *reassignable links*. These links are implemented differently than standard EPICS links. Reassignable links are channel-access links implemented with the recDynLink library (originally written by Marty Kraimer and Ned Arnold; modified to use callbacks and currently maintained by Tim Mooney). These links perform writes with the channel-access function, ca\_put\_callback(), and the sscan record expects the resulting callback function to be called only after all processing caused by the write operation has completed. (I'll call this expectation the *completion-callback criterion*, in this documentation, and I'll describe the conditions under which it is met.) @@ -236,14 +229,11 @@ For simple positioners and detectors, this is never a problem. Individual record If a positioner or detector is implemented with a collection of linked records all of which individually satisfy the completion-callback criterion, the whole series of records will also satisfy the criterion if all links in the processing chain started by the sscan record's write have the attribute PP, and all of the records that process are scan-passive (i.e., their SCAN fields are set to "Passive"). Databases that do not satisfy this criterion can still satisfy the completion-callback criterion very simply: at least one record in the database must refrain from executing its Forward Link until the operation is finished, and that record must be either be the record written to, or it must be driven by that record via an unbroken series of PP links. -If a positioner or detector is implemented with the help of a CA client, such as an SNL program, see the subsection on "putNotify-based completion signalling" in section [__1.3.6__ Handshaking with CA clients that implement positioners or detectors](#HEADING_1-3-6). +If a positioner or detector is implemented with the help of a CA client, such as an SNL program, see the subsection on "putNotify-based completion signalling" in section [__1.3.6__ Handshaking with CA clients that implement positioners or detectors](#136-handshaking-with-ca-clients-that-implement-positioners-or-detectors). Database developers should note that a PP link from a record in one IOC to a record in another IOC will silently be converted to a CA link, which will not satisfy the completion-callback criterion. In this case, there are two options: the *busy*-record solution, detailed above, and the use of a buffer record that can do a ca\_put\_callback() to make the link between IOCs. I know of six record types that can do a ca\_put\_callback(): the sscan, swait, and sseq records; an ai record with soft asynchronous device support; and the sCalcout and aCalcout records. (The sscan, swait, sseq, sCalcout, and aCalcout records are distributed with synApps. swait, sCalcout, and aCalcout are variants of the calcout record in EPICS base; sseq is a variant of the seq record in EPICS base.) - - - 1.5. Fly scans ---------------- +## 1.5. Fly scans The term "fly scan" is generic for several types of scans that behave and are configured differently, though all involve one or more positioners moving while data are being acquired. Such positioners are said to be in "fly mode". Currently, the sscan record treats fly-mode positioners in essentially the same way for all types of fly scans: they are sent to their start positions (along with any non-fly-mode positioners), and the sscan record waits for all to complete; then, fly-mode positioners are launched toward their end points when detectors are triggered for the first (or only) time, and the sscan record does not wait for them to complete. @@ -251,9 +241,7 @@ Clearly, the speed at which a fly-mode positioner moves is an important consider From the viewpoint of the sscan record, there are two types of fly scans: *scalar-mode* fly scans, in which the sscan record directs the scan point by point; and *array-mode* fly scans, in which the scan record hands off the point-by-point direction to some other entity, such as a multichannel scaler. These types are distinguished by the value of ACQT (ACQuisition Type). - - -### 1.5.1 Scalar-mode fly scans +### 1.5.1 Scalar-mode fly scans If ACQT==SCALAR ("scalar mode", the default), the sscan record will direct the acquisition of NPTS data points individually, as we've been assuming thus far. That is, it will execute NPTS iterations of (move, trigger, read). A scalar-mode scan is a fly scan if one or more positioners have their step mode PVs (P*n*SM) set to FLY. The only difference between this type of scan and the scans we've considered up to now is the motion of those fly-mode positioners. @@ -275,9 +263,7 @@ In scalar mode, the sscan record executes fly scans as follows: Scalar-mode fly scans are relatively easy to configure, because the only external conditions that must be set are the positioner speed and the detector dwell time. However, the association between positioner and detector values is typically not as precise or as repeatable as in a step scan. This is because fly-mode positioners are read while they are moving, and because the timing of those reads is not tightly synchronized with the positioner's motion. In the simplest case (no non-fly-mode positioners) the time between positioner reads is the detector dwell time plus the sscan record's per-point overhead time, which varies because the IOC processor is doing other things in addition to scanning. - - -### 1.5.2 Array-mode fly scans +### 1.5.2 Array-mode fly scans If ACQT==1D ARRAY ("array mode"), the sscan record will direct the acquisition of only a single "data point", and that data point will be a set of one-dimensional arrays of length NPTS. In an array-mode scan, all positioners are treated as being in fly mode, whatever the values of their step-mode PVs, because the sscan record writes to them only twice. After moving positioners to their start positions, the sscan record will execute only one (move, trigger, read) sequence. @@ -294,30 +280,28 @@ In array mode, the sscan record executes fly scans as follows: 7. If PASM!=STAY, send all positioners to specified positions, and wait for completion. (It is assumed that these commands will redirect any fly-mode positioners that are still moving toward their end points.) 8. Execute the after-scan link. Wait for completion if ASWAIT==Wait. -Because the sscan record doesn't do any point-by-point writes to (or reads from) positioners during an array-mode scan, the data it acquires will represent an average over position, unless some external agent enforces a coordination between positioner values and the acquisition of detector-array elements. The advantage of array-mode fly scans over scalar-mode fly scans is that this coordination can be done externally by hardware that's capable of doing it well; the disavantage is that hardware positioner/detector coordination must be arranged. +Because the sscan record doesn't do any point-by-point writes to (or reads from) positioners during an array-mode scan, the data it acquires will represent an average over position, unless some external agent enforces a coordination between positioner values and the acquisition of detector-array elements. The advantage of array-mode fly scans over scalar-mode fly scans is that this coordination can be done externally by hardware that's capable of doing it well; the disadvantage is that hardware positioner/detector coordination must be arranged. In one common implementation of an array-mode scan, detector data are acquired by a multichannel scaler, which is advanced from channel to channel either by a periodic signal, or by pulses from a motor. In contrast to the scalar-mode fly scans discussed above, this type of fly scan can have a very precise and reproducible association between positioner and detector values. > In releases of the sscan module lower than 2.7, fly mode was implemented slightly differently, as follows (differences are in *italics*) If the sscan record was in scalar mode (ACQT==SCALAR), and a positioner's step mode had the value FLY, the sscan record sent it to the start position at the beginning of a scan, waited for it to get there, *acquired one data point (trigger, read),* sent the positioner to the end position, and began acquiring the remaining data points while the positioner was travelling to the end position. *If the record was in array mode (ACQT==1D ARRAY), positioners that were not explicitly in fly mode (PnSM!=FLY) were not sent to the end position at all.* - - - -- - - - - - - - - -sscan-Record Fields -=================== -- - - - - - +# 2. sscan-Record Fields Many options are available to control the execution of a scan. All parameters for a particular sscan record must be configured prior to initiating the scan, as the sscan record will not allow most fields to be written to while a scan is in progress. However, in a multidimensional scan, outer scans can modify the parameters of inner scans, because at the time an outer sscan record is writing to positioners, all inner sscan records are idle. You should use caution in programming such self modifying scans, because clients displaying or analyzing multidimensional scan data may have trouble dealing with parameters changing during a scan. In this documentation, many of the sscan-record fields will be listed in tables containing the following informational headings: -__Field__The name of the sscan-record field__Summary__Basic purpose of the field__Type__Data type of the field. If the field is a menu, the menu choices (text strings) are listed in quotes. (Don't include the quotes when you write to the field.) Note that if you write a numeric value to a menu field, the number will be interpreted as an index into the list of menu choices. The first item in the list has the index 0.__DCT__Can this field be modified by database-configuration tools?__Initial/Default__Value if the field is not specified in the .db file. If the field is a menu, the text string will be shown, followed by the corresponding index. __Read__Can user read this field?__Modify__Is user ever allowed to write to this field? (Note that the sscan record will reject writes to certain otherwise writable fields while a scan is underway.)__Posted__If the record should modify the field, will the new value be posted?__PP__Does a channel-access write to this field cause the record to process? +* __Field__ - The name of the sscan-record field +* __Summary__ - Basic purpose of the field +* __Type__ - Data type of the field. If the field is a menu, the menu choices (text strings) are listed in quotes. (Don't include the quotes when you write to the field.) Note that if you write a numeric value to a menu field, the number will be interpreted as an index into the list of menu choices. The first item in the list has the index 0. +* __DCT__ - Can this field be modified by database-configuration tools? +* __Initial/Default__ - Value if the field is not specified in the .db file. If the field is a menu, the text string will be shown, followed by the corresponding index. +* __Read__ - Can user read this field? +* __Modify__ - Is user ever allowed to write to this field? (Note that the sscan record will reject writes to certain otherwise writable fields while a scan is underway.) +* __Posted__ - If the record should modify the field, will the new value be posted? +* __PP__ - Does a channel-access write to this field cause the record to process? -2.1. Control Fields -------------------- +## 2.1. Control Fields | Field | Summary | Type | DCT | Initial/Default | Read | Modify | Posted | PP | |---|---|---|---|---|---|---|---|---| @@ -325,7 +309,16 @@ __Field__The name of the sscan-record field__Summary__Basic purpose of the field | MPTS | Maximum Number of Points | LONG | Yes | 100 | Yes | No | No | No | | PASM | Positioner After-Scan Mode | Menu ("STAY", "START POS", "PRIOR POS", "PEAK POS", "VALLEY POS", "+EDGE POS", "-EDGE POS", CNTR OF MASS) | Yes | "STAY" (0) | Yes | Yes | No | No | -> PASM allows the user to control where positioners are left after a scan is finished. Here are the possibilities: "STAY"Do nothing. Leave positioners where they were when the last data point was acquired. "START POS"Go the the position of the first data point acquired. "PRIOR POS"Go to the position they occupied prior to the scan. "PEAK POS"Attempt to find the highest point in the data from the detector specified by the REFD field. If a highest point is found, go to its position, else "STAY". "VALLEY POS"Attempt to find the lowest point in the data from the detector specified by the REFD field. If a lowest point is found, go to its position, else "STAY". "+EDGE POS"Take the derivative of the REFD data, then do "PEAK POS". "-EDGE POS"Take the derivative of the REFD data, then do "VALLEY POS". "CNTR OF MASS"Like "PEAK POS", but sends positioner(s) the position of the center of mass of the data, as calculated with reference to positioner 1. Note that the calculated center of mass depends on the distribution of positioner data. If multiple positioners are involved in a scan, they will not, in general, have the same center of mass. +> PASM allows the user to control where positioners are left after a scan is finished. Here are the possibilities: +> +> * **"STAY"** - Do nothing. Leave positioners where they were when the last data point was acquired. +> * **"START POS"** - Go to the position of the first data point acquired. +> * **"PRIOR POS"** - Go to the position they occupied prior to the scan. +> * **"PEAK POS"** - Attempt to find the highest point in the data from the detector specified by the REFD field. If a highest point is found, go to its position, else "STAY". +> * **"VALLEY POS"** - Attempt to find the lowest point in the data from the detector specified by the REFD field. If a lowest point is found, go to its position, else "STAY". +> * **"+EDGE POS"** - Take the derivative of the REFD data, then do "PEAK POS". +> * **"-EDGE POS"** - Take the derivative of the REFD data, then do "VALLEY POS". +> * **"CNTR OF MASS"** - Like "PEAK POS", but sends positioner(s) the position of the center of mass of the data, as calculated with reference to positioner 1. Note that the calculated center of mass depends on the distribution of positioner data. If multiple positioners are involved in a scan, they will not, in general, have the same center of mass. | Field | Summary | Type | DCT | Initial/Default | Read | Modify | Posted | PP | |---|---|---|---|---|---|---|---|---| @@ -361,10 +354,7 @@ __Field__The name of the sscan-record field__Summary__Basic purpose of the field > These fields control the posting of array data during a scan. ATIME is the minimal time period in seconds between array postings during a scan. If ATIME is greater than 0.1 (seconds), and if more than this time has elapsed since the last array posting of this scan's data, then the current-data arrays will be posted after the next data point has been acquired. __NOTE__: Posting current-data arrays also causes completed-scan data arrays to be posted (uselessly, because they were posted at the end of the previous scan, and the data they contain has not changed). Some display or storage clients may have a problem with this new behavior of the sscan record. If so, there are two alternatives: 1) leave ATIME at its default value of 0.0, or 2) have the client specify DBE\_LOG when it subscribes to the data array (using ca\_add\_event() or ca\_create\_subscription()). (If a client does not monitor data arrays, but instead uses ca\_get() to read them, then it won't care how often they are posted.) > > Some data-display clients (notably, MEDM) cannot use a PV to tell them how many valid data points are being sent. This results in bizarre looking plots that can be made to look correct by repeating the last valid array values to fill the unused array elements. This can be a time-consuming process, so by default it's only done once, at the end of a scan. But arrays posted during a scan also will not be plotted correctly by such clients, so you can specify that the last valid array elements be copied for arrays posted during a scan, by setting COPYTO to the number of array elements in the client's data buffer. If COPYTO == 0, no copying will be done; if COPYTO == -1, the last value will be copied to all unused array elements in the sscan record's data buffers. If COPYTO is set to a value larger than MPTS, the value used will be MPTS. - - -2.2. Positioner Fields ----------------------- +## 2.2. Positioner Fields Each sscan record can control up to four *positioners*, by which it sets conditions under which data will be acquired. A positioner is any numeric PV to which the sscan record can write, and you specify that a positioner is to be scanned by typing its PV name into one of the sscan record's fields PnPV. If the value written to the PV (the *desired* value) might not accurately indicate the true value of the underlying hardware positioner, you can specify a readback PV to retrieve a more accurate value. I'll sometimes call the PV that the sscan record writes to the "drive" PV. If no readback PV is specified, the drive PV will also be used as the readback PV. There are three possible modes for determining desired values for a positioner: LINEAR, TABLE, and FLY. Each positioner has its own mode PV, and you specify which mode you want for a positioner by setting its PnSM field (e.g., P1SM for positioner 1). If PnSM== LINEAR, the desired values are determined from parameters such as start position, step increment, number of points, and end position. If PnSM==TABLE, the desired values are found in an array (PnPA), which must have been loaded into the sscan record prior to initiating a scan. If PnSM==FLY, the desired values are the start and end positions for LINEAR mode. @@ -380,14 +370,12 @@ For *n* in \[1..4\]: | Field | Summary | Type | DCT | Initial/Default | Read | Modify | Posted | PP | |---|---|---|---|---|---|---|---|---| -| P*n*PV | Positioner *n* Process Variable ame | STRING \[40\] | Yes | Null | Yes | Yes | No | No | +| P*n*PV | Positioner *n* Process Variable Name | STRING \[40\] | Yes | Null | Yes | Yes | No | No | | P*n*NV | P*n*PV Name Valid | MENU("PV OK","No PV", "PV NoRead", "PV illegal1", "PV NoWrite", "PV illegal2", "PV BAD") "PV OK" and "No PV" are good states; all others will prevent a scan from starting | No | 0 | Yes | Yes | Yes | No | | P*n*SM | Positioner *n* step-mode | Menu ("LINEAR", "TABLE", "FLY") | Yes | "LINEAR" (0) | Yes | Yes | No | No | | P*n*AR | Positioner *n* Absolute/Relative Mode | Menu ("ABSOLUTE", "RELATIVE") | Yes | "ABSOLUTE" (0) | Yes | Yes | No | No | | P*n*DV | Pos. *n* Desired Value | DOUBLE | No | 0 | Yes | No | Yes | No | | P*n*LV | Pos. *n* Last Value | DOUBLE | No | 0 | Yes | No | No | No | -| P*n*DV | Pos. *n* Desired Value | DOUBLE | No | 0 | Yes | No | Yes | No | -| P*n*LV | Pos. *n* Last Value | DOUBLE | No | 0 | Yes | No | No | No | | P*n*EU | Positioner *n* Eng. Units | STRING \[16\] | Yes | 16 | Yes | Yes | No | No | | P*n*HR | Pos. *n* High Range | DOUBLE | Yes | 0 | Yes | Yes | No | No | | P*n*LR | Pos. *n* Low Range | DOUBLE | Yes | 0 | Yes | Yes | No | No | @@ -402,7 +390,7 @@ For *n* in \[1..4\]: | Field | Summary | Type | DCT | Initial/Default | Read | Modify | Posted | PP | |---|---|---|---|---|---|---|---|---| | R*n*PV | Readback *n* Process Variable | STRING \[40\] | Yes | Null | Yes | Yes | No | No | -| R*n*NV | Readback */n* Name Valid | MENU("PV OK","No PV", "PV NoRead", "PV illegal1", "PV NoWrite", "PV illegal2", "PV BAD") "PV OK" and "No PV" are good states; all others will prevent a scan from starting, though "PV NoWrite" is impossible for this readback PV | No | 0 | Yes | Yes | Yes | No | +| R*n*NV | Readback *n* Name Valid | MENU("PV OK","No PV", "PV NoRead", "PV illegal1", "PV NoWrite", "PV illegal2", "PV BAD") "PV OK" and "No PV" are good states; all others will prevent a scan from starting, though "PV NoWrite" is impossible for this readback PV | No | 0 | Yes | Yes | Yes | No | | R*n*DL | Readback *n* Difference Limit | DOUBLE | Yes | 0 | Yes | Yes | No | No | | R*n*CV | Readback *n* Current Value | DOUBLE | No | 0 | Yes | No | Yes | No | | R*n*LV | Readback *n* Last Value | DOUBLE | No | 0 | Yes | No | No | No | @@ -411,13 +399,11 @@ For *n* in \[1..4\]: Clients that display scan data will most likely be interested in only one of the two positioner-array fields: P*n*CA or P*n*RA. These array fields are backed by the same double-buffered arrays, so they cannot be posted separately. -P*n*CA will yield data from the scan that currently is executing. This array can be read at any time during the scan, and it may be posted with the mask, DBE\_VALUE, while the scan is in progress. (ATIMEcontrols this.) When the scan completes, P*n*CA will be posted with the mask, DBE\_VALUE\|DBE\_LOG. +P*n*CA will yield data from the scan that currently is executing. This array can be read at any time during the scan, and it may be posted with the mask, DBE\_VALUE, while the scan is in progress. (ATIME controls this.) When the scan completes, P*n*CA will be posted with the mask, DBE\_VALUE\|DBE\_LOG. P*n*RA will yield data from the most recently completed scan. This array's data will remain available while the next scan's data are being acquired, and will become unavailable when that scan completes. Clients interested only in completed-scan data should use this field. Clients that monitor this field should always specify the mask, DBE\_LOG, in their ca\_add\_event() or ca\_create\_subscription() call. If this field is monitored with the mask, DBE\_VALUE, the client may receive multiple postings of the same data. - - -### 2.2.1 LINEAR Mode +### 2.2.1 LINEAR Mode If a positioner's step-mode field (e.g., P1SM) has the value LINEAR, a scan can be fully defined by three parameters, e.g., the start position (P1SP), the step increment (P1SI), and the number of data points (NPTS). A scan involving *N* positioners is defined by merely 2*N*+1 parameters, since NPTS applies to all positioners. For the convenience of interactive users, and to support channel access clients that define scans differently, the first positioner can be specified by as many as six parameters: starting position (P1SP), ending position (P1EP), center position (P1CP), scan width (P1WD), step increments (P1SP), and NPTS. For the other three positioners, the same parameters are available minus the NPTS field, since that applies to all. The parameters that pertain to the same positioner are a set. The record imposes an upper limit (MPTS) on NPTS. Both MPTS and NPTS are configured by the user. The positioner width, configurable in the P1WD -P4WD fields, may be negative. For *n* in \[1..4\]: @@ -449,8 +435,6 @@ For *n* in \[1..4\]: Although this approach may seem to present the user with an overwhelming number of choices when it comes to linear scans, it should be noted that by default the user only has to configure NPTS, and the starting position (P*n*SP) and the step increment (P*n*SI) fields for each positioner in order to fully define the scan of a positioner. The operator interface (usually MEDM or another CA client) need only present the user with these fields. However, by changing the freeze flags from the defaults and presenting the user with different fields to fill in, the scan can be defined in a completely flexible way. The result is that a simple scan can be defined easily, but advanced users are not limited in flexibility. - - ### 2.2.2 TABLE Mode If a positioner's step-mode field (e.g., P1SM) has the value TABLE mode, the user specifies all positions to be visited during the scan by writing them into an array (e.g., P1PA) prior to the start of a scan. These arrays are used only in TABLE mode. For *n* in \[1..4\]: @@ -465,7 +449,7 @@ To load an array of positions into P*n*PA, you can use any channel-access client caput -m 2idb1:scan1.P1PA 0.,1.,2. ``` -works. For another version, you specify the flag `-a`, specify the number of values to be written, and specify the values separated by spaces: +works. For another version, you specify the flag `-a`, specify the number of values to be written, and specify the values separated by spaces: ``` caput -a 2idb1:scan1.P1PA 3 0. 1. 2. ``` @@ -481,8 +465,6 @@ array([ 1., 2., 3., 4., 5., 6., 7., 0., 0., 0., 0., 0., 0., ... ``` - - ### 2.2.3 FLY Mode If a positioner's step-mode field (e.g., P1SM) has the value FLY, the positioner is said to be in "fly mode", and it will make only two motions during the scan. The first motion is the same as that of a linear- or table-mode positioner: the sscan record sends the positioner to its start point, and waits for it to get there. The next motion is different: when the sscan record triggers detectors for the first time, it also launches fly-mode positioners to their end points. The sscan record doesn't wait for fly-mode positioners to reach their end points, and fly-mode positioners are not commanded again, unless the Positioner After-Scan Mode (PASM) requires this (for example, by specifying that all positioners return to their pre-scan positions). Note that the PV ACQT (ACQuisition Type) also affects positioner motion: when ACQT==1D ARRAY, all positioners are effectively in fly mode, because the sscan record will acquire only one (array valued) data point. In this case, the PVs P*n*SM serve only to determine the starting and ending points of the positioners' motions, as detailed in the following table for positioner 1: @@ -500,12 +482,9 @@ While fly-mode positioners are moving toward their end points, the sscan record If a fly-mode positioner has a specified readback PV (R*n*PV), its value will be read during the scan, but in many cases the value will be only approximately correct, because the positioner is in motion during the read, and because the sscan record doesn't cause the readback PV to process. If the positioner is a motor, for example, and the readback PV is posted periodically, the sscan record will read values that are imperfectly synchronized with the scan. - - -2.3. Detector-Trigger Fields ----------------------------- +## 2.3. Detector-Trigger Fields -If valid process variable names are entered into the detector trigger fields (T1PV-T4PV ) fields, the sscan record will write the specified command data (the floating point numbers T1CD-T4CD) to those process variables between the positioning phase and the data acquisition phase. If no detector trigger field contains a valid PV, the sscan record will skip this step and acquire the data immediately.For *n* in \[1..4\]: +If valid process variable names are entered into the detector trigger fields (T1PV-T4PV ) fields, the sscan record will write the specified command data (the floating point numbers T1CD-T4CD) to those process variables between the positioning phase and the data acquisition phase. If no detector trigger field contains a valid PV, the sscan record will skip this step and acquire the data immediately. For *n* in \[1..4\]: | Field | Summary | Type | DCT | Initial/Default | Read | Modify | Posted | PP | |---|---|---|---|---|---|---|---|---| @@ -513,17 +492,11 @@ If valid process variable names are entered into the detector trigger fields (T1 | T*n*NV | Trigger *n* Name Valid | MENU("PV OK","No PV", "PV NoRead", "PV illegal1", "PV NoWrite", "PV illegal2", "PV BAD") "PV OK" and "No PV" are good states; all others will prevent a scan from starting | No | 0 | Yes | Yes | Yes | No | | T*n*CD | Trigger *n* Command Data | FLOAT | Yes | 1 | Yes | Yes | No | No | - +## 2.4. Delay Fields -2.4. Delay Fields ------------------ +Generally, after the sscan record has written to positioners and waited for all positioners to declare themselves done, it waits an additional settling time, specified in seconds by the PDLY field, before entering the next scan phase. Similarly, after detector triggers have declared themselves done, the sscan record waits for DDLY seconds before reading positioner and detector data. If no positioners are defined, then PDLY is ignored. If no detector triggers are defined, then DDLY is ignored. PDLY does not apply to after-scan positioner motions. -Generally, after the sscan record has written to positioners and waited for all positioners to declare themselves done, it waits an additional settling time, specified in seconds by the PDLY field, before entering the next scan phase. Similarly, after detector triggers have declared themselves done, the sscan record waits for DDLY seconds before reading positioner and detector data. If no positioners are defined, then PDLY is ignored. If no detector triggers are defined, then DDLY is ignored. PDLYdoes not apply to after-scan positioner motions. - - - -2.5. Client Handshaking Fields ------------------------------- +## 2.5. Client Handshaking Fields Immediately before data are to be read from positioners and detectors, the sscan record checks the WCNT field. If this field is nonzero, the sscan record waits until it gets set to zero before reading data and continuing with the scan. The WCNT is not directly writable by clients. Instead, a client wanting to put a hold on the scan writes a 1 to the WAIT field, which increments WCNT by one. When the client is ready for the scan to continue, it writes a 0 to the WAIT field, which decrements the WCNT field. This mechanism allows several clients independently to handshake with the sscan record, and it is intended or two purposes: 1\) A data-storage client can put a hold on a sscan record whose data it is writing by writing to the AWAIT field. This hold doesn't prevent the record from executing, or even from acquiring new data, but it does prevent the record from switching data buffers. @@ -544,15 +517,8 @@ A client may not be able to write quickly enough to AWAIT to ensure that the sca | AWAIT | Waiting for data-storage client | SHORT | No | 0 | Yes | Yes | Yes | Yes | | AAWAIT | AutoWait for data-storage client | MENU ("NO","YES") | Yes | 0 | Yes | Yes | No | No | - -- - - - - - - - -2.6 Detector Fields -------------------- - -- - - - - - +## 2.6 Detector Fields Each sscan record can acquire data from up to 74 process variables (70 detector signals, D01-D70, and four positioner readbacks, R1-R4) at each point in the scan. These data will most commonly be from a detector or from a position readback (which would record the actual motor positions at each point and could then be compared to the desired position array). Although positioner readbacks, R1-R4, are normally used to confirm the position at which data actually were acquired (as opposed to the position to which the sscan record *told* a positioner to go), they can be used to record any data. These four variables are the only place to record double-precision scan data. Note that these readbacks are not full-fledged detectors, because the sscan record currently cannot read into them from a array-valued PV's, as it can for actual detectors. @@ -560,7 +526,7 @@ The scan results will most frequently be read as position arrays (P1RA-P4RA) and A one-dimensional scan is complete when the BUSY field goes back to zero (during the scan its value is 1). A client program monitoring the scan can read the position and data arrays when the DATA field is set to 1. (The client could have a monitor set on the data-array fields so the record will post them when the scan is finished.) -For two-dimensional scans, the client should read the arrays from the scan1 record after the completion of each inner scan and associate these data with the current outer-scan information. (Let's call the inner scan 'x', and the outer scan 'y'.) This will allow the client to display data after each x scan. The sscan record will buffer the data for only one x scan, so the client must read the arrays before the next x scan is completed. If the scan is too fast for this, see [section 1.3.4 - Handshaking with data-storage clients](#HEADING_1-3-4) +For two-dimensional scans, the client should read the arrays from the scan1 record after the completion of each inner scan and associate these data with the current outer-scan information. (Let's call the inner scan 'x', and the outer scan 'y'.) This will allow the client to display data after each x scan. The sscan record will buffer the data for only one x scan, so the client must read the arrays before the next x scan is completed. If the scan is too fast for this, see [section 1.3.5 - Handshaking with data-storage clients](#135-handshaking-with-data-storage-clients) During slow scans, the application program may want to display scan progress point-by-point. The sscan record posts monitors on fields that it updates each point, but it doesn't post point-by-point monitors faster than 20 times per second. If a scan is proceeding at a rate less than 20 points per second, every point will be posted. If a scan is proceeding at 100 steps per second, scalar values will be posted approximately every 5th point. In either case, the array data will contain every point at the completion of the scan. @@ -590,17 +556,8 @@ For *nn* in \[01..70\] (e.g., "D01PV", "D02PV", ... "D70PV") | D*nn*CV | Detector *nn* Current Value | FLOAT | No | 0 | Yes | No | Yes | No | | D*nn*LV | Detector *nn* Last Value | FLOAT | No | 0 | Yes | No | No | No | - - - -- - - - - - - - -2.7 Execution fields --------------------- - -- - - - - - +## 2.7 Execution fields A scan is started when a client writes 1 to the EXSC field. @@ -624,23 +581,14 @@ The command (CMND) field supports eight commands, as follows: | 6 | Clear all positioner-name and readback-name PVs, freeze flags, modes, and switches. | | 7 | Clear positioner-name and readback-name PVs. | - - - -- - - - - - - - -2.8 Status/Progress Fields -========================== - -- - - - - - +## 2.8 Status/Progress Fields These fields are used to process the record, to implement monitors for certain fields, and/or to keep track of data for processing and/or for the operator. None of these fields are configurable by a database configuration tool. Most of them can be accessed at run-time, and many can be modified at run-time. The Current Point (CPT) field contains the current point of an active scan. - The BUSY field indicates whether (1) or not (0) a scan is in progress. +The BUSY field indicates whether (1) or not (0) a scan is in progress. -The DATA field indicates the state of the data arrays. DATAis set to 0 at the beginning of a scan, and is set to 1 after the data arrays have been posted. Note that data arrays are not posted during a scan, but only at the end. +The DATA field indicates the state of the data arrays. DATA is set to 0 at the beginning of a scan, and is set to 1 after the data arrays have been posted. Note that data arrays are not posted during a scan, but only at the end. The VAL field is used only as a progress indicator. It is posted after all point-by-point PVs (e.g., R1CV, D01CV) have been posted. (So, if a PV you're interested in hasn't been posted by the time you get the VAL-field monitor, that PVs value hasn't changed since the last time it was posted.) @@ -691,17 +639,8 @@ The data-state (DSTATE) field indicates in what state is the processing of data | 6 | PACKED | Arrays are filled, and buffers have been switched, but they haven't yet been posted. If a client should read an array now, it would get last scan's data. | | 7 | POSTED | Data arrays have been posted. Now the data-storage client can read this scan's array data. | - - - -- - - - - - - - - -2.10 Miscellaneous Fields -------------------------- -- - - - - - +## 2.10 Miscellaneous Fields | Field | Summary | Type | DCT | Initial/Default | Read | Modify | Posted | PP | |---|---|---|---|---|---|---|---|---| diff --git a/docs/sscanReleaseNotes.md b/docs/sscanReleaseNotes.md index 1efce9c..bbb946c 100644 --- a/docs/sscanReleaseNotes.md +++ b/docs/sscanReleaseNotes.md @@ -1,49 +1,44 @@ --- layout: default title: Release Notes -nav_order: 3 +nav_order: 2 --- +# sscan Release Notes -sscan Release Notes -=================== +## Release 2-12 - May 6, 2026 -Release 2-11-6 - Sep 21, 2023 ------------------------------ +* Fix CPT field not posting reliably during scans +* Switched saveData path PVs to long strings, moving the character limit from 40 to 256 + +## Release 2-11-6 - Sep 21, 2023 * Fixed subdirectory detection issue for saveData on Windows * scan_settings.req changed to pull required PV's for scan.db, previous uses of scan_settings.req should instead use sscanRecord_settings.req - -Release 2-11-5 - Oct 19, 2021 ------------------------------ +## Release 2-11-5 - Oct 19, 2021 * Fixed build issues with newer versions of base -Release 2-11-4 - Oct 5, 2020 ----------------------------- +## Release 2-11-4 - Oct 5, 2020 * Fixed an issue where vxWorks was failing to save to netApps appliances * Added bob files, updated ui and edl files. -Release 2-11-3 - Jun 14, 2019 ------------------------------ +## Release 2-11-3 - Jun 14, 2019 * Req files now installed to top level db folder. -Release 2-11-2 - Feb 28, 2019 ------------------------------ +## Release 2-11-2 - Feb 28, 2019 * Fixes to build with EPICS base 3.16 and 7.0 -Release 2-11-1 - Dec 8, 2017 ----------------------------- +## Release 2-11-1 - Dec 8, 2017 * Check return code from fclose(), and retry if it failed. -Release 2-11 - Nov 30, 2017 ---------------------------- +## Release 2-11 - Nov 30, 2017 * Introduce end-of-line normalization * fix -Wformat @@ -57,71 +52,64 @@ Release 2-11 - Nov 30, 2017 * Enable Travis CI * Converted CRLF to LF -Release 2-10-2 - Nov 6, 2015 ----------------------------- +## Release 2-10-2 - Nov 6, 2015 * documented PnPP field -Release 2-10-1 - Mar 27, 2015 ------------------------------ +## Release 2-10-1 - Mar 27, 2015 * saveData: minor bug fix -* saveData\_writeXDR.c, writeXDR.c: small changes to support minGW compilation +* saveData_writeXDR.c, writeXDR.c: small changes to support minGW compilation * If a detector was connected to a remote array, the sscanRecord did not wait for retrace motion to finish before declaring itself done. -Release 2-10 - Oct 22, 2014 ---------------------------- +## Release 2-10 - Oct 22, 2014 -* Changed DBD\_INSTALLS to DBD for sscanSupport and sscanProgressSupport +* Changed DBD_INSTALLS to DBD for sscanSupport and sscanProgressSupport * Previously, a scan driven by a scanparm record from a different ioc was occasionally failing to get the new positioner's current value before starting the scan. -* sscanRecord: More thorough mutex lock around PV-status checks and updates, including essentially all of lookupPV() and the "psscan->faze == sscanFAZE\_SCAN\_PENDING" sections of posMonCallback() and pvSearchCallback(). Initialize p\_pp when positioner PV changes, so we can detect a value that hasn't been updated from the new PV. More thorough PV-status check in checkConnections (also check dbAddrNv or recDynLinkConnectionStatus()). checkConnections() was failing to check the recDynLinkConnectionStatus of the positioner-output link. -* For positioner-input links, check waitingForPosMon along with connectInProgress. Wait until links disconnect before calling lookupPV. Previously, was setting badOutPutPv before changing positioner-input links, and badInputPv before changing positioner-output links. No longer init p\_cv to -HUGE\_VAL (replaced by waitingForPosMon) +* sscanRecord: More thorough mutex lock around PV-status checks and updates, including essentially all of lookupPV() and the "psscan->faze == sscanFAZE_SCAN_PENDING" sections of posMonCallback() and pvSearchCallback(). Initialize p_pp when positioner PV changes, so we can detect a value that hasn't been updated from the new PV. More thorough PV-status check in checkConnections (also check dbAddrNv or recDynLinkConnectionStatus()). checkConnections() was failing to check the recDynLinkConnectionStatus of the positioner-output link. +* For positioner-input links, check waitingForPosMon along with connectInProgress. Wait until links disconnect before calling lookupPV. Previously, was setting badOutPutPv before changing positioner-input links, and badInputPv before changing positioner-output links. No longer init p_cv to -HUGE_VAL (replaced by waitingForPosMon) * When changing positioner links, and waiting for the first monitor callback after the change (to get current position for, e.g., relative scan about that position), do a recDynLinkGetCallback from posMonCallback() to defend against old monitors from the previous positioner PV. * scanAux.db: Make record name $(P)$(S), add macro for MPTS -* sscanRecord.c: check access-security permissions for links +* sscanRecord.c: check access-security permissions for links scan\*.adl: show write links that are read-only because of access security * recDynLink.c: added recDynLinkCheckReadWriteAccess() to check access-security permissions. -* scan\_full.adl: TnNV were implemented wrong: showed red with no PV name. +* scan_full.adl: TnNV were implemented wrong: showed red with no PV name. * sscanRecord.c had "#include epicsExport.h" in the wrong place, and this broke cygwin builds. -* Modified scanParms\_settings.req so it uses the same macro as scanParms.db (M instead of Q). Added scanAux\_settings.req, alignParms\_settings.req, scanParms2D\_settings.req, scanParmsRemote\_settings.re. +* Modified scanParms_settings.req so it uses the same macro as scanParms.db (M instead of Q). Added scanAux_settings.req, alignParms_settings.req, scanParms2D_settings.req, scanParmsRemote_settings.re. * saveData was failing to read its .req file correctly in some cases on Windows. Fixed by opening the file with fopen(file, "rb"), instead of as fopen(file, "r"). -Release 2-9 - Apr. 17, 2013 ---------------------------- +## Release 2-9 - Apr. 17, 2013 * Added Jon Tischler's scanProgress support. To use, include scanProgressSupport.dbd, link with libscanProgress, load scanProgress.db, start the scanProgress seq program, and view with scanProgress.adl. (The support is packaged separately from the rest of the sscan module, and built only if SNCSEQ is defined (typically, in configure/RELEASE), to avoid requiring existing users of the sscan module to build and run the sequencer.) -* Deleted the PV $(P)saveData\_config from saveData.db and scan\_saveData.adl. The PV was never used. +* Deleted the PV $(P)saveData_config from saveData.db and scan_saveData.adl. The PV was never used. * Included new version of mdautils-src.tar * New versions of op/python/\* (from utils/mdaPythonUtils). * Added CSS-BOY and caQtDM display files. * on vxWorks, use open(), rather than creat(), to check that we can open a new file. -Release 2-8-1 - Sept. 1, 2012 ------------------------------ +## Release 2-8-1 - Sept. 1, 2012 * Fixed minor problems in writeXDR.h and writeXDR.c that prevented it from compiling on Windows with Visual Studio compiler. -* Previously, saveData crashed with versions of vxWorks that have a 10-function xdr\_ops table. Now xdr\_stdio.c checks for 8-, 10-, and 12-function tables. +* Previously, saveData crashed with versions of vxWorks that have a 10-function xdr_ops table. Now xdr_stdio.c checks for 8-, 10-, and 12-function tables. -Release 2-8 - Feb 8, 2012 -------------------------- +## Release 2-8 - Feb 8, 2012 -* Previously, saveData could not be buit on WIN32 because Windows has no XDR library. The file saveData\_writeXDR.c uses a local implementation (writeXDR) of XDR's file-writing specification that doesn't require any help from the OS. This support runs on any OS, but it's likely to be slower than system implementations, and so should probably be used only for WIN32. +* Previously, saveData could not be buit on WIN32 because Windows has no XDR library. The file saveData_writeXDR.c uses a local implementation (writeXDR) of XDR's file-writing specification that doesn't require any help from the OS. This support runs on any OS, but it's likely to be slower than system implementations, and so should probably be used only for WIN32. * Previously, aborting a sscan record that was already idle was treated as an error (special returned -1, which could be confusing to clients, and served no useful purpose). Thanks to Sergey Stepanov for noticing this very long-standing problem. * Previously, saveData did not flush the channel access output buffer after doing cagets for extra PVs. This resulted in some PVs having stale values in the data file - particularly PVs repeated by a PV gateway. Thanks to Wang Xiaoqiang (PSI) for this fix. * Previously, on 64-bit architectures, saveData wrote 2D and higher files with NPTS\*4 extra bytes immediately preceding the name of outer-loop sscan-record names, because it calculated file offsets using sizeof(long) instead of sizeof(epicsInt32). Thanks to Eric Berryman for reporting the problem. -* Previously, writing an array of DBF\_LONG or DBF\_USHORT failed on 64-bit OS. This happened when such an array was specified in the "extraPV" section of saveData.req +* Previously, writing an array of DBF_LONG or DBF_USHORT failed on 64-bit OS. This happened when such an array was specified in the "extraPV" section of saveData.req -Release 2-7 - August 26, 2011 ------------------------------ +## Release 2-7 - August 26, 2011 * Previously, on Linux, Solaris, and probably other non-real-time operating systems, saveData could write corrupted data files for 2D and higher scans, because monitored DATA fields from sscan records could be received out of order. * Previously, scans with any fly-mode positioners and no detector triggers failed to launch fly-mode positioners to the end point. * The detailed order of operations has changed slightly for one type of fly scan. Previously, in soft fly scans (scans with ACQT="SCALAR", and one or more fly-mode positioners) detector triggers were executed and awaited after fly-mode positioners had arrived at the start point, and before they were launched toward the end point. This error caused the first point in such a fly scan to be different from all other data points, in that it was a static measurement, rather than an average over position. Now, fly mode positioners are launched toward their endpoints before detector triggers are executed for the first time. _Note that this change does not affect hardware-assisted fly scans (scans with ACQT="1D ARRAY"), which have always behaved in this way._ -* Increased the maximum size of the PV-name prefix specified to saveData\_Init() from 10 to 30 (PVNAME\_STRINGSZ/2). -* The sscan record's CMND field is now a DBF\_MENU, instead of a DBF\_ENUM. +* Increased the maximum size of the PV-name prefix specified to saveData_Init() from 10 to 30 (PVNAME_STRINGSZ/2). +* The sscan record's CMND field is now a DBF_MENU, instead of a DBF_ENUM. * Makefile was modified to build saveData on cygwin 1.7.x. * Added python programs for MDA files in sscanApp/op/python. -* In the scan\_more.adl and scan\_triggers.adl display files, t\*nv (the red numbers) were displaying error when assoc trigger link was not defined, instead of only when it was defined but not connected +* In the scan_more.adl and scan_triggers.adl display files, t\*nv (the red numbers) were displaying error when assoc trigger link was not defined, instead of only when it was defined but not connected * fixes for 64-bit architectures. * sscanRecord.html: better discussion of fly scans and examples of loading PnPA for table mode. * Modified RELEASE; deleted RELEASE.arch @@ -129,101 +117,91 @@ Release 2-7 - August 26, 2011 * standardScans.db: scanResumeSEQ was not defending against a change in the command value during the resume delay. As a result, resuming and then pausing during the resume delay did not leave the scan paused. * Previously, when an inner scan was paused while it was idle waiting for the next poke from the outer scan, the scan would not resume when the pause was rescinded. This problem was caused by my change that treated an attempt to execute a paused scan as an error. The sscan record no longer treats this as an error. -Release 2-6-6 - March 30, 2010 ------------------------------- +## Release 2-6-6 - March 30, 2010 -* Previously, a monitor on the file\_subdir PV could leave savaData in the STATUS\_ACTIVE\_FS\_ERROR state, if the PV hadn't actually changed. As a result, saveData booted up into the error state when the file\_subdir PV was blank. -* scan\_settings.req - added ATIME and COPYTO; deleted AAWAIT +* Previously, a monitor on the file_subdir PV could leave savaData in the STATUS_ACTIVE_FS_ERROR state, if the PV hadn't actually changed. As a result, saveData booted up into the error state when the file_subdir PV was blank. +* scan_settings.req - added ATIME and COPYTO; deleted AAWAIT * sscanRecord.c - fixes for 64-bit arch -* saveData.c - defend against saveData\_init() being called more than once. +* saveData.c - defend against saveData_init() being called more than once. -Release 2-6-5 -------------- +## Release 2-6-5 * Check all chid's before using them. -* Modified saveData so that, when it finds the filename it would like to use (e.g., base\_0001.mda) already in use, it writes, e.g., base\_0001\_01.mda, instead of base\_0001.mda\_01, as it used to do. +* Modified saveData so that, when it finds the filename it would like to use (e.g., base_0001.mda) already in use, it writes, e.g., base_0001_01.mda, instead of base_0001.mda_01, as it used to do. -Release 2-6-4 -------------- +## Release 2-6-4 * In 2.6.3, saveData crashed under tornado 2.2, because a vxWorks XDR structure changed. Now we define an old and a new structure, and identify the correct structure by its size. -Release 2-6-3 -------------- +## Release 2-6-3 * scanDetPlot.adl - added count * don't build busy record (split out into separate module) but retain a copy here for a while, since the busy module has new different version * saveData.c - don't include nfsDrv.h (which is renamed in tornado 2.2.2); instead, define nfsMount, nfsUnmount by hand. * sscanRecord.c - handle DBRprecision definition in EPICS 3.14.10; scanOnce() arg cast -Release 2-6-2 -------------- +## Release 2-6-2 -* Removed race conditions affecting callback counters, and added mutex to protect them. Changed timing of when to renew positioner links from now-last\_scan\_start to now-last\_scan\_end. -* display\_fields.adl uses new link-help displays from std R2.6 +* Removed race conditions affecting callback counters, and added mutex to protect them. Changed timing of when to renew positioner links from now-last_scan_start to now-last_scan_end. +* display_fields.adl uses new link-help displays from std R2.6 -Release 2-6-1 -------------- +## Release 2-6-1 * The sscan record didn't correctly handle reads or writes to PnPA, for n>1. As a result, table scans did not work with positioners 2-4. -* saveData didn't fail correctly when it could not find the \[basename\] section in its initialization file, and when it failed to connect to the basename PV. Instead, it aborted its initialization, and failed to connect to sscan records. +* saveData didn't fail correctly when it could not find the [basename] section in its initialization file, and when it failed to connect to the basename PV. Instead, it aborted its initialization, and failed to connect to sscan records. + +## Release 2-6 -Release 2-6 ------------ +* The sscan record can now post current-data arrays during a scan. While ATIME >= 0.1, ALL arrays will be posted when a new data point has been acquired and ATIME seconds have elapsed since the last array posting. New sets of array PV's have been added for this purpose, since the old array PV's must contain the previous scan's data to avoid breaking data-storage clients. The new PV's are PnCA (positioners, e.g., P1CA), and DnnCA (detectors, e.g., D01CA). During a scan, arrays are posted with the attribute DBE_VALUE; at end of scan, they are posted with DBE_LOG as well. + + Unfortunately, posting current-data arrays during a scan results unavoidably in the posting of the previous-data arrays, PnRA and DnnDA. Clients that monitor these PV's can regain their old behavior by specifying the mask DBE_LOG in their ca_add_event() or ca_create_subscription() call. -* The sscan record can now post current-data arrays during a scan. While ATIME >= 0.1, ALL arrays will be posted when a new data point has been acquired and ATIME seconds have elapsed since the last array posting. New sets of array PV's have been added for this purpose, since the old array PV's must contain the previous scan's data to avoid breaking data-storage clients. The new PV's are PnCA (positioners, e.g., P1CA), and DnnCA (detectors, e.g., D01CA). During a scan, arrays are posted with the attribute DBE\_VALUE; at end of scan, they are posted with DBE\_LOG as well. - - Unfortunately, posting current-data arrays during a scan results unavoidably in the posting of the previous-data arrays, PnRA and DnnDA. Clients that monitor these PV's can regain their old behavior by specifying the mask DBE\_LOG in their ca\_add\_event() or ca\_create\_subscription() call. - * The MEDM display scanDetPlot.adl now uses the new current-data PV's to display data. (These PV's also get end-of-scan data.) The MEDM display scanDetPlotRT.adl has been renamed scanDetPlotFromScalars.adl. * Previously, the sscan record repeated final data values out to the ends of arrays, when a scan was finished, to aid display clients that don't know how to plot only a PV-specified number of data points. Now this treatment can be done also during a scan, as controlled by the PV COPYTO. -Release 2-5-7 -------------- +## Release 2-5-7 -* Allow end user to specify the base name of data files written by saveData. Previously, the ioc prefix (modified to avoid characters illegal in file names on the supported operating systems) was used as the base file name. Now, if saveData.req contains the section \[basename\], and the PV named in that section exists, and the string value of that PV is not the empty string, then saveData will use the string value, instead of the ioc prefix, as the base file name (onto which the scan number and the string ".mda" will be appended). -* Previously, saveData's init file could not usefully specify PV names containing the characters '-', '\[', '\]', '<', '>', or ';', even though these are legal characters for a PV name. +* Allow end user to specify the base name of data files written by saveData. Previously, the ioc prefix (modified to avoid characters illegal in file names on the supported operating systems) was used as the base file name. Now, if saveData.req contains the section [basename], and the PV named in that section exists, and the string value of that PV is not the empty string, then saveData will use the string value, instead of the ioc prefix, as the base file name (onto which the scan number and the string ".mda" will be appended). +* Previously, saveData's init file could not usefully specify PV names containing the characters '-', '[', ']', '<', '>', or ';', even though these are legal characters for a PV name. * New documentation files: saveData.req and scanParmRecord.html * Busy record now pays attention to it's UDF and alarm fields, executes its its DOL link only if that link is not CONSTANT. * If recDynLink encounters a link structure that thinks it has an instance on queue, but the queue is in fact empty, then the link structure is corrected. * saveData's stack size increased.to epicsThreadStackBig. -Release 2-5-6 -------------- +## Release 2-5-6 * scan.db database separated into standardScans.db and saveData.db. -* Added standardScans\_settings.req and sscanRecord\_settings.req. This allows a script to more easily write a new auto\_settings.req file, since the request file has the same name as the database it supports. Also, this makes it easy to load more than one copy of standardScans.db. +* Added standardScans_settings.req and sscanRecord_settings.req. This allows a script to more easily write a new auto_settings.req file, since the request file has the same name as the database it supports. Also, this makes it easy to load more than one copy of standardScans.db. * Win32-specific .dbd file is no longer needed, since Mark Rivers added saveDataWin32.c, which contains stub functions for commands that could not be built for Win32. -* saveData now checks all data-file writes for errors, and retries until file is successfully written, or user-specified number of retries has been done. User also specifies the time between retries. The new PV's were added to scan\_saveData.adl +* saveData now checks all data-file writes for errors, and retries until file is successfully written, or user-specified number of retries has been done. User also specifies the time between retries. The new PV's were added to scan_saveData.adl * In sscan module 2.5.3, saveData was writing scan-dimensions to the wrong file offset, under certain circumstances. This is fixed. * recDynLink now calls epicsAtExit, so it can avoid making CA calls after CA has been shut down. * recDynLink handles null and empty PV names more gracefully. * sscan record now has a CMND-field value for clearing positioner drive and readback PV's, and the default medm display file uses this value for it's "CLEAR" button. -Release 2-5-3 -------------- +## Release 2-5-3 * Added sscanApp/op/python directory, with the following programs: - + addMDA.py - + Front end for adding MDA files, uses readMDA, opMDA, and writeMDA from mda.py - + mda.py - + Python API for MDA files. Supports reading, writing, and arithmetic operations for up to 4-dimensional MDA files - + mdaAsc.py - + Uses mda.py to render a 1-dimensional MDA file as ascii text. - + opMDA.py - + Front end for operating on MDA files, uses readMDA, opMDA, and writeMDA from mda.py - + * Fixed problems in the communication between the sscan record and saveData that caused corrupted data files to be written: * The basic problem was that saveData was getting bufferred data arrays, but an unbuffered copy of the sscan record's CPT field. The sscan record now maintains the field BCPT (bufferred CPT) which is posted when data array buffers are switched. - * A second problem was that saveData was not able to put AWAIT=1 quickly enough to stop a very fast scan in time to ensure integrity of the data file. saveData now writes '1' to the sscan record's AAWAIT field on init, and writes '0' if it ever exits (not a supported operation at this time). As a consequence, AAWAIT no longer occurs in the autosave-request file scan\_settings.req. + * A second problem was that saveData was not able to put AWAIT=1 quickly enough to stop a very fast scan in time to ensure integrity of the data file. saveData now writes '1' to the sscan record's AAWAIT field on init, and writes '0' if it ever exits (not a supported operation at this time). As a consequence, AAWAIT no longer occurs in the autosave-request file scan_settings.req. * A remaining problem, thus far seen only on cygwin, is that multidimensional scans can get saveData into trouble because CA monitors sometimes are received by saveData in a different order than they were posted by the sscan records. Currently, neither the sscan record nor saveData defend against this. * Added Dohn Arms' 'mdautils' software in the sscanApp/src directory. This software can convert an MDA file to ascii, print info about an MDA file, and read an MDA file into C data structures. * Fixed a race condition in the sscan record that was responsible for hanging scans at the last point (and maybe other things as well). @@ -231,20 +209,18 @@ Release 2-5-3 * If retrace or after-scan fails because recDynLinkPutCallback returns an error, skip the action rather than hang. * If the sscan record attempts to connect to a PV while an earlier connection attempt is still in progress, it now waits and retries. * recDynLinkQsize is now exported for use by the ioc shell. -* recDynLink used to crash if one of its callback functions received an event\_handler\_args structure with a status element whose value was not == ECA\_NORMAL. Now it declines to process the event or to pass it on to the client. +* recDynLink used to crash if one of its callback functions received an event_handler_args structure with a status element whose value was not == ECA_NORMAL. Now it declines to process the event or to pass it on to the client. * saveData used to check directory permissions by attempting to create a file whose name was illegal (contained ':') on some operating systems. * rewrote sscanRecord.html -Release 2-5-2 -------------- +## Release 2-5-2 * sscanRecord checks parameters more closely, allows before-scan and after-scan links to write to selected PV's of their own sscan record. * New after-scan action: Move to center of mass of peak (this choice has problems with multiple positioners, since they won't, in general, have the same peak position). * In previous versions, recDynLink would deadlock if asked to clear the link to a PV while an action for that PV was still on queue. This is fixed. * saveData zeros unused points in its XDR buffer, because XDR doesn't manage this well. -Release 2-4 ------------ +## Release 2-4 This version is intended to build with EPICS base 3.14.7. @@ -265,30 +241,24 @@ This version is intended to build with EPICS base 3.14.7. * Added recDynLinkGetCallback() to recDynLink library. Also fixed some bugs. * [cvs log](cvsLog.txt) -Release 2-3 ------------ +## Release 2-3 This is the first release of the synApps sscan module. Version numbering for this module begins with 2.3 because this module was split from version 2.2 of the std module, and I wanted to retain the CVS histories of module contents. This version is intended to build with EPICS base 3.14.5. Differences from software as previously released in std 2.2: * Converted to EPICS 3.14. Currently saveData runs on vxWorks only. - + * Docs updated and moved to sscan/documentation - + * saveData - added iocsh support; changed number of data points from short to long int, to support very large scans. The data file format is unchanged, however, because the number of points was already being written as a four-byte quantity. - + * sscanRecord - Number of points in a scan is essentially limited only by available memory. save-restored value of NPTS is now checked against MPTS. Array mode (ACQT="1D ARRAY") was broken. (The change from ACQM="ARRAYS" to ACQT="1D ARRAY" wasn't done correctly.) - + Previously, the sscan record's response to an abort request (.EXSC=0) while no scan was in progress (.BUSY==0) was to return nonzero from special(), and EPICS tolerated this without comment. Now it signals an error to the client. But we don't (always?) want this action to be regarded as an error. For now, the scan database just declines to abort a sscan record that isn't busy, but clients writing directly to the sscan record directly can still get this error message. - -* recDynLink - Fixed memory leak (epicsMutex created but not destroyed). Switched communication with link tasks from ring buffer to message queue. recDynOut was calling ca\_pend\_event, which used to flush the ca buffer, but evidently no longer does; replaced with ca\_flush\_io. - -* saveData\_settings.req - new file. - -* scan\_settings.req - added fields ACQT and ACQM. - - -Suggestions and Comments to: -[Tim Mooney](mailto:mooney@aps.anl.gov) : (mooney@aps.anl.gov) -Last modified: May 26, 2008 + +* recDynLink - Fixed memory leak (epicsMutex created but not destroyed). Switched communication with link tasks from ring buffer to message queue. recDynOut was calling ca_pend_event, which used to flush the ca buffer, but evidently no longer does; replaced with ca_flush_io. + +* saveData_settings.req - new file. + +* scan_settings.req - added fields ACQT and ACQM. diff --git a/sscanApp/Db/saveData.db b/sscanApp/Db/saveData.db index 57345ae..93dfbf4 100644 --- a/sscanApp/Db/saveData.db +++ b/sscanApp/Db/saveData.db @@ -30,30 +30,33 @@ record(stringout, "$(P)saveData_comment2") { field(DTYP, "Soft Channel") } -record(stringout, "$(P)saveData_fileName") { +record(lso, "$(P)saveData_fileName") { field(DTYP, "Soft Channel") + field(SIZV, "256") } -record(stringout, "$(P)saveData_fileSystem") { +record(lso, "$(P)saveData_fileSystem") { field(DTYP, "Soft Channel") + field(SIZV, "256") } record(stringout, "$(P)saveData_message") { field(DTYP, "Soft Channel") } -record(stringout, "$(P)saveData_subDir") { +record(lso, "$(P)saveData_subDir") { field(DTYP, "Soft Channel") + field(SIZV, "256") } -record(stringout, "$(P)saveData_baseName") { +record(lso, "$(P)saveData_baseName") { field(DTYP, "Soft Channel") + field(SIZV, "256") } -record(waveform, "$(P)saveData_fullPathName") { +record(lso, "$(P)saveData_fullPathName") { field(DTYP, "Soft Channel") - field(NELM, "100") - field(FTVL, "CHAR") + field(SIZV, "512") } record(longout, "$(P)saveData_maxAllowedRetries") { diff --git a/sscanApp/Db/saveData_settings.req b/sscanApp/Db/saveData_settings.req index b14fad3..3f941e2 100644 --- a/sscanApp/Db/saveData_settings.req +++ b/sscanApp/Db/saveData_settings.req @@ -1,6 +1,6 @@ -$(P)saveData_fileSystem -$(P)saveData_subDir -$(P)saveData_baseName +$(P)saveData_fileSystem.$ +$(P)saveData_subDir.$ +$(P)saveData_baseName.$ $(P)saveData_scanNumber $(P)saveData_realTime1D $(P)saveData_maxAllowedRetries diff --git a/sscanApp/op/adl/scan_saveData.adl b/sscanApp/op/adl/scan_saveData.adl index 40f99b1..4ad07b5 100644 --- a/sscanApp/op/adl/scan_saveData.adl +++ b/sscanApp/op/adl/scan_saveData.adl @@ -1,7 +1,7 @@ file { - name="/home/oxygen4/MOONEY/epics/synAppsSVN/support/sscan/sscanApp/op/adl/scan_saveData.adl" - version=030107 + name="/home/beams1/KLANG/Documents/Projects/Repository/git/sscan/sscanApp/op/adl/scan_saveData.adl" + version=030111 } display { object { @@ -107,7 +107,7 @@ text { height=20 } control { - chan="$(P)saveData_fileSystem" + chan="$(P)saveData_fileSystem.$" clr=14 bclr=51 } @@ -238,58 +238,47 @@ text { } textix="Example: //" } -composite { +text { object { x=10 + y=63 + width=80 + height=14 + } + "basic attribute" { + clr=14 + } + textix="Subdirectory" +} +"text entry" { + object { + x=100 y=60 - width=380 + width=260 height=20 } - "composite name"="" - children { - text { - object { - x=10 - y=63 - width=80 - height=14 - } - "basic attribute" { - clr=14 - } - textix="Subdirectory" - } - "text entry" { - object { - x=100 - y=60 - width=260 - height=20 - } - control { - chan="$(P)saveData_subDir" - clr=14 - bclr=51 - } - format="string" - limits { - } - } - "text entry" { - object { - x=370 - y=60 - width=20 - height=20 - } - control { - chan="$(P)saveData_subDir.DISP" - clr=14 - bclr=51 - } - limits { - } - } + control { + chan="$(P)saveData_subDir.$" + clr=14 + bclr=51 + } + format="string" + limits { + } +} +"text entry" { + object { + x=370 + y=60 + width=20 + height=20 + } + control { + chan="$(P)saveData_subDir.DISP" + clr=14 + bclr=51 + } + limits { } } text { @@ -312,7 +301,7 @@ text { height=20 } control { - chan="$(P)saveData_baseName" + chan="$(P)saveData_baseName.$" clr=14 bclr=51 } @@ -746,74 +735,63 @@ composite { } } } -composite { +"text update" { + object { + x=50 + y=150 + width=340 + height=14 + } + monitor { + chan="$(P)saveData_fileName.$" + clr=53 + bclr=1 + } + format="string" + limits { + } +} +"text update" { + object { + x=50 + y=130 + width=340 + height=14 + } + monitor { + chan="$(P)saveData_fullPathName.$" + clr=53 + bclr=1 + } + format="string" + limits { + } +} +text { object { x=10 y=130 - width=380 - height=34 + width=40 + height=14 } - "composite name"="" - children { - "text update" { - object { - x=50 - y=150 - width=340 - height=14 - } - monitor { - chan="$(P)saveData_fileName" - clr=53 - bclr=1 - } - format="string" - limits { - } - } - "text update" { - object { - x=50 - y=130 - width=340 - height=14 - } - monitor { - chan="$(P)saveData_fullPathName" - clr=53 - bclr=1 - } - format="string" - limits { - } - } - text { - object { - x=10 - y=130 - width=40 - height=14 - } - "basic attribute" { - clr=14 - } - textix="PATH:" - align="horiz. centered" - } - text { - object { - x=10 - y=150 - width=40 - height=14 - } - "basic attribute" { - clr=14 - } - textix="NAME:" - align="horiz. centered" - } + "basic attribute" { + clr=14 + } + textix="PATH:" + align="horiz. centered" +} +text { + object { + x=10 + y=150 + width=40 + height=14 } + "basic attribute" { + clr=14 + } + textix="NAME:" + align="horiz. centered" } text { object { diff --git a/sscanApp/op/bob/autoconvert/scan_saveData.bob b/sscanApp/op/bob/autoconvert/scan_saveData.bob index af58254..3a37ee1 100644 --- a/sscanApp/op/bob/autoconvert/scan_saveData.bob +++ b/sscanApp/op/bob/autoconvert/scan_saveData.bob @@ -1,4 +1,5 @@ + scan_saveData 814 @@ -29,7 +30,7 @@ text entry #9 - $(P)saveData_fileSystem + $(P)saveData_fileSystem.$ 90 15 270 @@ -204,59 +205,53 @@ true - - composite #44 + + text #44 + Subdirectory 10 + 63 + 80 + 14 + true + + + text entry #47 + $(P)saveData_subDir.$ + 100 60 - 380 - 20 - - true - - text #47 - Subdirectory - 3 - 80 - 14 - true - - - text entry #50 - $(P)saveData_subDir - 90 - 260 - - - + 260 + + - - - - - 6 - false - false - - - text entry #54 - $(P)saveData_subDir.DISP - 360 - 20 - - - + + + + + + 6 + false + false + + + text entry #51 + $(P)saveData_subDir.DISP + 370 + 60 + 20 + + - - - - - 1 - false - false - + + + + + + 1 + false + false - text #58 + text #55 Base Name 10 88 @@ -265,8 +260,8 @@ true - text entry #61 - $(P)saveData_baseName + text entry #58 + $(P)saveData_baseName.$ 100 85 260 @@ -283,7 +278,7 @@ false - text entry #65 + text entry #62 $(P)saveData_baseName.DISP 370 85 @@ -301,7 +296,7 @@ false - text entry #69 + text entry #66 $(P)saveData_scanNumber 183 170 @@ -319,7 +314,7 @@ false - text #73 + text #70 Next scan number 15 170 @@ -331,7 +326,7 @@ true - text update #76 + text update #73 $(P)saveData_status 130 195 @@ -352,7 +347,7 @@ false - text #80 + text #77 Save status 15 195 @@ -364,7 +359,7 @@ true - menu #83 + menu #80 $(P)saveData_realTime1D 250 236 @@ -381,7 +376,7 @@ false - text update #86 + text update #83 $(P)saveData_message 10 215 @@ -404,7 +399,7 @@ false - text #90 + text #87 Write 1D data at each data point? 10 240 @@ -413,7 +408,7 @@ true - text #93 + text #90 Comments for data file: 10 260 @@ -422,7 +417,7 @@ true - text entry #96 + text entry #93 $(P)saveData_comment1 10 275 @@ -441,7 +436,7 @@ false - text entry #100 + text entry #97 $(P)saveData_comment2 10 295 @@ -460,7 +455,7 @@ false - rectangle #104 + rectangle #101 12 326 380 @@ -475,7 +470,7 @@ - text entry #107 + text entry #104 $(P)saveData_maxAllowedRetries 167 349 @@ -493,7 +488,7 @@ false - text entry #111 + text entry #108 $(P)saveData_retryWaitInSecs 167 369 @@ -511,7 +506,7 @@ false - text #115 + text #112 max per write 27 349 @@ -520,7 +515,7 @@ true - text #118 + text #115 retry wait (s) 27 369 @@ -529,7 +524,7 @@ true - text #121 + text #118 Retries 17 329 @@ -541,7 +536,7 @@ true - composite #124 + composite #121 337 329 50 @@ -549,7 +544,7 @@ true - text update #127 + text update #124 $(P)saveData_abandonedWrites 20 50 @@ -570,7 +565,7 @@ false - text #131 + text #128 ABANDONED 50 10 @@ -581,7 +576,7 @@ 1 - text #134 + text #131 WRITES 10 50 @@ -594,16 +589,11 @@ - polyline #137 + polyline #134 10 325 382 72 - 2 - - - - @@ -612,18 +602,18 @@ + 2 + + + + - polyline #141 + polyline #138 11 325 382 72 - 2 - - - - @@ -632,9 +622,14 @@ + 2 + + + + - composite #145 + composite #142 262 329 50 @@ -642,7 +637,7 @@ true - text update #148 + text update #145 $(P)saveData_totalRetries 20 50 @@ -663,7 +658,7 @@ false - text #152 + text #149 SINCE 50 10 @@ -674,7 +669,7 @@ 1 - text #155 + text #152 BOOT 10 50 @@ -687,7 +682,7 @@ - composite #158 + composite #155 212 329 40 @@ -695,7 +690,7 @@ true - text update #161 + text update #158 $(P)saveData_currRetries 20 40 @@ -716,7 +711,7 @@ false - text #165 + text #162 CURRENT 40 10 @@ -727,7 +722,7 @@ 1 - text #168 + text #165 WRITE 10 40 @@ -739,69 +734,64 @@ 1 - - composite #171 + + text update #168 + $(P)saveData_fileName.$ + 50 + 150 + 340 + 14 + + + + + + + + + 6 + false + false + + + text update #172 + $(P)saveData_fullPathName.$ + 50 + 130 + 340 + 14 + + + + + + + + + 6 + false + false + + + text #176 + PATH: 10 130 - 380 - 34 - - true - - text update #174 - $(P)saveData_fileName - 40 - 20 - 340 - 14 - - - - - - - - - 6 - false - false - - - text update #178 - $(P)saveData_fullPathName - 40 - 340 - 14 - - - - - - - - - 6 - false - false - - - text #182 - PATH: - 40 - 14 - 1 - - - text #185 - NAME: - 20 - 40 - 14 - 1 - + 40 + 14 + 1 + + + text #179 + NAME: + 10 + 150 + 40 + 14 + 1 - text #188 + text #182 NOTE: for scanSee, basename must end with '_' 10 105 diff --git a/sscanApp/op/ui/autoconvert/scan_saveData.ui b/sscanApp/op/ui/autoconvert/scan_saveData.ui index cd9bd52..2dc7a6a 100644 --- a/sscanApp/op/ui/autoconvert/scan_saveData.ui +++ b/sscanApp/op/ui/autoconvert/scan_saveData.ui @@ -14,94 +14,7 @@ QWidget#centralWidget {background: rgba(200, 200, 200, 255);} - -caTable { - font: 10pt; - background: cornsilk; - alternate-background-color: wheat; -} - -caLineEdit { - border-radius: 1px; - background: lightyellow; - color: black; - } - -caTextEntry { - color: rgb(127, 0, 63); - background-color: cornsilk; - selection-color: #0a214c; - selection-background-color: wheat; - border: 1px groove black; - border-radius: 1px; - padding: 1px; -} - -caTextEntry:focus { - padding: 0px; - border: 2px groove darkred; - border-radius: 1px; -} - -QPushButton { - border-color: #00b; - border-radius: 2px; - padding: 3px; - border-width: 1px; - - background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, - stop:0 rgba(224, 239, 255, 255), - stop:0.5 rgba(199, 215, 230, 255), - stop:1 rgba(184, 214, 236, 255)); -} -QPushButton:hover { - background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, - stop:0 rgba(201, 226, 255, 255), - stop:0.5 rgba(177, 204, 230, 255), - stop:1 rgba(163, 205, 236, 255)); -} -QPushButton:pressed { - background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, - stop:0 rgba(174, 219, 255, 255), - stop:0.5 rgba(165, 199, 230, 255), - stop:1 rgba(134, 188, 236, 255)); -} - -QPushButton:disabled { - background: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, - stop:0 rgba(174, 219, 255, 255), - stop:0.5 rgba(165, 199, 230, 255), - stop:1 rgba(134, 188, 236, 255)); -} - -caChoice { - background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #E1E1E1, stop: 0.4 #DDDDDD, - stop: 0.5 #D8D8D8, stop: 1.0 #D3D3D3); -} - -caChoice > QPushButton { - text-align: left; - padding: 1px; -} - -caSlider::groove:horizontal { -border: 1px solid #bbb; -background: lightgrey; -height: 20px; -border-radius: 4px; -} - -caSlider::handle:horizontal { -background: red; -border: 1px solid #777; -width: 13px; -margin-top: -2px; -margin-bottom: -2px; -border-radius: 2px; -} - - +QPushButton::menu-indicator {image: url(none.png); width: 0} @@ -155,7 +68,7 @@ border-radius: 2px; caLineEdit::WidthAndHeight - $(P)saveData_fileSystem + $(P)saveData_fileSystem.$ @@ -568,155 +481,7 @@ border-radius: 2px; Qt::AlignAbsolute|Qt::AlignLeft|Qt::AlignVCenter - - - - 10 - 60 - 382 - 22 - - - - - QFrame::NoFrame - - - - 0 - 0 - 0 - - - - - 0 - 0 - 0 - - - - Subdirectory - - - ESimpleLabel::WidthAndHeight - - - - 0 - 3 - 80 - 14 - - - - Qt::AlignAbsolute|Qt::AlignLeft|Qt::AlignVCenter - - - - - - 90 - 0 - 260 - 20 - - - - caLineEdit::WidthAndHeight - - - $(P)saveData_subDir - - - - 0 - 0 - 0 - - - - - 115 - 223 - 255 - - - - caLineEdit::Channel - - - caLineEdit::Channel - - - caLineEdit::Channel - - - 0.0 - - - 1.0 - - - caLineEdit::Static - - - string - - - - - - 360 - 0 - 20 - 20 - - - - caLineEdit::WidthAndHeight - - - $(P)saveData_subDir.DISP - - - - 0 - 0 - 0 - - - - - 115 - 223 - 255 - - - - caLineEdit::Channel - - - caLineEdit::Channel - - - caLineEdit::Channel - - - 0.0 - - - 1.0 - - - caLineEdit::Static - - - decimal - - - - + QFrame::NoFrame @@ -735,7 +500,7 @@ border-radius: 2px; - Base Name + Subdirectory ESimpleLabel::WidthAndHeight @@ -743,7 +508,7 @@ border-radius: 2px; 10 - 88 + 63 80 14 @@ -752,11 +517,11 @@ border-radius: 2px; Qt::AlignAbsolute|Qt::AlignLeft|Qt::AlignVCenter - + 100 - 85 + 60 260 20 @@ -765,7 +530,7 @@ border-radius: 2px; caLineEdit::WidthAndHeight - $(P)saveData_baseName + $(P)saveData_subDir.$ @@ -803,11 +568,11 @@ border-radius: 2px; string - + 370 - 85 + 60 20 20 @@ -816,7 +581,7 @@ border-radius: 2px; caLineEdit::WidthAndHeight - $(P)saveData_baseName.DISP + $(P)saveData_subDir.DISP @@ -854,12 +619,48 @@ border-radius: 2px; decimal - + + + QFrame::NoFrame + + + + 0 + 0 + 0 + + + + + 0 + 0 + 0 + + + + Base Name + + + ESimpleLabel::WidthAndHeight + - 183 - 170 - 60 + 10 + 88 + 80 + 14 + + + + Qt::AlignAbsolute|Qt::AlignLeft|Qt::AlignVCenter + + + + + + 100 + 85 + 260 20 @@ -867,7 +668,7 @@ border-radius: 2px; caLineEdit::WidthAndHeight - $(P)saveData_scanNumber + $(P)saveData_baseName.$ @@ -902,51 +703,15 @@ border-radius: 2px; caLineEdit::Static - decimal - - - - - QFrame::NoFrame - - - - 0 - 0 - 0 - - - - - 0 - 0 - 0 - - - - Next scan number - - - ESimpleLabel::WidthAndHeight - - - - 15 - 170 - 160 - 20 - - - - Qt::AlignAbsolute|Qt::AlignLeft|Qt::AlignVCenter + string - + - 130 - 195 - 100 + 370 + 85 + 20 20 @@ -954,20 +719,20 @@ border-radius: 2px; caLineEdit::WidthAndHeight - $(P)saveData_status + $(P)saveData_baseName.DISP - 42 - 99 - 228 + 0 + 0 + 0 - 200 - 200 - 200 + 115 + 223 + 255 @@ -985,7 +750,145 @@ border-radius: 2px; 1.0 - + + caLineEdit::Static + + + decimal + + + + + + 183 + 170 + 60 + 20 + + + + caLineEdit::WidthAndHeight + + + $(P)saveData_scanNumber + + + + 0 + 0 + 0 + + + + + 115 + 223 + 255 + + + + caLineEdit::Channel + + + caLineEdit::Channel + + + caLineEdit::Channel + + + 0.0 + + + 1.0 + + + caLineEdit::Static + + + decimal + + + + + QFrame::NoFrame + + + + 0 + 0 + 0 + + + + + 0 + 0 + 0 + + + + Next scan number + + + ESimpleLabel::WidthAndHeight + + + + 15 + 170 + 160 + 20 + + + + Qt::AlignAbsolute|Qt::AlignLeft|Qt::AlignVCenter + + + + + + 130 + 195 + 100 + 20 + + + + caLineEdit::WidthAndHeight + + + $(P)saveData_status + + + + 42 + 99 + 228 + + + + + 200 + 200 + 200 + + + + caLineEdit::Channel + + + caLineEdit::Channel + + + caLineEdit::Channel + + + 0.0 + + + 1.0 + + Qt::AlignAbsolute|Qt::AlignLeft|Qt::AlignVCenter @@ -1532,7 +1435,7 @@ border-radius: 2px; Qt::AlignAbsolute|Qt::AlignLeft|Qt::AlignVCenter - + 337 @@ -1701,7 +1604,7 @@ border-radius: 2px; Solid - 381,1; + 1,71;1,1;381,1; @@ -1737,10 +1640,10 @@ border-radius: 2px; Solid - 1,71; + 381,1;381,71;1,71; - + 262 @@ -1876,7 +1779,7 @@ border-radius: 2px; - + 212 @@ -2012,195 +1915,185 @@ border-radius: 2px; - + + + + 50 + 150 + 340 + 14 + + + + caLineEdit::WidthAndHeight + + + $(P)saveData_fileName.$ + + + + 42 + 99 + 228 + + + + + 236 + 236 + 236 + + + + caLineEdit::Channel + + + caLineEdit::Channel + + + caLineEdit::Channel + + + 0.0 + + + 1.0 + + + Qt::AlignAbsolute|Qt::AlignLeft|Qt::AlignVCenter + + + string + + + caLineEdit::Static + + + + + + 50 + 130 + 340 + 14 + + + + caLineEdit::WidthAndHeight + + + $(P)saveData_fullPathName.$ + + + + 42 + 99 + 228 + + + + + 236 + 236 + 236 + + + + caLineEdit::Channel + + + caLineEdit::Channel + + + caLineEdit::Channel + + + 0.0 + + + 1.0 + + + Qt::AlignAbsolute|Qt::AlignLeft|Qt::AlignVCenter + + + string + + + caLineEdit::Static + + + + + QFrame::NoFrame + + + + 0 + 0 + 0 + + + + + 0 + 0 + 0 + + + + PATH: + + + ESimpleLabel::WidthAndHeight + + + Qt::AlignAbsolute|Qt::AlignHCenter|Qt::AlignVCenter + 10 130 - 382 - 36 + 40 + 14 + + + + + + QFrame::NoFrame + + + + 0 + 0 + 0 + + + + + 0 + 0 + 0 + + + + NAME: + + + ESimpleLabel::WidthAndHeight + + + Qt::AlignAbsolute|Qt::AlignHCenter|Qt::AlignVCenter + + + + 10 + 150 + 40 + 14 - - - - 40 - 20 - 340 - 14 - - - - caLineEdit::WidthAndHeight - - - $(P)saveData_fileName - - - - 42 - 99 - 228 - - - - - 236 - 236 - 236 - - - - caLineEdit::Channel - - - caLineEdit::Channel - - - caLineEdit::Channel - - - 0.0 - - - 1.0 - - - Qt::AlignAbsolute|Qt::AlignLeft|Qt::AlignVCenter - - - string - - - caLineEdit::Static - - - - - - 40 - 0 - 340 - 14 - - - - caLineEdit::WidthAndHeight - - - $(P)saveData_fullPathName - - - - 42 - 99 - 228 - - - - - 236 - 236 - 236 - - - - caLineEdit::Channel - - - caLineEdit::Channel - - - caLineEdit::Channel - - - 0.0 - - - 1.0 - - - Qt::AlignAbsolute|Qt::AlignLeft|Qt::AlignVCenter - - - string - - - caLineEdit::Static - - - - - QFrame::NoFrame - - - - 0 - 0 - 0 - - - - - 0 - 0 - 0 - - - - PATH: - - - ESimpleLabel::WidthAndHeight - - - Qt::AlignAbsolute|Qt::AlignHCenter|Qt::AlignVCenter - - - - 0 - 0 - 40 - 14 - - - - - - QFrame::NoFrame - - - - 0 - 0 - 0 - - - - - 0 - 0 - 0 - - - - NAME: - - - ESimpleLabel::WidthAndHeight - - - Qt::AlignAbsolute|Qt::AlignHCenter|Qt::AlignVCenter - - - - 0 - 20 - 40 - 14 - - - @@ -2249,7 +2142,6 @@ border-radius: 2px; caLabel_8 caLabel_9 caLabel_10 - caFrame_0 caLabel_11 caLabel_12 caLabel_13 @@ -2261,18 +2153,17 @@ border-radius: 2px; caLabel_18 caLabel_19 caLabel_20 - caFrame_1 + caFrame_0 caPolyLine_0 caPolyLine_1 caLabel_21 caLabel_22 - caFrame_2 + caFrame_1 caLabel_23 caLabel_24 - caFrame_3 + caFrame_2 caLabel_25 caLabel_26 - caFrame_4 caLabel_27 caTextEntry_0 caTextEntry_1 diff --git a/sscanApp/src/recDynLink.c b/sscanApp/src/recDynLink.c index f9b51f8..2456382 100644 --- a/sscanApp/src/recDynLink.c +++ b/sscanApp/src/recDynLink.c @@ -429,9 +429,9 @@ long epicsShareAPI recDynLinkGetUnits(recDynLink *precDynLink,char *units,int ma pdynLinkPvt = precDynLink->pdynLinkPvt; if (pdynLinkPvt->state!=stateConnected) return(-1); maxToCopy = MAX_UNITS_SIZE; - if (maxlenunits,maxToCopy); - if (maxToCopy controlLow = pdata->lower_ctrl_limit; pdynLinkPvt -> controlHigh = pdata->upper_ctrl_limit; pdynLinkPvt -> precision = pdata->precision; - strncpy(pdynLinkPvt->units,pdata->units,MAX_UNITS_SIZE); + strncpy(pdynLinkPvt->units,pdata->units,MAX_UNITS_SIZE - 1); + pdynLinkPvt->units[MAX_UNITS_SIZE - 1] = '\0'; if (pdynLinkPvt->scalar) { pdynLinkPvt->nRequest = 1; } else { diff --git a/sscanApp/src/req_file.c b/sscanApp/src/req_file.c index 710adbc..455d4b8 100644 --- a/sscanApp/src/req_file.c +++ b/sscanApp/src/req_file.c @@ -33,8 +33,8 @@ LOCAL MACRO* initMacros(char* macro) MACRO* head; MACRO* cur; int i; - char mc_name[9]; - char mc_value[20]; + char mc_name[MACRO_NAME_SIZE]; + char mc_value[MACRO_VALUE_SIZE]; head= NULL; @@ -46,7 +46,7 @@ LOCAL MACRO* initMacros(char* macro) if(END(macro)) return head; i= 0; - while(!END(macro) && (*macro!=' ') && (*macro!='=') && (i<8)) + while(!END(macro) && (*macro!=' ') && (*macro!='=') && (i< MACRO_NAME_SIZE-1)) mc_name[i++]= *(macro++); if(END(macro)) return head; while(!END(macro) && (*macro!='=')) macro++; @@ -57,7 +57,7 @@ LOCAL MACRO* initMacros(char* macro) if(END(macro)) return head; i= 0; - while(!END(macro) && (*macro!=' ') && (*macro!=',') && (i<19)) + while(!END(macro) && (*macro!=' ') && (*macro!=',') && (i< MACRO_VALUE_SIZE-1)) mc_value[i++]= *(macro++); mc_value[i]= '\0'; @@ -88,7 +88,7 @@ LOCAL MACRO* searchMacro(REQ_FILE* rf, char* name) LOCAL MACRO* readMacro(REQ_FILE* rf) { - char macName[9]; + char macName[MACRO_NAME_SIZE]; int i; req_skipSpace(rf); @@ -99,7 +99,7 @@ LOCAL MACRO* readMacro(REQ_FILE* rf) req_readChar(rf); i= 0; - while(!eos(rf) && (current(rf)!=')') && (i<8)) { + while(!eos(rf) && (current(rf)!=')') && (i< MACRO_NAME_SIZE-1)) { macName[i++]= current(rf); req_readChar(rf); } diff --git a/sscanApp/src/req_file.h b/sscanApp/src/req_file.h index cb1c476..efc286d 100644 --- a/sscanApp/src/req_file.h +++ b/sscanApp/src/req_file.h @@ -18,14 +18,17 @@ #include #define FILENAME_LENGTH 160 +#define MACRO_NAME_SIZE 21 +#define MACRO_VALUE_SIZE 41 + /************************************************************************/ /* TYPES */ /*----------------------------------------------------------------------*/ /* macro structure */ typedef struct macro { - char name[21]; - char value[41]; + char name[MACRO_NAME_SIZE]; + char value[MACRO_VALUE_SIZE]; struct macro* nxt; } MACRO; diff --git a/sscanApp/src/saveData.c b/sscanApp/src/saveData.c index 4df3410..69842e6 100644 --- a/sscanApp/src/saveData.c +++ b/sscanApp/src/saveData.c @@ -1848,7 +1848,7 @@ LOCAL void extraValCallback(struct event_handler_args eha) PV_NODE * pnode = eha.usr; long type = eha.type; long count = eha.count; - READONLY DBR_VAL * pval = eha.dbr; + const DBR_VAL * pval = eha.dbr; char *string; size_t size=0; @@ -1895,7 +1895,7 @@ LOCAL void extraValCallback(struct event_handler_args eha) LOCAL void extraDescCallback(struct event_handler_args eha) { PV_NODE * pnode = eha.usr; - READONLY DBR_VAL * pval = eha.dbr; + const DBR_VAL * pval = eha.dbr; epicsMutexLock(pnode->lock); diff --git a/sscanApp/src/saveData_writeXDR.c b/sscanApp/src/saveData_writeXDR.c index 81c8628..a118cfe 100644 --- a/sscanApp/src/saveData_writeXDR.c +++ b/sscanApp/src/saveData_writeXDR.c @@ -167,7 +167,8 @@ #include #include typedef unsigned int u_int; - #define mkdir(PATH, PERMIS) (mkdir(PATH)) + + #define mkdir(PATH, PERMIS) (_mkdir(PATH)) #endif #ifdef vxWorks @@ -205,15 +206,14 @@ #include /* for PVNAME_STRINGSZ */ #include /* for MAX_STRING_SIZE */ #include /* for epicsSnprintf() */ -#include /* for READONLY */ #define MAX(a,b) ((a)>(b)?(a):(b)) #define MIN(a,b) ((a)<(b)?(a):(b)) #define DESC_SIZE 30 #define EGU_SIZE 16 -#define PREFIX_SIZE PVNAME_STRINGSZ/2 -#define BASENAME_SIZE 20 +#define PREFIX_SIZE PVNAME_STRINGSZ - 15 +#define COMPONENT_STRING_SIZE 256 #include "req_file.h" #include "writeXDR.h" @@ -352,7 +352,7 @@ LOCAL const char save_data_version[]=SAVE_DATA_VERSION; #define HANDSHAKE_BUSY 1 #define HANDSHAKE_DONE 0 -#define FNAMELEN 100 +#define FNAMELEN 512 typedef struct scan { /*========================= PRIVATE FIELDS ===========================*/ short state; /* state of the structure */ @@ -460,7 +460,7 @@ typedef struct scan { /*---------------------- saveDataTask's message queue ------------------*/ #define MAX_MSG 1000 /* max # of messages in saveDataTask's queue */ -#define MAX_SIZE 80 /* max size in byte of the messages */ +#define MAX_SIZE 512 /* max size in byte of the messages */ #define MSG_SCAN_DATA 1 /* save scan */ #define MSG_SCAN_NPTS 2 /* NPTS changed */ @@ -571,7 +571,7 @@ typedef struct string_msg { int type; epicsTimeStamp time; char* pdest; /* specified as user arg in ca_create_subscription() call */ - char string[MAX_STRING_SIZE]; + char string[COMPONENT_STRING_SIZE]; } STRING_MSG; #define STRING_SIZE (sizeof(STRING_MSG)first_scan= TRUE; pscan->scan_dim= 1; - strncpy(pscan->name, name, PVNAME_STRINGSZ-1); - pscan->name[PVNAME_STRINGSZ-1]='\0'; + epicsSnprintf(pscan->name, PVNAME_STRINGSZ, "%s", name); pscan->nxt= NULL; epicsTimeGetCurrent(&(pscan->cpt_time)); pscan->cpt_monitored= FALSE; @@ -1660,17 +1671,17 @@ LOCAL void descMonitor(struct event_handler_args eha) LOCAL void fileSystemMonitor(struct event_handler_args eha) { - sendStringMsgWait(MSG_FILE_SYSTEM, NULL, eha.dbr); + sendStringMsgWait(MSG_FILE_SYSTEM, NULL, (const char*) eha.dbr); } LOCAL void fileSubdirMonitor(struct event_handler_args eha) { - sendStringMsgWait(MSG_FILE_SUBDIR, NULL, eha.dbr); + sendStringMsgWait(MSG_FILE_SUBDIR, NULL, (const char*) eha.dbr); } LOCAL void fileBasenameMonitor(struct event_handler_args eha) -{ - sendStringMsgWait(MSG_FILE_BASENAME, NULL, eha.dbr); +{ + sendStringMsgWait(MSG_FILE_BASENAME, NULL, (const char*) eha.dbr); } LOCAL void realTime1DMonitor(struct event_handler_args eha) @@ -1698,14 +1709,16 @@ LOCAL int connectFileSystem(char* fs) epicsSnprintf(fs_disp, 80, "%s.DISP", fs); - ca_search(fs, &file_system_chid); - ca_search(fs_disp, &file_system_disp_chid); + ca_create_channel(fs, NULL, NULL, 0, &file_system_chid); + //ca_search(fs_disp, &file_system_disp_chid); if (ca_pend_io(0.5)!=ECA_NORMAL) { printf("saveData: Unable to connect %s\nsaveDataTask not initialized\n", fs); return -1; } else { - if (ca_add_event(DBR_STRING, file_system_chid, - fileSystemMonitor, NULL, NULL)!=ECA_NORMAL) { + evid id; + + if (ca_create_subscription(DBR_CHAR, COMPONENT_STRING_SIZE, file_system_chid, DBE_VALUE, + fileSystemMonitor, NULL, &id)!=ECA_NORMAL) { printf("saveData: Can't monitor %s\nsaveDataTask not initialized\n", fs); ca_clear_channel(file_system_chid); return -1; @@ -1719,14 +1732,17 @@ LOCAL int connectSubdir(char* sd) char sd_disp[80]; epicsSnprintf(sd_disp, 80, "%s.DISP", sd); - ca_search(sd, &file_subdir_chid); - ca_search(sd_disp, &file_subdir_disp_chid); + + ca_create_channel(sd, NULL, NULL, 0, &file_subdir_chid); + //ca_search(sd_disp, &file_subdir_disp_chid); if (ca_pend_io(0.5)!=ECA_NORMAL) { printf("saveData: Unable to connect %s\nsaveDataTask not initialized\n", sd); return -1; } else { - if (ca_add_event(DBR_STRING, file_subdir_chid, - fileSubdirMonitor, NULL, NULL)!=ECA_NORMAL) { + evid id; + + if (ca_create_subscription(DBR_CHAR, COMPONENT_STRING_SIZE, file_subdir_chid, DBE_VALUE, + fileSubdirMonitor, NULL, &id)!=ECA_NORMAL) { printf("saveData: Can't monitor %s\nsaveDataTask not initialized\n", sd); ca_clear_channel(file_subdir_chid); return -1; @@ -1740,15 +1756,17 @@ LOCAL int connectBasename(char* bn) char bn_disp[80]; epicsSnprintf(bn_disp, 80, "%s.DISP", bn); - ca_search(bn, &file_basename_chid); - ca_search(bn_disp, &file_basename_disp_chid); + ca_create_channel(bn, NULL, NULL, 0, &file_basename_chid); + //ca_search(bn_disp, &file_basename_disp_chid); if (ca_pend_io(0.5)!=ECA_NORMAL) { printf("saveData: Unable to connect %s\n", bn); file_basename_chid = NULL; return 0; } else { - if (ca_add_event(DBR_STRING, file_basename_chid, - fileBasenameMonitor, NULL, NULL)!=ECA_NORMAL) { + evid id; + + if (ca_create_subscription(DBR_CHAR, COMPONENT_STRING_SIZE, file_basename_chid, DBE_VALUE, + fileBasenameMonitor, NULL, &id)!=ECA_NORMAL) { printf("saveData: Can't monitor %s\n", bn); ca_clear_channel(file_basename_chid); file_basename_disp_chid = NULL; @@ -1803,24 +1821,29 @@ LOCAL int connectRetryPVs(char *prefix) { char pvName[PVNAME_STRINGSZ]; - strncpy(pvName, prefix, PVNAME_STRINGSZ); - strncat(pvName, "saveData_currRetries", PVNAME_STRINGSZ-strlen(pvName)); + strncpy(pvName, prefix, PVNAME_STRINGSZ - 1); + pvName[PVNAME_STRINGSZ - 1] = '\0'; + strncat(pvName, "saveData_currRetries", PVNAME_STRINGSZ - 1 - strlen(pvName)); ca_search(pvName, &currRetries_chid); - strncpy(pvName, prefix, PVNAME_STRINGSZ); - strncat(pvName, "saveData_maxAllowedRetries", PVNAME_STRINGSZ-strlen(pvName)); + strncpy(pvName, prefix, PVNAME_STRINGSZ - 1); + pvName[PVNAME_STRINGSZ - 1] = '\0'; + strncat(pvName, "saveData_maxAllowedRetries", PVNAME_STRINGSZ - 1 - strlen(pvName)); ca_search(pvName, &maxAllowedRetries_chid); - strncpy(pvName, prefix, PVNAME_STRINGSZ); - strncat(pvName, "saveData_totalRetries", PVNAME_STRINGSZ-strlen(pvName)); + strncpy(pvName, prefix, PVNAME_STRINGSZ - 1); + pvName[PVNAME_STRINGSZ - 1] = '\0'; + strncat(pvName, "saveData_totalRetries", PVNAME_STRINGSZ - 1 - strlen(pvName)); ca_search(pvName, &totalRetries_chid); - strncpy(pvName, prefix, PVNAME_STRINGSZ); - strncat(pvName, "saveData_retryWaitInSecs", PVNAME_STRINGSZ-strlen(pvName)); + strncpy(pvName, prefix, PVNAME_STRINGSZ - 1); + pvName[PVNAME_STRINGSZ - 1] = '\0'; + strncat(pvName, "saveData_retryWaitInSecs", PVNAME_STRINGSZ - 1 - strlen(pvName)); ca_search(pvName, &retryWaitInSecs_chid); - strncpy(pvName, prefix, PVNAME_STRINGSZ); - strncat(pvName, "saveData_abandonedWrites", PVNAME_STRINGSZ-strlen(pvName)); + strncpy(pvName, prefix, PVNAME_STRINGSZ - 1); + pvName[PVNAME_STRINGSZ - 1] = '\0'; + strncat(pvName, "saveData_abandonedWrites", PVNAME_STRINGSZ - 1 - strlen(pvName)); ca_search(pvName, &abandonedWrites_chid); if (ca_pend_io(0.5)!=ECA_NORMAL) { @@ -1846,7 +1869,7 @@ LOCAL void extraValCallback(struct event_handler_args eha) PV_NODE * pnode = eha.usr; long type = eha.type; long count = eha.count; - READONLY DBR_VAL * pval = eha.dbr; + const DBR_VAL * pval = eha.dbr; char *string; size_t size=0; @@ -1893,11 +1916,12 @@ LOCAL void extraValCallback(struct event_handler_args eha) LOCAL void extraDescCallback(struct event_handler_args eha) { PV_NODE * pnode = eha.usr; - READONLY DBR_VAL * pval = eha.dbr; + const DBR_VAL * pval = eha.dbr; epicsMutexLock(pnode->lock); - strncpy(pnode->desc, (char *)pval, MAX_STRING_SIZE); + strncpy(pnode->desc, (char *)pval, MAX_STRING_SIZE - 1); + pnode->desc[MAX_STRING_SIZE - 1] = '\0'; if (pnode->desc_chid) ca_clear_channel(pnode->desc_chid); epicsMutexUnlock(pnode->lock); @@ -2035,7 +2059,7 @@ LOCAL int initSaveDataTask() server_pathname[0]= '\0'; server_subdir= server_pathname; - strncpy(local_pathname, "/data/", 200); + strncpy(local_pathname, "/data/", COMPONENT_STRING_SIZE); local_subdir= &local_pathname[strlen(local_pathname)]; rf= req_open_file(req_file, req_macros); @@ -2105,6 +2129,7 @@ LOCAL int initSaveDataTask() if (req_readMacId(rf, buff1, PVNAME_STRINGSZ)==0) { printf("saveData: fullPathName pv name not defined\n"); } else { + strncat(buff1, ".$", PVNAME_STRINGSZ - strlen(buff1) - 1); ca_search(buff1, &full_pathname_chid); ca_pend_io(0.5); } @@ -2134,6 +2159,9 @@ LOCAL int initSaveDataTask() printf("saveData: fileSystem pv name not defined\n"); return -1; } + + strncat(buff1, ".$", PVNAME_STRINGSZ - strlen(buff1) - 1); + if (connectFileSystem(buff1)==-1) { printf("saveData: connectFileSystem(%s) failed \n", buff1); return -1; @@ -2149,6 +2177,9 @@ LOCAL int initSaveDataTask() printf("saveData: subDir pv name not defined\n"); return -1; } + + strncat(buff1, ".$", PVNAME_STRINGSZ - strlen(buff1) - 1); + if (connectSubdir(buff1)==-1) { printf("saveData: connectSubdir(%s) failed \n", buff1); return -1; @@ -2161,6 +2192,8 @@ LOCAL int initSaveDataTask() if (req_readMacId(rf, buff1, PVNAME_STRINGSZ)==0) { printf("saveData: baseName pv name not defined\n"); } else { + strncat(buff1, ".$", PVNAME_STRINGSZ - strlen(buff1) - 1); + if (connectBasename(buff1)==-1) { printf("saveData: failed to connect to baseName pv\n"); } @@ -2182,10 +2215,12 @@ LOCAL int initSaveDataTask() } else { buff2[0]= '\0'; } - strncpy(buff2, buff1, PVNAME_STRINGSZ); - strncat(buff2, ".AWAIT", PVNAME_STRINGSZ-strlen(buff2)); - strncpy(buff3, buff1, PVNAME_STRINGSZ); - strncat(buff3, ".AAWAIT", PVNAME_STRINGSZ-strlen(buff3)); + strncpy(buff2, buff1, PVNAME_STRINGSZ - 1); + buff2[PVNAME_STRINGSZ - 1] = '\0'; + strncat(buff2, ".AWAIT", PVNAME_STRINGSZ - 1 - strlen(buff2)); + strncpy(buff3, buff1, PVNAME_STRINGSZ - 1); + buff3[PVNAME_STRINGSZ - 1] = '\0'; + strncat(buff3, ".AAWAIT", PVNAME_STRINGSZ - 1 - strlen(buff3)); Debug2(2,"saveData: call connectScan(%s,%s)\n", buff1, buff2); connectScan(buff1, buff2, buff3); } @@ -2348,6 +2383,7 @@ LOCAL int writeScanRecInProgress(SCAN *pscan, epicsTimeStamp stamp, int isRetry) /* Attempt to open data file */ Debug1(3, "saveData:writeScanRecInProgress: Opening file '%s'\n", pscan->ffname); epicsTimeGetCurrent(&openTime); + fd = fopen(pscan->ffname, pscan->first_scan ? "wb+" : "rb+"); if ((fd==NULL) || (fileStatus(pscan->ffname) == ERROR)) { @@ -2908,7 +2944,8 @@ LOCAL void proc_scan_data(SCAN_TS_SHORT_MSG* pmsg) } /* Make file name */ if (scanFile_basename[0] == '\0') { - strncpy(scanFile_basename, ioc_prefix, BASENAME_SIZE); + strncpy(scanFile_basename, ioc_prefix, COMPONENT_STRING_SIZE - 1); + scanFile_basename[COMPONENT_STRING_SIZE - 1] = '\0'; } epicsSnprintf(pscan->fname, FNAMELEN, "%s%.4d.mda", scanFile_basename, (int)pscan->counter); #ifdef vxWorks @@ -2964,8 +3001,8 @@ LOCAL void proc_scan_data(SCAN_TS_SHORT_MSG* pmsg) pscan->name, pscan->scan_dim, pscan->dims_offset); pscan->nxt->regular_offset= pscan->regular_offset; #endif - strncpy(pscan->nxt->fname, pscan->fname, FNAMELEN); - strncpy(pscan->nxt->ffname, pscan->ffname, FNAMELEN); + epicsSnprintf(pscan->nxt->fname, sizeof(pscan->nxt->fname), "%s", pscan->fname); + epicsSnprintf(pscan->nxt->ffname, sizeof(pscan->nxt->ffname), "%s", pscan->ffname); } pscan->savedSeekPos = 0; @@ -3264,8 +3301,7 @@ LOCAL void proc_scan_pxsm(STRING_MSG* pmsg) { epicsTimeStamp now; - strncpy(pmsg->pdest, pmsg->string, MAX_STRING_SIZE-1); - pmsg->pdest[MAX_STRING_SIZE-1]='\0'; + epicsSnprintf(pmsg->pdest, MAX_STRING_SIZE, "%s", pmsg->string); epicsTimeGetCurrent(&now); DebugMsg2(2, "MSG_SCAN_PXSM(%s)= %f\n", pmsg->string, @@ -3471,7 +3507,8 @@ LOCAL void proc_scan_txnv(SCAN_INDEX_MSG* pmsg) pscan->txpv[i][0]='\0'; pscan->txpvRec[i][0]='\0'; } else { - strncpy(pscan->txpvRec[i], pscan->txpv[i], PVNAME_STRINGSZ); + strncpy(pscan->txpvRec[i], pscan->txpv[i], PVNAME_STRINGSZ - 1); + pscan->txpvRec[i][PVNAME_STRINGSZ - 1] = '\0'; len= strcspn(pscan->txpvRec[i], "."); pscan->txsc[i]= strncmp(&pscan->txpv[i][len], ".EXSC", 6); pscan->txpvRec[i][len]='\0'; @@ -3507,8 +3544,7 @@ LOCAL void proc_desc(STRING_MSG* pmsg) { epicsTimeStamp now; - strncpy(pmsg->pdest, pmsg->string, MAX_STRING_SIZE-1); - pmsg->pdest[MAX_STRING_SIZE-1]= '\0'; + epicsSnprintf(pmsg->pdest, MAX_STRING_SIZE, "%s", pmsg->string); epicsTimeGetCurrent(&now); DebugMsg2(2, "MSG_DESC(%s)= %f\n", pmsg->string, @@ -3519,8 +3555,7 @@ LOCAL void proc_egu(STRING_MSG* pmsg) { epicsTimeStamp now; - strncpy(pmsg->pdest, pmsg->string, 15); - pmsg->pdest[15]= '\0'; + epicsSnprintf(pmsg->pdest, 16, "%s", pmsg->string); epicsTimeGetCurrent(&now); DebugMsg2(2, "MSG_EGU(%s)= %f\n", pmsg->string, @@ -3550,7 +3585,7 @@ LOCAL void remount_file_system(char* filesystem) #ifdef vxWorks nfsUnmount("/data"); #endif - + file_system_state= FS_NOT_MOUNTED; save_status= STATUS_ACTIVE_FS_ERROR; @@ -3570,6 +3605,7 @@ LOCAL void remount_file_system(char* filesystem) /* extract the host name */ int i = 0; cout= hostname; + while ((*filesystem!='\0') && (*filesystem!='/') && i<40) { *(cout++)= *(filesystem++); i++; @@ -3601,11 +3637,11 @@ LOCAL void remount_file_system(char* filesystem) #endif if (file_system_state == FS_MOUNTED) { - if (filesystem[0] != '\0') - { - strncpy(server_pathname, filesystem, 200); - strncat(server_pathname, "/", 200-strlen(server_pathname)); - } + if (filesystem[0] != '\0') { + strncpy(server_pathname, filesystem, COMPONENT_STRING_SIZE - 1); + server_pathname[COMPONENT_STRING_SIZE - 1] = '\0'; + strncat(server_pathname, "/", COMPONENT_STRING_SIZE - 1 - strlen(server_pathname)); + } server_subdir= &server_pathname[strlen(server_pathname)]; if (checkRWpermission(path)!=OK) { @@ -3704,8 +3740,8 @@ LOCAL void proc_file_subdir(STRING_MSG* pmsg) LOCAL void proc_file_basename(STRING_MSG* pmsg) { - strncpy(scanFile_basename, pmsg->string, BASENAME_SIZE-1); - scanFile_basename[BASENAME_SIZE-1] = '\0'; + strncpy(scanFile_basename, pmsg->string, COMPONENT_STRING_SIZE-1); + scanFile_basename[COMPONENT_STRING_SIZE-1] = '\0'; DebugMsg1(2, "MSG_FILE_BASENAME(%s)\n", pmsg->string); } diff --git a/sscanApp/src/scanparmRecord.c b/sscanApp/src/scanparmRecord.c index 701182f..7b7ec6f 100644 --- a/sscanApp/src/scanparmRecord.c +++ b/sscanApp/src/scanparmRecord.c @@ -108,7 +108,7 @@ rset scanparmRSET = { }; epicsExportAddress(rset, scanparmRSET); -static void monitor(); +static void monitor(scanparmRecord *psr); static long init_record(dbCommon *pcommon, int pass) { @@ -272,8 +272,7 @@ static long process(dbCommon *pcommon) return(status); } -static void monitor(psr) -scanparmRecord *psr; +static void monitor(scanparmRecord *psr) { unsigned short monitor_mask; diff --git a/sscanApp/src/sscanRecord.c b/sscanApp/src/sscanRecord.c index 9f95a84..bdfdcbe 100644 --- a/sscanApp/src/sscanRecord.c +++ b/sscanApp/src/sscanRecord.c @@ -326,6 +326,8 @@ #include /* for enumStrings stuff */ #include /* for LT_EPICSBASE macro */ +#include +#include #include "epicsExport.h" #include "recDynLink.h" @@ -568,6 +570,24 @@ typedef struct detFields { epicsInt16 d_pr; /* D1 Display Precision */ } detFields; +/* + * Compile-time verification that the posFields and detFields structs match + * the field layout stride in the generated sscanRecord. If the DBD field + * order ever changes, these assertions will fail at compile time. + */ +STATIC_ASSERT(sizeof(posFields) == offsetof(sscanRecord, p2pp) - offsetof(sscanRecord, p1pp)); +STATIC_ASSERT(sizeof(detFields) == offsetof(sscanRecord, d02hr) - offsetof(sscanRecord, d01hr)); + +/* + * Helper macros for obtaining posFields/detFields pointers from a sscanRecord. + * Casting through (char *) + offsetof avoids GCC -Warray-bounds false positives + * that arise from casting &psscan->p1pp (an epicsFloat64*) to posFields*. + */ +#define POS_FIELDS(psscan) \ + ((posFields *)((char *)(psscan) + offsetof(sscanRecord, p1pp))) +#define DET_FIELDS(psscan) \ + ((detFields *)((char *)(psscan) + offsetof(sscanRecord, d01hr))) + /* calledBy values */ #define UNKNOWN 0x00 #define SPECIAL_PAUS 0x01 @@ -792,7 +812,7 @@ init_record(dbCommon *pcommon, int pass) /* Readbacks need double buffering. Allocate space and initialize */ /* Fill pointer and readback array pointers */ precPvt->validBuf = A_BUFFER; - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); for (i = 0; i < NUM_RDKS; i++, pPos++) { precPvt->posBufPtr[i].pBufA = (double *) calloc(psscan->mpts, sizeof(double)); @@ -809,7 +829,7 @@ init_record(dbCommon *pcommon, int pass) precPvt->nullArray = (float *) calloc(psscan->mpts, sizeof(float)); precPvt->nullArray2 = (float *) calloc(psscan->mpts, sizeof(float)); - pDet = (detFields *) & psscan->d01hr; + pDet = DET_FIELDS(psscan); for (i = 0; i < NUM_DET; i++, pDet++) { puserPvt = (recDynLinkPvt *) precPvt->caLinkStruct[D1_IN + i].puserPvt; if (i < 4) { @@ -941,16 +961,16 @@ process(dbCommon *pcommon) if (psscan->wcnt) {psscan->wcnt = 0; POST(&psscan->wcnt);} if (psscan->wtng) {psscan->wtng = 0; POST(&psscan->wtng);} if (numPosCb) { - sprintf(psscan->smsg, "NOTE: positioner still active"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "NOTE: positioner still active"); POST(&psscan->smsg); } else if (numTrigCb) { - sprintf(psscan->smsg, "NOTE: detector still active"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "NOTE: detector still active"); POST(&psscan->smsg); } else if (numAReadCb) { - sprintf(psscan->smsg, "NOTE: array-read still active"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "NOTE: array-read still active"); POST(&psscan->smsg); } else if (numGetCb) { - sprintf(psscan->smsg, "NOTE: outstanding getCallback(s)"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "NOTE: outstanding getCallback(s)"); POST(&psscan->smsg); } psscan->alrt = 0; POST(&psscan->alrt); @@ -992,7 +1012,7 @@ process(dbCommon *pcommon) */ psscan->dstate = sscanDSTATE_PACKED; POST(&psscan->dstate); psscan->await = 0; POST(&psscan->await); - sprintf(psscan->smsg, "Abandoning unsaved scan data"); POST(&psscan->smsg); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Abandoning unsaved scan data"); POST(&psscan->smsg); errlogPrintf("%s:process(): Abandoning unsaved scan data\n", psscan->name); } packData(psscan, 0); @@ -1018,13 +1038,13 @@ process(dbCommon *pcommon) if (psscan->xsc) { /* Make sure it's ok to go */ if (psscan->paus) { - sprintf(psscan->smsg, "Scan is paused ..."); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scan is paused ..."); POST(&psscan->smsg); /*precPvt->calledBy = UNKNOWN;*/ return(-1); } if (psscan->wtng) { - sprintf(psscan->smsg, "waiting for client ..."); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "waiting for client ..."); POST(&psscan->smsg); /*precPvt->calledBy = UNKNOWN;*/ return(-1); @@ -1075,9 +1095,9 @@ process(dbCommon *pcommon) numTrigCb, numAReadCb, numGetCb, psscan->xsc, psscan->pxsc); } if (psscan->paus) { - sprintf(psscan->smsg, "Scan is paused"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scan is paused"); } else { - sprintf(psscan->smsg, "Already busy! PTAG_CBs=%1d_%1d_%1d_%02d; CB=0x%x", numPosCb, + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Already busy! PTAG_CBs=%1d_%1d_%1d_%02d; CB=0x%x", numPosCb, numTrigCb, numAReadCb, numGetCb, precPvt->calledBy); } POST(&psscan->smsg); @@ -1089,7 +1109,7 @@ process(dbCommon *pcommon) /* Brand new scan */ if (psscan->busy) { - sprintf(psscan->smsg, "Still busy! PTAG_CBs=%1d_%1d_%1d_%02d; CB=0x%x", numPosCb, + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Still busy! PTAG_CBs=%1d_%1d_%1d_%02d; CB=0x%x", numPosCb, numTrigCb, numAReadCb, numGetCb, precPvt->calledBy); /*precPvt->calledBy = UNKNOWN;*/ return (status); @@ -1138,13 +1158,13 @@ process(dbCommon *pcommon) packData(psscan, 1); checkMonitors(psscan); } - sprintf(psscan->smsg, "Abort: waiting for callback(s)"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Abort: waiting for callback(s)"); POST(&psscan->smsg); /*precPvt->calledBy = UNKNOWN;*/ return(status); } else { if (strlen(psscan->smsg) == 0) { - sprintf(psscan->smsg, "Scan aborted by operator"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scan aborted by operator"); POST(&psscan->smsg); } if (psscan->wait) {psscan->wait = 0; POST(&psscan->wait);} @@ -1165,7 +1185,7 @@ process(dbCommon *pcommon) epicsMutexUnlock(precPvt->pvStatSem); if (badPv) { psscan->alrt = 1; POST(&psscan->alrt); - sprintf(psscan->smsg, "Lost connection to Control PV"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Lost connection to Control PV"); POST(&psscan->smsg); psscan->exsc = 0; POST(&psscan->exsc); psscan->xsc = 0; POST(&psscan->xsc); @@ -1185,7 +1205,7 @@ process(dbCommon *pcommon) if (psscan->dstate < sscanDSTATE_PACKED) { packData(psscan, 2); if (psscan->dstate < sscanDSTATE_PACKED) { - sprintf(psscan->smsg, "waiting for packData"); POST(&psscan->smsg); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "waiting for packData"); POST(&psscan->smsg); /*precPvt->calledBy = UNKNOWN;*/ return(status); } @@ -1219,7 +1239,7 @@ process(dbCommon *pcommon) if (psscan->busy && (psscan->faze == sscanFAZE_SCAN_DONE) && (psscan->dstate == sscanDSTATE_POSTED)) { psscan->busy = 0; POST(&psscan->busy); psscan->faze = sscanFAZE_IDLE; POST(&psscan->faze); - sprintf(psscan->smsg, "SCAN Complete"); POST(&psscan->smsg); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "SCAN Complete"); POST(&psscan->smsg); recGblFwdLink(psscan); if (sscanRecordDebug>=2) { epicsTimeGetCurrent(&timeCurrent); @@ -1351,14 +1371,14 @@ special(struct dbAddr *paddr, int after) if (psscan->exsc) { if (psscan->xsc) { /* redundant request to start scan */ - sprintf(psscan->smsg, "Already scanning"); POST(&psscan->smsg); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Already scanning"); POST(&psscan->smsg); return(-1); } else if (psscan->busy) { /* Not scanning, but not done either (saveData wait?) */ if (psscan->dstate == sscanDSTATE_SAVE_DATA_WAIT) { - sprintf(psscan->smsg, "Waiting for saveData"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Waiting for saveData"); } else { - sprintf(psscan->smsg, "Waiting for callback"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Waiting for callback"); } db_post_events(psscan, &psscan->smsg, DBE_VAL_LOG); return(-1); @@ -1439,21 +1459,21 @@ special(struct dbAddr *paddr, int after) /* EXSC == 0 */ if (psscan->xsc) { psscan->xsc = 0; POST(&psscan->xsc); - sprintf(psscan->smsg, "Aborting scan"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Aborting scan"); db_post_events(psscan, &psscan->smsg, DBE_VAL_LOG); return(0); } else if (psscan->faze != sscanFAZE_IDLE) { /* The first abort didn't succeed, or is taking too long */ psscan->kill++; errlogPrintf("%s:special(): Killing scan (kill=%1d/3).\n", psscan->name, psscan->kill); - sprintf(psscan->smsg, "Killing scan (kill=%1d/3)", psscan->kill); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Killing scan (kill=%1d/3)", psscan->kill); db_post_events(psscan, &psscan->smsg, DBE_VAL_LOG); /* Cancel any outstanding active timer */ if (precPvt->dlyCallback.timer) epicsTimerCancel(precPvt->dlyCallback.timer); return(0); } else { /* request to abort scan that is not active. (This is no longer an error 02/03/2012) */ - sprintf(psscan->smsg, "Scan record is idle"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scan record is idle"); db_post_events(psscan, &psscan->smsg, DBE_VAL_LOG); return(0); } @@ -1494,14 +1514,14 @@ special(struct dbAddr *paddr, int after) epicsMutexUnlock(precPvt->numCallbacksSem); - sprintf(psscan->smsg, "Scan pause rescinded"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scan pause rescinded"); POST(&psscan->smsg); if ((numTrigCb == 0) && (numPosCb == 0) && (numAReadCb == 0) && (numGetCb == 0)) { /* The P, T, R, or G callback that would have sent us to the next scan * phase came in while we were paused, so we must get the record processed. */ if (psscan->wtng || psscan->await) { - sprintf(psscan->smsg, "Waiting for client"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Waiting for client"); POST(&psscan->smsg); } else { precPvt->calledBy = SPECIAL_PAUS; @@ -1515,7 +1535,7 @@ special(struct dbAddr *paddr, int after) } else { /* Cancel any outstanding delayed unpause */ if (precPvt->dlyCallback.timer) epicsTimerCancel(precPvt->dlyCallback.timer); - sprintf(psscan->smsg, "Scan pause asserted"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scan pause asserted"); POST(&psscan->smsg); } } @@ -1622,10 +1642,10 @@ special(struct dbAddr *paddr, int after) if (psscan->wtng && (psscan->wcnt == 0)) { psscan->wtng = 0; POST(&psscan->wtng); if (psscan->paus) { - sprintf(psscan->smsg, "Wait end, but scan is paused ..."); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Wait end, but scan is paused ..."); POST(&psscan->smsg); } else { - sprintf(psscan->smsg, "Scanning ..."); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scanning ..."); POST(&psscan->smsg); precPvt->calledBy = SPECIAL_WAIT; (void) scanOnce((struct dbCommon *)psscan); @@ -1735,7 +1755,7 @@ special(struct dbAddr *paddr, int after) case (SPC_SC_MO): /* Step Mode changed for a positioner */ - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); for (i = 0; i < NUM_POS; i++, pPos++) { if (paddr->pfield == (void *) &pPos->p_sm) { /* Entering Table mode ? */ @@ -1745,7 +1765,7 @@ special(struct dbAddr *paddr, int after) zeroPosParms(psscan, (unsigned short) i); precPvt->prevSm[i] = pPos->p_sm; if (precPvt->tablePts[i] < psscan->npts) { - sprintf(psscan->smsg, "Pts in P%d Table < # of steps.", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Pts in P%d Table < # of steps.", i + 1); POST(&psscan->smsg); if (!psscan->alrt) { psscan->alrt = 1; POST(&psscan->alrt); @@ -1826,8 +1846,8 @@ static long cvt_dbaddr(struct dbAddr *paddr) { sscanRecord *psscan = (sscanRecord *) paddr->precord; - posFields *pPos = (posFields *) & psscan->p1pp; - detFields *pDet = (detFields *) & psscan->d01hr; + posFields *pPos = POS_FIELDS(psscan); + detFields *pDet = DET_FIELDS(psscan); int i, fieldIndex = dbGetFieldIndex(paddr); unsigned short numFieldsInGroup; @@ -2007,7 +2027,7 @@ put_array_info(struct dbAddr *paddr, long nNew) precPvt->tablePts[group] = nNew; if (nNew < psscan->npts) { - sprintf(psscan->smsg, "Pts in P%d Table < # of Steps.", group + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Pts in P%d Table < # of Steps.", group + 1); POST(&psscan->smsg); if (!psscan->alrt) { psscan->alrt = 1; POST(&psscan->alrt); @@ -2029,8 +2049,8 @@ static long get_units(struct dbAddr *paddr, char *units) { sscanRecord *psscan = (sscanRecord *) paddr->precord; - posFields *pPos = (posFields *) & psscan->p1pp; - detFields *pDet = (detFields *) & psscan->d01hr; + posFields *pPos = POS_FIELDS(psscan); + detFields *pDet = DET_FIELDS(psscan); int i, fieldIndex = dbGetFieldIndex(paddr); if (fieldIndex >= sscanRecordP1PP) { @@ -2055,8 +2075,8 @@ static long get_precision(const struct dbAddr *paddr, long *precision) { sscanRecord *psscan = (sscanRecord *) paddr->precord; - posFields *pPos = (posFields *) & psscan->p1pp; - detFields *pDet = (detFields *) & psscan->d01hr; + posFields *pPos = POS_FIELDS(psscan); + detFields *pDet = DET_FIELDS(psscan); int i, fieldIndex = dbGetFieldIndex(paddr); if (fieldIndex >= sscanRecordP1PP) { @@ -2083,8 +2103,8 @@ static long get_graphic_double(struct dbAddr *paddr, struct dbr_grDouble *pgd) { sscanRecord *psscan = (sscanRecord *) paddr->precord; - posFields *pPos = (posFields *) & psscan->p1pp; - detFields *pDet = (detFields *) & psscan->d01hr; + posFields *pPos = POS_FIELDS(psscan); + detFields *pDet = DET_FIELDS(psscan); int i, fieldIndex = dbGetFieldIndex(paddr); if (fieldIndex >= sscanRecordP1PP) { @@ -2133,8 +2153,8 @@ static void checkMonitors(sscanRecord *psscan) { recPvtStruct *precPvt = (recPvtStruct *) psscan->rpvt; - detFields *pDet = (detFields *) & psscan->d01hr; - posFields *pPos = (posFields *) & psscan->p1pp; + detFields *pDet = DET_FIELDS(psscan); + posFields *pPos = POS_FIELDS(psscan); epicsTimeStamp timeCurrent; int i, end_of_scan; @@ -2170,11 +2190,15 @@ checkMonitors(sscanRecord *psscan) } } - if (psscan->pcpt != psscan->cpt) { - POST(&psscan->cpt); - psscan->pcpt = psscan->cpt; - if (psscan->cpt) POST(&psscan->val); - } + } + + /* Always post CPT when it changes -- not rate-limited, since CPT only + * changes once per scan point and clients depend on it for progress. + */ + if (psscan->pcpt != psscan->cpt) { + POST(&psscan->cpt); + psscan->pcpt = psscan->cpt; + if (psscan->cpt) POST(&psscan->val); } end_of_scan = (psscan->dstate == sscanDSTATE_PACKED); @@ -2400,7 +2424,7 @@ delayCallback(CALLBACK *pCB) if (sscanRecordDebug > 10) errlogPrintf("%s:delayCallback:entry\n", psscan->name); if (psscan->wcnt) { psscan->wtng = 1; POST(&psscan->wtng); - sprintf(psscan->smsg, "Waiting for client"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Waiting for client"); POST(&psscan->smsg); } else { precPvt->calledBy |= DELAY; @@ -2430,7 +2454,7 @@ notifyCallback(recDynLink * precDynLink) if (psscan->faze == sscanFAZE_IDLE) { /* we must have been aborted */ - sprintf(psscan->smsg, "callback while scan record is idle"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "callback while scan record is idle"); POST(&psscan->smsg); return; } @@ -2443,7 +2467,7 @@ notifyCallback(recDynLink * precDynLink) if (sscanRecordDebug >= 5) errlogPrintf("%s:notifyCallback: FATAL_ERROR, ending scan\n", psscan->name); psscan->xsc = 0; POST(&psscan->xsc); psscan->exsc = 0; POST(&psscan->exsc); - sprintf(psscan->smsg, "Scan aborted by notifyCallback"); POST(&psscan->smsg); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scan aborted by notifyCallback"); POST(&psscan->smsg); psscan->alrt = 1; POST(&psscan->alrt); /* Probably shouldn't say the scan is done when limit trouble is * encountered, because multidimensional scan could appear to be acquiring @@ -2460,14 +2484,14 @@ notifyCallback(recDynLink * precDynLink) epicsMutexUnlock(precPvt->numCallbacksSem); if (numTrigCb == 0) { if (psscan->paus) { - sprintf(psscan->smsg, "Scan paused by operator"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scan paused by operator"); POST(&psscan->smsg); return; } if (psscan->ddly < .001) { if (psscan->wcnt) { psscan->wtng = 1; POST(&psscan->wtng); - sprintf(psscan->smsg, "Waiting for client"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Waiting for client"); POST(&psscan->smsg); } else { precPvt->calledBy = NOTIFY_TRIG; @@ -2486,7 +2510,7 @@ notifyCallback(recDynLink * precDynLink) epicsMutexUnlock(precPvt->numCallbacksSem); if (numAReadCb == 0) { if (psscan->paus) { - sprintf(psscan->smsg, "Scan paused by operator"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scan paused by operator"); POST(&psscan->smsg); return; } @@ -2501,7 +2525,7 @@ notifyCallback(recDynLink * precDynLink) epicsMutexUnlock(precPvt->numCallbacksSem); if (numPosCb == 0) { if (psscan->paus) { - sprintf(psscan->smsg, "Scan paused by operator"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scan paused by operator"); POST(&psscan->smsg); return; } @@ -2572,7 +2596,7 @@ userGetCallback(recDynLink * precDynLink) if (numGetCb == 0) { if (psscan->paus) { - sprintf(psscan->smsg, "Scan paused by operator"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scan paused by operator"); POST(&psscan->smsg); return; } @@ -2600,8 +2624,8 @@ pvSearchCallback(recDynLink * precDynLink) recDynLinkPvt *puserPvt = (recDynLinkPvt *) precDynLink->puserPvt; sscanRecord *psscan = puserPvt->psscan; recPvtStruct *precPvt = (recPvtStruct *) psscan->rpvt; - posFields *pPos = (posFields *) & psscan->p1pp; - detFields *pDet = (detFields *) & psscan->d01hr; + posFields *pPos = POS_FIELDS(psscan); + detFields *pDet = DET_FIELDS(psscan); unsigned short linkIndex = puserPvt->linkIndex; unsigned short pvIndex = linkIndex % NUM_PVS; unsigned short detIndex, rdbkIndex; @@ -2728,7 +2752,8 @@ pvSearchCallback(recDynLink * precDynLink) status = dbGet(puserPvt->pAddr, DBR_FLOAT, precPvt->pDynLinkInfo, &options, &nRequest, NULL); if (status == OK) { - strcpy(pPos->p_eu, precPvt->pDynLinkInfo->units); + strncpy(pPos->p_eu, precPvt->pDynLinkInfo->units, sizeof(pPos->p_eu)); + pPos->p_eu[sizeof(pPos->p_eu) - 1] = '\0'; #if LT_EPICSBASE(3,14,10,0) pPos->p_pr = precPvt->pDynLinkInfo->precision; #else @@ -2756,7 +2781,7 @@ pvSearchCallback(recDynLink * precDynLink) nelem = puserPvt->pAddr->no_elements; } if (nelem > 1) { - sprintf(psscan->smsg, "Array-valued positioner read-back"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Array-valued positioner read-back"); POST(&psscan->smsg); } @@ -2791,7 +2816,8 @@ pvSearchCallback(recDynLink * precDynLink) status = dbGet(puserPvt->pAddr, DBR_FLOAT, precPvt->pDynLinkInfo, &options, &nRequest, NULL); if (status == OK) { - strcpy(pDet->d_eu, precPvt->pDynLinkInfo->units); + strncpy(pDet->d_eu, precPvt->pDynLinkInfo->units, sizeof(pDet->d_eu)); + pDet->d_eu[sizeof(pDet->d_eu) - 1] = '\0'; #if LT_EPICSBASE(3,14,10,0) pPos->p_pr = precPvt->pDynLinkInfo->precision; #else @@ -2814,7 +2840,7 @@ pvSearchCallback(recDynLink * precDynLink) } if (nelem > 1) { - sprintf(psscan->smsg, "Array-valued detector"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Array-valued detector"); POST(&psscan->smsg); } /* @@ -2887,7 +2913,7 @@ pvSearchCallback(recDynLink * precDynLink) if (sscanRecordDebug >= 2) errlogPrintf("%s:pvSearchCallback: pending scan was aborted\n", psscan->name); psscan->faze = sscanFAZE_IDLE; POST(&psscan->faze); - sprintf(psscan->smsg, "Scan aborted"); POST(&psscan->smsg); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scan aborted"); POST(&psscan->smsg); } else { /* Have any of the positioners involved in the scan still not called back with a value? */ @@ -2907,7 +2933,7 @@ pvSearchCallback(recDynLink * precDynLink) precPvt->calledBy = PVSEARCH; if (sscanRecordDebug) { - pPos = (posFields *) &psscan->p1pp; + pPos = POS_FIELDS(psscan); errlogPrintf("%s:pvSearchCallback: scan pending - call scanOnce() p1cv=%f\n", psscan->name, pPos->p_cv); } scanOnce((struct dbCommon *)psscan); @@ -2926,7 +2952,7 @@ posMonCallback(recDynLink * precDynLink) recPvtStruct *precPvt = (recPvtStruct *) psscan->rpvt; unsigned short linkIndex = puserPvt->linkIndex; unsigned short pvIndex = linkIndex % NUM_PVS; - posFields *pPos = (posFields *) & psscan->p1pp + pvIndex; + posFields *pPos = POS_FIELDS(psscan) + pvIndex; long status; size_t nRequest = 1; unsigned short *pPvStat = &psscan->p1nv + pvIndex; @@ -2979,7 +3005,7 @@ posMonCallbackGetCB(recDynLink * precDynLink) recPvtStruct *precPvt = (recPvtStruct *) psscan->rpvt; unsigned short linkIndex = puserPvt->linkIndex; unsigned short pvIndex = linkIndex % NUM_PVS; - posFields *pPos = (posFields *) & psscan->p1pp + pvIndex; + posFields *pPos = POS_FIELDS(psscan) + pvIndex; long i; size_t nRequest = 1; unsigned short *pPvStat = &psscan->p1nv + pvIndex; @@ -3013,7 +3039,7 @@ posMonCallbackGetCB(recDynLink * precDynLink) if (sscanRecordDebug) errlogPrintf("%s:posMonCallbackGetCB: pending scan was aborted\n", psscan->name); psscan->faze = sscanFAZE_IDLE; POST(&psscan->faze); - sprintf(psscan->smsg, "Scan aborted"); POST(&psscan->smsg); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scan aborted"); POST(&psscan->smsg); epicsMutexUnlock(precPvt->pvStatSem); return; } @@ -3038,7 +3064,7 @@ posMonCallbackGetCB(recDynLink * precDynLink) precPvt->calledBy = POSMON; if (sscanRecordDebug) { - pPos = (posFields *) &psscan->p1pp; + pPos = POS_FIELDS(psscan); errlogPrintf("%s:posMonCallbackGetCB: scan pending - call scanOnce() p1cv=%f\n", psscan->name, pPos->p_cv); } scanOnce((struct dbCommon *)psscan); @@ -3242,7 +3268,7 @@ initScan(sscanRecord *psscan) /* Then calculate the starting position */ precPvt->haveFlyModePositioner = precPvt->flying = 0; /* clear haveFlyModePositioner flag */ pPvStat = &psscan->p1nv; - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); for (i = 0; i < precPvt->valPosPvs; i++, pPos++, pPvStat++) { if (*pPvStat == PV_OK) { /* Figure out starting positions for each positioner */ @@ -3261,11 +3287,11 @@ initScan(sscanRecord *psscan) if ((psscan->bsnv == PV_OK) && (psscan->faze == sscanFAZE_INIT_SCAN)) { psscan->faze = sscanFAZE_BEFORE_SCAN; POST(&psscan->faze); - sprintf(psscan->smsg, "Before Scan FLNK ..."); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Before Scan FLNK ..."); POST(&psscan->smsg); } else { psscan->faze = sscanFAZE_MOVE_MOTORS; POST(&psscan->faze); - sprintf(psscan->smsg, "Scanning ..."); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scanning ..."); POST(&psscan->smsg); } /* request callback to do dbPutFields */ @@ -3277,8 +3303,8 @@ static void contScan(sscanRecord *psscan) { recPvtStruct *precPvt = (recPvtStruct *) psscan->rpvt; - posFields *pPos = (posFields *) & psscan->p1pp; - detFields *pDet = (detFields *) & psscan->d01hr; + posFields *pPos = POS_FIELDS(psscan); + detFields *pDet = DET_FIELDS(psscan); recDynLinkPvt *puserPvt; epicsTimeStamp currentTime; unsigned short *pPvStat; @@ -3319,7 +3345,7 @@ contScan(sscanRecord *psscan) if ((pPos->r_dl > 0) && (fabs(pPos->p_dv - pPos->r_cv) > pPos->r_dl)) { - sprintf(psscan->smsg, "SCAN Aborted: P%1d Error > delta", i+1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "SCAN Aborted: P%1d Error > delta", i+1); POST(&psscan->smsg); precPvt->scanErr = 1; errlogPrintf("%s: P%1d Error > delta. Ending scan.\n", psscan->name, i+1); @@ -3329,7 +3355,7 @@ contScan(sscanRecord *psscan) (fabs(pPos->p_dv - pPos->r_cv) > fabs(pPos->p_si * NINT(pPos->r_dl))) ) { - sprintf(psscan->smsg, "SCAN Aborted: P%1d Error > stepsize", i+1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "SCAN Aborted: P%1d Error > stepsize", i+1); POST(&psscan->smsg); precPvt->scanErr = 1; errlogPrintf("%s: P%1d Error > stepsize. Ending scan.\n", psscan->name, i+1); @@ -3341,7 +3367,7 @@ contScan(sscanRecord *psscan) if (precPvt->haveFlyModePositioner && !precPvt->flying) { /* determine target position for fly-mode positioners. */ - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); pPvStat = &psscan->p1nv; for (i = 0; i < precPvt->valPosPvs; i++, pPos++, pPvStat++) { if ((*pPvStat == PV_OK) && @@ -3383,7 +3409,7 @@ contScan(sscanRecord *psscan) /* Positioner readbacks */ pPvStat = &psscan->r1nv; pPvStatPos = &psscan->p1nv; - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); for (i = 0; i < NUM_POS; i++, pPos++, pPvStatPos++, pPvStat++) { /* if readback PV is OK, use that value */ puserPvt = precPvt->caLinkStruct[i + NUM_POS].puserPvt; @@ -3400,7 +3426,7 @@ contScan(sscanRecord *psscan) /* Detectors */ pPvStat = &psscan->d01nv; - pDet = (detFields *) & psscan->d01hr; + pDet = DET_FIELDS(psscan); for (i = 0; i < precPvt->valDetPvs; i++, pDet++, pPvStat++) { if (precPvt->acqDet[i] && (precPvt->detBufPtr[i].pFill != NULL)) { if (*pPvStat == PV_OK) { @@ -3439,7 +3465,7 @@ contScan(sscanRecord *psscan) /* from RxCV or PxDV or TIME */ pPvStat = &psscan->r1nv; pPvStatPos = &psscan->p1nv; - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); for (i = 0; i < NUM_POS; i++, pPos++, pPvStatPos++, pPvStat++) { /* if readback PV is OK, use that value */ puserPvt = precPvt->caLinkStruct[i + NUM_POS].puserPvt; @@ -3484,7 +3510,7 @@ contScan(sscanRecord *psscan) /* read each valid detector PV, place data in buffered array */ status = 0; pPvStat = &psscan->d01nv; - pDet = (detFields *) & psscan->d01hr; + pDet = DET_FIELDS(psscan); for (i = 0; i < precPvt->valDetPvs; i++, pDet++, pPvStat++) { if (precPvt->acqDet[i] && (precPvt->detBufPtr[i].pFill != NULL)) { if (*pPvStat == PV_OK) { @@ -3532,7 +3558,7 @@ contScan(sscanRecord *psscan) /* Has number of points been reached ? */ if (psscan->cpt < (psscan->npts)) { /* determine next desired position for each positioner */ - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); pPvStat = &psscan->p1nv; /* Figure out next position for non-fly-mode positioners. */ @@ -3598,8 +3624,8 @@ static void readArrays(sscanRecord *psscan) { recPvtStruct *precPvt = (recPvtStruct *) psscan->rpvt; - posFields *pPos = (posFields *) & psscan->p1pp; - detFields *pDet = (detFields *) & psscan->d01hr; + posFields *pPos = POS_FIELDS(psscan); + detFields *pDet = DET_FIELDS(psscan); recDynLinkPvt *puserPvt; unsigned short *pPvStat; unsigned short *pPvStatPos; @@ -3632,7 +3658,7 @@ readArrays(sscanRecord *psscan) */ pPvStat = &psscan->r1nv; pPvStatPos = &psscan->p1nv; - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); for (i = 0; i < NUM_POS; i++, pPos++, pPvStatPos++, pPvStat++) { /* if positioner-readback PV is OK, use it */ puserPvt = precPvt->caLinkStruct[i + NUM_POS].puserPvt; @@ -3659,7 +3685,7 @@ readArrays(sscanRecord *psscan) /* Queue reads for array-valued detectors, if any. */ status = 0; pPvStat = &psscan->d01nv; - pDet = (detFields *) & psscan->d01hr; + pDet = DET_FIELDS(psscan); for (i = 0; i < precPvt->valDetPvs; i++, pDet++, pPvStat++) { if (precPvt->acqDet[i] && (precPvt->detBufPtr[i].pFill != NULL)) { puserPvt = precPvt->caLinkStruct[i + D1_IN].puserPvt; @@ -3702,7 +3728,7 @@ readArrays(sscanRecord *psscan) /* Read array-valued positioner readbacks, if any */ pPvStat = &psscan->r1nv; pPvStatPos = &psscan->p1nv; - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); for (i = 0; i < NUM_POS; i++, pPos++, pPvStatPos++, pPvStat++) { pDbuff = precPvt->posBufPtr[i].pFill; /* if readback PV is OK, use that value */ @@ -3746,7 +3772,7 @@ readArrays(sscanRecord *psscan) addToPrev = (psscan->acqm == sscanACQM_ADD) || ((psscan->acqm == sscanACQM_ACC) && (precPvt->prevACQM == sscanACQM_ACC)); pPvStat = &psscan->d01nv; - pDet = (detFields *) & psscan->d01hr; + pDet = DET_FIELDS(psscan); for (i = 0; i < precPvt->valDetPvs; i++, pDet++, pPvStat++) { pFbuff = addToPrev ? (float *)precPvt->dataBuffer : precPvt->detBufPtr[i].pFill; if (precPvt->acqDet[i] && (precPvt->detBufPtr[i].pFill != NULL)) { @@ -3812,7 +3838,7 @@ static void copyLastPoint(sscanRecord *psscan, long pointNumber, long copyTo) else copyTo = MIN(copyTo, psscan->mpts); pointNumber = MAX(0, pointNumber); - pDet = (detFields *) & psscan->d01hr; + pDet = DET_FIELDS(psscan); for (i = 0; i < precPvt->valDetPvs; i++, pDet++) { if (precPvt->acqDet[i]) { d = precPvt->detBufPtr[i].pFill[pointNumber]; @@ -3903,7 +3929,7 @@ packData(sscanRecord *psscan, int caller) } moveToRef = pos_ok && (psscan->cpt > 1) && precPvt->acqDet[psscan->refd - 1]; if (!moveToRef) { - sprintf(psscan->smsg, "Can't move to %s ", sscanPASM_strings[psscan->pasm]); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Can't move to %s ", sscanPASM_strings[psscan->pasm]); POST(&psscan->smsg); psscan->alrt = 1; POST(&psscan->alrt); } @@ -4013,7 +4039,7 @@ packData(sscanRecord *psscan, int caller) if ((markIndex >= 0) && (markIndex < (psscan->cpt))) found = 1; - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); for (i = 0; i < precPvt->valPosPvs; i++, pPos++) { pPBuf = precPvt->posBufPtr[i].pFill; pPos->p_dv = found ? pPBuf[markIndex] : pPos->p_pp; @@ -4025,7 +4051,7 @@ packData(sscanRecord *psscan, int caller) /* Do for all valid positioners */ pPBuf = NULL; pPvStat = &psscan->p1nv; - pPos = (posFields *) &psscan->p1pp; + pPos = POS_FIELDS(psscan); for (j=0; jposBufPtr[j].pFill; @@ -4055,10 +4081,10 @@ packData(sscanRecord *psscan, int caller) for (i=0, pf=precPvt->nullArray; i < psscan->cpt; i++) pf[i] = 0.; if (found) { - sprintf(psscan->smsg, "%s found.", sscanPASM_strings[psscan->pasm]); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "%s found.", sscanPASM_strings[psscan->pasm]); POST(&psscan->smsg); } else { - sprintf(psscan->smsg, "%s NOT found.", sscanPASM_strings[psscan->pasm]); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "%s NOT found.", sscanPASM_strings[psscan->pasm]); POST(&psscan->smsg); psscan->alrt = 1; POST(&psscan->alrt); } @@ -4135,7 +4161,7 @@ doPuts(CALLBACK *pCB) errlogPrintf("%s:doPuts:entry:faze='%s'\n", psscan->name, sscanFAZE_strings[psscan->faze]); if (psscan->paus) { - sprintf(psscan->smsg, "Scan paused by operator"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scan paused by operator"); POST(&psscan->smsg); return; } @@ -4174,7 +4200,7 @@ doPuts(CALLBACK *pCB) errlogPrintf("%s:doPuts:...TRIG_ARRAY_READ: notify in progress\n", psscan->name); } psscan->alrt = NOTIFY_IN_PROGRESS; POST(&psscan->alrt); - sprintf(psscan->smsg, "Array-read trigger %d is busy", i+1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Array-read trigger %d is busy", i+1); POST(&psscan->smsg); } } @@ -4202,7 +4228,7 @@ doPuts(CALLBACK *pCB) epicsMutexUnlock(precPvt->numCallbacksSem); if (status == NOTIFY_IN_PROGRESS) { psscan->alrt = NOTIFY_IN_PROGRESS; POST(&psscan->alrt); - sprintf(psscan->smsg, "Before-scan link is busy"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Before-scan link is busy"); POST(&psscan->smsg); } } @@ -4228,7 +4254,7 @@ doPuts(CALLBACK *pCB) errlogPrintf("%s:doPuts:MOVE_MOTORS - Point %ld\n", psscan->name, (long)psscan->cpt); } - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); pPvStat = &psscan->p1nv; /* @@ -4258,7 +4284,7 @@ doPuts(CALLBACK *pCB) epicsMutexUnlock(precPvt->numCallbacksSem); if (status == NOTIFY_IN_PROGRESS) { psscan->alrt = NOTIFY_IN_PROGRESS; POST(&psscan->alrt); - sprintf(psscan->smsg, "Positioner %1d is already busy", i); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Positioner %1d is already busy", i); POST(&psscan->smsg); } } @@ -4288,7 +4314,7 @@ doPuts(CALLBACK *pCB) } /* On first point, launch fly-mode positioners, using Put instead of PutCallback. */ - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); pPvStat = &psscan->p1nv; for (i = 0; i < precPvt->valPosPvs; i++, pPos++, pPvStat++) { int flyMode = (pPos->p_sm == sscanP1SM_On_The_Fly) || (psscan->acqt == sscanACQT_1D_ARRAY); @@ -4342,7 +4368,7 @@ doPuts(CALLBACK *pCB) errlogPrintf("%s:doPuts:...TRIG_DETCTRS: notify in progress\n", psscan->name); } psscan->alrt = NOTIFY_IN_PROGRESS; POST(&psscan->alrt); - sprintf(psscan->smsg, "Detector %d is busy", i+1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Detector %d is busy", i+1); POST(&psscan->smsg); } } @@ -4363,14 +4389,14 @@ doPuts(CALLBACK *pCB) * if it had decremented numTriggerCallbacks to 0. */ if (psscan->paus) { - sprintf(psscan->smsg, "Scan paused by operator"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Scan paused by operator"); POST(&psscan->smsg); return; } if (psscan->ddly < .001) { if (psscan->wcnt) { psscan->wtng = 1; POST(&psscan->wtng); - sprintf(psscan->smsg, "Waiting for client"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Waiting for client"); POST(&psscan->smsg); } else { precPvt->calledBy = DO_PUTS_TRIG; @@ -4395,7 +4421,7 @@ doPuts(CALLBACK *pCB) if (psscan->pasm) { if (sscanRecordDebug >= 5) {errlogPrintf("%s:doPuts:RETRACE\n", psscan->name);} - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); pPvStat = &psscan->p1nv; for (i = 0; i < precPvt->valPosPvs; i++, pPos++, pPvStat++) { if (*pPvStat == PV_OK) { @@ -4433,7 +4459,7 @@ doPuts(CALLBACK *pCB) precPvt->numPositionerCallbacks--; epicsMutexUnlock(precPvt->numCallbacksSem); psscan->alrt = 1; POST(&psscan->alrt); - sprintf(psscan->smsg, "Can't retrace positioner %1d", i); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Can't retrace positioner %1d", i); POST(&psscan->smsg); } } @@ -4470,7 +4496,7 @@ doPuts(CALLBACK *pCB) precPvt->numPositionerCallbacks--; epicsMutexUnlock(precPvt->numCallbacksSem); psscan->alrt = 1; POST(&psscan->alrt); - sprintf(psscan->smsg, "Can't fire After-scan link"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Can't fire After-scan link"); POST(&psscan->smsg); } else { numPutCallbacks++; @@ -4520,7 +4546,7 @@ adjLinParms(paddr) { sscanRecord *psscan = (sscanRecord *) (paddr->precord); recPvtStruct *precPvt = (recPvtStruct *) psscan->rpvt; - struct posFields *pParms = (posFields *) & psscan->p1pp; + struct posFields *pParms = POS_FIELDS(psscan); int special_type = paddr->special; int i; @@ -4541,7 +4567,7 @@ adjLinParms(paddr) if (pParms->p_sm == sscanP1SM_Table) { /* if positioner is in table mode, zero parms and return */ zeroPosParms(psscan, (unsigned short) i); - sprintf(psscan->smsg, "Positioner #%1d is in Table Mode !", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Positioner #%1d is in Table Mode !", i + 1); psscan->alrt = 1; return; } @@ -4568,7 +4594,7 @@ adjLinParms(paddr) } if (psscan->npts > psscan->mpts) { psscan->npts = psscan->mpts; - sprintf(psscan->smsg, "P%1d Request Exceeded Maximum Points!", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%1d Request Exceeded Maximum Points!", i + 1); /* adjust changed field to be consistent */ pParms->p_sp = pParms->p_ep - (pParms->p_si * (psscan->npts - 1)); POST(&pParms->p_sp); @@ -4608,7 +4634,7 @@ adjLinParms(paddr) } if (psscan->npts > psscan->mpts) { psscan->npts = psscan->mpts; - sprintf(psscan->smsg, "P%1d Request Exceeded Maximum Points!", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%1d Request Exceeded Maximum Points!", i + 1); /* adjust changed field to be consistent */ pParms->p_sp = pParms->p_cp - ((pParms->p_si * MAX(1,(psscan->npts - 1))) / 2); POST(&pParms->p_sp); @@ -4649,7 +4675,7 @@ adjLinParms(paddr) } if (psscan->npts > psscan->mpts) { psscan->npts = psscan->mpts; - sprintf(psscan->smsg, "P%1d Request Exceeded Maximum Points !", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%1d Request Exceeded Maximum Points !", i + 1); /* adjust changed field to be consistent, avoid div by zero */ pParms->p_si = (pParms->p_ep - pParms->p_sp) / MAX(1,(psscan->npts - 1)); POST(&pParms->p_si); @@ -4689,7 +4715,7 @@ adjLinParms(paddr) } else { /* too constrained !! */ pParms->p_si = (pParms->p_ep - pParms->p_sp) / MAX(1,(psscan->npts - 1)); POST(&pParms->p_si); - sprintf(psscan->smsg, "P%1d SCAN Parameters Too Constrained !", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%1d SCAN Parameters Too Constrained !", i + 1); psscan->alrt = 1; return; } @@ -4714,7 +4740,7 @@ adjLinParms(paddr) } if (psscan->npts > psscan->mpts) { psscan->npts = psscan->mpts; - sprintf(psscan->smsg, "P%1d Request Exceeded Maximum Points !", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%1d Request Exceeded Maximum Points !", i + 1); /* adjust changed field to be consistent */ pParms->p_ep = pParms->p_sp + (pParms->p_si * MAX(1,(psscan->npts - 1))); POST(&pParms->p_ep); @@ -4753,7 +4779,7 @@ adjLinParms(paddr) } if (psscan->npts > psscan->mpts) { psscan->npts = psscan->mpts; - sprintf(psscan->smsg, "P%1d Request Exceeded Maximum Points !", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%1d Request Exceeded Maximum Points !", i + 1); /* adjust changed field to be consistent */ pParms->p_ep = pParms->p_cp + (pParms->p_si * MAX(1,(psscan->npts - 1)) / 2); POST(&pParms->p_ep); @@ -4770,7 +4796,7 @@ adjLinParms(paddr) } else { /* too constrained !! */ pParms->p_ep = pParms->p_sp + (MAX(1,(psscan->npts - 1)) * pParms->p_si); POST(&pParms->p_ep); - sprintf(psscan->smsg, "P%1d SCAN Parameters Too Constrained !", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%1d SCAN Parameters Too Constrained !", i + 1); psscan->alrt = 1; return; } @@ -4814,7 +4840,7 @@ adjLinParms(paddr) } if (psscan->npts > psscan->mpts) { psscan->npts = psscan->mpts; - sprintf(psscan->smsg, "P%1d Request Exceeded Maximum Points !", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%1d Request Exceeded Maximum Points !", i + 1); /* adjust changed field to be consistent */ pParms->p_cp = pParms->p_sp + (pParms->p_si * MAX(1,(psscan->npts - 1)) / 2); POST(&pParms->p_cp); @@ -4837,7 +4863,7 @@ adjLinParms(paddr) } if (psscan->npts > psscan->mpts) { psscan->npts = psscan->mpts; - sprintf(psscan->smsg, "P%1d Request Exceeded Maximum Points !", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%1d Request Exceeded Maximum Points !", i + 1); /* adjust changed field to be consistent */ pParms->p_cp = pParms->p_ep - (pParms->p_si * MAX(1,(psscan->npts - 1)) / 2); POST(&pParms->p_cp); @@ -4854,7 +4880,7 @@ adjLinParms(paddr) } else { /* too constrained !! */ pParms->p_cp = pParms->p_sp + (MAX(1,(psscan->npts - 1)) * pParms->p_si) / 2; POST(&pParms->p_cp); - sprintf(psscan->smsg, "P%1d SCAN Parameters Too Constrained !", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%1d SCAN Parameters Too Constrained !", i + 1); psscan->alrt = 1; return; } @@ -4879,7 +4905,7 @@ adjLinParms(paddr) } if (psscan->npts > psscan->mpts) { psscan->npts = psscan->mpts; - sprintf(psscan->smsg, "P%1d Request Exceeded Maximum Points !", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%1d Request Exceeded Maximum Points !", i + 1); /* adjust changed field to be consistent */ pParms->p_wd = (pParms->p_si * MAX(1,(psscan->npts - 1))); POST(&pParms->p_wd); @@ -4920,7 +4946,7 @@ adjLinParms(paddr) } if (psscan->npts > psscan->mpts) { psscan->npts = psscan->mpts; - sprintf(psscan->smsg, "P%1d Request Exceeded Maximum Points !", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%1d Request Exceeded Maximum Points !", i + 1); /* adjust changed field to be consistent */ pParms->p_wd = (pParms->p_si * MAX(1,(psscan->npts - 1))); POST(&pParms->p_wd); @@ -4943,7 +4969,7 @@ adjLinParms(paddr) } if (psscan->npts > psscan->mpts) { psscan->npts = psscan->mpts; - sprintf(psscan->smsg, "P%1d Request Exceeded Maximum Points !", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%1d Request Exceeded Maximum Points !", i + 1); /* adjust changed field to be consistent */ pParms->p_wd = (pParms->p_si * MAX(1,(psscan->npts - 1))); POST(&pParms->p_wd); @@ -4960,7 +4986,7 @@ adjLinParms(paddr) } else { /* too constrained !! */ pParms->p_wd = (pParms->p_ep - pParms->p_sp); POST(&pParms->p_wd); - sprintf(psscan->smsg, "P%1d SCAN Parameters Too Constrained !", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%1d SCAN Parameters Too Constrained !", i + 1); psscan->alrt = 1; return; } @@ -4990,7 +5016,7 @@ changedNpts(psscan) { recPvtStruct *precPvt = (recPvtStruct *) psscan->rpvt; - posFields *pParms = (posFields *) & psscan->p1pp; + posFields *pParms = POS_FIELDS(psscan); int i; unsigned short freezeState = 0, *pPvStat = &psscan->p1nv; @@ -5000,7 +5026,7 @@ changedNpts(psscan) /* Check if Positioner is in TABLE Mode */ if ((*pPvStat == PV_OK) && (pParms->p_sm == sscanP1SM_Table)) { if (precPvt->tablePts[i] < psscan->npts) { - sprintf(psscan->smsg, "Pts in P%d Table < # of Steps!", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Pts in P%d Table < # of Steps!", i + 1); if (!psscan->alrt) { psscan->alrt = 1; } @@ -5083,7 +5109,7 @@ changedNpts(psscan) /* The following freezeStates are known to be "Too Constrained" */ /* 9,11,13,14,15,25,26,27,28,29,30,31 */ default: /* too constrained !! */ - sprintf(psscan->smsg, "P%1d SCAN Parameters Too Constrained !", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%1d SCAN Parameters Too Constrained !", i + 1); psscan->alrt = 1; break; } @@ -5105,7 +5131,7 @@ checkScanLimits(psscan) recDynLinkPvt *puserPvt; recPvtStruct *precPvt = (recPvtStruct *) psscan->rpvt; - posFields *pPos = (posFields *) & psscan->p1pp; + posFields *pPos = POS_FIELDS(psscan); unsigned short *pPvStat = &psscan->p1nv; @@ -5131,7 +5157,7 @@ checkScanLimits(psscan) } /* Update "previous position" of positioners to use in relative mode */ pPvStat = &psscan->p1nv; - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); for (i = 0; i < NUM_POS; i++, pPos++, pPvStat++) { j = i+1; if (*pPvStat == PV_OK) { @@ -5152,7 +5178,7 @@ checkScanLimits(psscan) psscan->name, j, pPos->p_pp, status); if (status) { errlogPrintf("%s:checkScanLimits: could not get current value\n", psscan->name); - sprintf(psscan->smsg, "Can't get current position"); POST(&psscan->smsg); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Can't get current position"); POST(&psscan->smsg); if (!psscan->alrt) {psscan->alrt = 1; POST(&psscan->alrt);} return(ERROR); } @@ -5165,12 +5191,12 @@ checkScanLimits(psscan) /* First check if any valid pos'rs are in Table mode with insufficient points */ pPvStat = &psscan->p1nv; - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); for (i = 0; i < NUM_POS; i++, pPos++, pPvStat++) { if ((*pPvStat == PV_OK) && (pPos->p_sm == sscanP1SM_Table) && (precPvt->tablePts[i] < psscan->npts)) { - sprintf(psscan->smsg, "Pts in P%ld Table < # of Steps", i + 1); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "Pts in P%ld Table < # of Steps", i + 1); POST(&psscan->smsg); if (!psscan->alrt) {psscan->alrt = 1; POST(&psscan->alrt);} return (ERROR); @@ -5181,7 +5207,7 @@ checkScanLimits(psscan) /* Stop on first error */ pPvStat = &psscan->p1nv; - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); for (i = 0; i < NUM_POS; i++, pPos++, pPvStat++) { if (*pPvStat == PV_OK) { for (j = 0; j < psscan->npts; j++) { @@ -5223,11 +5249,11 @@ checkScanLimits(psscan) } if ((pPos->p_lr != 0) && (value < pPos->p_lr)) { - sprintf(psscan->smsg, "P%-ld Value < LO_Limit @ point %1ld", i + 1, j); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%-ld Value < LO_Limit @ point %1ld", i + 1, j); psscan->alrt = 1; return (ERROR); } else if ((pPos->p_hr != 0) && (value > pPos->p_hr)) { - sprintf(psscan->smsg, "P%-ld Value > HI_Limit @ point %1ld", i + 1, j); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "P%-ld Value > HI_Limit @ point %1ld", i + 1, j); psscan->alrt = 1; return (ERROR); } @@ -5236,7 +5262,7 @@ checkScanLimits(psscan) } /* No errors if we made it here ... */ - sprintf(psscan->smsg, "SCAN Values within limits"); + epicsSnprintf(psscan->smsg, sizeof(psscan->smsg), "SCAN Values within limits"); return (OK); } @@ -5269,7 +5295,7 @@ previewScan(psscan) /* Update "previous position" of positioners to use in relative mode */ pPvStat = &psscan->p1nv; - pPos = (posFields *) & psscan->p1pp; + pPos = POS_FIELDS(psscan); for (i = 0; i < NUM_POS; i++, pPos++, pPvStat++) { if (*pPvStat == PV_OK) { puserPvt = precPvt->caLinkStruct[i].puserPvt; @@ -5286,8 +5312,8 @@ previewScan(psscan) /* Run through entire scan for each valid positioner */ pPvStat = &psscan->p1nv; - pPos = (posFields *) & psscan->p1pp; - pDet = (detFields *) & psscan->d01hr; + pPos = POS_FIELDS(psscan); + pDet = DET_FIELDS(psscan); for (i = 0; i < NUM_POS; i++, pPos++, pPvStat++, pDet++) { if (*pPvStat == PV_OK) { /* must use the current buffer pointer */ @@ -5362,7 +5388,7 @@ saveFrzFlags(psscan) { recPvtStruct *precPvt = (recPvtStruct *) psscan->rpvt; - posFields *pPos = (posFields *) & psscan->p1pp; + posFields *pPos = POS_FIELDS(psscan); int i; @@ -5382,7 +5408,7 @@ savePosParms(sscanRecord * psscan, unsigned short i) { recPvtStruct *precPvt = (recPvtStruct *) psscan->rpvt; - posFields *pPos = (posFields *) & psscan->p1pp + i; + posFields *pPos = POS_FIELDS(psscan) + i; /* save state of linear scan parameters 0 */ /* Do this when in table mode so operator is not confused */ @@ -5397,7 +5423,7 @@ static void zeroPosParms(sscanRecord * psscan, unsigned short i) { - posFields *pPos = (posFields *) & psscan->p1pp + i; + posFields *pPos = POS_FIELDS(psscan) + i; /* set them to 0 */ /* Do this when in table mode so operator is not confused */ @@ -5413,7 +5439,7 @@ resetFrzFlags(psscan) sscanRecord *psscan; { - posFields *pPos = (posFields *) & psscan->p1pp; + posFields *pPos = POS_FIELDS(psscan); int i; /* reset each frzFlag, post monitor if changed */ @@ -5439,7 +5465,7 @@ restoreFrzFlags(psscan) { recPvtStruct *precPvt = (recPvtStruct *) psscan->rpvt; - posFields *pPos = (posFields *) & psscan->p1pp; + posFields *pPos = POS_FIELDS(psscan); int i; @@ -5466,7 +5492,7 @@ restorePosParms(sscanRecord * psscan, unsigned short i) { recPvtStruct *precPvt = (recPvtStruct *) psscan->rpvt; - posFields *pPos = (posFields *) & psscan->p1pp + i; + posFields *pPos = POS_FIELDS(psscan) + i; pPos->p_sp = precPvt->posParms[i].p_sp; POST(&pPos->p_sp); pPos->p_si = precPvt->posParms[i].p_si; POST(&pPos->p_si);