Source code for secsgem.gem.equipmenthandler

#####################################################################
# equipmenthandler.py
#
# (c) Copyright 2013-2015, Benjamin Parzella. All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#####################################################################
"""Handler for GEM equipment."""

from ..common.fysom import Fysom
from ..gem.handler import GemHandler
from ..secs.variables import SecsVarString, SecsVarU1, SecsVarU2, SecsVarU4, SecsVarU8, SecsVarArray, SecsVarI1, SecsVarI2, SecsVarI4, SecsVarI8, SecsVarBinary, SecsVarDynamic

from datetime import datetime
from dateutil.tz import tzlocal

ECID_ESTABLISH_COMMUNICATIONS_TIMEOUT = 1
ECID_TIME_FORMAT = 2

SVID_CLOCK = 1001
SVID_CONTROL_STATE = 1002
SVID_EVENTS_ENABLED = 1003

CEID_EQUIPMENT_OFFLINE = 1
CEID_CONTROL_STATE_LOCAL = 2
CEID_CONTROL_STATE_REMOTE = 3


[docs]class DataValue: """Data value definition You can manually set the secs-type of the id with the 'id_type' keyword argument. Custom parameters can be set with the keyword arguments, they will be passed to the GemEquipmentHandlers callback :func:`secsgem.gem.equipmenthandler.GemEquipmentHandler.on_dv_value_request`. If use_callbacks is disabled, you can set the value with the value property. :param dvid: ID of the data value :type dvid: various :param name: long name of the data value :type name: string :param value_type: type of the data value :type value_type: type of class inherited from :class:`secsgem.secs.variables.SecsVar` :param use_callback: use the GemEquipmentHandler callbacks to get variable (True) or use internal value :type use_callback: boolean """ def __init__(self, dvid, name, value_type, use_callback=True, **kwargs): self.dvid = dvid self.name = name self.value_type = value_type self.use_callback = use_callback self.value = 0 if isinstance(self.dvid, int): self.id_type = SecsVarU4 else: self.id_type = SecsVarString for key, value in kwargs.items(): setattr(self, key, value)
[docs]class StatusVariable: """Status variable definition You can manually set the secs-type of the id with the 'id_type' keyword argument. Custom parameters can be set with the keyword arguments, they will be passed to the GemEquipmentHandlers callback :func:`secsgem.gem.equipmenthandler.GemEquipmentHandler.on_sv_value_request`. If use_callbacks is disabled, you can set the value with the value property. :param svid: ID of the status variable :type svid: various :param name: long name of the status variable :type name: string :param unit: unit (see SEMI E5, Units of Measure) :type unit: string :param value_type: type of the status variable :type value_type: type of class inherited from :class:`secsgem.secs.variables.SecsVar` :param use_callback: use the GemEquipmentHandler callbacks to get variable (True) or use internal value :type use_callback: boolean """ def __init__(self, svid, name, unit, value_type, use_callback=True, **kwargs): self.svid = svid self.name = name self.unit = unit self.value_type = value_type self.use_callback = use_callback self.value = 0 if isinstance(self.svid, int): self.id_type = SecsVarU4 else: self.id_type = SecsVarString for key, value in kwargs.items(): setattr(self, key, value)
[docs]class CollectionEvent: """Collection event definition You can manually set the secs-type of the id with the 'id_type' keyword argument. Custom parameters can be set with the keyword arguments, they will be passed to the GemEquipmentHandlers callback :func:`secsgem.gem.equipmenthandler.GemEquipmentHandler.on_dv_value_request`. If use_callbacks is disabled, you can set the value with the value property. :param ceid: ID of the collection event :type ceid: various :param name: long name of the collection event :type name: string :param data_values: data values available for this event :type data_values: list of DVIDs """ def __init__(self, ceid, name, data_values, **kwargs): self.ceid = ceid self.name = name self.data_values = data_values if isinstance(self.ceid, int): self.id_type = SecsVarU4 else: self.id_type = SecsVarString for key, value in kwargs.items(): setattr(self, key, value)
[docs]class CollectionEventReport: """Report definition for registered collection events You can manually set the secs-type of the id with the 'id_type' keyword argument. :param rptid: ID of the report :type rptid: various :param vars: long name of the collection event :type vars: string """ def __init__(self, rptid, variables, **kwargs): self.rptid = rptid self.vars = variables if isinstance(self.rptid, int): self.id_type = SecsVarU4 else: self.id_type = SecsVarString for key, value in kwargs.items(): setattr(self, key, value)
[docs]class EquipmentConstant: """Equipment constant definition You can manually set the secs-type of the id with the 'id_type' keyword argument. Custom parameters can be set with the keyword arguments, they will be passed to the GemEquipmentHandlers callbacks :func:`secsgem.gem.equipmenthandler.GemEquipmentHandler.on_ec_value_request` and :func:`secsgem.gem.equipmenthandler.GemEquipmentHandler.on_ec_value_update` . If use_callbacks is disabled, you can set the value with the value property. :param svid: ID of the equipment constant :type svid: various :param name: long name :type name: string :param min_value: minimum value :type min_value: various :param max_value: maximum value :type max_value: various :param default_value: default value :type default_value: various :param unit: unit (see SEMI E5, Units of Measure) :type unit: string :param value_type: type of the status variable :type value_type: type of class inherited from :class:`secsgem.secs.variables.SecsVar` :param use_callback: use the GemEquipmentHandler callbacks to get and set variable (True) or use internal value :type use_callback: boolean """ def __init__(self, ecid, name, min_value, max_value, default_value, unit, value_type, use_callback=True, **kwargs): self.ecid = ecid self.name = name self.min_value = min_value self.max_value = max_value self.default_value = default_value self.unit = unit self.value_type = value_type self.use_callback = use_callback self.value = default_value if isinstance(self.ecid, int): self.id_type = SecsVarU4 else: self.id_type = SecsVarString for key, value in kwargs.items(): setattr(self, key, value)
[docs]class GemEquipmentHandler(GemHandler): """Baseclass for creating equipment models. Inherit from this class and override required functions. :param address: IP address of remote host :type address: string :param port: TCP port of remote host :type port: integer :param active: Is the connection active (*True*) or passive (*False*) :type active: boolean :param session_id: session / device ID to use for connection :type session_id: integer :param name: Name of the underlying configuration :type name: string :param event_handler: object for event handling :type event_handler: :class:`secsgem.common.EventHandler` :param custom_connection_handler: object for connection handling (ie multi server) :type custom_connection_handler: :class:`secsgem.hsms.connections.HsmsMultiPassiveServer` :param initial_control_state: initial state for the control state model, one of ["EQUIPMENT_OFFLINE", "ATTEMPT_ONLINE", "HOST_OFFLINE", "ONLINE"] :type initial_control_state: string """ def __init__(self, address, port, active, session_id, name, event_handler=None, custom_connection_handler=None, initial_control_state="ATTEMPT_ONLINE", initial_online_control_state="REMOTE"): GemHandler.__init__(self, address, port, active, session_id, name, event_handler, custom_connection_handler) self.isHost = False self.initialControlStates = ["EQUIPMENT_OFFLINE", "ATTEMPT_ONLINE", "HOST_OFFLINE", "ONLINE"] self.initialControlState = initial_control_state self.onlineControlStates = ["LOCAL", "REMOTE"] self.onlineControlState = initial_online_control_state self.controlState = Fysom({ 'initial': "INIT", 'events': [ {'name': 'start', 'src': 'INIT', 'dst': 'CONTROL'}, # 1 {'name': 'initial_offline', 'src': 'CONTROL', 'dst': 'OFFLINE'}, # 1 {'name': 'initial_equipment_offline', 'src': 'OFFLINE', 'dst': 'EQUIPMENT_OFFLINE'}, # 2 {'name': 'initial_attempt_online', 'src': 'OFFLINE', 'dst': 'ATTEMPT_ONLINE'}, # 2 {'name': 'initial_host_offline', 'src': 'OFFLINE', 'dst': 'HOST_OFFLINE'}, # 2 {'name': 'switch_online', 'src': 'EQUIPMENT_OFFLINE', 'dst': 'ATTEMPT_ONLINE'}, # 3 {'name': 'attempt_online_fail_equipment_offline', 'src': 'ATTEMPT_ONLINE', 'dst': 'EQUIPMENT_OFFLINE'}, # 4 {'name': 'attempt_online_fail_host_offline', 'src': 'ATTEMPT_ONLINE', 'dst': 'HOST_OFFLINE'}, # 4 {'name': 'attempt_online_success', 'src': 'ATTEMPT_ONLINE', 'dst': 'ONLINE'}, # 5 {'name': 'switch_offline', 'src': ["ONLINE", "ONLINE_LOCAL", "ONLINE_REMOTE"], 'dst': 'EQUIPMENT_OFFLINE'}, # 6, 12 {'name': 'initial_online', 'src': 'CONTROL', 'dst': 'ONLINE'}, # 1 {'name': 'initial_online_local', 'src': 'ONLINE', 'dst': 'ONLINE_LOCAL'}, # 7 {'name': 'initial_online_remote', 'src': 'ONLINE', 'dst': 'ONLINE_REMOTE'}, # 7 {'name': 'switch_online_local', 'src': 'ONLINE_REMOTE', 'dst': 'ONLINE_LOCAL'}, # 8 {'name': 'switch_online_remote', 'src': 'ONLINE_LOCAL', 'dst': 'ONLINE_REMOTE'}, # 9 {'name': 'remote_offline', 'src': ["ONLINE", "ONLINE_LOCAL", "ONLINE_REMOTE"], 'dst': 'HOST_OFFLINE'}, # 10 {'name': 'remote_online', 'src': 'HOST_OFFLINE', 'dst': 'ONLINE'}, # 11 ], 'callbacks': { 'onCONTROL': self._on_control_state_control, # 1, forward online/offline depending on configuration 'onOFFLINE': self._on_control_state_offline, # 2, forward to configured offline state 'onATTEMPT_ONLINE': self._on_control_state_attempt_online, # 3, send S01E01 'onONLINE': self._on_control_state_online, # 7, forward to configured online state 'oninitial_online_local': self._on_control_state_initial_online_local, # 7, send collection event 'onswitch_online_local': self._on_control_state_initial_online_local, # 8, send collection event 'oninitial_online_remote': self._on_control_state_initial_online_remote, # 8, send collection event 'onswitch_online_remote': self._on_control_state_initial_online_remote, # 9, send collection event }, 'autoforward': [ # {'src': 'OFFLINE', 'dst': 'EQUIPMENT_OFFLINE'}, # 2 # {'src': 'EQUIPMENT_INITIATED_CONNECT', 'dst': 'WAIT_CRA'}, # 5 # {'src': 'HOST_INITIATED_CONNECT', 'dst': 'WAIT_CR_FROM_HOST'}, # 10 ] }) self.controlState.start() self.register_callback(1, 3, self.s01f03_handler) self.register_callback(1, 11, self.s01f11_handler) self.register_callback(1, 15, self.s01f15_handler) self.register_callback(1, 17, self.s01f17_handler) self.register_callback(2, 13, self.s02f13_handler) self.register_callback(2, 15, self.s02f15_handler) self.register_callback(2, 29, self.s02f29_handler) self.register_callback(2, 33, self.s02f33_handler) self.register_callback(2, 35, self.s02f35_handler) self.register_callback(2, 37, self.s02f37_handler) self.register_callback(6, 15, self.s06f15_handler) self._time_format = 1 self._data_values = { } self._status_variables = { SVID_CLOCK: StatusVariable(SVID_CLOCK, "Clock", "", SecsVarString), SVID_CONTROL_STATE: StatusVariable(SVID_CONTROL_STATE, "ControlState", "", SecsVarBinary), SVID_EVENTS_ENABLED: StatusVariable(SVID_EVENTS_ENABLED, "EventsEnabled", "", SecsVarArray), } self._collection_events = { CEID_EQUIPMENT_OFFLINE: CollectionEvent(CEID_EQUIPMENT_OFFLINE, "EquipmentOffline", []), CEID_CONTROL_STATE_LOCAL: CollectionEvent(CEID_CONTROL_STATE_LOCAL, "ControlStateLocal", []), CEID_CONTROL_STATE_REMOTE: CollectionEvent(CEID_CONTROL_STATE_REMOTE, "ControlStateRemote", []), } self._equipment_constants = { ECID_ESTABLISH_COMMUNICATIONS_TIMEOUT: EquipmentConstant(ECID_ESTABLISH_COMMUNICATIONS_TIMEOUT, "EstablishCommunicationsTimeout", 10, 120, 10, "sec", SecsVarI2), ECID_TIME_FORMAT: EquipmentConstant(ECID_TIME_FORMAT, "TimeFormat", 0, 2, 1, "", SecsVarI4), } self._registered_reports = {} self._registered_collection_events = {} # control state model def _on_control_state_control(self, _): if self.initialControlState == "ONLINE": self.controlState.initial_online() else: self.controlState.initial_offline() def _on_control_state_offline(self, _): if self.initialControlState == "EQUIPMENT_OFFLINE": self.controlState.initial_equipment_offline() elif self.initialControlState == "ATTEMPT_ONLINE": self.controlState.initial_attempt_online() elif self.initialControlState == "HOST_OFFLINE": self.controlState.initial_host_offline() def _on_control_state_attempt_online(self, _): if not self.communicationState.isstate("COMMUNICATING"): self.controlState.attempt_online_fail_host_offline() return response = self.are_you_there() if response is None: self.controlState.attempt_online_fail_host_offline() return if response.header.stream != 1 or response.header.function != 2: self.controlState.attempt_online_fail_host_offline() return self.controlState.attempt_online_success() def _on_control_state_online(self, _): if self.onlineControlState == "REMOTE": self.controlState.initial_online_remote() else: self.controlState.initial_online_local() def _on_control_state_initial_online_local(self, _): self.trigger_collection_events([CEID_CONTROL_STATE_LOCAL]) def _on_control_state_initial_online_remote(self, _): self.trigger_collection_events([CEID_CONTROL_STATE_REMOTE])
[docs] def control_switch_online(self): """Operator switches to online control state """ self.controlState.switch_online()
[docs] def control_switch_offline(self): """Operator switches to offline control state """ self.controlState.switch_offline() self.trigger_collection_events([CEID_EQUIPMENT_OFFLINE])
[docs] def control_switch_online_local(self): """Operator switches to the local online control state """ self.controlState.switch_online_local() self.onlineControlState = "LOCAL"
[docs] def control_switch_online_remote(self): """Operator switches to the local online control state """ self.controlState.switch_online_remote() self.onlineControlState = "REMOTE"
[docs] def s01f15_handler(self, handler, packet): """Callback handler for Stream 1, Function 15, Request offline .. seealso:: :func:`secsgem.common.StreamFunctionCallbackHandler.register_callback` :param handler: handler the message was received on :type handler: :class:`secsgem.hsms.handler.HsmsHandler` :param packet: complete message received :type packet: :class:`secsgem.hsms.packets.HsmsPacket` """ # message = self.secs_decode(packet) OFLACK = 0 if self.controlState.current in ["ONLINE", "ONLINE_LOCAL", "ONLINE_REMOTE"]: self.controlState.remote_offline() self.trigger_collection_events([CEID_EQUIPMENT_OFFLINE]) handler.send_response(self.stream_function(1, 16)(OFLACK), packet.header.system)
[docs] def s01f17_handler(self, handler, packet): """Callback handler for Stream 1, Function 17, Request online .. seealso:: :func:`secsgem.common.StreamFunctionCallbackHandler.register_callback` :param handler: handler the message was received on :type handler: :class:`secsgem.hsms.handler.HsmsHandler` :param packet: complete message received :type packet: :class:`secsgem.hsms.packets.HsmsPacket` """ # message = self.secs_decode(packet) ONLACK = 1 if self.controlState.isstate("HOST_OFFLINE"): self.controlState.remote_online() ONLACK = 0 elif self.controlState.current in ["ONLINE", "ONLINE_LOCAL", "ONLINE_REMOTE"]: ONLACK = 2 handler.send_response(self.stream_function(1, 18)(ONLACK), packet.header.system)
# data values @property def data_values(self): """The list of the data values :returns: Data value list :rtype: list of :class:`secsgem.gem.equipmenthandler.DataValue` """ return self._data_values
[docs] def on_dv_value_request(self, dvid, dv): """Get the data value depending on its configuation. Override in inherited class to provide custom data value request handling. :param dvid: Id of the data value encoded in the corresponding type :type dvid: :class:`secsgem.secs.variables.SecsVar` :param dv: The data value requested :type dv: :class:`secsgem.gem.equipmenthandler.DataValue` :returns: The value encoded in the corresponding type :rtype: :class:`secsgem.secs.variables.SecsVar` """ return dv.value_type(value=dv.value)
def _get_dv_value(self, dv): """Get the data value depending on its configuation :param dv: The data value requested :type dv: :class:`secsgem.gem.equipmenthandler.DataValue` :returns: The value encoded in the corresponding type :rtype: :class:`secsgem.secs.variables.SecsVar` """ if dv.use_callback: return self.on_dv_value_request(dv.id_type(value=dv.dvid), dv) else: return dv.value_type(value=dv.value) # status variables @property def status_variables(self): """The list of the status variables :returns: Status variable list :rtype: list of :class:`secsgem.gem.equipmenthandler.StatusVariables` """ return self._status_variables
[docs] def on_sv_value_request(self, svid, sv): """Get the status variable value depending on its configuation. Override in inherited class to provide custom status variable request handling. :param svid: Id of the status variable encoded in the corresponding type :type svid: :class:`secsgem.secs.variables.SecsVar` :param sv: The status variable requested :type sv: :class:`secsgem.gem.equipmenthandler.StatusVariable` :returns: The value encoded in the corresponding type :rtype: :class:`secsgem.secs.variables.SecsVar` """ return sv.value_type(value=sv.value)
def _get_sv_value(self, sv): """Get the status variable value depending on its configuation :param sv: The status variable requested :type sv: :class:`secsgem.gem.equipmenthandler.StatusVariable` :returns: The value encoded in the corresponding type :rtype: :class:`secsgem.secs.variables.SecsVar` """ if sv.svid == SVID_CLOCK: return sv.value_type(value=self._get_clock()) if sv.svid == SVID_CONTROL_STATE: return sv.value_type(value=self._get_control_state_id()) if sv.svid == SVID_EVENTS_ENABLED: events = self._get_events_enabled() return sv.value_type(SecsVarDynamic([SecsVarString, SecsVarU1, SecsVarU2, SecsVarU4, SecsVarU8, SecsVarI1, SecsVarI2, SecsVarI4, SecsVarI8]), value=events) if sv.use_callback: return self.on_sv_value_request(sv.id_type(value=sv.svid), sv) else: return sv.value_type(value=sv.value)
[docs] def s01f03_handler(self, handler, packet): """Callback handler for Stream 1, Function 3, Equipment status request .. seealso:: :func:`secsgem.common.StreamFunctionCallbackHandler.register_callback` :param handler: handler the message was received on :type handler: :class:`secsgem.hsms.handler.HsmsHandler` :param packet: complete message received :type packet: :class:`secsgem.hsms.packets.HsmsPacket` """ message = self.secs_decode(packet) responses = [] if len(message) == 0: for svid in self._status_variables: sv = self._status_variables[svid] responses.append(self._get_sv_value(sv)) else: for svid in message: if svid not in self._status_variables: responses.append(SecsVarArray([])) else: sv = self._status_variables[svid] responses.append(self._get_sv_value(sv)) handler.send_response(self.stream_function(1, 4)(responses), packet.header.system)
[docs] def s01f11_handler(self, handler, packet): """Callback handler for Stream 1, Function 11, SV namelist request .. seealso:: :func:`secsgem.common.StreamFunctionCallbackHandler.register_callback` :param handler: handler the message was received on :type handler: :class:`secsgem.hsms.handler.HsmsHandler` :param packet: complete message received :type packet: :class:`secsgem.hsms.packets.HsmsPacket` """ message = self.secs_decode(packet) responses = [] if len(message) == 0: for svid in self._status_variables: sv = self._status_variables[svid] responses.append({"SVID": sv.svid, "SVNAME": sv.name, "UNITS": sv.unit}) else: for svid in message: if svid not in self._status_variables: responses.append({"SVID": svid, "SVNAME": "", "UNITS": ""}) else: sv = self._status_variables[svid] responses.append({"SVID": sv.svid, "SVNAME": sv.name, "UNITS": sv.unit}) handler.send_response(self.stream_function(1, 12)(responses), packet.header.system)
# collection events @property def collection_events(self): """The list of the collection events :returns: Collection event list :rtype: list of :class:`secsgem.gem.equipmenthandler.CollectionEvent` """ return self._collection_events @property def registered_reports(self): """The list of the subscribed reports :returns: Collection event report list :rtype: dictionary of subscribed reports """ return self._registered_reports @property def registered_collection_events(self): """The list of the subscribed collection events :returns: Collection event list :rtype: dictionary of :class:`secsgem.gem.equipmenthandler.CollectionEventLink` """ return self._registered_collection_events
[docs] def trigger_collection_events(self, ceids): """Triggers the supplied collection events :param ceids: List of collection events :type ceids: list of various """ if not isinstance(ceids, list): ceids = [ceids] for ceid in ceids: if ceid in self._registered_collection_events: if self._registered_collection_events[ceid].enabled: reports = self._build_collection_event(ceid) self.send_and_waitfor_response(self.stream_function(6, 11)({"DATAID": 1, "CEID": ceid, "RPT": reports}))
[docs] def s02f33_handler(self, handler, packet): """Callback handler for Stream 2, Function 33, Define Report .. seealso:: :func:`secsgem.common.StreamFunctionCallbackHandler.register_callback` :param handler: handler the message was received on :type handler: :class:`secsgem.hsms.handler.HsmsHandler` :param packet: complete message received :type packet: :class:`secsgem.hsms.packets.HsmsPacket` """ message = self.secs_decode(packet) # 0 = Accept # 1 = Denied. Insufficient space. # 2 = Denied. Invalid format. # 3 = Denied. At least one RPTID already defined. # 4 = Denied. At least VID does not exist. # >4 = Other errors DRACK = 0 # pre check message for errors for report in message.DATA: if report.RPTID in self._registered_reports: DRACK = 3 else: for vid in report.VID: if (vid not in self._data_values) and (vid not in self._status_variables): DRACK = 4 # pre check okay if DRACK == 0: # no data -> remove all reports and links if not message.DATA: self._registered_collection_events.clear() self._registered_reports.clear() else: for report in message.DATA: # no vids -> remove this reports and links if not report.VID: # remove report from linked collection events for collection_event in self._registered_collection_events: if report.RPTID in self._registered_collection_events[collection_event].reports: self._registered_collection_events[collection_event].reports.remove(report.RPTID) # remove collection event link if no collection events present if not self._registered_collection_events[collection_event].reports: del self._registered_collection_events[collection_event] # remove report if report.RPTID in self._registered_reports: del self._registered_reports[report.RPTID] else: # add report self._registered_reports[report.RPTID] = CollectionEventReport(report.RPTID, report.VID) handler.send_response(self.stream_function(2, 34)(DRACK), packet.header.system)
[docs] def s02f35_handler(self, handler, packet): """Callback handler for Stream 2, Function 35, Link event report .. seealso:: :func:`secsgem.common.StreamFunctionCallbackHandler.register_callback` :param handler: handler the message was received on :type handler: :class:`secsgem.hsms.handler.HsmsHandler` :param packet: complete message received :type packet: :class:`secsgem.hsms.packets.HsmsPacket` """ message = self.secs_decode(packet) # 0 = Accepted # 1 = Denied. Insufficient space # 2 = Denied. Invalid format # 3 = Denied. At least one CEID link already defined # 4 = Denied. At least one CEID does not exist # 5 = Denied. At least one RPTID does not exist # >5 = Other errors LRACK = 0 # pre check message for errors for event in message.DATA: if event.CEID not in self._collection_events: LRACK = 4 for rptid in event.RPTID: if event.CEID in self._registered_collection_events: ce = self._registered_collection_events[event.CEID] if rptid in ce.reports: LRACK = 3 if rptid not in self._registered_reports: LRACK = 5 # pre check okay if LRACK == 0: for event in message.DATA: # no report ids, remove all links for collection event if not event.RPTID: if event.CEID in self._registered_collection_events: del self._registered_collection_events[event.CEID] else: if event.CEID in self._registered_collection_events: ce = self._registered_collection_events[event.CEID] for rptid in event.RPTID: ce.reports.append(rptid) else: self._registered_collection_events[event.CEID] = CollectionEventLink(self._collection_events[event.CEID], event.RPTID) handler.send_response(self.stream_function(2, 36)(LRACK), packet.header.system)
[docs] def s02f37_handler(self, handler, packet): """Callback handler for Stream 2, Function 37, En-/Disable Event Report .. seealso:: :func:`secsgem.common.StreamFunctionCallbackHandler.register_callback` :param handler: handler the message was received on :type handler: :class:`secsgem.hsms.handler.HsmsHandler` :param packet: complete message received :type packet: :class:`secsgem.hsms.packets.HsmsPacket` """ message = self.secs_decode(packet) # 0 = Accepted # 1 = Denied. At least one CEID does not exist ERACK = 0 if not self._set_ce_state(message.CEED, message.CEID): ERACK = 1 handler.send_response(self.stream_function(2, 38)(ERACK), packet.header.system)
[docs] def s06f15_handler(self, handler, packet): """Callback handler for Stream 6, Function 15, event report request .. seealso:: :func:`secsgem.common.StreamFunctionCallbackHandler.register_callback` :param handler: handler the message was received on :type handler: :class:`secsgem.hsms.handler.HsmsHandler` :param packet: complete message received :type packet: :class:`secsgem.hsms.packets.HsmsPacket` """ message = self.secs_decode(packet) ceid = message.get() reports = [] if ceid in self._registered_collection_events: if self._registered_collection_events[ceid].enabled: reports = self._build_collection_event(ceid) handler.send_response(self.stream_function(6, 16)({"DATAID": 1, "CEID": ceid, "RPT": reports}), packet.header.system)
def _set_ce_state(self, ceed, ceids): """En-/Disable event reports for the supplied ceids (or all, if ceid is an empty list) :param ceed: Enable (True) or disable (False) event reports :type ceed: bool :param ceids: List of collection events :type ceids: list of integer :returns: True if all ceids were ok, False if illegal ceid was supplied :rtype: bool """ result = True if not ceids: for ceid in self._registered_collection_events: self._registered_collection_events[ceid].enabled = ceed else: for ceid in ceids: if ceid in self._registered_collection_events: self._registered_collection_events[ceid].enabled = ceed else: result = False return result def _build_collection_event(self, ceid): """Build reports for a collection event :param ceid: collection event to build :type ceid: integer :returns: collection event data :rtype: array """ reports = [] for rptid in self._registered_collection_events[ceid].reports: report = self._registered_reports[rptid] variables = [] for var in report.vars: if var in self._status_variables: v = self._get_sv_value(self._status_variables[var]) variables.append(v) elif var in self._data_values: v = self._get_dv_value(self._data_values[var]) variables.append(v) else: raise ValueError("asdfg/ remove me") reports.append({"RPTID": rptid, "V": variables}) return reports # equipment constants @property def equipment_constants(self): """The list of the equipments contstants :returns: Equipment constant list :rtype: list of :class:`secsgem.gem.equipmenthandler.EquipmentConstant` """ return self._equipment_constants
[docs] def on_ec_value_request(self, ecid, ec): """Get the equipment constant value depending on its configuation. Override in inherited class to provide custom equipment constant request handling. :param ecid: Id of the equipment constant encoded in the corresponding type :type ecid: :class:`secsgem.secs.variables.SecsVar` :param ec: The equipment constant requested :type ec: :class:`secsgem.gem.equipmenthandler.EquipmentConstant` :returns: The value encoded in the corresponding type :rtype: :class:`secsgem.secs.variables.SecsVar` """ return ec.value_type(value=ec.value)
[docs] def on_ec_value_update(self, ecid, ec, value): """Set the equipment constant value depending on its configuation. Override in inherited class to provide custom equipment constant update handling. :param ecid: Id of the equipment constant encoded in the corresponding type :type ecid: :class:`secsgem.secs.variables.SecsVar` :param ec: The equipment constant to be updated :type ec: :class:`secsgem.gem.equipmenthandler.EquipmentConstant` :param value: The value encoded in the corresponding type :type value: :class:`secsgem.secs.variables.SecsVar` """ ec.value = value
def _get_ec_value(self, ec): """Get the equipment constant value depending on its configuation :param ec: The equipment requested :type ec: :class:`secsgem.gem.equipmenthandler.EquipmentConstant` :returns: The value encoded in the corresponding type :rtype: :class:`secsgem.secs.variables.SecsVar` """ if ec.ecid == ECID_ESTABLISH_COMMUNICATIONS_TIMEOUT: return ec.value_type(value=self.establishCommunicationTimeout) if ec.ecid == ECID_TIME_FORMAT: return ec.value_type(value=self._time_format) if ec.use_callback: return self.on_ec_value_request(ec.id_type(value=ec.ecid), ec) else: return ec.value_type(value=ec.value) def _set_ec_value(self, ec, value): """Get the equipment constant value depending on its configuation :param ec: The equipment requested :type ec: :class:`secsgem.gem.equipmenthandler.EquipmentConstant` :param value: The value encoded in the corresponding type :type value: :class:`secsgem.secs.variables.SecsVar` """ if ec.ecid == ECID_ESTABLISH_COMMUNICATIONS_TIMEOUT: self.establishCommunicationTimeout = value if ec.ecid == ECID_TIME_FORMAT: self._time_format = value if ec.use_callback: self.on_ec_value_update(ec.id_type(value=ec.ecid), ec, value) else: ec.value = value
[docs] def s02f13_handler(self, handler, packet): """Callback handler for Stream 2, Function 13, Equipment constant request .. seealso:: :func:`secsgem.common.StreamFunctionCallbackHandler.register_callback` :param handler: handler the message was received on :type handler: :class:`secsgem.hsms.handler.HsmsHandler` :param packet: complete message received :type packet: :class:`secsgem.hsms.packets.HsmsPacket` """ message = self.secs_decode(packet) responses = [] if len(message) == 0: for ecid in self._equipment_constants: ec = self._equipment_constants[ecid] responses.append(self._get_ec_value(ec)) else: for ecid in message: if ecid not in self._equipment_constants: responses.append(SecsVarArray([])) else: ec = self._equipment_constants[ecid] responses.append(self._get_ec_value(ec)) handler.send_response(self.stream_function(2, 14)(responses), packet.header.system)
[docs] def s02f15_handler(self, handler, packet): """Callback handler for Stream 2, Function 15, Equipment constant send .. seealso:: :func:`secsgem.common.StreamFunctionCallbackHandler.register_callback` :param handler: handler the message was received on :type handler: :class:`secsgem.hsms.handler.HsmsHandler` :param packet: complete message received :type packet: :class:`secsgem.hsms.packets.HsmsPacket` """ message = self.secs_decode(packet) eac = 0 for ec in message: if ec.ECID not in self._equipment_constants: eac = 1 if eac == 0: for ec in message: self._set_ec_value(self._equipment_constants[ec.ECID], ec.ECV) handler.send_response(self.stream_function(2, 16)(eac), packet.header.system)
[docs] def s02f29_handler(self, handler, packet): """Callback handler for Stream 2, Function 29, EC namelist request .. seealso:: :func:`secsgem.common.StreamFunctionCallbackHandler.register_callback` :param handler: handler the message was received on :type handler: :class:`secsgem.hsms.handler.HsmsHandler` :param packet: complete message received :type packet: :class:`secsgem.hsms.packets.HsmsPacket` """ message = self.secs_decode(packet) responses = [] if len(message) == 0: for ecid in self._equipment_constants: ec = self._equipment_constants[ecid] responses.append({"ECID": ec.ecid, "ECNAME": ec.name, "ECMIN": ec.min_value, "ECMAX": ec.max_value, "ECDEF": ec.default_value, "UNITS": ec.unit}) else: for ecid in message: if ecid not in self._equipment_constants: responses.append({"ECID": ecid, "ECNAME": "", "ECMIN": "", "ECMAX": "", "ECDEF": "", "UNITS": ""}) else: ec = self._equipment_constants[ecid] responses.append({"ECID": ec.ecid, "ECNAME": ec.name, "ECMIN": ec.min_value, "ECMAX": ec.max_value, "ECDEF": ec.default_value, "UNITS": ec.unit}) handler.send_response(self.stream_function(2, 30)(responses), packet.header.system)
# helpers def _get_clock(self): """Returns the clock depending on configured time format :returns: time code :rtype: string """ now = datetime.now(tzlocal()) if self._time_format == 0: return now.strftime("%y%m%d%H%M%S") elif self._time_format == 2: return now.isoformat() else: return now.strftime("%Y%m%d%H%M%S") + now.strftime("%f")[0:2] def _get_control_state_id(self): """the id of the control state for the current control state :returns: control state :rtype: integer """ if self.controlState.isstate("EQUIPMENT_OFFLINE"): return 1 if self.controlState.isstate("ATTEMPT_ONLINE"): return 2 if self.controlState.isstate("HOST_OFFLINE"): return 3 if self.controlState.isstate("ONLINE_LOCAL"): return 4 if self.controlState.isstate("ONLINE_REMOTE"): return 5 return 0 def _get_events_enabled(self): """list of the enabled collection events :returns: collection event :rtype: list of various """ enabled_ceid = [] for ceid in self._registered_collection_events: if self._registered_collection_events[ceid].enabled: enabled_ceid.append(ceid) return enabled_ceid
[docs] def on_connection_closed(self, connection): """Connection was closed""" # call parent handlers GemHandler.on_connection_closed(self, connection) # update control state self.controlState.switch_offline() self.controlState.switch_online()