""" Implementation of the MKS ASCII Protocol as specified in "ASCII Protocol V1.8.pdf" The """ from __future__ import absolute_import __author__ = "Georg Schlisio = 3.7, "Minimum python requirement not met: "+str(sys.version_info[0] + sys.version_info[1]*0.1)+". We need 3.7 or above for fstrings and queue features." #### composer = ComposeMessage() class MKSListener(): def __init__(self, ip, up_queue, down_queue, port=10014, local_port=10000, target_state="connected"): """ Talk to an MKS mass spectrometer over the ethernet interface. :param ip: IPv4 address of RGA device as string :param up_queue: queue of messages towards the RGA device (type: queue.SimpleQueue) :param down_queue: queue of messages from the RGA device (type: queue.SimpleQueue) :param port: port on the RGA device (default: 10014) :param local_port: local port (default 10000) :param target_state: """ self.up_queue = up_queue # queue of messages towards the RGA device self.down_queue = down_queue # queue of messages from the RGA device self._target_state = target_state # networking self._remote_ip = ip # self._remote_port = port self._local_port = local_port self._s = None # socket object # connection state self._connected = False # connection state self._target_state = "disconnected" # target connection state self.device_sn = None # device SN currently connected to # protocol versioning and formatting self._remote_min_protocol_version = None # remote requirement of min protocol, retrieved from remote device self._min_protocol_version = 1.6 # assumed minimum protocol version self._protocol_version = __protocol_version__ self.format_with_tab = False # whether to request FormatWithTab (reduces networkload slighlty) # program flow control self.loop_min_time = 0.01 # minimum duration of a listener loop in seconds # Mass spectrometer properties self.filament = None # number of filament in use def turn_on(self): self._target_state = "connected" def turn_off(self): self._target_state = "disconnected" def run(self): """ Main loop. :return: """ while True: logging.info(f"starting new loop") start_time = time.time() if self._target_state == "connected" and not self._connected: self._connect() if self._connected: self._check_for_messages() if self._target_state == "disconnected": self._disconnect() break if self._target_state == "exit": self._disconnect() exit(0) end_time = time.time() if end_time - start_time < self.loop_min_time: sleeptime = self.loop_min_time - (end_time - start_time) logging.info(f"sleep for {sleeptime}s") time.sleep(sleeptime) def _send(self, msg, args=None): """ Wrapper for sending messages :param msg: :return: """ self._s.send(composer(msg, args=args)) def _connect(self): """ Initiate connection :return: """ assert not self._connected, "Cannot connect " self._s = socket.socket(family=socket.AF_INET) self._host = socket.gethostname() self._s.bind((self._host, self._local_port)) self._s.connect((self._remote_ip, self._remote_port)) self.connected = True self._protocol_version_check() # control message formatting if self.format_with_tab: self._send("FormatWithTab True") # negotiate protocol version self._send("AcceptProtocol 1.6") # TODO check if actual protocol needs to be signaled def _protocol_version_check(self, tries = 10): """ Wait for first message and evaluate protocol compatibility :return: """ while True: self._check_for_messages() if self.down_queue.qsize() > 0: break time.sleep(1) tries -= 1 if tries == 0: logging.error(f"Connection not successfully established: received no greeting. Exiting.") exit(201) message = self.down_queue.pop(0) assert message.type == "MKSRGA", f"Unexpected message type: {message.type}" assert message.parms[message.type] in ("Single", "Multi"), f"Unexpected connection type: {message.parms[message.type]}" assert message.parms[message.type] == "Single", f"Connection to QMS Servers of type 'Multi' is not supported." # To support this, implement the commands "Sensors" and "Select" assert "Protocol_Revision" in message.parms assert "Min_Compatibility" in message.parms self._remote_protocol_version = float(message.parms["Protocol_Revision"]) self._remote_min_protocol_version = float(message.parms["Min_Compatibility"]) if self._remote_min_protocol_version > self._protocol_version: logging.error(f"Version mismatches: remote requires V{self._remote_min_protocol_version} but we have only V{self._protocol_version}, exiting") exit(202) def _disconnect(self): """ Terminate connection :return: """ # send message "Release" self._s.send(composer(mtype="Release")) # TODO: replace sleep with actual check for "Release Ok" time.sleep(2) self._s.close() self._connected = False def _check_for_messages(self): """ Read socket buffer, parse messages and enqueue them into the message queue :return: """ rawinput = self._s.recv(4096).decode("ASCII").split("\r\r") if len(rawinput) > 1: logging.info(f"found {len(rawinput) - 1} messages") for i in range(len(rawinput) - 1): self.down_queue.put(ParseMessage(rawinput[i])) class MKSOperator(): _commands = { # controlling message formatting "FormatWithTab": {"type": "Controlling message formatting", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "AcceptProtocol": {"type": "Controlling message formatting", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.5}, # sensor information commands "Sensors": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "Select": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "SensorState": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "Info": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "EGains": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "InletInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "RFInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MultiplierInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "SourceInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "DetectorInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "FilamentInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "TotalPressureInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "AnalogInputInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "AnalogOutputInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "DigitalInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "RolloverInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "RVCInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "CirrusInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "PECal_Info": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "Sensor": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "SourceAlignmentInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "SourceResolutionInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "SourceTuningInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "DiagnosticInputInfo": {"type": "Sensor Information Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "Control": {"type": "Gaining control of a sensor", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "Release": {"type": "Gaining control of a sensor", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, # sensor control commands "FilamentControl": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "FilamentSelect": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "FilamentOnTime": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "AddAnalog": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "AddBarchart": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "AddPeakJump": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "AddSinglePeak": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementAccuracy": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementAddMass": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementChangeMass": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementDetectorIndex": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementEGainIndex": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementFilterMode": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementMass": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementPointsPerPeak": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementRemoveMass": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementSourceIndex": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementRolloverCorrection": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementZeroBeamOff": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementZeroBufferDepth": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementZeroBufferMode": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementZeroReTrigger": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementZeroMass": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MultiplierProtect": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "RunDiagnostics": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "SetTotalPressure": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "TotalPressureCalFactor": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "TotalPressureCalDate": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "CalibrationOptions": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "DetectorFactor": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "DetectorCalDate": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "DetectorVoltage": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "InletFactor": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "ScanAdd": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "ScanStart": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "ScanStop": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "ScanResume": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "ScanRestart": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.3}, "MeasurementSelect": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementStartMass": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementEndMass": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementMultSkipAutoProtect": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.4}, "MeasurementMultSkipMassAdd": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.4}, "MeasurementMultSkipMassRemove": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.4}, "MeasurementMultSkipMassRemoveAll": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.4}, "MeasurementRemoveAll": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MeasurementRemove": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "SourceIonEnergy": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "SourceEmission": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "SourceExtract": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "SourceElectronEnergy": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "SourceLowMassResolution": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "SourceLowMassAlignment": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "SourceHighMassAlignment": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "SourceHighMassResolution": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "AnalogInputAverageCount": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "AnalogInputEnable": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "AnalogInputInterval": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "AnalogOutput": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "AudioFrequency": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "AudioMode": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "CirrusCapillaryHeater": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "CirrusHeater": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "CirrusPump": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "CirrusValvePosition": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "DigitalMaxPB67OnTime": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "DigitalOutput": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "PECal_DateMsg": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "PECal_Flush": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "PECal_Inlet": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "PECal_MassMethodContribution": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "PECal_Pressures": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "PECal_Select": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "RolloverScaleFactor": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "RolloverVariables": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "RVCAlarm": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "RVCCloseAllValves": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "RVCHeater": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "RVCPump": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "RVCValveControl": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "RVCValveMode": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "SaveChanges": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "StartDegas": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "StopDegas": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "SourceAlignmentCopyToAll": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "SourceAlignmentUpdate": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "SourceAlignmentRemove": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "SourceResolutionCopyToAll": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "SourceResolutionUpdate": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "SourceResolutiontRemove": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "SourcePoleBias": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "DiagnosticInputAverageCount": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "DiagnosticInputEnable": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "DiagnosticInputInterval": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "InletSelect": {"type": "Sensor Control Commands", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.7}, # asynchronous sensor notifications "MKSRGA": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "FilamentStatus": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "FilamentTimeRemaining": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "StartingScan": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "StartingMeasurement": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "ZeroReading": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "MassReading": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.4}, "MultAutoSkip": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.4}, "MultiplierStatus": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "RFTripState": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1}, "InletChange": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "AnalogInput": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "TotalPressure": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, "DigitalPortChange": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "RVCPumpStatus": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "RVCHeaterStatus": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "RVCValveStatus": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "RVCInterlocks": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "RVCStatus": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "RVCDigitalInput": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "LinkDown": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "VSCEvent": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "DegasReading": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.2}, "DiagnosticInput": {"type": "Asynchronous Sensor Notifications", "description": "", "action": NotImplementedError, "expected_response": "", "from_version": 1.6}, } def __init__(self, up_queue:queue.SimpleQueue, down_queue:queue.SimpleQueue, command_actions:dict={}, target_state="run"): #raise NotImplementedError() self._target_state = target_state self._current_protocol_version = 1 self.up_queue = up_queue self.down_queue = down_queue # default pre-set actions self.register_command_actions({ "FormatWithTab": self._message_tab_formatting, "AcceptProtocol": self._current_protocol_version}) # update actions provided by user self.register_command_actions(command_actions) def run(self): while self._target_state == "run": message = self.down_queue.get(block=True, timeout=None) self._digest(message) def register_command_actions(self, actions:dict): """ Register actions for Protocol commands :param actions: dict, where the key is the command name and value is the callable. The callable must accept one argument (the message) :return: Nothing """ for command in actions: if command not in self._commands: logging.warning(f"Unknown command: {command}. Ignoring.") continue if self._commands[command]["from_version"] > self._current_protocol_version: logging.warning(f"Unsupported command {command} for our version {self._current_protocol_version}. Ignoring.") continue assert hasattr(self._commands[command]['action'], "__call__"), f"{command} callable has not __call__" self._commands[command]["action"] = actions[command] def _digest_command(self, message): """ Receive Message and act upon it :param message: :return: :raises: """ assert isinstance(message, ParseMessage) if message.type not in self._commands: logging.error(f"Received unknown message type: {message.type}") raise NotImplementedError() try: self._commands[message.type].action(message) except NotImplementedError as e: logging.warning("Received unimplemented message {message.type} with content {message.body}") except Exception as e: logging.warning(f"Error during digestion of {message.type}: {e}") def _set_protocol_version(self, message): """ :param message: :return: """ assert message.type == "AcceptProtocol" raise NotImplementedError() def _message_tab_formatting(self, message): # no action needed - all code is invariant to tabs vs spaces # using spaces saves a few bits of bandwith due to less padding spaces, so use it anyways pass def _available_sensors(self, message): raise NotImplementedError() class ProtocolVersionError(Exception): def __init__(self, version, message="Version mismatch: "): self.message = message + str(version) super().__init__(self.message) def run(obj): obj.run() if __name__ == "__main__": # communicating queues: up -> towards MKS instrument, down -> from MKS intrument upq = queue.Queue() downq = queue.Queue() # create instances of listener and operator l = MKSListener(ip="127.0.0.1", up_queue=upq, down_queue=downq) o = MKSOperator(up_queue=upq, down_queue=downq) # create thread for each listenerThread = threading.Thread(target=run, args=(l,)) operatorThread = threading.Thread(target=run, args=(o,)) # start threads operatorThread.start() listenerThread.start()