pyvisa / pyvisa-sim

A PyVISA backend that simulates a large part of the "Virtual Instrument Software Architecture" (VISA_)
https://pyvisa-sim.readthedocs.io/en/latest/
MIT License
69 stars 39 forks source link

Is there a "reset instrument to defaults from yaml file" function or similar? #83

Open dougthor42 opened 1 year ago

dougthor42 commented 1 year ago

Is there a function/method that will reset a simulated instrument to the defaults defined in the yaml file?

What I'd like to be able to do is basically:

import pyvisa
rm = pyvisa.ResourceManager("myfile.yaml@sim")
instr = rm.OpenResource("ASRL1::INSTR")

instr.query("volt")  # 5, the default defined by myfile.yaml
instr.write("volt 10")
instr.query("volt")  # 10
instr.reset_to_defaults()  # magic function that I'm asking about
instr.query("volt")  # 5, the default defined by myfile.yaml

Does such a method exist? Closing the resource and/or the resource manager does not reset things (this might be related to #82).

Steps to Reproduce

Create this YAML file. I called it "doug_example.yaml": ```yaml --- spec: "1.1" devices: MyDevice: eom: ASRL INSTR: q: "\r" r: "\r\n" error: response: command_error: "invalid command. Update the yaml file." query_error: "empty buffer" dialogues: - q: ";LC:DONE?" r: "done" properties: echo: default: "0" setter: q: "echo {}" r: "OK" getter: q: "echo?" r: "{}" resources: "ASRL1::INSTR": device: "MyDevice" ```
Create this Python file. I called it "doug_example.py": ```python import pathlib import pyvisa VISA_ADDR = "ASRL1::INSTR" SIM_BACKEND = str(pathlib.Path(__file__).parent / "doug_example.yaml@sim") def create_instr() -> tuple[pyvisa.ResourceManager, pyvisa.Resource]: rm = pyvisa.ResourceManager(visa_library=SIM_BACKEND) instr = rm.open_resource(VISA_ADDR) instr.read_termination = "\r\n" instr.write_termination = "\r" print(f"ResourceManager session: {rm.session}") return rm, instr rm, instr = create_instr() want = "0" got = instr.query("echo?") correct = got == want print(f"{got = }\t{want = }\t{correct = }") want = "OK" got = instr.query("echo 1") correct = got == want print(f"{got = }\t{want = }\t{correct = }") want = "1" got = instr.query("echo?") correct = got == want print(f"{got = }\t{want = }\t{correct = }") # Magic reset command print("Running reset") rm.close() print("Creating new resource manager and instrument") rm, instr = create_instr() want = "0" got = instr.query("echo?") correct = got == want print(f"{got = }\t{want = }\t{correct = }") ```

Run python doug_example.py and get this output:

$ python doug_example.py 
ResourceManager session: 5282223
got = '0'       want = '0'      correct = True
got = 'OK'      want = 'OK'     correct = True
got = '1'       want = '1'      correct = True
Running reset
Creating new resource manager and instrument
ResourceManager session: 1299675
got = '1'       want = '0'      correct = False
MatthieuDartiailh commented 1 year ago

Sorry for the delay but I think we have no such thing. You may be able to use CLS to achieve something similar but we could also envision adding a custom method.

dougthor42 commented 1 year ago

No worries! After all, I still haven't addressed https://github.com/pyvisa/pyvisa-py/issues/374 lol

I assume that, at it's core, pyvisa-sim is a glorified dictionary where "queries" are the keys and "responses" are the values. It seems like we should just be able to empty that dictionary to reset things. Am I on the right track there?

Where is this dictionary-like data structure defined? I'd imagine that a custom method on the Resource Manager or perhaps the individual Resource is probably easy to implement.

bilderbuchi commented 1 year ago

Closing the [...] resource manager does not reset things (this might be related to #82).

Are you sure? I just tripped on this/#82 again, and closing the resource manager so far seems sufficient to clean up hidden state.

jenshnielsen commented 11 months ago

TLDR: calling resource_manager.visalib._init() will reset all devices loaded from the same yaml file.

I spent a bit of time debugging why this happens. As far as I can see the state is kept in the Device class The device class is in turn stored as a member of the Devices class which is in turn kept in the SimVisaLibrary. SimVisaLibrary is a subclass of VisaLibraryBase which in turn implements a registry of loaded libraries here Which means that if a SimVisaLibrary for a given yaml path exists, it will be reused next time you load SimVisaLibrary for that yaml path.

As the Device is loaded via _init only when SimVisaLibrary is created this in turn means that the device state is kept as long as an instance of SimVisaLibrary is kept alive. One can force a reload from disk by calling the private method _init on the SimVisaLibrary class bound to a resource manager. Probably via resource_manager.visalib as resource_manager.visalib._init()

I did not trace all objects that keep a reference to the visa library but it could be that the resource_manager is the only object that does this so closing the resource manager would under some circumstances trigger the visalib to be garbage collected explaining the observations above (the mentioned registry in VisaLibraryBase is a WeakValueDict so it does it self add to the refcount)

MatthieuDartiailh commented 8 months ago

Thanks for investigating this @jenshnielsen

Since it seems a needed feature, I would welcome a PR in that direction.