cleanup, comments
This commit is contained in:
442
mks_ascii_protocol/MKS_ASCII_Protocol.py
Normal file
442
mks_ascii_protocol/MKS_ASCII_Protocol.py
Normal file
@@ -0,0 +1,442 @@
|
||||
|
||||
"""
|
||||
Implementation of the MKS ASCII Protocol
|
||||
as specified in "ASCII Protocol V1.8.pdf"
|
||||
|
||||
The
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
__author__ = "Georg Schlisio <georg.schlisio@ipp.mpg.de"
|
||||
__version__ = 0.1
|
||||
__protocol_version__ = 1.8
|
||||
|
||||
|
||||
#### imports
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
#import numpy as np
|
||||
import logging
|
||||
import threading
|
||||
import queue
|
||||
import socket
|
||||
__softwarename__ = "MKS ASCII server on " + socket.gethostname()
|
||||
from message import ParseMessage, ComposeMessage
|
||||
|
||||
## basic config
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
assert (sys.version_info[0] + sys.version_info[1]*0.1 ) >= 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):
|
||||
"""
|
||||
Target wrapper for thread object invocation
|
||||
"""
|
||||
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()
|
||||
0
mks_ascii_protocol/__init__.py
Normal file
0
mks_ascii_protocol/__init__.py
Normal file
122
mks_ascii_protocol/message.py
Normal file
122
mks_ascii_protocol/message.py
Normal file
@@ -0,0 +1,122 @@
|
||||
import time
|
||||
import logging
|
||||
|
||||
class ParseMessage():
|
||||
|
||||
table_commands = ["Sensors", "EGains", "InletInfo", "DetectorInfo", "AnalogInputInfo",
|
||||
"AnalogOutputInfo", "DigitalInfo", "SourceAlignmentInfo", "SourceResolutionInfo",
|
||||
"DiagnosticInputInfo", "RunDiagnostics", ""]
|
||||
verbose_commands = ["DiagnosticInput", "DigitalPortChange", "AnalogInput", "MassReading", "ZeroReading",
|
||||
"StartingScan", "FilamentStatus", ""] # commands that feel special and don't submit to the usual pattern of command - status
|
||||
|
||||
def __init__(self, raw):
|
||||
"""
|
||||
Parse incoming binary message
|
||||
:param raw: raw string
|
||||
"""
|
||||
self.time = time.time()
|
||||
self.is_table = False
|
||||
self.type = None
|
||||
self.status = None
|
||||
self.parms = {}
|
||||
self.table_head = None
|
||||
self.table_body = None
|
||||
self.raw = raw
|
||||
self.parse(raw)
|
||||
|
||||
def parse(self, raw):
|
||||
"""
|
||||
Parser for incoming messages.
|
||||
:return:
|
||||
"""
|
||||
# split message at newline indicators and get rid of empty entries
|
||||
linebyline = list(filter(None, raw.split("\r\n")))
|
||||
|
||||
# first line contains command and status or more
|
||||
line1 = linebyline[0].split()
|
||||
self.type = line1[0]
|
||||
if self.type in self.table_commands:
|
||||
self.is_table = True
|
||||
if len(line1) == 1:
|
||||
pass
|
||||
elif len(line1) == 2:
|
||||
self.type = line1[0]
|
||||
self.status = line1[1]
|
||||
else:
|
||||
assert self.type in self.verbose_commands
|
||||
self.parms[self.type] = line1[1:]
|
||||
|
||||
# terminate here for one-line commands
|
||||
if len(linebyline) == 1:
|
||||
return
|
||||
|
||||
# following lines
|
||||
if self.is_table:
|
||||
# for table commands we split table head and body
|
||||
thead = linebyline[1].split()
|
||||
try:
|
||||
tbody = [linebyline[i].split() for i in range(len(linebyline[2:]))]
|
||||
except IndexError:
|
||||
logging.warning(f"Digesting message {self.type}: no table body found")
|
||||
tbody = []
|
||||
self.table_head = thead
|
||||
self.table_body = tbody
|
||||
else:
|
||||
# for other commands we make a dictionary with key [values, ..]
|
||||
try:
|
||||
self.parms.update({line.split()[0]: line.split()[1:] for line in linebyline[1:]})
|
||||
except IndexError:
|
||||
raise NotImplementedError("this should not happen")
|
||||
|
||||
class ComposeMessage():
|
||||
def __init__(self):
|
||||
pass
|
||||
def __call__(self, mtype, args=None):
|
||||
"""
|
||||
Compose raw byte string to send over ethernet
|
||||
TODO: check argument type
|
||||
TODO: make sure all commands fit the scheme
|
||||
:param mtype: command as static string
|
||||
:param args: list of argument strings (optional, default None)
|
||||
:return: raw byte string
|
||||
"""
|
||||
assert isinstance(mtype, str)
|
||||
#assert isinstance(args, list)
|
||||
raw = mtype
|
||||
if args is not None:
|
||||
raw += " "+" ".join(args)
|
||||
raw += "\r\n\r\r"
|
||||
return raw.encode("ASCII")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sample_messages = {
|
||||
"SensorState": 'SensorState OK\r\n State InUse\r\n UserApplication "Process Eye Professional"\r\n UserVersion V5.2\r\n UserAddress 127.0.0.1\r\n',
|
||||
"Info": 'Info OK\r\n SerialNumber LM70-00197021\r\n Name "Chamber A"\r\n State Ready\r\n UserApplication N/A' +\
|
||||
'\r\n UserVersion N/A\r\n UserAddress N/A\r\n ProductID 70 MicroVision+\r\n RFConfiguration 0 "Smart Head"' +\
|
||||
'\r\n DetectorType 0 Faraday\r\n SEMSupply 3000 3.0kV\r\n ExternalHardware 0 None\r\n TotalPressureGauge 0 ' +\
|
||||
'"Not Fitted"\r\n Page 13 of 158\r\n FilamentType 0 Tungsten\r\n ControlUnitUse 4 "Standard RGA"\r\n' +\
|
||||
' SensorType 1 "Standard Open Source"\r\n InletType 1 None\r\n Version V3.70\r\n NumEGains 3\r\n' +\
|
||||
' NumDigitalPorts 2\r\n NumAnalogInputs 4\r\n NumAnalogOutputs 1\r\n NumSourceSettings 6\r\n ' +\
|
||||
'NumInlets 1\r\n MaxMass 200\r\n ActiveFilament 1\r\n FullScaleADCAmps 0.000002\r\n FullScaleADCCount' +\
|
||||
' 8388608\r\n PeakResolution 32\r\n ConfigurableIonSource Yes\r\n RolloverCompensation No\r\n',
|
||||
"EGains": 'EGains OK\r\n 1\r\n 100\r\n 20000\r\n',
|
||||
"InletInfo": 'InletInfo OK\r\n Automatic Yes \r\nActiveInlet 0\r\n Factor Fixed CanCalibrate DefaultFactor TypeName\r\n 1 Yes No 1 "Process Chamber direct"\r\n',
|
||||
"Release": "Release Ok\r\n",
|
||||
#"AcceptProtocol":
|
||||
"initiate": "MKSRGA Single\r\n Protocol_Revision 1.1\r\n Min_Compatibility 1.1\r\n\r\n",
|
||||
"Error": 'command ERROR\r\n Number 200\r\n Description "err description"\r\n\r\n',
|
||||
"Sensors": "Sensors OK\r\n State SerialNumber Name\r\n Ready LM70-00197021 “Chamber A”\r\n Ready LM70-00198021 “Chamber B”\r\n\r\n",
|
||||
"DiagnosticInput": "DiagnosticInput 0 3.2745\r\n\r\n",
|
||||
"FilamentStatus": "FilamentStatus 1 OFF\r\n Trip None\r\n Drive Off\r\n EmissionTripState OK\r\n ExternalTripState OK\r\n RVCTripState OK",
|
||||
}
|
||||
cm = ComposeMessage()
|
||||
#bmess = cm(mtype="Select", args=["LM70-00197021"])
|
||||
|
||||
#smess = bmess.decode("ASCII")
|
||||
|
||||
for m in sample_messages:
|
||||
pm = ParseMessage(sample_messages[m])
|
||||
print(pm.type, pm.status, pm.is_table)
|
||||
|
||||
#print(pm.type, pm.parms, pm.time)
|
||||
Reference in New Issue
Block a user