|
| 1 | +# File OxInstIPS_SCPI.protocol |
| 2 | +# |
| 3 | +# Stream Device protocol file for the Oxford Instruments Modular IPS |
| 4 | +# superconducting magnet power supplies. |
| 5 | +# This protocol supports the SCPI commands for the IPS, replacing the legacy command set. |
| 6 | +# |
| 7 | +# The full protocol is described in the IPS Operators Handbook. |
| 8 | +# |
| 9 | +# The commands are case-sensitive. |
| 10 | +# Keywords are a maximum of four characters long. Keywords longer than four characters |
| 11 | +# generate an invalid command response. |
| 12 | +# Keywords are separated by a colon: (ASCII 0x3Ah). |
| 13 | +# The maximum line length is 1024 bytes (characters), including line terminators. |
| 14 | +# All command lines are terminated by the new line character \n (ASCII 0x0Ah). |
| 15 | +# |
| 16 | +Terminator = "\n"; |
| 17 | + |
| 18 | +readtimeout = 500; |
| 19 | +replytimeout = 5000; |
| 20 | +locktimeout = 20000; |
| 21 | +PollPeriod = 500; |
| 22 | +ExtraInput = Ignore; |
| 23 | + |
| 24 | +# Device board/slot names |
| 25 | +magnet_temperature_sensor = "MB1.T1"; |
| 26 | +level_meter = "DB1.L1"; |
| 27 | +magnet_supply = "GRPZ"; |
| 28 | +temperature_sensor_10T = "DB8.T1"; |
| 29 | +pressure_sensor_10T = "DB5.P1"; |
| 30 | + |
| 31 | +######################################################################################### |
| 32 | +# --------- |
| 33 | +# *IDN? |
| 34 | +# --------- |
| 35 | +# IDN:OXFORD INSTRUMENTS:MERCURY dd:ss:ff |
| 36 | +# Where: |
| 37 | +# dd is the basic instrument type (iPS , iPS, Cryojet etc.) |
| 38 | +# ss is the serial number of the main board |
| 39 | +# ff is the firmware version of the instrument |
| 40 | +# Get the unit version information - returns unit type and firmware information. |
| 41 | +# Manual says letter commands always reply with themselves at the start, but |
| 42 | +# found out this one does not. Manual also shows a copyright symbol, which is not an |
| 43 | +# ASCII symbol and might cause problems for EPICS. In practice found (c) instead. |
| 44 | +# The %s format grabs the string upto the first space, the %c grabs the rest. It |
| 45 | +# was too long to fit into one EPICS string record value. |
| 46 | +getVersion { out "*IDN?"; |
| 47 | + in "IDN:OXFORD INSTRUMENTS:%(\$1MODEL.VAL)[ a-zA-Z0-9]:%*[ a-zA-Z0-9]:%(\$1VERSION.VAL)s";} |
| 48 | + |
| 49 | +######################################################################################### |
| 50 | + |
| 51 | +# Get measured power supply voltage in volts |
| 52 | +# The return string is of the form: STAT:DEV:GRPZ:PSU:SIG:VOLT:-0.0002V |
| 53 | +getSupplyVoltage { out "READ:DEV:" $magnet_supply ":PSU:SIG:VOLT"; |
| 54 | + in "STAT:DEV:" $magnet_supply ":PSU:SIG:VOLT:%f%*s";} |
| 55 | + |
| 56 | +# Get demand current (output current) in amps. We are only interested in the Z axis magnet group. |
| 57 | +# The return string is of the form: STAT:DEV:GRPZ:PSU:CSET:<value>:A |
| 58 | +getDemandCurrent { out "READ:DEV:" $magnet_supply ":PSU:SIG:CSET"; |
| 59 | + in "STAT:DEV:" $magnet_supply ":PSU:SIG:CSET:%f%*s";} |
| 60 | + |
| 61 | +# Get measured magnet curren in amps - ER=no. |
| 62 | +# The return string is of the form: STAT:DEV:GRPZ:PSU:SIG:CURR:-0.0001A |
| 63 | +getMeasuredMagnetCurrent { out "READ:DEV:" $magnet_supply ":PSU:SIG:CURR"; |
| 64 | + in "STAT:DEV:" $magnet_supply ":PSU:SIG:CURR:%f%*s";} |
| 65 | + |
| 66 | +# Get set point (target current) in amps ER=yes. |
| 67 | +getSetpointCurrent { out "READ:DEV:" $magnet_supply ":PSU:SIG:CSET"; |
| 68 | + in "STAT:DEV:" $magnet_supply ":PSU:SIG:CSET:%f%*s";} |
| 69 | + |
| 70 | +# Get current sweep rate in amps per minute ER=yes. |
| 71 | +getCurrentSweepRate { out "READ:DEV:" $magnet_supply ":PSU:SIG:RCST"; |
| 72 | + in "STAT:DEV:" $magnet_supply ":PSU:SIG:RCST:%f%*s";} |
| 73 | + |
| 74 | +# Get demand field (output field) in tesla ER=yes. |
| 75 | +getDemandField { out "READ:DEV:" $magnet_supply ":PSU:SIG:FLD"; |
| 76 | + in "STAT:DEV:" $magnet_supply ":PSU:SIG:FLD:%f%*s";} |
| 77 | + |
| 78 | +# Get set point (target field) in tesla ER=yes. |
| 79 | +getSetpointField { out "READ:DEV:" $magnet_supply ":PSU:SIG:FSET"; |
| 80 | + in "STAT:DEV:" $magnet_supply ":PSU:SIG:FSET:%f%*s";} |
| 81 | + |
| 82 | +# Get field sweep rate in tesla per minute ER=yes. |
| 83 | +# Returns status like: STAT:DEV:GRPZ:PSU:SIG:RFST:0.3850T/m |
| 84 | +getFieldSweepRate { out "READ:DEV:" $magnet_supply ":PSU:SIG:RFST"; |
| 85 | + in "STAT:DEV:" $magnet_supply ":PSU:SIG:RFST:%f%*s";} |
| 86 | + |
| 87 | +# Get software voltage limit in volts ER=no. |
| 88 | +# The documentation states that a float is returned, but in reality, a string may be returned instead, such as 'N/A' |
| 89 | +getSoftwareVoltageLimit { out "READ:DEV:" $magnet_supply ":PSU:VLIM"; |
| 90 | + in "STAT:DEV:" $magnet_supply ":PSU:VLIM:%f%*s"; @mismatch{in "%*s";} wait 100;} |
| 91 | + |
| 92 | +# Get persistent magnet current in amps ER=yes. |
| 93 | +getPersistentMagnetCurrent { out "READ:DEV:" $magnet_supply ":PSU:SIG:PCUR"; |
| 94 | + in "STAT:DEV:" $magnet_supply ":PSU:SIG:PCUR:%f%*s";} |
| 95 | + |
| 96 | +# Get persistent magnetic field in tesla ER=yes. |
| 97 | +getPersistentMagnetField { out "READ:DEV:" $magnet_supply ":PSU:SIG:PFLD"; |
| 98 | + in "STAT:DEV:" $magnet_supply ":PSU:SIG:PFLD:%f%*s";} |
| 99 | + |
| 100 | +# Get switch heater current in milliamp ER=no. |
| 101 | +getHeaterCurrent { out "READ:DEV:" $magnet_supply ":PSU:SHTC"; |
| 102 | + in "STAT:DEV:" $magnet_supply ":PSU:SHTC:%f%*s";} |
| 103 | + |
| 104 | +# Get safe current limit, most negative in amps ER=no. |
| 105 | +getNegCurrentLimit { out "READ:DEV:" $magnet_supply ":PSU:CLIM"; |
| 106 | + in "STAT:DEV:" $magnet_supply ":PSU:CLIM:%f%*s";} |
| 107 | + |
| 108 | +# Get safe current limit, most positive in amps ER=no. |
| 109 | +# no units appended to the value, so assume amps. |
| 110 | +getPosCurrentLimit { out "READ:DEV:" $magnet_supply ":PSU:CLIM"; |
| 111 | + in "STAT:DEV:" $magnet_supply ":PSU:CLIM:%f";} |
| 112 | + |
| 113 | +# Get lead resistance (PTC/NTC) Ohms. |
| 114 | +# unit appended 'R' |
| 115 | +getLeadResistance { out "READ:DEV:" $magnet_temperature_sensor ":TEMP:SIG:RES"; |
| 116 | + in "STAT:DEV:" $magnet_temperature_sensor ":TEMP:SIG:RES:%f%*s";} |
| 117 | + |
| 118 | +# Get magnet inductance in henry ER=no. |
| 119 | +# no units appended |
| 120 | +getMagnetInductance { out "READ:DEV:" $magnet_supply ":PSU:IND"; |
| 121 | + in "STAT:DEV:" $magnet_supply ":PSU:IND:%f";} |
| 122 | + |
| 123 | +# Get Activity status (analogous to the legacy A command) |
| 124 | +getActivity { out "READ:DEV:" $magnet_supply ":PSU:ACTN"; |
| 125 | + in "STAT:DEV:" $magnet_supply ":PSU:ACTN:%#{HOLD=0|RTOS=1|RTOZ=2|CLMP=4}";} |
| 126 | + |
| 127 | +getHeaterStatus { out "READ:DEV:" $magnet_supply ":PSU:SIG:SWHT"; |
| 128 | + in "STAT:DEV:" $magnet_supply ":PSU:SIG:SWHT:%{OFF|ON}";} |
| 129 | + |
| 130 | +# *** Need to know what is returned on no alarms present, as it is not documented. *** |
| 131 | +# *** Found empirically that it returns an empty string (afterSTAT:SYS:ALRM:) |
| 132 | +# when no alarms are present. *** |
| 133 | +# Note: For the input we must use %#s (not just %s) as there may be a tab character delimiter |
| 134 | +getSysAlarms { out "READ:SYS:ALRM"; |
| 135 | + in "STAT:SYS:ALRM:%#s";} |
| 136 | + |
| 137 | + |
| 138 | +# --------------- The following work around limitation of getting legacy status from SCPI protocol ------------- |
| 139 | +# Get PSU Status status DWORD |
| 140 | +getMagnetSupplyStatus { out "READ:DEV:" $magnet_supply ":PSU:STAT"; |
| 141 | + in "STAT:DEV:" $magnet_supply ":PSU:STAT:%x";} |
| 142 | + |
| 143 | +# -------------------------------------------------------------------------------------------------------------- |
| 144 | + |
| 145 | + |
| 146 | +# --------- |
| 147 | +# SYS:LOCK Command |
| 148 | +# --------- |
| 149 | +# Set Control mode - grab control of the unit from local users. |
| 150 | +# OFF | SOFT | ON |
| 151 | +# With a real device, this command always replies: STAT:SET:SYS:LOCK:OFF:DENIED |
| 152 | +# where 'OFF' may be 'OFF', 'ON' or 'SOFT'. |
| 153 | +# It's also not possible to read the LOCK status. So this feature might be totally useless. |
| 154 | +setControl { out "SET:SYS:LOCK:%{OFF|SOFT|ON}"; in "READ:SYS:LOCK:%*s";} |
| 155 | + |
| 156 | +# --------- |
| 157 | +# DEV:<UID>:PSU:ACTN |
| 158 | +# --------- |
| 159 | +# Set the activity - i.e. Make it do something. |
| 160 | +# HOLD -> Hold |
| 161 | +# RTOS -> To Set Point |
| 162 | +# RTOZ -> To Zero |
| 163 | +# CLMP -> Clamp |
| 164 | +setActivity { out "SET:DEV:" $magnet_supply ":PSU:ACTN:%#{HOLD=0|RTOS=1|RTOZ=2|CLMP=4}"; |
| 165 | + in "STAT:SET:DEV:" $magnet_supply ":PSU:ACTN:%*s";} |
| 166 | + |
| 167 | +# --------- |
| 168 | +# SWHT Command |
| 169 | +# --------- |
| 170 | +# |
| 171 | +# Set the status of the heater. |
| 172 | +# 0 = heater off (close switch) |
| 173 | +# 1 = heater on (open switch) [Checks that magnet curr == psu curr before having any effect, so safer] |
| 174 | +# Returns status of this form: STAT:SET:DEV:GRPZ:PSU:SIG:SWHT:OFF:VALID |
| 175 | +# DO NOT USE SWHN command!! |
| 176 | +# |
| 177 | +setHeaterStatus { out "SET:DEV:" $magnet_supply ":PSU:SIG:SWHT:%#{OFF=0|ON=1}"; |
| 178 | + in "STAT:SET:DEV:" $magnet_supply ":PSU:SIG:SWHT:%*s"; |
| 179 | + @init {getHeaterStatus;} } |
| 180 | + |
| 181 | +# Set the setpoint (target) current. |
| 182 | +# Returns status like: STAT:SET:DEV:GRPZ:PSU:SIG:CSET:0.0004:VALID |
| 183 | +setSetpointCurrent { out "SET:DEV:" $magnet_supply ":PSU:SIG:CSET:%#.4f"; |
| 184 | + in "STAT:SET:DEV:" $magnet_supply ":PSU:SIG:CSET:%*f:%*s"; |
| 185 | + @init {getSetpointCurrent;} } |
| 186 | + |
| 187 | +# Set the setpoint (target) field. |
| 188 | +# Returns status like: STAT:SET:DEV:GRPZ:PSU:SIG:FSET:0.00:VALID |
| 189 | +setSetpointField { out "SET:DEV:" $magnet_supply ":PSU:SIG:FSET:%#.5f"; |
| 190 | + in "STAT:SET:DEV:" $magnet_supply ":PSU:SIG:FSET:%*f%*s"; |
| 191 | + @init {getSetpointField;} } |
| 192 | + |
| 193 | +# Set current sweep rate. |
| 194 | +# Rate at which current will be ramped or swept to target, either the setpoint or zero. |
| 195 | +# Returns status like: STAT:DEV:GRPZ:PSU:SIG:RCST:5.5:VALID |
| 196 | +setCurrentSweepRate { out "SET:DEV:" $magnet_supply ":PSU:SIG:RCST:%#.3f"; |
| 197 | + in "STAT:SET:DEV:" $magnet_supply ":PSU:SIG:RCST:%*f%*s"; |
| 198 | + @init {getCurrentSweepRate;} } |
| 199 | + |
| 200 | +# Set field sweep rate. |
| 201 | +# Rate at which field will be ramped or swept to target, either the setpoint or zero. |
| 202 | +# Returns status like: STAT:SET:DEV:GRPZ:PSU:SIG:RFST:0.3850:VALID |
| 203 | +setFieldSweepRate { out "SET:DEV:" $magnet_supply ":PSU:SIG:RFST:%#.4f"; |
| 204 | + in "STAT:SET:DEV:" $magnet_supply ":PSU:SIG:RFST:%*f%*s"; |
| 205 | + @init {getFieldSweepRate;} } |
| 206 | + |
| 207 | +# ------------------------------------------------------- |
| 208 | +# LEVELS BOARD COMMANDS |
| 209 | +# ------------------------------------------------------- |
| 210 | +getLevelNitFreqZero { out "READ:DEV:" $level_meter ":LVL:NIT:FREQ:ZERO"; |
| 211 | + in "STAT:DEV:" $level_meter ":LVL:NIT:FREQ:ZERO:%f";} |
| 212 | + |
| 213 | +setLevelNitFreqZero { out "SET:DEV:" $level_meter ":LVL:NIT:FREQ:ZERO:%d"; |
| 214 | + in "STAT:SET:DEV:" $level_meter ":LVL:NIT:FREQ:ZERO:%*d%*s"; |
| 215 | + @init {getLevelNitFreqZero;} } |
| 216 | + |
| 217 | +getLevelNitFreqFull { out "READ:DEV:" $level_meter ":LVL:NIT:FREQ:FULL"; |
| 218 | + in "STAT:DEV:" $level_meter ":LVL:NIT:FREQ:FULL:%f";} |
| 219 | + |
| 220 | +setLevelNitFreqFull { out "SET:DEV:" $level_meter ":LVL:NIT:FREQ:FULL:%d"; |
| 221 | + in "STAT:SET:DEV:" $level_meter ":LVL:NIT:FREQ:FULL:%*d%*s"; |
| 222 | + @init {getLevelNitFreqFull;}} |
| 223 | + |
| 224 | +getLevelHeEmptyRes { out "READ:DEV:" $level_meter ":LVL:HEL:RES:ZERO"; |
| 225 | + in "STAT:DEV:" $level_meter ":LVL:HEL:RES:ZERO:%f";} |
| 226 | + |
| 227 | +setLevelHeEmptyRes { out "SET:DEV:" $level_meter ":LVL:HEL:RES:ZERO:%f"; |
| 228 | + in "STAT:SET:DEV:" $level_meter ":LVL:HEL:RES:ZERO:%*f%*s"; |
| 229 | + @init {getLevelHeEmptyRes;}} |
| 230 | + |
| 231 | +getLevelHeFullRes { out "READ:DEV:" $level_meter ":LVL:HEL:RES:FULL"; |
| 232 | + in "STAT:DEV:" $level_meter ":LVL:HEL:RES:FULL:%f";} |
| 233 | + |
| 234 | +setLevelHeFullRes { out "SET:DEV:" $level_meter ":LVL:HEL:RES:FULL:%f"; |
| 235 | + in "STAT:SET:DEV:" $level_meter ":LVL:HEL:RES:FULL:%*f%*s"; |
| 236 | + @init {getLevelHeFullRes;}} |
| 237 | + |
| 238 | + |
| 239 | +getLevelHeFillStartThreshold { out "READ:DEV:" $level_meter ":LVL:HEL:LOW"; |
| 240 | + in "STAT:DEV:" $level_meter ":LVL:HEL:LOW:%d";} |
| 241 | + |
| 242 | +setLevelHeFillStartThreshold { out "SET:DEV:" $level_meter ":LVL:HEL:LOW:%d"; |
| 243 | + in "STAT:SET:DEV:" $level_meter ":LVL:HEL:LOW:%*d%*s"; |
| 244 | + @init {getLevelHeFillStartThreshold;}} |
| 245 | + |
| 246 | +getLevelHeFillStopThreshold { out "READ:DEV:" $level_meter ":LVL:HEL:HIGH"; |
| 247 | + in "STAT:DEV:" $level_meter ":LVL:HEL:HIGH:%d";} |
| 248 | + |
| 249 | +setLevelHeFillStopThreshold { out "SET:DEV:" $level_meter ":LVL:HEL:HIGH:%d"; |
| 250 | + in "STAT:SET:DEV:" $level_meter ":LVL:HEL:HIGH:%*d%*s"; |
| 251 | + @init {getLevelHeFillStopThreshold;}} |
| 252 | + |
| 253 | +# The documentation is wrong!: |
| 254 | +# READ:DEV:DB1.L1:LVL:HEL:RFL actually returns which relay output is used to turn the |
| 255 | +# autofill on or off (or indeed can be used to set which relay is used), not the current status. |
| 256 | +# If this is implemented in future, it will need to be modified. |
| 257 | +#getLevelHeRefilling { out "READ:DEV:" $level_meter ":LVL:HEL:RFL"; |
| 258 | +# in "STAT:DEV:" $level_meter ":LVL:HEL:RFL:%{OFF|ON}";} |
| 259 | + |
| 260 | +getLevelHeReadingRate { out "READ:DEV:" $level_meter ":LVL:HEL:PULS:SLOW"; |
| 261 | + in "STAT:DEV:" $level_meter ":LVL:HEL:PULS:SLOW:%{OFF|ON}";} |
| 262 | + |
| 263 | +setLevelHeReadingRate { out "SET:DEV:" $level_meter ":LVL:HEL:PULS:SLOW:%{OFF|ON}"; |
| 264 | + in "STAT:SET:DEV:" $level_meter ":LVL:HEL:PULS:SLOW:%*s"; |
| 265 | + @init {getLevelHeReadingRate;}} |
| 266 | + |
| 267 | + |
| 268 | +getLevelNitReadInterval { out "READ:DEV:" $level_meter ":LVL:NIT:PPS"; |
| 269 | + in "STAT:DEV:" $level_meter ":LVL:NIT:PPS:%d"; |
| 270 | + @init {getLevelHeFillStopThreshold;}} |
| 271 | + |
| 272 | +setLevelNitReadInterval { out "SET:DEV:" $level_meter ":LVL:NIT:PPS:%d"; |
| 273 | + in "STAT:DEV:" $level_meter ":LVL:NIT:PPS:%*d%*s"; |
| 274 | + @init {getLevelNitReadInterval;}} |
| 275 | + |
| 276 | + |
| 277 | +getLevelNitFillStartThreshold { out "READ:DEV:" $level_meter ":LVL:NIT:LOW"; |
| 278 | + in "STAT:DEV:" $level_meter ":LVL:NIT:LOW:%d";} |
| 279 | + |
| 280 | +setLevelNitFillStartThreshold { out "SET:DEV:" $level_meter ":LVL:NIT:LOW:%d"; |
| 281 | + in "STAT:SET:DEV:" $level_meter ":LVL:NIT:LOW:%*d%*s"; |
| 282 | + @init {getLevelNitFillStartThreshold;}} |
| 283 | + |
| 284 | +getLevelNitFillStopThreshold { out "READ:DEV:" $level_meter ":LVL:NIT:HIGH"; |
| 285 | + in "STAT:DEV:" $level_meter ":LVL:NIT:HIGH:%d";} |
| 286 | + |
| 287 | +setLevelNitFillStopThreshold { out "SET:DEV:" $level_meter ":LVL:NIT:HIGH:%d"; |
| 288 | + in "STAT:SET:DEV:" $level_meter ":LVL:NIT:HIGH:%*d%*s"; |
| 289 | + @init {getLevelNitFillStopThreshold;}}; |
| 290 | + |
| 291 | +# The documentation is wrong!: |
| 292 | +# READ:DEV:DB1.L1:LVL:NIT:RFL actually returns which relay output is used to turn the |
| 293 | +# autofill on or off (or indeed can be used to set which relay is used), not the current status. |
| 294 | +# If this is implemented in future, it will need to be modified. |
| 295 | +#getLevelNitRefilling { out "READ:DEV:" $level_meter ":LVL:NIT:RFL"; |
| 296 | +# in "STAT:DEV:" $level_meter ":LVL:NIT:RFL:%{OFF|ON}";} |
| 297 | + |
| 298 | +getLevelNitrogenLevel { out "READ:DEV:" $level_meter ":LVL:SIG:NIT:LEV"; |
| 299 | + in "STAT:DEV:" $level_meter ":LVL:SIG:NIT:LEV:%f";} |
| 300 | + |
| 301 | +getLevelHeliumLevel { out "READ:DEV:" $level_meter ":LVL:SIG:HEL:LEV"; |
| 302 | + in "STAT:DEV:" $level_meter ":LVL:SIG:HEL:LEV:%f";} |
| 303 | + |
| 304 | +# ------------------------------------------------------- |
| 305 | +# TEMPERATURE BOARD COMMANDS |
| 306 | +# ------------------------------------------------------- |
| 307 | +getMagnetTemperature { out "READ:DEV:" $magnet_temperature_sensor ":TEMP:SIG:TEMP"; |
| 308 | + in "STAT:DEV:" $magnet_temperature_sensor ":TEMP:SIG:TEMP:%f%*s";} |
| 309 | + |
| 310 | +getLambdaPlateTemperature { out "READ:DEV:" $temperature_sensor_10T ":TEMP:SIG:TEMP"; |
| 311 | + in "STAT:DEV:" $temperature_sensor_10T ":TEMP:SIG:TEMP:%f%*s";} |
| 312 | + |
| 313 | + |
| 314 | +# ------------------------------------------------------- |
| 315 | +# PRESSURE BOARD COMMANDS |
| 316 | +# ------------------------------------------------------- |
| 317 | +getPressure { out "READ:DEV:" $pressure_sensor_10T ":PRES:SIG:PRES"; |
| 318 | + in "STAT:DEV:" $pressure_sensor_10T ":PRES:SIG:PRES:%f%*s";} |
| 319 | + |
| 320 | + |
| 321 | + |
| 322 | + |
0 commit comments