def __init__( self, producer: KafkaProducer, context: PVAContext, pv_name: str, output_topic: str, schema: str, periodic_update_ms: Optional[int] = None, ): self._logger = get_logger() self._producer = producer self._output_topic = output_topic request = context.makeRequest("field(value,timeStamp,alarm)") self._sub = context.monitor(pv_name, self._monitor_callback, request=request) self._pv_name = pv_name self._cached_update: Optional[Tuple[Value, int]] = None self._output_type = None self._stop_timer_flag = Event() self._repeating_timer = None self._cache_lock = Lock() try: self._message_publisher = schema_publishers[schema] except KeyError: raise ValueError( f"{schema} is not a recognised supported schema, use one of {list(schema_publishers.keys())}" ) if periodic_update_ms is not None: self._repeating_timer = RepeatTimer( milliseconds_to_seconds(periodic_update_ms), self.publish_cached_update) self._repeating_timer.start()
class P4PProvider(QObject,NTNDA_Channel_Provider) : callbacksignal = pyqtSignal() def __init__(self): QObject.__init__(self) NTNDA_Channel_Provider.__init__(self) self.callbacksignal.connect(self.mycallback) self.callbackDoneEvent = Event() self.firstCallback = True self.isClosed = True def start(self) : self.ctxt = Context('pva') self.firstCallback = True self.isClosed = False self.subscription = self.ctxt.monitor( self.getChannelName(), self.p4pcallback, request='field(value,dimension,codec,compressedSize,uncompressedSize)', notify_disconnect=True) def stop(self) : self.isClosed = True self.ctxt.close() def done(self) : pass def callback(self,arg) : self.NTNDA_Viewer.callback(arg) def p4pcallback(self,arg) : if self.isClosed : return self.struct = arg; self.callbacksignal.emit() self.callbackDoneEvent.wait() self.callbackDoneEvent.clear() def mycallback(self) : struct = self.struct arg = dict() try : argtype = str(type(struct)) if argtype.find('Disconnected')>=0 : arg["status"] = "disconnected" self.callback(arg) self.firstCallback = True self.callbackDoneEvent.set() return if self.firstCallback : arg = dict() arg["status"] = "connected" self.callback(arg) self.firstCallback = False self.callback(arg) arg = dict() arg['value'] = struct['value'] arg['dimension'] = struct['dimension'] arg['codec'] = struct['codec'] arg['compressedSize'] = struct['compressedSize'] arg['uncompressedSize'] = struct['uncompressedSize'] self.callback(arg) self.callbackDoneEvent.set() return except Exception as error: arg["exception"] = repr(error) self.callback(arg) self.callbackDoneEvent.set() return
class CustomController: """ Controller class used to access process variables. Controllers are used for interfacing with both Channel Access and pvAccess process variables. The controller object is initialized using a single protocol has methods for both getting and setting values on the process variables. Attributes: protocol (str): Protocol for getting values from variables ("pva" for pvAccess, "ca" for Channel Access) context (Context): P4P threaded context instance for use with pvAccess. set_ca (bool): Update Channel Access variable on put. set_pva (bool): Update pvAccess variable on put. pv_registry (dict): Registry mapping pvname to dict of value and pv monitor Example: ``` # create PVAcess controller controller = Controller("pva") value = controller.get_value("scalar_input") image_value = controller.get_image("image_input") controller.close() ``` """ def __init__(self, protocol: str, prefix, track_inputs: bool = False, input_pvs: list = None): """ Initializes controller. Stores protocol and creates context attribute if using pvAccess. Args: protocol (str): Protocol for getting values from variables ("pva" for pvAccess, "ca" for Channel Access) """ self.protocol = protocol self.last_update = "" self.pv_registry = defaultdict() self.track_inputs = track_inputs self.input_pvs = [f"{prefix}:{variable}" for variable in input_pvs] self.prefix = prefix # initalize context for pva self.context = None if self.protocol == "pva": self.context = Context("pva") def ca_value_callback(self, pvname, value, *args, **kwargs): """Callback executed by Channel Access monitor. Args: pvname (str): Process variable name value (Union[np.ndarray, float]): Value to assign to process variable. """ self.pv_registry[pvname]["value"] = value if self.track_inputs: if pvname in self.input_pvs: self.last_update = datetime.now().strftime( '%m/%d/%Y, %H:%M:%S') def ca_connection_callback(self, *, pvname, conn, pv): """Callback used for monitoring connection and setting values to None on disconnect. """ # if disconnected, set value to None if not conn: self.pv_registry[pvname]["value"] = None def pva_value_callback(self, pvname, value): """Callback executed by pvAccess monitor. Args: pvname (str): Process variable name value (Union[np.ndarray, float]): Value to assign to process variable. """ if isinstance(value, Disconnected): self.pv_registry[pvname]["value"] = None else: self.pv_registry[pvname]["value"] = value if self.track_inputs: if pvname in self.input_pvs: self.last_update = datetime.now().strftime( '%m/%d/%Y, %H:%M:%S') def setup_pv_monitor(self, pvname): """Set up process variable monitor. Args: pvname (str): Process variable name """ if pvname in self.pv_registry: return if self.protocol == "ca": # add to registry (must exist for connection callback) self.pv_registry[pvname] = {"pv": None, "value": None} # create the pv pv_obj = PV(pvname, callback=self.ca_value_callback, connection_callback=self.ca_connection_callback) # update registry self.pv_registry[pvname]["pv"] = pv_obj elif self.protocol == "pva": cb = partial(self.pva_value_callback, pvname) # populate registry s.t. initially disconnected will populate self.pv_registry[pvname] = {"pv": None, "value": None} # create the monitor obj mon_obj = self.context.monitor(pvname, cb, notify_disconnect=True) # update registry with the monitor self.pv_registry[pvname]["pv"] = mon_obj def get(self, pvname: str) -> np.ndarray: """ Accesses and returns the value of a process variable. Args: pvname (str): Process variable name """ self.setup_pv_monitor(pvname) pv = self.pv_registry.get(pvname, None) if pv: #return pv.get("value", None) return pv["value"] return None def get_value(self, pvname): """Gets scalar value of a process variable. Args: pvname (str): Image process variable name. """ value = self.get(pvname) if value is None: value = DEFAULT_SCALAR_VALUE return value def get_image(self, pvname) -> dict: """Gets image data via controller protocol. Args: pvname (str): Image process variable name """ image = None if self.protocol == "ca": image_flat = self.get(f"{pvname}:ArrayData_RBV") nx = self.get(f"{pvname}:ArraySizeX_RBV") ny = self.get(f"{pvname}:ArraySizeY_RBV") x = self.get(f"{pvname}:MinX_RBV") y = self.get(f"{pvname}:MinY_RBV") x_max = self.get(f"{pvname}:MaxX_RBV") y_max = self.get(f"{pvname}:MaxY_RBV") if all([ image_def is not None for image_def in [image_flat, nx, ny, x, y, x_max, y_max] ]): dw = x_max - x dh = y_max - y image = image_flat.reshape(int(nx), int(ny)) elif self.protocol == "pva": # context returns numpy array with WRITEABLE=False # copy to manipulate array below image = self.get(pvname) if image is not None: attrib = image.attrib x = attrib["x_min"] y = attrib["y_min"] dw = attrib["x_max"] - attrib["x_min"] dh = attrib["y_max"] - attrib["y_min"] image = copy.copy(image) if image is not None: return { "image": [image], "x": [x], "y": [y], "dw": [dw], "dh": [dh], } else: return DEFAULT_IMAGE_DATA def put(self, pvname, value: Union[np.ndarray, float], timeout=1.0) -> None: """Assign the value of a process variable. Args: pvname (str): Name of the process variable value (Union[np.ndarray, float]): Value to assing to process variable. timeout (float): Operation timeout in seconds """ self.setup_pv_monitor(pvname) # allow no puts before a value has been collected registered = self.get(pvname) # if the value is registered if registered is not None: if self.protocol == "ca": self.pv_registry[pvname]["pv"].put(value, timeout=timeout) elif self.protocol == "pva": self.context.put(pvname, value, throw=False, timeout=timeout) else: logger.debug(f"No initial value set for {pvname}.") def close(self): if self.protocol == "pva": self.context.close()
def main(self): cli = Context() pvs = {} # table of detected "features" self.features = pvs[args.output + 'features'] = SharedPV( nt=NTTable(columns=[ ('X', 'd'), ('Y', 'd'), ('W', 'd'), ('H', 'd'), ('idx', 'd'), ]), initial=[]) # output image (example) self.imgOut = pvs[args.output + 'img'] = SharedPV(nt=NTNDArray(), initial=np.zeros( (0, 0), dtype='u1')) # display execution time self.execTime = pvs[args.output + 'etime'] = SharedPV( nt=NTScalar('d', display=True), initial={ 'value': 0.0, 'display.units': 's', }) # background threshold level bg = pvs[args.output + 'bg'] = SharedPV(nt=NTScalar('I', display=True), initial={ 'value': self.bgLvl, 'display.units': 'px', }) @bg.put def set_bg(pv, op): self.bgLvl = max(1, int(op.value())) pv.post(self.bgLvl) op.done() # image flattening mode imode = pvs[args.output + 'imode'] = SharedPV( nt=NTEnum(), initial={'choices': [e.name for e in ImageMode]}) @imode.put def set_imode(pv, op): self.imode = ImageMode(op.value()) pv.post(self.imode) op.done() # separately publish info of largest feature self.X = pvs[args.output + 'x'] = SharedPV(nt=NTScalar('d'), initial=0.0) self.Y = pvs[args.output + 'y'] = SharedPV(nt=NTScalar('d'), initial=0.0) self.W = pvs[args.output + 'w'] = SharedPV(nt=NTScalar('d'), initial=0.0) self.H = pvs[args.output + 'h'] = SharedPV(nt=NTScalar('d'), initial=0.0) print("Output PVs", list(pvs.keys())) # subscribe to input image PV and run local server with cli.monitor(self.args.input, self.on_image, request='record[pipeline=true,queueSize=2]'), Server( providers=[pvs]): # park while work happens in other tasks done = threading.Event() signal.signal(signal.SIGINT, lambda x, y: done.set()) done.wait()
class P4PProvider(QObject): callbacksignal = pyqtSignal() def __init__(self): QObject.__init__(self) self.callbacksignal.connect(self.mycallback) self.callbackDoneEvent = Event() self.firstCallback = True self.isClosed = True self.channelName = "13SIM1:Pva1:Image" def setChannelName(self, channelName): self.channelName = channelName def getChannelName(self): return self.channelName def start(self): self.ctxt = Context("pva") self.firstCallback = True self.isClosed = False self.subscription = self.ctxt.monitor( self.getChannelName(), self.p4pcallback, request= "field(value,dimension,codec,compressedSize,uncompressedSize)", notify_disconnect=True, ) def stop(self): self.isClosed = True self.ctxt.close() def callback(self, arg): self.NTNDA_Viewer.callback(arg) def p4pcallback(self, arg): if self.isClosed: return self.struct = arg self.callbacksignal.emit() self.callbackDoneEvent.wait() self.callbackDoneEvent.clear() def mycallback(self): struct = self.struct arg = dict() try: argtype = str(type(struct)) if argtype.find("Disconnected") >= 0: arg["status"] = "disconnected" self.callback(arg) self.firstCallback = True self.callbackDoneEvent.set() return if self.firstCallback: arg = dict() arg["status"] = "connected" self.callback(arg) self.firstCallback = False self.callback(arg) arg = dict() arg["value"] = struct["value"] arg["dimension"] = struct["dimension"] arg["codec"] = struct["codec"] arg["compressedSize"] = struct["compressedSize"] arg["uncompressedSize"] = struct["uncompressedSize"] self.callback(arg) self.callbackDoneEvent.set() return except Exception as error: arg["exception"] = repr(error) self.callback(arg) self.callbackDoneEvent.set() return
if now < self._next_allowed: S.pause() TQ.push(S.resume, delay=self._next_allowed - now) else: self._next_allowed = N = now + self.period with iolock: print(now, self.name, V) period = 1.0 / args.rate subscriptions = [] for n in range(args.count): name = '%s%d' % (args.pvbase, n) S = ctxt.monitor(name, RateLimit(name, period), request='record[pipeline=True,queueSize=2]', subscription_arg=True) subscriptions.append(S) # must keep Subscription from being collected try: while True: time.sleep(1000) except KeyboardInterrupt: pass finally: TQ.join()
class Controller: """ Controller class used to access process variables. Controllers are used for interfacing with both Channel Access and pvAccess process variables. The controller object is initialized using a single protocol has methods for both getting and setting values on the process variables. Attributes: _protocol (str): Protocol for getting values from variables ("pva" for pvAccess, "ca" for Channel Access) _context (Context): P4P threaded context instance for use with pvAccess. _pv_registry (dict): Registry mapping pvname to dict of value and pv monitor _input_pvs (dict): Dictionary of input process variables _output_pvs (dict): Dictionary out output process variables _prefix (str): Prefix to use for accessing variables last_input_update (datetime): Last update of input variables last_output_update (datetime): Last update of output variables Example: ``` # create PVAcess controller controller = Controller("pva") value = controller.get_value("scalar_input") image_value = controller.get_image("image_input") controller.close() ``` """ def __init__(self, protocol: str, input_pvs: dict, output_pvs: dict, prefix): """ Initializes controller. Stores protocol and creates context attribute if using pvAccess. Args: protocol (str): Protocol for getting values from variables ("pva" for pvAccess, "ca" for Channel Access) input_pvs (dict): Dict mapping input variable names to variable output_pvs (dict): Dict mapping output variable names to variable """ self._protocol = protocol self._pv_registry = defaultdict() self._input_pvs = input_pvs self._output_pvs = output_pvs self._prefix = prefix self.last_input_update = "" self.last_output_update = "" # initalize context for pva self._context = None if self._protocol == "pva": self._context = Context("pva") # initialize controller for variable in {**input_pvs, **output_pvs}.values(): if variable.variable_type == "image": self.get_image(variable.name) elif variable.variable_type == "array": self.get_array(variable.name) else: self.get_value(variable.name) def _ca_value_callback(self, pvname, value, *args, **kwargs): """Callback executed by Channel Access monitor. Args: pvname (str): Process variable name value (Union[np.ndarray, float]): Value to assign to process variable. """ pvname = pvname.replace(f"{self._prefix}:", "") self._pv_registry[pvname]["value"] = value if pvname in self._input_pvs: self.last_input_update = datetime.now().strftime( "%m/%d/%Y, %H:%M:%S") if pvname in self._output_pvs: self.last_output_update = datetime.now().strftime( "%m/%d/%Y, %H:%M:%S") def _ca_connection_callback(self, *, pvname, conn, pv): """Callback used for monitoring connection and setting values to None on disconnect. """ # if disconnected, set value to None pvname = pvname.replace(f"{self._prefix}:", "") if not conn: self._pv_registry[pvname]["value"] = None def _pva_value_callback(self, pvname, value): """Callback executed by pvAccess monitor. Args: pvname (str): Process variable name value (Union[np.ndarray, float]): Value to assign to process variable. """ if isinstance(value, Disconnected): self._pv_registry[pvname]["value"] = None else: self._pv_registry[pvname]["value"] = value if pvname in self._input_pvs: self.last_input_update = datetime.now().strftime( "%m/%d/%Y, %H:%M:%S") if pvname in self._output_pvs: self.last_output_update = datetime.now().strftime( "%m/%d/%Y, %H:%M:%S") def _set_up_pv_monitor(self, pvname): """Set up process variable monitor. Args: pvname (str): Process variable name """ if pvname in self._pv_registry: return if self._protocol == "ca": # add to registry (must exist for connection callback) self._pv_registry[pvname] = {"pv": None, "value": None} # create the pv pv_obj = PV( f"{self._prefix}:{pvname}", callback=self._ca_value_callback, connection_callback=self._ca_connection_callback, ) # update registry self._pv_registry[pvname]["pv"] = pv_obj elif self._protocol == "pva": cb = partial(self._pva_value_callback, pvname) # populate registry s.t. initially disconnected will populate self._pv_registry[pvname] = {"pv": None, "value": None} # create the monitor obj mon_obj = self._context.monitor(f"{self._prefix}:{pvname}", cb, notify_disconnect=True) # update registry with the monitor self._pv_registry[pvname]["pv"] = mon_obj def get(self, pvname: str) -> np.ndarray: """ Accesses and returns the value of a process variable. Args: pvname (str): Process variable name """ self._set_up_pv_monitor(pvname) pv = self._pv_registry.get(pvname, None) if pv: return pv["value"] return None def get_value(self, pvname): """Gets scalar value of a process variable. Args: pvname (str): Process variable name. """ value = self.get(pvname) if value is None: value = DEFAULT_SCALAR_VALUE return value def get_image(self, pvname) -> dict: """Gets image data via controller protocol. Args: pvname (str): Image process variable name """ image = None if self._protocol == "ca": image_flat = self.get(f"{pvname}:ArrayData_RBV") nx = self.get(f"{pvname}:ArraySizeX_RBV") ny = self.get(f"{pvname}:ArraySizeY_RBV") x = self.get(f"{pvname}:MinX_RBV") y = self.get(f"{pvname}:MinY_RBV") x_max = self.get(f"{pvname}:MaxX_RBV") y_max = self.get(f"{pvname}:MaxY_RBV") if all([ image_def is not None for image_def in [image_flat, nx, ny, x, y, x_max, y_max] ]): dw = x_max - x dh = y_max - y image = image_flat.reshape(int(nx), int(ny)) elif self._protocol == "pva": # context returns numpy array with WRITEABLE=False # copy to manipulate array below image = self.get(pvname) if image is not None: attrib = image.attrib x = attrib["x_min"] y = attrib["y_min"] dw = attrib["x_max"] - attrib["x_min"] dh = attrib["y_max"] - attrib["y_min"] image = copy.copy(image) if image is not None: return { "image": [image], "x": [x], "y": [y], "dw": [dw], "dh": [dh], } else: return DEFAULT_IMAGE_DATA def get_array(self, pvname) -> dict: """Gets array data via controller protocol. Args: pvname (str): Image process variable name """ array = None if self._protocol == "ca": array_flat = self.get(f"{pvname}:ArrayData_RBV") shape = self.get(f"{pvname}:ArraySize_RBV") if all( [array_def is not None for array_def in [array_flat, shape]]): array = np.array(array_flat).reshape(shape) elif self._protocol == "pva": # context returns numpy array with WRITEABLE=False # copy to manipulate array below array = self.get(pvname) if array is not None: return array else: return np.array([]) def put(self, pvname, value: float, timeout=1.0) -> None: """Assign the value of a scalar process variable. Args: pvname (str): Name of the process variable value (float): Value to assing to process variable. timeout (float): Operation timeout in seconds """ self._set_up_pv_monitor(pvname) # allow no puts before a value has been collected registered = self.get(pvname) # if the value is registered if registered is not None: if self._protocol == "ca": self._pv_registry[pvname]["pv"].put(value, timeout=timeout) elif self._protocol == "pva": self._context.put(f"{self._prefix}:{pvname}", value, throw=False, timeout=timeout) else: logger.debug(f"No initial value set for {pvname}.") def put_image( self, pvname, image_array: np.ndarray = None, x_min: float = None, x_max: float = None, y_min: float = None, y_max: float = None, timeout: float = 1.0, ) -> None: """Assign the value of a image process variable. Allows updates to individual attributes. Args: pvname (str): Name of the process variable image_array (np.ndarray): Value to assing to process variable. x_min (float): Minimum x value x_max (float): Maximum x value y_min (float): Minimum y value y_max (float): Maximum y value timeout (float): Operation timeout in seconds """ self._set_up_pv_monitor(pvname) # allow no puts before a value has been collected registered = self.get_image(pvname) # if the value is registered if registered is not None: if self._protocol == "ca": if image_array is not None: self._pv_registry[f"{pvname}:ArrayData_RBV"]["pv"].put( image_array.flatten(), timeout=timeout) if x_min: self._pv_registry[f"{pvname}:MinX_RBV"]["pv"].put( x_min, timeout=timeout) if x_max: self._pv_registry[f"{pvname}:MaxX_RBV"]["pv"].put( x_max, timeout=timeout) if y_min: self._pv_registry[f"{pvname}:MinY_RBV"]["pv"].put( y_min, timeout=timeout) if y_max: self._pv_registry[f"{pvname}:MaxY_RBV"]["pv"].put( y_max, timeout=timeout) elif self._protocol == "pva": # compose normative type pv = self._pv_registry[pvname] pv_array = pv["value"] if image_array: image_array.attrib = pv_array.attrib else: image_array = pv_array if x_min: image_array.attrib.x_min = x_min if x_max: image_array.attrib.x_max = x_max if y_min: image_array.attrib.y_min = y_min if y_max: image_array.attrib.y_max = y_max self._context.put(pvname, image_array, throw=False, timeout=timeout) else: logger.debug(f"No initial value set for {pvname}.") def put_array( self, pvname, array: np.ndarray = None, timeout: float = 1.0, ) -> None: """Assign the value of an array process variable. Allows updates to individual attributes. Args: pvname (str): Name of the process variable array (np.ndarray): Value to assing to process variable. timeout (float): Operation timeout in seconds """ self._set_up_pv_monitor(pvname) # allow no puts before a value has been collected registered = self.get_array(pvname) # if the value is registered if registered is not None: if self._protocol == "ca": if array is not None: self._pv_registry[f"{pvname}:ArrayData_RBV"]["pv"].put( array.flatten(), timeout=timeout) elif self._protocol == "pva": # compose normative type pv = self._pv_registry[pvname] array = pv["value"] self._context.put(pvname, array, throw=False, timeout=timeout) else: logger.debug(f"No initial value set for {pvname}.") def close(self): if self._protocol == "pva": self._context.close()
class pvGetClient(object): def __init__(self, pvName, monitor='False', provider='pva', timeout=5.0, repeat=1.0, showValue=False, throw=False, verbose=False, checkPriorCount=False): self._lock = Lock() self._pvName = pvName self._history = {} self._priorValue = None self._Q = Queue() self._S = None self._T = None self._Op = None self._noConnectionYet = True self._shutDown = False self._provider = provider self._throw = throw #self._monitor = monitor self._repeat = repeat self._showValue = showValue self._timeout = timeout self._verbose = verbose self._checkPriorCount = checkPriorCount self._ctxt = Context(provider) #self._pvGetDone = threading.Event() self._pvGetPending = threading.Event() self._T = threading.Thread(target=self.pvGetTimeoutLoop, args=(self._timeout, self._throw, self._verbose)) self._T.start() def __del__(self): if self._ctxt is not None: self._ctxt.close() self._ctxt = None def is_alive(self): if not self._T: return False return self._T.is_alive() def pvMonitor(self): # Monitor is is easy as p4p provides it. if self._ctxt is None: self._ctxt = Context(self._provider) self._S = self._ctxt.monitor(self._pvName, self.callback, notify_disconnect=True) # Above code does this: # R = Subscription( self._ctxt, self._pvName, self.callback, notify_disconnect=True ) # R._S = super(Context, self).monitor( name, R._event, request ) # self._S = R def pvGetInitiate(self, timeout=5.0, throw=False, verbose=True): #pdb.set_trace() # This code block does a synchronous get() #return self._ctxt.get( self._pvName, self.callback ) # Initiate async non-blocking pvGet using a ClientOperation. # self.pvGetCallback() handles the response and places it on self._Q if self._ctxt is None: self._ctxt = Context(self._provider) raw_get = super(Context, self._ctxt).get try: assert self._Op is None self._Op = raw_get(self._pvName, self.pvGetCallback) self._pvGetPending.set() except: raise return def pvGetCallback(self, cbData): result = self.callback(cbData) #self._ctxt.disconnect() #self._noConnectionYet = True if result is not None: assert self._Q try: self._Q.put_nowait(result) if self._verbose: print("%s: Added result to queue. %d on queue." % (self._pvName, self._Q.qsize())) except: print("pvGetCallback %s: Error queuing result" % self._pvName) return def handleResult(self): # Get pvGet result from self._Q result = False try: result = self._Q.get(timeout=self._timeout) except Empty: curTime = time.time() raw_stamp = (int(curTime), int((curTime - int(curTime)) * 1e9)) strTimeStamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(raw_stamp[0])) print('%s %s.%03d Timeout' % (self._pvName, strTimeStamp, float(raw_stamp[1]) / 1e6)) if self._throw: raise TimeoutError() if isinstance(result, Exception): if self._throw: raise result return None return result def pvGetTimeoutLoop(self, timeout=5.0, throw=False, verbose=True): status = False while not self._shutDown: # Wait for something to do. status = self._pvGetPending.wait(timeout=timeout) self._pvGetPending.clear() status = self.handleResult() if self._Op: self._Op.close() self._Op = None #self._ctxt.disconnect() #self._noConnectionYet = True self._ctxt.close() self._ctxt = None self._noConnectionYet = True if self._repeat is None: self._shutDown = True if self._shutDown: break time.sleep(self._repeat) self.pvGetInitiate() # Return on shutdown return def pvName(self): return self._pvName def callback(self, cbData): pvName = self._pvName if isinstance(cbData, (RemoteError, Disconnected, Cancelled)): if self._noConnectionYet and isinstance(cbData, Disconnected): return None if not isinstance(cbData, Cancelled): print('%s: %s' % (pvName, cbData)) return None self._noConnectionYet = False pvValue = cbData # Make sure we have a raw_stamp #pdb.set_trace() raw_stamp = None if hasattr(pvValue, 'raw_stamp'): raw_stamp = pvValue.raw_stamp #elif hasattr( pvValue, 'timestamp' ): # tsSec = pvValue.timestamp # raw_stamp = ( int(tsSec), int((tsSec - int(tsSec)) * 1e9) ) elif isinstance(pvValue, dict): if 'raw_stamp' in pvValue: raw_stamp = pvValue['raw_stamp'] if 'timestamp' in pvValue: raw_stamp = pvValue['timestamp'] if 'timeStamp' in pvValue: raw_stamp = pvValue['timeStamp'] if raw_stamp is None or len(raw_stamp) != 2 or raw_stamp[0] == 0: if self._verbose: print("%s: No timestamp found. Using TOD" % pvName) tsSec = time.time() raw_stamp = (int(tsSec), int((tsSec - int(tsSec)) * 1e9)) if isinstance(pvValue, p4p.nt.scalar.ntwrappercommon): self.saveNtScalar(pvName, raw_stamp, pvValue) return cbData if isinstance(pvValue, p4p.wrapper.Value): if self._verbose: print('%s: ID=%s, type=%s' % (pvName, pvValue.getID(), type(pvValue))) pvType = pvValue.type() if 'timeStamp' in pvValue: fieldTs = (pvValue['timeStamp.secondsPastEpoch'], pvValue['timeStamp.nanoseconds']) if fieldTs[0]: raw_stamp = fieldTs if pvValue.getID().startswith('epics:nt/NTTable:'): tableValue = pvValue['value'] tableType = pvType['value'] S, id, tableFields = tableType.aspy() assert S == 'S' assert id == 'structure' tableItems = tableValue.items() nCols = len(tableItems) nRows = len(tableItems[0][1]) if self._verbose: print("%s NTTable: nRows=%d, nCols=%d\n%s" % (pvName, nRows, nCols, tableItems)) for row in range(nRows): # Build up fullName fullName = pvName for col in range(nCols): spec = tableFields[col][1] if spec == 'as': fullName += '.' + tableItems[col][1][row] elif spec != 'av' and spec != 'aU' and spec != 'aS': self.saveValue( fullName + '.' + tableFields[col][0], raw_stamp, tableItems[col][1][row]) return cbData # This method works fpr p2p/Stats and potentially other # simple PVStruct based PVs. #if pvValue.getID() == 'epics:p2p/Stats:1.0': for fieldName in pvValue.keys(): pvField = pvValue[fieldName] if 'timeStamp' in pvField: fieldTs = (pvField['timeStamp.secondsPastEpoch'], pvField['timeStamp.nanoseconds']) if fieldTs[0]: raw_stamp = fieldTs fullName = pvName + '.' + fieldName if isinstance(pvField, p4p.nt.scalar.ntwrappercommon): cbData = self.saveNtScalar(fullName, raw_stamp, pvField['value']) elif pvField.getID().startswith('epics:nt/NTScalar:'): cbData = self.saveValue(fullName, raw_stamp, pvField['value']) # TODO: Handle other nt types return cbData def saveNtScalar(self, pvName, raw_stamp, pvValue): if self._verbose: print('%s: type=%s' % (pvName, type(pvValue))) if self._checkPriorCount: newValue = int(pvValue) if self._priorValue is not None: # Check for missed count expectedValue = self._priorValue + 1 if expectedValue != newValue: print('%s: missed %d counts!' % (pvName, newValue - expectedValue)) self._priorValue = newValue # Save value self.saveValue(pvName, raw_stamp, pvValue) return def saveValue(self, pvName, raw_stamp, value): #pdb.set_trace() if isinstance(value, p4p.nt.scalar.ntnumericarray): if value.size == 0: return value = value[0] if isinstance(value, list): if len(value) == 0: return value = value[0] # assert pvValue.type() == Scalar: if pvName not in self._history: self._history[pvName] = [] self._history[pvName] += [[raw_stamp, value]] if self._showValue: strTimeStamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(raw_stamp[0])) print('%s %s.%03d %s' % (pvName, strTimeStamp, float(raw_stamp[1]) / 1e6, float(value))) if self._verbose: print('%s: value raw_stamp = %s' % (pvName, raw_stamp)) print('%s: Num values = %d' % (pvName, len(self._history[pvName]))) def writeValues(self, dirName): if not os.path.isdir(dirName): os.mkdir(dirName) for pvName in self._history: saveFile = os.path.join(dirName, pvName + '.pvget') try: pvHistory = self._history[pvName] with open(saveFile, "w") as f: if self._verbose or True: print("Writing %d values to %s ..." % (len(pvHistory), saveFile)) # Not using json.dump so output matches similar # stressTestClient pvCapture output #json.dump( pvHistory, f, indent=4 ) #continue f.write('[\n') if len(pvHistory) > 1: for tsVal in pvHistory[0:-1]: f.write("\t[ [ %d, %d ], %d ],\n" % (tsVal[0][0], tsVal[0][1], tsVal[1])) if len(pvHistory) > 0: # Write last value tsVal = pvHistory[-1] f.write("\t[ [ %d, %d ], %d ],\n" % (tsVal[0][0], tsVal[0][1], tsVal[1])) f.write(']\n') except BaseException as e: print("Error: %s" % e) print("Unable to write values to %s" % saveFile) def closeSubscription(self): self._shutDown = True if self._S is not None: if self._verbose: print("Closing subscription to %s" % self._pvName) self._S.close() self._S = None def __exit__(self): self.closeSubscription()
class P4PProvider(QObject) : callbacksignal = pyqtSignal() def __init__(self): QObject.__init__(self) self.callbacksignal.connect(self.mycallback) self.callbackDoneEvent = Event() self.firstCallback = True self.isClosed = True self.monitorRateOnly = False self.ncallbacks = 0 self.lastTime = time.time() def start(self) : self.ctxt = Context('pva') self.firstCallback = True self.isClosed = False self.subscription = self.ctxt.monitor( getDynamicRecordName(), self.p4pcallback, request='field()', notify_disconnect=True) def stop(self) : self.isClosed = True self.ctxt.close() def done(self) : pass def callback(self,arg) : self.viewer.callback(arg) def p4pcallback(self,arg) : if self.monitorRateOnly : self.ncallbacks += 1 timenow = time.time() timediff = timenow - self.lastTime if timediff<1 : return print('rate=',round(self.ncallbacks/timediff)) self.lastTime = timenow self.ncallbacks = 0 return if self.isClosed : return self.struct = arg; self.callbacksignal.emit() self.callbackDoneEvent.wait() self.callbackDoneEvent.clear() def mycallback(self) : struct = self.struct arg = dict() try : argtype = str(type(struct)) if argtype.find('Disconnected')>=0 : arg["status"] = "disconnected" self.callback(arg) self.firstCallback = True self.callbackDoneEvent.set() return if self.firstCallback : arg = dict() arg["status"] = "connected" self.callback(arg) self.firstCallback = False self.callback(arg) data = DynamicRecordData() data.name = struct['name'] data.x = struct['x'] data.y = struct['y'] data.xmin = struct['xmin'] data.xmax = struct['xmax'] data.ymin = struct['ymin'] data.ymax = struct['ymax'] arg = dict() arg['value'] = data self.callback(arg) self.callbackDoneEvent.set() return except Exception as error: arg["exception"] = repr(error) self.callback(arg) self.callbackDoneEvent.set() return