Correct bugs in db and sets up simulation mode
This commit is contained in:
1
Makefile
1
Makefile
@@ -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
60
README.md
Normal 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).
|
||||||
10
db/mdif.db
10
db/mdif.db
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
8
scripts/sim-ioc.sh
Executable 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
13
scripts/sim-st.cmd
Executable 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
87
sim/mdif_sim.py
Executable 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')
|
||||||
Reference in New Issue
Block a user