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
|
||||
|
||||
SCRIPTS += scripts/mdif.cmd
|
||||
SCRIPTS += sim/mdif_sim.py
|
||||
|
||||
# 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")
|
||||
{
|
||||
field(DESC, "Unexpected received response")
|
||||
field(DTYP, "devDAQStringError")
|
||||
field(FLNK, "$(INSTR)$(NAME):INVALID-CONFIG")
|
||||
}
|
||||
|
||||
record(longout,"$(INSTR)$(NAME):DELAY")
|
||||
{
|
||||
field(DESC, "Starting measurement time after trigger signal")
|
||||
field(DESC, "delay after trigger signal")
|
||||
field(DTYP, "stream")
|
||||
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(OUT, "@$(PROTO) readDelay($(INSTR)$(NAME):) $(ASYN_PORT)")
|
||||
field(INP, "@$(PROTO) readDelay($(INSTR)$(NAME):) $(ASYN_PORT)")
|
||||
field(SCAN, "1 second")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
require asyn
|
||||
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")
|
||||
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)")
|
||||
|
||||
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