diff --git a/pyvisa_sim/channels.py b/pyvisa_sim/channels.py index d204dca..451f260 100644 --- a/pyvisa_sim/channels.py +++ b/pyvisa_sim/channels.py @@ -80,7 +80,7 @@ def __init__(self, device: "Device", ids: List[str], can_select: bool): self._getters = ChDict(__default__={}) self._dialogues = ChDict(__default__={}) - def add_dialogue(self, query: str, response: str) -> None: + def add_dialogue(self, query: str, response: str, sources: dict = {}) -> None: """Add dialogue to channel. Parameters @@ -91,7 +91,12 @@ def add_dialogue(self, query: str, response: str) -> None: Response sent in response to a query. """ - self._dialogues["__default__"][to_bytes(query)] = to_bytes(response) + if sources: + dialogue = {"func": response, "sources": (sources)} + self._dialogues["__default__"][to_bytes(query)] = dialogue + + else: + self._dialogues["__default__"][to_bytes(query)] = to_bytes(response) def add_property( self, diff --git a/pyvisa_sim/component.py b/pyvisa_sim/component.py index 279bc92..552c3f4 100644 --- a/pyvisa_sim/component.py +++ b/pyvisa_sim/component.py @@ -193,8 +193,9 @@ def __init__(self) -> None: self._properties = {} self._getters = {} self._setters = [] + self.devices = {} - def add_dialogue(self, query: str, response: str) -> None: + def add_dialogue(self, query: str, response: str, sources: dict = None) -> None: """Add dialogue to device. Parameters @@ -205,7 +206,12 @@ def add_dialogue(self, query: str, response: str) -> None: Response to the dialog query. """ - self._dialogues[to_bytes(query)] = to_bytes(response) + if sources: + dialogue = {"func": response, "sources": (sources)} + self._dialogues[to_bytes(query)] = dialogue + + else: + self._dialogues[to_bytes(query)] = to_bytes(response) def add_property( self, @@ -244,6 +250,13 @@ def add_property( (name, stringparser.Parser(query), to_bytes(response_), to_bytes(error)) ) + def set_devices(self, devices: dict) -> None: + """ "Add all initialized devices + + :param devices: storage for devices + """ + self.devices = devices + def match(self, query: bytes) -> Optional[OptionalBytes]: """Try to find a match for a query in the instrument commands.""" raise NotImplementedError() @@ -288,7 +301,30 @@ def _match_dialog( # Try to match in the queries if query in dialogues: - response = dialogues[query] + # if connection + if type(dialogues[query]) == dict: + dialogue = dialogues[query] + function = dialogue["func"] + sources = dialogue["sources"] + prop_dict = {} + + for source in sources: + # Find source for connection + if source["name"] in self.devices._internal.keys(): + device = self.devices._internal[source["name"]] + # Find correct property + property = device._properties[source["parameter"]] + # Add property and value to prop_dict + value = property._value + prop_dict[property.name] = value + + # Populate and run function + func = function.format(**prop_dict) + response = to_bytes(str(eval(func))) + + else: + response = dialogues[query] + logger.debug("Found response in queries: %s" % repr(response)) return response diff --git a/pyvisa_sim/default.yaml b/pyvisa_sim/default.yaml index ecf6e59..9985771 100644 --- a/pyvisa_sim/default.yaml +++ b/pyvisa_sim/default.yaml @@ -242,6 +242,140 @@ devices: max: 6 type: float + device 5: + eom: + ASRL INSTR: + q: "\r\n" + r: "\n" + USB INSTR: + q: "\r\n" + r: "\n" + TCPIP INSTR: + q: "\r\n" + r: "\n" + TCPIP SOCKET: + q: "\r\n" + r: "\n" + GPIB INSTR: + q: "\r\n" + r: "\n" + error: ERROR + dialogues: + - q: "*IDN?" + r: "HEWLETT-PACKARD, E3632A,0,1.2-5.0-1.0" + - q: "SYST:ERR?" + r: "NO ERROR" + - q: "SOUR1:POW?" + r: "2" + - q: "READ1" + r: "0.23 * ({amplitude} ** 2) - {frequency}" + sources: [ + {name: "GPIB0::2::INSTR", parameter: amplitude}, + {name: "GPIB0::22::INSTR", parameter: frequency} + ] + properties: + frequency: + default: 1.0 + getter: + q: "SENS1:FREQ?" + r: "{:.2f}" + setter: + q: "SENS1:FREQ {:.2f} MHZ" + r: "" + e: "FREQ ERROR" + specs: + min: 0.0 + max: 27.0 + type: float + current limit: + default: 1.0 + getter: + q: "MEAS:CURR?" + r: "{:.2f}" + setter: + q: "CURR:LIM {:}" + r: "" + e: 'CURRENT_ERROR' + specs: + min: 0.0 + max: 7.0 + type: float + voltage: + default: 1.0 + getter: + q: "MEAS:VOLT?" + r: "{:.2f}" + setter: + q: "VOLT {:.2f}" + r: "" + specs: + min: 0 + max: 30 + type: float + output_enabled: + default: '0' + getter: + q: "OUTPUT:STATE?" + r: "{:s}" + setter: + q: "OUTPUT:STATE {:s}" + r: "" + specs: + valid: ['0', '1', 'ON', 'OFF'] + type: str + device 6: + eom: + ASRL INSTR: + q: "\r\n" + r: "\n" + USB INSTR: + q: "\r\n" + r: "\n" + TCPIP INSTR: + q: "\r\n" + r: "\n" + TCPIP SOCKET: + q: "\r\n" + r: "\n" + GPIB INSTR: + q: "\r\n" + r: "\n" + error: ERROR + dialogues: + - q: "*IDN?" + r: "Hewlett-Packard, ESG-D4000B, GB40050924, B.03.86" + - q: "*OPC?" + r: "Hewlett-Packard, ESG-D4000B, GB40050924, B.03.86" + - q: "SYST:ERR?" + r: "NO ERROR" + properties: + frequency: + default: 1.0 + getter: + q: "FREQ?" + r: "{:.2f}" + setter: + q: "FREQ {:.2f}MHZ" + r: "" + e: "FREQ ERROR" + specs: + min: 0.0 + max: 6.0 + type: float + amplitude: + default: 0.0 + getter: + q: "POW:LEV?" + r: "{:.2f}" + setter: + q: "POW:LEV {:.2f}" + r: "" + e: "POW ERROR" + specs: + min: -100.0 + max: 20.0 + type: float + resources: ASRL1::INSTR: device: device 1 @@ -279,3 +413,7 @@ resources: device: device 4 USB::0x1111::0x2222::0x4445::RAW: device: device 1 + GPIB0::22::INSTR: + device: device 5 + GPIB0::2::INSTR: + device: device 6 diff --git a/pyvisa_sim/parser.py b/pyvisa_sim/parser.py index 94ae7da..aa7dcd0 100644 --- a/pyvisa_sim/parser.py +++ b/pyvisa_sim/parser.py @@ -61,6 +61,22 @@ def __getitem__(self, key: K) -> V: raise KeyError(key) +def _get_dialogue(dd: Dict[str, str]) -> Tuple[str, str, Dict[str, str]]: + """Return a dialogue from a dialogue dictionary. + + :param dd: Dialogue dictionary. + :type dd: Dict[str, str] or Dict[str, str, str] + :return: (query, response, sources) + :rtype: (str, str, str) + """ + if "sources" in dd.keys(): + sources = dd["sources"] + else: + sources = None + + return dd["q"].strip(" "), dd["r"].strip(" "), sources + + def _get_pair(dd: Dict[str, str]) -> Tuple[str, str]: """Return a pair from a dialogue dictionary.""" return dd["q"].strip(" "), dd["r"].strip(" ") @@ -122,12 +138,15 @@ def parse_file(fullpath: Union[str, pathlib.Path]) -> Dict[str, Any]: def update_component( - name: str, comp: Component, component_dict: Dict[str, Any] + name: str, + comp: Component, + component_dict: Dict[str, Any], + devices: Dict[str, Device], ) -> None: """Get a component from a component dict.""" for dia in component_dict.get("dialogues", ()): try: - comp.add_dialogue(*_get_pair(dia)) + comp.add_dialogue(*_get_dialogue(dia)) except Exception as e: msg = "In device %s, malformed dialogue %s\n%r" raise Exception(msg % (name, dia, e)) @@ -149,6 +168,12 @@ def update_component( msg = "In device %s, malformed property %s\n%r" raise type(e)(msg % (name, prop_name, format_exc())) + try: + comp.set_devices(devices) + except Exception as e: + msg = "In device %s, malformed devices %s\n%r" + raise Exception(msg % (name, devices, e)) + def get_bases(definition_dict: Dict[str, Any], loader: "Loader") -> Dict[str, Any]: """Collect inherited behaviors.""" @@ -171,6 +196,7 @@ def get_channel( channel_dict: Dict[str, Any], loader: "Loader", resource_dict: Dict[str, Any], + devices: Dict[str, Device], ) -> Channels: """Get a channels from a channels dictionary. @@ -201,7 +227,7 @@ def get_channel( can_select = False if channel_dict.get("can_select") == "False" else True channels = Channels(device, ids, can_select) - update_component(ch_name, channels, cd) + update_component(ch_name, channels, cd, devices) return channels @@ -211,6 +237,7 @@ def get_device( device_dict: Dict[str, Any], loader: "Loader", resource_dict: Dict[str, str], + devices: Dict[str, Device], ) -> Device: """Get a device from a device dictionary. @@ -241,11 +268,12 @@ def get_device( for itype, eom_dict in device_dict.get("eom", {}).items(): device.add_eom(itype, *_get_pair(eom_dict)) - update_component(name, device, device_dict) + update_component(name, device, device_dict, devices) for ch_name, ch_dict in device_dict.get("channels", {}).items(): device.add_channels( - ch_name, get_channel(device, ch_name, ch_dict, loader, resource_dict) + ch_name, + get_channel(device, ch_name, ch_dict, loader, resource_dict, devices), ) return device @@ -410,7 +438,7 @@ def get_devices(filename: Union[str, pathlib.Path], bundled: bool) -> Devices: ) devices.add_device( - resource_name, get_device(device_name, dd, loader, resource_dict) + resource_name, get_device(device_name, dd, loader, resource_dict, devices) ) return devices diff --git a/pyvisa_sim/testsuite/test_all.py b/pyvisa_sim/testsuite/test_all.py index 5fb9b89..862e336 100644 --- a/pyvisa_sim/testsuite/test_all.py +++ b/pyvisa_sim/testsuite/test_all.py @@ -24,10 +24,12 @@ def test_list(resource_manager): "USB0::0x1111::0x2222::0x3692::0::INSTR", "USB0::0x1111::0x2222::0x4444::0::INSTR", "USB0::0x1111::0x2222::0x4445::0::RAW", + "GPIB0::2::INSTR", "GPIB0::4::INSTR", "GPIB0::8::INSTR", "GPIB0::9::INSTR", "GPIB0::10::INSTR", + "GPIB0::22::INSTR", } @@ -179,3 +181,14 @@ def test_instrument_for_error_state(resource, resource_manager): inst.write(":VOLT:IMM:AMPL 0") assert_instrument_response(inst, ":SYST:ERR?", "1, Command error") + + +def test_dialogue_connection(resource_manager): + inst1 = resource_manager.open_resource("GPIB0::22::INSTR", read_termination="\n") + inst2 = resource_manager.open_resource("GPIB0::2::INSTR", read_termination="\n") + inst2.query("POW:LEV 8.5") + response = inst1.query("READ1") + assert response == "15.6175" + inst2.query("POW:LEV 1.0") + response = inst1.query("READ1") + assert response == "-0.77"