Correct bugs in db and sets up simulation mode

This commit is contained in:
2025-09-19 14:41:41 +02:00
parent 042aeadb7c
commit 2f4e279c47
7 changed files with 183 additions and 7 deletions

View File

@@ -15,5 +15,6 @@ TEMPLATES += db/mdif.db
TEMPLATES += db/mdif.proto TEMPLATES += db/mdif.proto
SCRIPTS += scripts/mdif.cmd SCRIPTS += scripts/mdif.cmd
SCRIPTS += sim/mdif_sim.py
# MISCS would be the place to keep the stream device template files # MISCS would be the place to keep the stream device template files

60
README.md Normal file
View File

@@ -0,0 +1,60 @@
MDIF Epics Module
-----------------
An Asyn and Stream Device based driver for the Multi-Detector Interface used at
some instrument within SINQ.
## How to Use
Unless a custom database is needed, a device can be configure simply by setting
the required environment variables when calling the MDIF start script.
Required Variables
| Environment Variable | Purpose |
|----------------------|-----------------------------------------|
| INSTR | Prefix of all device specific PVs |
| NAME | First field in all PVs after Prefix |
| MDIF\_IP | Network IP of device |
| MDIF\_PORT | Network Port of device |
All PVs take the form
```
$(INSTR)$(NAME):*
```
Available device startup scripts
* scripts/mdif.cmd
For example
```
epicsEnvSet("INSTR", "SQ:INSTRUMENT:") # can also be set in runScript call
runScript "$(mdif_DIR)mdif.cmd" "NAME=MDIF, MDIF_IP=focus-ts, MDIF_PORT=3016"
```
## PVs of Interest
| PV | Description |
|------------------------------|--------------------------------------------------|
| \$(INSTR)\$(NAME):MsgTxt | Contains unexpected response to executed command |
| \$(INSTR)\$(NAME):DELAY | Used to write a new delay value to the MDIF |
| \$(INSTR)\$(NAME):DELAY\_RBV | Read back the delay value configured in the MDIF |
## Simulation
Simulation of the Hardware can be toggled on as follows:
```
epicsEnvSet("SET_SIM_MODE","") # run MDIF simulation instead of connecting to actual system
runScript "$(mdif_DIR)mdif.cmd" "NAME=MDIF, MDIF_IP=localhost, MDIF_PORT=3016"
```
In such a case, the provided `MDIF_IP` is ignored, and a python program
simulating the hardware is started in the background, listening at the
specified `MDIF_PORT`.
See [sim/mdif\_sim.py](sim/mdif_sim.py).

View File

@@ -1,21 +1,19 @@
record(stringin, "$(INSTR)$(NAME):MsgTxt") record(stringin, "$(INSTR)$(NAME):MsgTxt")
{ {
field(DESC, "Unexpected received response") field(DESC, "Unexpected received response")
field(DTYP, "devDAQStringError")
field(FLNK, "$(INSTR)$(NAME):INVALID-CONFIG")
} }
record(longout,"$(INSTR)$(NAME):DELAY") record(longout,"$(INSTR)$(NAME):DELAY")
{ {
field(DESC, "Starting measurement time after trigger signal") field(DESC, "delay after trigger signal")
field(DTYP, "stream") field(DTYP, "stream")
field(OUT, "@$(PROTO) writeDelay($(INSTR)$(NAME):) $(ASYN_PORT)") field(OUT, "@$(PROTO) writeDelay($(INSTR)$(NAME):) $(ASYN_PORT)")
} }
record(longin,"$(INSTR)$(NAME):DELAY") record(longin,"$(INSTR)$(NAME):DELAY_RBV")
{ {
field(DESC, "Starting measurement time after trigger signal") field(DESC, "delay after trigger signal")
field(DTYP, "stream") field(DTYP, "stream")
field(OUT, "@$(PROTO) readDelay($(INSTR)$(NAME):) $(ASYN_PORT)") field(INP, "@$(PROTO) readDelay($(INSTR)$(NAME):) $(ASYN_PORT)")
field(SCAN, "1 second") field(SCAN, "1 second")
} }

View File

@@ -1,6 +1,15 @@
require asyn require asyn
require stream require stream
epicsEnvSet("$(NAME)_MDIF_HOST", "$(MDIF_IP):$(MDIF_PORT=2000)")
$(SET_SIM_MODE=#) $(SET_SIM_MODE) require misc
$(SET_SIM_MODE=#) $(SET_SIM_MODE) epicsEnvSet("$(NAME)_MDIF_HOST", "127.0.0.1:$(MDIF_PORT=3004)")
$(SET_SIM_MODE=#) $(SET_SIM_MODE) system "$(mdif_DIR)mdif_sim.py $(MDIF_PORT=3004) &"
# starting the python socket seems to take a while
# and need misc to use built in sleep command
$(SET_SIM_MODE=#) $(SET_SIM_MODE) sleep 3
epicsEnvSet("PROTO", "$(mdif_DB)mdif.proto") epicsEnvSet("PROTO", "$(mdif_DB)mdif.proto")
drvAsynIPPortConfigure("ASYN_$(NAME)", "$(HOST)", 0, 0, 0) drvAsynIPPortConfigure("ASYN_$(NAME)", "$($(NAME)_MDIF_HOST)", 0, 0, 0)
dbLoadRecords("$(mdif_DB)mdif.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME)") dbLoadRecords("$(mdif_DB)mdif.db", "INSTR=$(INSTR), NAME=$(NAME), PROTO=$(PROTO), ASYN_PORT=ASYN_$(NAME)")

8
scripts/sim-ioc.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/bash
export EPICS_HOST_ARCH=linux-x86_64
export EPICS_BASE=/usr/local/epics/base-7.0.7
PARENT_PATH="$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )"
exec /usr/local/bin/procServ -o -L - -f -i ^D^C 20001 "${PARENT_PATH}/sim-st.cmd"

13
scripts/sim-st.cmd Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/local/bin/iocsh
on error break
require mdif, wall_e
epicsEnvSet("STREAM_PROTOCOL_PATH","./db")
epicsEnvSet("INSTR","SQ:SIM:")
epicsEnvSet("SET_SIM_MODE","") # Run Simulation Instead of Actual Interface
runScript "$(mdif_DIR)mdif.cmd" "NAME=MDIF, MDIF_IP=127.0.0.1, MDIF_PORT=3004"
iocInit()

87
sim/mdif_sim.py Executable file
View File

@@ -0,0 +1,87 @@
#!/usr/bin/env python3
import os
import re
import socket
import sys
import time
from random import randrange
HOST = "127.0.0.1" # Localhost
PORT = int(sys.argv[1]) # Port to listen on
LOG2FILE = False if len(sys.argv) < 3 else bool(int(sys.argv[2]))
import logging
logger = logging.getLogger('mdif')
if LOG2FILE:
logging.basicConfig(filename=os.path.join(os.getcwd(), 'mdif_sim.log'), level=logging.INFO)
class MDIF:
def __init__(self):
self.delay = 1000
def getDelay(self):
return self.delay
def setDelay(self, delay):
self.delay = delay
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
conn, addr = s.accept()
with conn:
def send(data: str):
if data:
logger.info(f'SENDING: "{data}"')
return conn.sendall(f'{data}\r'.encode())
else:
logger.info(f'SENDING: ""')
return conn.sendall(b'\r')
def receive():
data = conn.recv(1024)
if data:
# also removes terminator
received = data.decode('ascii').rstrip()
else:
received = ''
logger.info(f'RECEIVED: "{received}"')
return received
mdif = MDIF()
while True:
data = receive()
if not data: # Empty implies client disconnected
break # Close Server
try:
if data == 'RMT 1':
send('')
elif data == 'ECHO 0':
send('')
elif data == 'DT':
send('%d' % mdif.getDelay())
elif re.fullmatch(r'DT (\d+)', data):
new_delay = int(re.fullmatch(r'DT (\d+)', data).group(1))
mdif.setDelay(new_delay)
send('')
else:
send('?2') # Bad command
except Exception as e:
logger.exception('Simulation Broke')
send('?OV')