diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9585212 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,29 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.c text +*.cpp text +*.h text +*.hpp text +*.java text +*.sh text eol=lf +*.bat text +*.cmd text +*.db text +*.dbd text +*.template text +*.substitutions text +*.py text +*.rst text +*.uxf text + +# Declare files that will always have CRLF line endings on checkout. +*.sln text eol=crlf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary +*.class binary +*.vi binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..43df806 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +O.*/ +/db +/bin +/dbd +/include +/lib +/templates +test-reports +envPaths +cdCommands +dllPath.bat +runIOC.bat +runIOC.sh +relPaths.sh +*.tag +/data/ +/doc/ +*_info_positions.req +*_info_settings.req +*.py[cod] +__pycache__/ diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..45589b4 --- /dev/null +++ b/LICENCE @@ -0,0 +1,30 @@ +BSD 3-Clause License + +Copyright (c) 2023, Science and Technology Facilities Council +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..12a7415 --- /dev/null +++ b/Makefile @@ -0,0 +1,37 @@ +# Makefile for Asyn rknmntr support +# +# Created by wtn43451 on Wed Sep 27 14:08:57 2023 +# Based on the Asyn streamSCPI template + +TOP = . +include $(TOP)/configure/CONFIG + +DIRS += configure +DIRS += $(wildcard *Sup) +DIRS += $(wildcard *App) +DIRS += $(wildcard *Top) +DIRS += $(wildcard iocBoot) + +# The build order is controlled by these dependency rules: + +# All dirs except configure depend on configure +$(foreach dir, $(filter-out configure, $(DIRS)), \ + $(eval $(dir)_DEPEND_DIRS += configure)) + +# Any *App dirs depend on all *Sup dirs +$(foreach dir, $(filter %App, $(DIRS)), \ + $(eval $(dir)_DEPEND_DIRS += $(filter %Sup, $(DIRS)))) + +# Any *Top dirs depend on all *Sup and *App dirs +$(foreach dir, $(filter %Top, $(DIRS)), \ + $(eval $(dir)_DEPEND_DIRS += $(filter %Sup %App, $(DIRS)))) + +# iocBoot depends on all *App dirs +iocBoot_DEPEND_DIRS += $(filter %App,$(DIRS)) + +# Add any additional dependency rules here: + +include $(TOP)/configure/RULES_TOP + +ioctests: + .\system_tests\run_tests.bat diff --git a/configure/CONFIG b/configure/CONFIG new file mode 100644 index 0000000..c1a4703 --- /dev/null +++ b/configure/CONFIG @@ -0,0 +1,29 @@ +# CONFIG - Load build configuration data +# +# Do not make changes to this file! + +# Allow user to override where the build rules come from +RULES = $(EPICS_BASE) + +# RELEASE files point to other application tops +include $(TOP)/configure/RELEASE +-include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH).Common +ifdef T_A +-include $(TOP)/configure/RELEASE.Common.$(T_A) +-include $(TOP)/configure/RELEASE.$(EPICS_HOST_ARCH).$(T_A) +endif + +CONFIG = $(RULES)/configure +include $(CONFIG)/CONFIG + +# Override the Base definition: +INSTALL_LOCATION = $(TOP) + +# CONFIG_SITE files contain other build configuration settings +include $(TOP)/configure/CONFIG_SITE +-include $(TOP)/configure/CONFIG_SITE.$(EPICS_HOST_ARCH).Common +ifdef T_A + -include $(TOP)/configure/CONFIG_SITE.Common.$(T_A) + -include $(TOP)/configure/CONFIG_SITE.$(EPICS_HOST_ARCH).$(T_A) +endif + diff --git a/configure/CONFIG_SITE b/configure/CONFIG_SITE new file mode 100644 index 0000000..212485e --- /dev/null +++ b/configure/CONFIG_SITE @@ -0,0 +1,43 @@ +# CONFIG_SITE + +# Make any application-specific changes to the EPICS build +# configuration variables in this file. +# +# Host/target specific settings can be specified in files named +# CONFIG_SITE.$(EPICS_HOST_ARCH).Common +# CONFIG_SITE.Common.$(T_A) +# CONFIG_SITE.$(EPICS_HOST_ARCH).$(T_A) + +# CHECK_RELEASE controls the consistency checking of the support +# applications pointed to by the RELEASE* files. +# Normally CHECK_RELEASE should be set to YES. +# Set CHECK_RELEASE to NO to disable checking completely. +# Set CHECK_RELEASE to WARN to perform consistency checking but +# continue building even if conflicts are found. +CHECK_RELEASE = YES + +# Set this when you only want to compile this application +# for a subset of the cross-compiled target architectures +# that Base is built for. +#CROSS_COMPILER_TARGET_ARCHS = vxWorks-ppc32 + +# To install files into a location other than $(TOP) define +# INSTALL_LOCATION here. +#INSTALL_LOCATION= + +# Set this when the IOC and build host use different paths +# to the install location. This may be needed to boot from +# a Microsoft FTP server say, or on some NFS configurations. +#IOCS_APPL_TOP = + +# For application debugging purposes, override the HOST_OPT and/ +# or CROSS_OPT settings from base/configure/CONFIG_SITE +#HOST_OPT = NO +#CROSS_OPT = NO + +# These allow developers to override the CONFIG_SITE variable +# settings without having to modify the configure/CONFIG_SITE +# file itself. +-include $(TOP)/../CONFIG_SITE.local +-include $(TOP)/configure/CONFIG_SITE.local + diff --git a/configure/Makefile b/configure/Makefile new file mode 100644 index 0000000..9254309 --- /dev/null +++ b/configure/Makefile @@ -0,0 +1,8 @@ +TOP=.. + +include $(TOP)/configure/CONFIG + +TARGETS = $(CONFIG_TARGETS) +CONFIGS += $(subst ../,,$(wildcard $(CONFIG_INSTALLS))) + +include $(TOP)/configure/RULES diff --git a/configure/RELEASE b/configure/RELEASE new file mode 100644 index 0000000..7fc8365 --- /dev/null +++ b/configure/RELEASE @@ -0,0 +1,5 @@ +# optional extra local definitions here +-include $(TOP)/configure/RELEASE.private + +include $(TOP)/../../../ISIS_CONFIG +-include $(TOP)/../../../ISIS_CONFIG.$(EPICS_HOST_ARCH) diff --git a/configure/RULES b/configure/RULES new file mode 100644 index 0000000..6d56e14 --- /dev/null +++ b/configure/RULES @@ -0,0 +1,6 @@ +# RULES + +include $(CONFIG)/RULES + +# Library should be rebuilt because LIBOBJS may have changed. +$(LIBNAME): ../Makefile diff --git a/configure/RULES_DIRS b/configure/RULES_DIRS new file mode 100644 index 0000000..3ba269d --- /dev/null +++ b/configure/RULES_DIRS @@ -0,0 +1,2 @@ +#RULES_DIRS +include $(CONFIG)/RULES_DIRS diff --git a/configure/RULES_TOP b/configure/RULES_TOP new file mode 100644 index 0000000..d09d668 --- /dev/null +++ b/configure/RULES_TOP @@ -0,0 +1,3 @@ +#RULES_TOP +include $(CONFIG)/RULES_TOP + diff --git a/documentation/devrknmntr.html b/documentation/devrknmntr.html new file mode 100644 index 0000000..8640236 --- /dev/null +++ b/documentation/devrknmntr.html @@ -0,0 +1,78 @@ + + + + + + rknmntr Instrument Support + + + + +

Using rknmntr instrument support in an application

+ +

Several files need minor modifications to use rknmntr instrument support in +an application.

+
    +
  1. Add the full path to the rknmntr support directory to the + application configure/RELEASE file:
    + rknmntr=xxxx/modules/instrument/rknmntr/<release>
    +Where <release> is the release number of of the rknmntr support.
  2. +
  3. Add stream and asyn support to application database definition file
    + The application database definition file must include the database + definition files for the stream package and for any needed ASYN + drivers. There are two ways that this can be done: + +
  4. +
  5. Add the stream and asyn support libraries to the application
    + You must link the stream support library and the ASYN support library + with the application. Add the following lines:
    + xxx_LIBS += stream
    + xxx_LIBS += asyn
    + before the
    + xxx_LIBS += $(EPICS_BASE_IOC_LIBS)
    + in the application Makefile.
  6. +
  7. Load the rknmntr support database records in the application startup script:
    + cd $(rknmntr)      (cd rknmntr if using the vxWorks shell)
    + dbLoadRecords("db/devrknmntr.db,"P=<P>,R=<R>,PORT=<PORT>,A=<A>")
    + You'll have to provide appropriate values for the PV name prefixes + (<P> and <R>), the port name (<PORT>) and the device address + (<A>). The port name must match the value specified in + an ASYN drvxxxxxConfigure command. +
  8. +
+

Installation and Building

+After obtaining a copy of the distribution, it must be installed and built +for use at your site. +
    +
  1. Create an installation directory for the module. The path name + of this directory should end with modules/instrument/rknmntr.
  2. +
  3. Place the distribution file into this directory.
  4. +
  5. Execute the following commands:
    + cd modules/instrument/rknmntr
    + gunzip rknmntr<release>.tar.gz
    + tar xvf rknmntr<release>.tar
    + cd <release>
    +Where <release> is the release number of of the rknmntr support. +
  6. +
  7. Edit the configure/RELEASE file and set the paths to your + installation of EPICS base, stream and ASYN support modules.
  8. +
  9. Execute make in the top level directory.
  10. +
+ + + + diff --git a/rknmntrSup/Makefile b/rknmntrSup/Makefile new file mode 100644 index 0000000..11d94da --- /dev/null +++ b/rknmntrSup/Makefile @@ -0,0 +1,12 @@ +TOP=.. +include $(TOP)/configure/CONFIG +#======================================= + +# Install .dbd and .db files +DB += rknmntr.db +DB += RIKEN_TEMP_CALC.db +DB += test_PSU_PVs.db +DB += test_block_curr_PVs.db + +#======================================= +include $(TOP)/configure/RULES diff --git a/rknmntrSup/RIKEN_TEMP_CALC.substitutions b/rknmntrSup/RIKEN_TEMP_CALC.substitutions new file mode 100644 index 0000000..e2a0f56 --- /dev/null +++ b/rknmntrSup/RIKEN_TEMP_CALC.substitutions @@ -0,0 +1,86 @@ +# Macros: +# +# MAG - magnet name +# TAP - tap name +# GAIN - gain for the tap (needed for calculations) +# INITIAL_RES - initial resistance (needed for the calculations) + +file RIKEN_TEMP_CALC.template { + pattern { MAGNET, TAP, GAIN, INITIAL_RES } + + { "RQ1", "TAP01", "2", "7.7" } + { "RQ1", "TAP02", "2", "7.2" } + { "RQ1", "TAP03", "2", "7.1" } + { "RQ1", "TAP04", "2", "5.9" } + { "RQ1", "TAP05", "2", "6.0" } + { "RQ1", "TAP06", "2", "6.4" } + { "RQ1", "TAP07", "2", "6.5" } + { "RQ1", "TAP08", "2", "6.2" } + { "RQ1", "TAP09", "2", "5.8" } + { "RQ1", "TAP10", "2", "7.6" } + { "RQ1", "TAP11", "2", "7.0" } + { "RQ1", "TAP12", "2", "7.8" } + + { "RQ1", "TAP13", "2", "8.0" } + { "RQ1", "TAP14", "2", "6.9" } + { "RQ1", "TAP15", "2", "6.8" } + { "RQ1", "TAP16", "2", "6.5" } + { "RQ1", "TAP17", "2", "6.2" } + { "RQ1", "TAP18", "2", "6.1" } + { "RQ1", "TAP19", "2", "6.0" } + { "RQ1", "TAP20", "2", "6.4" } + { "RQ1", "TAP21", "2", "6.3" } + { "RQ1", "TAP22", "2", "6.8" } + { "RQ1", "TAP23", "2", "7.5" } + { "RQ1", "TAP24", "2", "7.5" } + + + { "RQ2", "TAP01", "5", "5.5" } + { "RQ2", "TAP02", "5", "3.7" } + { "RQ2", "TAP03", "5", "5.3" } + { "RQ2", "TAP04", "5", "3.8" } + { "RQ2", "TAP05", "5", "5.2" } + { "RQ2", "TAP06", "5", "4.8" } + { "RQ2", "TAP07", "5", "5.1" } + { "RQ2", "TAP08", "5", "4.9" } + { "RQ2", "TAP09", "5", "5.5" } + { "RQ2", "TAP10", "5", "5.1" } + { "RQ2", "TAP11", "5", "4.8" } + { "RQ2", "TAP12", "5", "5.3" } + + { "RQ2", "TAP13", "5", "3.8" } + { "RQ2", "TAP14", "5", "5.3" } + { "RQ2", "TAP15", "5", "3.7" } + { "RQ2", "TAP16", "5", "5.4" } + { "RQ2", "TAP17", "5", "6.1" } + { "RQ2", "TAP18", "5", "3.7" } + { "RQ2", "TAP19", "5", "5.3" } + { "RQ2", "TAP20", "5", "3.8" } + { "RQ2", "TAP21", "5", "5.2" } + { "RQ2", "TAP22", "5", "4.9" } + { "RQ2", "TAP23", "5", "5.0" } + { "RQ2", "TAP24", "5", "5.0" } + + { "RQ2", "TAP25", "5", "5.8" } + { "RQ2", "TAP26", "5", "5.0" } + { "RQ2", "TAP27", "5", "4.8" } + { "RQ2", "TAP28", "5", "5.2" } + { "RQ2", "TAP29", "5", "3.8" } + { "RQ2", "TAP30", "5", "5.4" } + { "RQ2", "TAP31", "5", "3.7" } + { "RQ2", "TAP32", "5", "5.4" } + + + { "RB1", "TAP01", "1.25", "16.0"} + { "RB1", "TAP02", "1.25", "16.0"} + { "RB1", "TAP03", "1.25", "16.1"} + { "RB1", "TAP04", "1.25", "16.0"} + { "RB1", "TAP05", "1.25", "16.1"} + { "RB1", "TAP06", "1.25", "16.0"} + { "RB1", "TAP07", "1.25", "16.0"} + { "RB1", "TAP08", "1.25", "16.1"} + { "RB1", "TAP09", "1.25", "16.0"} + { "RB1", "TAP10", "1.25", "16.1"} + { "RB1", "TAP11", "1.25", "16.0"} + { "RB1", "TAP12", "1.25", "16.0"} +} diff --git a/rknmntrSup/RIKEN_TEMP_CALC.template b/rknmntrSup/RIKEN_TEMP_CALC.template new file mode 100644 index 0000000..bb73b6e --- /dev/null +++ b/rknmntrSup/RIKEN_TEMP_CALC.template @@ -0,0 +1,65 @@ + +# Calculations for $(MAGNET) $(TAP) + +record(calc, "$(P)$(MAGNET):$(TAP):VOLT:RAW") { + field(DESC, "Raw Voltage at magnet $(MAGNET) at $(TAP)") + field(VAL, 0) + # This below only works if P prefix is the same machine as the one running SCHNDR IOC (must be RIKENFE) + field(INPA, "$(HOST)SCHNDR_01:$(MAGNET):TEMPMON:$(TAP) CP") + field(CALC, "A") + field(PREC, "2") + field(FLNK, "$(P)$(MAGNET):$(TAP):VOLT:ADC") + field(SDIS, "$(P)DISABLE") +} + +record(calc, "$(P)$(MAGNET):$(TAP):VOLT:ADC") { + field(DESC, "Digital Voltage at magnet $(MAGNET) at $(TAP)") + field(VAL, 0) + field(INPA, "$(P)$(MAGNET):$(TAP):VOLT:RAW") + # The tap value is a signed 12-bit integer measured by the PLC ADC which + # has a 10V reference. This needs to be converted back to a voltage + field(CALC, "((A/((2**12)-1))*10)") + field(PREC, "2") + field(EGU, "V") + field(FLNK, "$(P)$(MAGNET):$(TAP):VOLT") + field(SDIS, "$(P)DISABLE") +} + +record(calc, "$(P)$(MAGNET):$(TAP):VOLT") { + field(DESC, "Actual Voltage at magnet $(MAGNET) at $(TAP)") + field(VAL, 0) + field(INPA, "$(P)$(MAGNET):$(TAP):VOLT:ADC") + field(INPB, "$(GAIN)") + # The PLC measures the signal conditioned voltage and so needs to be divided + # by a gain to get the actual voltage at the magnet terminals + field(CALC, "(B#0)?A/B:0") + field(PREC, "2") + field(EGU, "V") + field(FLNK, "$(P)$(MAGNET):$(TAP):RES") + field(SDIS, "$(P)DISABLE") +} + +record(calc, "$(P)$(MAGNET):$(TAP):RES") { + field(DESC, "Resistance at magnet $(MAGNET) at $(TAP)") + field(VAL, 0) + field(INPA, "$(P)$(MAGNET):$(TAP):VOLT") + # This PV below references the block corresponding to magnet so it depends on correct block configuration + field(INPB, "$(HOST)CS:SB:$(MAGNET)_CURR") + field(CALC, "(B#0)?(A/B)*1000:0") # Convert to milliohm + field(PREC, "2") + field(EGU, "mOhm") + field(FLNK, "$(P)$(MAGNET):$(TAP):TEMP") + field(SDIS, "$(P)DISABLE") +} + +record(calc, "$(P)$(MAGNET):$(TAP):TEMP") { + field(DESC, "Temperature of magnet $(MAGNET) at $(TAP)") + field(VAL, 0) + field(INPA, "$(P)$(MAGNET):$(TAP):RES") + field(INPB, "$(INITIAL_RES)") + field(CALC, "(A#0&&B#0)?(((A/B)-1)/0.004041)+23:0") + field(PREC, "2") + field(EGU, "C") + info(archive, "VAL") + field(SDIS, "$(P)DISABLE") +} diff --git a/rknmntrSup/rknmntr.db b/rknmntrSup/rknmntr.db new file mode 100644 index 0000000..a737e7f --- /dev/null +++ b/rknmntrSup/rknmntr.db @@ -0,0 +1,9 @@ +record(bo, "$(P)DISABLE") +{ + field(DESC, "Disable comms") + field(PINI, "YES") + field(VAL, "$(DISABLE=0)") + field(OMSL, "supervisory") + field(ZNAM, "COMMS ENABLED") + field(ONAM, "COMMS DISABLED") +} diff --git a/rknmntrSup/test_PSU_PVs.substitutions b/rknmntrSup/test_PSU_PVs.substitutions new file mode 100644 index 0000000..39e1964 --- /dev/null +++ b/rknmntrSup/test_PSU_PVs.substitutions @@ -0,0 +1,79 @@ +file test_PSU_PVs.template { + pattern { MAGNET, TAP } + + { "RQ1", "TAP01" } + { "RQ1", "TAP02" } + { "RQ1", "TAP03" } + { "RQ1", "TAP04" } + { "RQ1", "TAP05" } + { "RQ1", "TAP06" } + { "RQ1", "TAP07" } + { "RQ1", "TAP08" } + { "RQ1", "TAP09" } + { "RQ1", "TAP10" } + { "RQ1", "TAP11" } + { "RQ1", "TAP12" } + + { "RQ1", "TAP13" } + { "RQ1", "TAP14" } + { "RQ1", "TAP15" } + { "RQ1", "TAP16" } + { "RQ1", "TAP17" } + { "RQ1", "TAP18" } + { "RQ1", "TAP19" } + { "RQ1", "TAP20" } + { "RQ1", "TAP21" } + { "RQ1", "TAP22" } + { "RQ1", "TAP23" } + { "RQ1", "TAP24" } + + + { "RQ2", "TAP01" } + { "RQ2", "TAP02" } + { "RQ2", "TAP03" } + { "RQ2", "TAP04" } + { "RQ2", "TAP05" } + { "RQ2", "TAP06" } + { "RQ2", "TAP07" } + { "RQ2", "TAP08" } + { "RQ2", "TAP09" } + { "RQ2", "TAP10" } + { "RQ2", "TAP11" } + { "RQ2", "TAP12" } + + { "RQ2", "TAP13" } + { "RQ2", "TAP14" } + { "RQ2", "TAP15" } + { "RQ2", "TAP16" } + { "RQ2", "TAP17" } + { "RQ2", "TAP18" } + { "RQ2", "TAP19" } + { "RQ2", "TAP20" } + { "RQ2", "TAP21" } + { "RQ2", "TAP22" } + { "RQ2", "TAP23" } + { "RQ2", "TAP24" } + + { "RQ2", "TAP25" } + { "RQ2", "TAP26" } + { "RQ2", "TAP27" } + { "RQ2", "TAP28" } + { "RQ2", "TAP29" } + { "RQ2", "TAP30" } + { "RQ2", "TAP31" } + { "RQ2", "TAP32" } + + + { "RB1", "TAP01" } + { "RB1", "TAP02" } + { "RB1", "TAP03" } + { "RB1", "TAP04" } + { "RB1", "TAP05" } + { "RB1", "TAP06" } + { "RB1", "TAP07" } + { "RB1", "TAP08" } + { "RB1", "TAP09" } + { "RB1", "TAP10" } + { "RB1", "TAP11" } + { "RB1", "TAP12" } +} diff --git a/rknmntrSup/test_PSU_PVs.template b/rknmntrSup/test_PSU_PVs.template new file mode 100644 index 0000000..459358e --- /dev/null +++ b/rknmntrSup/test_PSU_PVs.template @@ -0,0 +1,6 @@ +# Dummy records for testing $(MAGNET), $(TAP) + +record(ai, "$(P)SCHNDR_01:$(MAGNET):TEMPMON:$(TAP)") { + field(DESC, "Dummy PV for testing") +} + diff --git a/rknmntrSup/test_block_curr_PVs.substitutions b/rknmntrSup/test_block_curr_PVs.substitutions new file mode 100644 index 0000000..185ff1c --- /dev/null +++ b/rknmntrSup/test_block_curr_PVs.substitutions @@ -0,0 +1,7 @@ +file test_block_curr_PVs.template { + pattern { MAGNET } + + { "RQ1" } + { "RQ2" } + { "RB1" } +} diff --git a/rknmntrSup/test_block_curr_PVs.template b/rknmntrSup/test_block_curr_PVs.template new file mode 100644 index 0000000..4921518 --- /dev/null +++ b/rknmntrSup/test_block_curr_PVs.template @@ -0,0 +1,4 @@ + +record(ai, "$(P)CS:SB:$(MAGNET)_CURR") { + field(DESC, "Dummy PV for testing") +} diff --git a/system_tests/lewis_emulators/Rknmntr/__init__.py b/system_tests/lewis_emulators/Rknmntr/__init__.py new file mode 100644 index 0000000..ab27daf --- /dev/null +++ b/system_tests/lewis_emulators/Rknmntr/__init__.py @@ -0,0 +1,5 @@ +from .device import SimulatedRknmntr +from ..lewis_versions import LEWIS_LATEST + +framework_version = LEWIS_LATEST +__all__ = ['SimulatedRknmntr'] diff --git a/system_tests/lewis_emulators/Rknmntr/device.py b/system_tests/lewis_emulators/Rknmntr/device.py new file mode 100644 index 0000000..62060ed --- /dev/null +++ b/system_tests/lewis_emulators/Rknmntr/device.py @@ -0,0 +1,25 @@ +from collections import OrderedDict +from .states import DefaultState +from lewis.devices import StateMachineDevice + + +class SimulatedRknmntr(StateMachineDevice): + + def _initialize_data(self): + """ + Initialize all of the device's attributes. + """ + pass + + def _get_state_handlers(self): + return { + 'default': DefaultState(), + } + + def _get_initial_state(self): + return 'default' + + def _get_transition_handlers(self): + return OrderedDict([ + ]) + diff --git a/system_tests/lewis_emulators/Rknmntr/interfaces/__init__.py b/system_tests/lewis_emulators/Rknmntr/interfaces/__init__.py new file mode 100644 index 0000000..2425422 --- /dev/null +++ b/system_tests/lewis_emulators/Rknmntr/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import RknmntrStreamInterface + +__all__ = ['RknmntrStreamInterface'] diff --git a/system_tests/lewis_emulators/Rknmntr/interfaces/stream_interface.py b/system_tests/lewis_emulators/Rknmntr/interfaces/stream_interface.py new file mode 100644 index 0000000..a5676f2 --- /dev/null +++ b/system_tests/lewis_emulators/Rknmntr/interfaces/stream_interface.py @@ -0,0 +1,32 @@ +from lewis.adapters.stream import StreamInterface, Cmd +from lewis.utils.command_builder import CmdBuilder +from lewis.core.logging import has_log +from lewis.utils.replies import conditional_reply + + +@has_log +class RknmntrStreamInterface(StreamInterface): + + in_terminator = "\r\n" + out_terminator = "\r\n" + + def __init__(self): + super(RknmntrStreamInterface, self).__init__() + # Commands that we expect via serial during normal operation + self.commands = { + CmdBuilder(self.catch_all).arg("^#9.*$").build() # Catch-all command for debugging + } + + def handle_error(self, request, error): + """ + If command is not recognised print and error + + Args: + request: requested string + error: problem + + """ + self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) + + def catch_all(self, command): + pass diff --git a/system_tests/lewis_emulators/Rknmntr/states.py b/system_tests/lewis_emulators/Rknmntr/states.py new file mode 100644 index 0000000..e4ca48e --- /dev/null +++ b/system_tests/lewis_emulators/Rknmntr/states.py @@ -0,0 +1,5 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + pass diff --git a/system_tests/lewis_emulators/__init__.py b/system_tests/lewis_emulators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/system_tests/lewis_emulators/lewis_versions.py b/system_tests/lewis_emulators/lewis_versions.py new file mode 100644 index 0000000..0f6c45a --- /dev/null +++ b/system_tests/lewis_emulators/lewis_versions.py @@ -0,0 +1,2 @@ +LEWIS_1_3_0 = "1.3.0" +LEWIS_LATEST = LEWIS_1_3_0 diff --git a/system_tests/run_tests.bat b/system_tests/run_tests.bat new file mode 100644 index 0000000..56f9c25 --- /dev/null +++ b/system_tests/run_tests.bat @@ -0,0 +1,13 @@ +@echo off +REM Run this directory's tests using the IOC Testing Framework + +SET CurrentDir=%~dp0 + +call "%~dp0..\..\..\..\config_env.bat" + +set "PYTHONUNBUFFERED=1" + +REM Command line arguments always passed to the test script +SET ARGS=--test_and_emulator %~dp0 +call %PYTHON3% "%EPICS_KIT_ROOT%\support\IocTestFramework\master\run_tests.py" %ARGS% %* +IF %ERRORLEVEL% NEQ 0 EXIT /b %errorlevel% diff --git a/system_tests/tests/__init__.py b/system_tests/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/system_tests/tests/rknmntr.py b/system_tests/tests/rknmntr.py new file mode 100644 index 0000000..764407e --- /dev/null +++ b/system_tests/tests/rknmntr.py @@ -0,0 +1,95 @@ +import unittest + +from utils.channel_access import ChannelAccess +from utils.ioc_launcher import get_default_ioc_dir +from utils.test_modes import TestModes +from utils.testing import get_running_lewis_and_ioc, parameterized_list + +from parameterized import parameterized +import random + +DEVICE_PREFIX = "RKNMNTR_01" + + +IOCS = [ + { + "name": DEVICE_PREFIX, + "directory": get_default_ioc_dir("RKNMNTR"), + "macros": {}, + "emulator": "Rknmntr", + }, +] + + +TEST_MODES = [TestModes.RECSIM] + +MAGNET_TAP_PAIRS = { + "RQ1": [f"TAP{i:02d}" for i in range(1, 25)], + "RQ2": [f"TAP{i:02d}" for i in range(1, 25)], + "RB1": [f"TAP{i:02d}" for i in range(1, 13)], +} + +class RknmntrTests(unittest.TestCase): + """ + Tests for the Rknmntr IOC. + """ + def setUp(self): + self._lewis, self._ioc = get_running_lewis_and_ioc("Rknmntr", DEVICE_PREFIX) + self.ca = ChannelAccess(device_prefix=DEVICE_PREFIX) + + def _get_pv_for_magnet_tap(self, magnet, tap): + return f"{magnet}:{tap}:" + + def test_GIVEN_ioc_running_THEN_all_pvs_exist(self): + for magnet in MAGNET_TAP_PAIRS: + for tap in MAGNET_TAP_PAIRS[magnet]: + pv_magnet_tap = self._get_pv_for_magnet_tap(magnet, tap) + self.ca.assert_that_pv_exists(f"{pv_magnet_tap}VOLT:RAW") + self.ca.assert_that_pv_exists(f"{pv_magnet_tap}VOLT:ADC") + self.ca.assert_that_pv_exists(f"{pv_magnet_tap}VOLT") + self.ca.assert_that_pv_exists(f"{pv_magnet_tap}RES") + self.ca.assert_that_pv_exists(f"{pv_magnet_tap}TEMP") + + @parameterized.expand(parameterized_list([ + "RQ1", "RQ2", "RB1" + ])) + def test_WHEN_raw_voltage_THEN_values_calculated(self, _, magnet): + # Remove IOC prefix from prefix, leaving only the host machine prefix + self.ca.prefix = self.ca.host_prefix + + # Set magnet current to a non-zero value + pv_magnet_curr = f"CS:SB:{magnet}_CURR" + self.ca.set_pv_value(pv_magnet_curr, 1) + + for tap in MAGNET_TAP_PAIRS[magnet]: + # WHEN + # Simulate a raw voltage on each tap + volt_raw = random.randrange(10, 1000) + pv = f"SCHNDR_01:{magnet}:TEMPMON:{tap}" + self.ca.set_pv_value(pv, volt_raw) + + pv_magnet_tap = f"RKNMNTR_01:{magnet}:{tap}:" + pv_raw = f"{pv_magnet_tap}VOLT:RAW" + pv_volt_adc = f"{pv_magnet_tap}VOLT:ADC" + pv_volt = f"{pv_magnet_tap}VOLT" + pv_res = f"{pv_magnet_tap}RES" + pv_temp = f"{pv_magnet_tap}TEMP" + + gain = self.ca.get_pv_value(f"{pv_volt}.B") # Retrieve gain for magnet from calc record B field (loaded in there from macro) + curr = self.ca.get_pv_value(pv_magnet_curr) + initial_res = self.ca.get_pv_value(f"{pv_temp}.B") # Retrieve initial resistance for magnet from calc record B field (loaded in there from macro) + + #These calculations convert raw/analogue voltages into digital. The conversion calculations were provided by instrument scientists, + #and can be found here: https://github.com/ISISComputingGroup/IBEX/issues/7975 + expected_volt_adc = volt_raw / ((2**12)-1)*10 + expected_volt = expected_volt_adc / gain + expected_res = expected_volt / curr * 1000 + expected_temp = (((expected_res / initial_res) - 1) / 0.004041) + 23 + + # ASSERT + # That calculations all happen + self.ca.assert_that_pv_is(pv_raw, volt_raw) + self.ca.assert_that_pv_is(pv_volt_adc, expected_volt_adc) + self.ca.assert_that_pv_is(pv_volt, expected_volt) + self.ca.assert_that_pv_is(pv_res, expected_res) + self.ca.assert_that_pv_is(pv_temp, expected_temp)