Repository: google/openhtf
Pull request #1223: Tutorial to check value of a resistor
Author: login: ghost
Created: 2025-04-14T09:32:28Z | Merged: 2025-04-16T20:30:46Z

The goal of this PR is to add a tutorial for openHTF. It is meant to be complimentary to the examples files which don't give a walk through of openHTF functionalities. 

The tutorial allows a new user to understand how to use a plug for a measurement device and how to determine the outcome of a test based on a measurement. 

It is meant for a user who has basic knowledge the of OpenHTF concepts that are explained on the github main page.

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/google/openhtf/1223)
<!-- Reviewable:end -->



Diff:
diff --git a/examples/README.md b/examples/README.md
index e76d38729..d2aade794 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -65,3 +65,8 @@ prompt to continue the example.
     phases with their own setup and teardown logic.
 5.  [`repeat.py`](repeat.py): uses `openhtf.PhaseResult.REPEAT` to
     conditionally repeat execution of a phase in a test.
+
+### Tutorials
+
+1. [resistor_tutorial](resistor_tutorial/): Walk-through on how to validate a resistor using Measurements and Plugs. 
+
diff --git a/examples/resistor_tutorial/main_test.py b/examples/resistor_tutorial/main_test.py
new file mode 100644
index 000000000..868b5bfa3
--- /dev/null
+++ b/examples/resistor_tutorial/main_test.py
@@ -0,0 +1,64 @@
+import time
+
+import resistor_plugs
+
+import openhtf as htf
+from openhtf.output.callbacks import console_summary
+from openhtf.util import configuration, units
+
+CONF = configuration.CONF
+SIMULATE_MODE = CONF.declare(
+    "simulate", default_value=False, description="Set simulation mode"
+)
+CONF.load(simulate=True)  # Change to True if running simulated hardware
+
+
+MultimeterPlug = configuration.bind_init_args(
+    resistor_plugs.MultimeterPlug, SIMULATE_MODE
+)
+
+PowerSupplyPlug = configuration.bind_init_args(
+    resistor_plugs.PowerSupplyPlug, SIMULATE_MODE
+)
+
+
+@htf.measures(
+    htf.Measurement("resistor_val")
+    .doc("Computed resistor value")
+    .in_range(5320, 5880)
+    .with_units(units.OHM)
+)
+@htf.plug(dmm=MultimeterPlug)
+@htf.plug(supply=PowerSupplyPlug)
+def resistor_test(
+    test, dmm: resistor_plugs.MultimeterPlug, supply: resistor_plugs.PowerSupplyPlug
+) -> None:
+    """Check if resistor value in acceptable.
+
+    Args:
+        test (Test): Openhtf Test object
+        dmm (MultimeterPlug): Multimeter object
+        supply (PowerSupplyPlug): Powr supply object
+    """
+    supply.connect()
+    dmm.connect()
+
+    input_voltage = 4  # in V
+    supply.set_voltage(input_voltage)
+
+    time.sleep(3)
+    current = float(dmm.read_current()[1])
+    measured_r = round(input_voltage / current, 3)
+    test.measurements["resistor_val"] = measured_r
+
+    print(f"R value is: {measured_r}")
+
+
+def main():
+    test = htf.Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/examples/resistor_tutorial/resistor_plugs.py b/examples/resistor_tutorial/resistor_plugs.py
new file mode 100644
index 000000000..86ab1fed0
--- /dev/null
+++ b/examples/resistor_tutorial/resistor_plugs.py
@@ -0,0 +1,167 @@
+import random
+import time
+
+import pyvisa
+
+from openhtf.core import base_plugs
+
+### To use pyvisa ###
+# Use rm.list_resources() to find the devices plugged in
+# this will return a list of the connected devices such as:
+# ('USB0::0x1AB1::0x0E11::DP8C1234567890::INSTR',
+#   'ASRL1::INSTR', 'ASRL10::INSTR')
+#
+# Then run device = rm.open_resource(
+#     'USB0::0x1AB1::0x0E11::DP8C1234567890::INSTR'
+#     )
+# to connect to the device.
+# You can check the device ID with device.query("*IDN?")
+
+
+class MultimeterPlug(base_plugs.BasePlug):
+    """Plug for control of a multimeter with pyvisa. Using the simulate flag
+    will bypass the hardware.
+
+    Args:
+        BasePlug (Plug): OpeHTF Plug object.
+    """
+
+    def __init__(self, simulate: bool = False) -> None:
+        self.simulate = simulate
+        self.logger.info("Logging mode is %s", self.simulate)
+        self.rm = None
+        self.multimeter = None
+
+    def connect(self) -> None:
+        """Connect to the device using pysisa.
+        Does nothing if using simulation mode.
+        """
+        if not self.simulate:
+            try:
+                self.rm = pyvisa.ResourceManager()
+                self.multimeter = self.rm.open_resource(
+                    "USB0::6833::8458::DM8A265201811::0::INSTR"
+                )
+                self.logger.info("Connected to Multimeter")
+            except ConnectionRefusedError as error:
+                self.logger.warning(
+                    """Connection refused : %s,
+                    Device might already be connected""",
+                    error,
+                )
+
+            time.sleep(0.1)
+
+    def read_current(self) -> tuple[float]:
+        """Read current value from the multimter in A.
+        If simultation mode is active, will return a random value.
+
+        Returns:
+            tuple[float]: Tuple containing the timestamp
+            of the measurement and the actual reading
+        """
+        timestamp = time.time()
+
+        if not self.simulate:
+            current = self.multimeter.query("MEASure:CURRent:DC? AUTO,1E-3")[:-1]
+        else:
+            current = str(random.uniform(1, 10))
+
+        self.logger.info("Reading current: %s A", current)
+        return (timestamp, current)
+
+    def read_voltage(self) -> tuple[float]:
+        """Read voltage value from the multimter in V.
+        If simultation mode is active, will return a random value.
+
+        Returns:
+            tuple[float]: Tuple containing the timestamp
+            of the measurement and the actual reading
+        """
+        timestamp = time.time()
+
+        if not self.simulate:
+            voltage = self.multimeter.query("MEASure:VOLTage:DC? 10,1E-3")[:-1]
+        else:
+            voltage = str(random.uniform(1, 10))
+
+        self.logger.info("Reading voltage: %s V", voltage)
+        return (timestamp, voltage)
+
+    def tearDown(self) -> None:
+        """Disconnect from the multimeter.
+        Important if running multiple tests sequentially.
+        This can solve "USB busy" errors.
+        """
+        if not self.simulate:
+            self.rm.close()
+
+
+class PowerSupplyPlug(base_plugs.BasePlug):
+    """Plug for control of a power supply with pyvisa.
+    Using the simulate flag will bypass the hardware.
+    """
+
+    def __init__(self, simulate: bool = False):
+        self.simulate = simulate
+        self.rm = None
+        self.supply = None
+
+    def connect(self):
+        """Connect to the device using pyvisa.
+        Does nothing if using simulation mode.
+        """
+        if not self.simulate:
+            try:
+                self.rm = pyvisa.ResourceManager()
+                self.supply = self.rm.open_resource(
+                    "USB0::6833::42152::DP9D264501253::0::INSTR"
+                )
+                self.supply.write(":OUTP ALL, OFF")
+                self.logger.info("Connected to Power Supply")
+            except ConnectionRefusedError as error:
+                self.logger.warning(
+                    """Connection refused : %s, 
+                    Device might already be connected""",
+                    error,
+                )
+
+            time.sleep(0.1)
+
+    def set_voltage(self, voltage: float, channel: str = "CH1") -> None:
+        """Set output voltage for the power supply.
+
+        Args:
+            voltage (float): Output voltage value (V).
+            channel (str, optional): Output channel. Defaults to "CH1".
+        """
+        if not self.simulate:
+            command = f":APPL {channel}, {voltage}"
+            self.supply.write(command)
+            self.supply.write(f":OUTP {channel}, ON")
+        self.logger.info("Setting voltage to: %s V", voltage)
+
+    def close_channels(self) -> None:
+        """Shut down all output channels."""
+        if not self.simulate:
+            for source in ["CH1", "CH2", "CH3"]:
+                self.supply.write(f":APPL {source}, 0")
+            self.supply.write(":OUTP ALL, OFF")
+        self.logger.info("Shutting down all channels")
+
+    def tearDown(self) -> None:
+        """Disconnect from the multimeter.
+        Important if running multiple tests sequentially.
+        This can solve "USB busy" errors.
+        """
+        if not self.simulate:
+            self.close_channels()
+            self.rm.close()
+
+
+if __name__ == "__main__":
+    my_multimeter = MultimeterPlug()
+    my_supply = PowerSupplyPlug()
+    my_multimeter.connect()
+
+    print(my_multimeter.read_current())
diff --git a/examples/resistor_tutorial/resistor_tutorial.md b/examples/resistor_tutorial/resistor_tutorial.md
new file mode 100644
index 000000000..c6135335a
--- /dev/null
+++ b/examples/resistor_tutorial/resistor_tutorial.md
@@ -0,0 +1,293 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $V = R.I$, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, Measurements and Configurations. 
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+import openhtf as htf
+
+def resistor_test(test):
+    """A placeholder phase that we will fill out later in the example."""
+
+def main():
+    test = htf.Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+import openhtf as htf
+
+@htf.measures(
+        htf.Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    """A placeholder phase that we will fill out later in the example."""
+
+def main():
+    test = htf.Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+import openhtf as htf
+
+
+@htf.measures(
+        htf.Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    """A placeholder phase that we will fill out later in the example."""
+
+def main():
+    test = htf.Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@htf.measures(
+        htf.Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developed with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+import resistor_plugs
+
+import time
+```
+and then 
+```python
+@htf.measures(
+        htf.Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@htf.plug(dmm=resistor_plugs.MultimeterPlug)
+@htf.plug(supply=resistor_plugs.PowerSupplyPlug)
+def resistor_test(test, dmm: resistor_plugs.MultimeterPlug, supply: resistor_plugs.PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@htf.measures(
+        htf.Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .with_units(units.OHM)
+        )
+@htf.plug(dmm=resistor_plugs.MultimeterPlug)
+@htf.plug(supply=resistor_plugs.PowerSupplyPlug)
+def resistor_test(test, dmm: resistor_plugs.MultimeterPlug, supply: resistor_plugs.PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_r = input_voltage/current
+    test.measurements["resistor_val"] = measured_r
+    print(f"R value is: {measured_r}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments (multimeter and power supply) wired properly, with the resistor and the ammeter in series.
+
+
+Install the required libraries : 
+```shell 
+(.venv-htf)$ pip install pyvisa pyvisa-py pyusb
+```
+
+Then run the script : 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 5866.153540919828
+:PASS
+
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+Cool! This test was done with a resistor rated at $5.6\text K\Omega$ $\pm 5 \%$ so our measured value of $5.87 \text K\Omega$ is fine.
+
+The final step is going to be to add a rule so that our test fails if we have a bad resistor. For that $5\%$ margin, it means we want values in the $[5.320\text K\Omega: 5.880\text K\Omega]$ range. 
+
+OpenHTF provides a convenient feature called a Validator to check this easily. Since we want to check the value of our measurement, we define this validation rule inside of the measurement decorator. For now we're using one of the default validators but it is possible to make your own custom validators for more specific use cases.
+
+This is also a good opportunity to mention the `openhtf.utils.units` library that OpenHTF provides. This allows you to declare the unit of a measurement within the `@htf.measures()` statement.
+
+```python
+from openhtf.util import units
+
+@htf.measures(
+        htf.Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+        .with_units(units.OHM)
+)
+@htf.plug(dmm=resistor_plugs.MultimeterPlug)
+@htf.plug(supply=resistor_plugs.PowerSupplyPlug)
+def resistor_test(test, dmm: resistor_plugs.MultimeterPlug, supply: resistor_plugs.PowerSupplyPlug) -> None:
+```
+
+And now if we run the test with a resistor that is out of range (here a 220 $\Omega$ resistor), we get a fail: 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 220.493
+:FAIL
+failed phase: resistor_test [ran for 3.38 sec]
+  failed_item: resistor_val (Outcome.FAIL)
+    measured_value: 220.493
+    validators:
+      validator: 5320 <= x <= 5880
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+Congratulations, you have a working OpenHTF test that can be deployed in a resistor manufacturing plant!
+
+## Using Simulation mode
+
+As mentionned previously, this tutorial can also be done without actual hardware. For this we will use the fact that our plugs have been provided with a simulation mode. In this case, they will return random values when querying data. 
+
+This means we won't have any correlation between the applied voltage and the measured current, but at least we can check that our test sequence works. 
+
+Right now, we make the call to our plugs inside of the `@plug(dmm=MultimeterPlug)` decorator. However this does not allow us to use the fact that our MultimeterPlug object can be instantiated with the `simulate` argument which we will need: 
+
+```python
+class MultimeterPlug(BasePlug):
+    def __init__(self, simulate: bool = False)-> None:
+        self.simulate = simulate
+```
+
+To do so, we'll use the configurations built in OpenHTF : `from openhtf.util import configuration`.  
+Then we load the useful configuration parameters inside of a configuration object:
+
+```python
+CONF = configuration.CONF
+SIMULATE_MODE = CONF.declare("simulate", 
+                             default_value=False, 
+                             description="Set if the test setup is in simulation mode")
+CONF.load(simulate=True)
+```
+
+Finally, we create instances of the MultimeterPlug and PowerSupplyPlug that use this configuration. All that is left to do is to make sure we call these new plugs inside of our phase decorators:
+
+```python
+MultimeterPlug = configuration.bind_init_args(resistor_plugs.MultimeterPlug, SIMULATE_MODE)
+PowerSupplyPlug = configuration.bind_init_args(resistor_plugs.PowerSupplyPlug, SIMULATE_MODE)
+
+@htf.measures(
+        htf.Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+        .with_units(units.OHM)
+)
+@htf.plug(dmm=MultimeterPlug)
+@htf.plug(supply=PowerSupplyPlug )
+def resistor_test(test, dmm: resistor_plugs.MultimeterPlug, supply: resistor_plugs.PowerSupplyPlug) -> None:
+```
+
+
+> *Note* : In this example we're manually declaring and uploading a configuration parameter, but it is also possible to load these parameters from a yaml file or a dict. You can read the `configuration.py` module doc for more details.   
+
+If we run it, we'll have a random current value, so the test will most likely fail with our validator, but at least we have something running:
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 0.733
+:FAIL
+failed phase: resistor_test [ran for 3.00 sec]
+  failed_item: resistor_val (Outcome.FAIL)
+    measured_value: 0.733
+    validators:
+      validator: 5320 <= x <= 5880
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```


Reviews:
user: login: glados-verma
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T23:48:47Z
user: login: glados-verma
body: Thanks for your contribution! I'm dropping a bunch of comments, address as many as you can. The rest I can take care of by cloning your branch.

Also can you please format the new .py files with the Black formatter? https://black.readthedocs.io/en/stable/ (I should add this to CONTRIBUTING.md...)
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T00:26:19Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-15T13:48:51Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-15T14:09:34Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-15T14:34:01Z
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-15T14:40:18Z
user: login: glados-verma
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T19:41:43Z
user: login: glados-verma
body: Looks good overall! The last change request is to rename Resistor_Tutorial to resistor_tutorial to match the convention
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T19:54:53Z
user: login: glados-verma
body: LGTM
<img class="emoji" title=":lgtm:" alt=":lgtm:" align="absmiddle" src="https://reviewable.io/lgtm.png" height="20" width="61"/>

> *[Reviewable](https://reviewable.io/reviews/google/openhtf/1223#-:-ONznhiv1rRUrsTipg9-:b3hosot)* status: 0 of 4 files reviewed, 17 unresolved discussions (waiting on @jpl-harmattan)



<!-- Sent from Reviewable.io -->

state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-16T18:14:20Z

Review comments:
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Please also list this example in examples/README.md
created_at: 2025-04-14T23:48:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Typo functionalities
created_at: 2025-04-14T23:49:27Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: The $ signs didn't render correctly in this file
created_at: 2025-04-14T23:51:12Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Can just say "OpenHTF Plugs, Measurements and Configurations"
created_at: 2025-04-14T23:51:45Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: This is duplicating information from the main README.md - instead, just point to that. For this section, add any special installs needed for the example. I realize you have them later in the hardware section; you can keep those there or move up here.
created_at: 2025-04-14T23:52:56Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Please follow the convention for this codebase, to not import names from modules (only import modules). So, import openhtf, and use openhtf.Test in the code.
Full style guide: https://google.github.io/styleguide/pyguide.html
created_at: 2025-04-14T23:53:52Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: It's almost always better to add a line comment instead of a `pass` statement e.g. here you could say:

"""A placeholder phase that we will fill out later in the example."""
created_at: 2025-04-14T23:54:55Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Typo "developed"
created_at: 2025-04-14T23:58:03Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Add a space after the semicolon and before the typehint
created_at: 2025-04-14T23:59:30Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Openhtf has the `util.units` module, do you want to introduce that here, instead of commenting that this is a voltage?
created_at: 2025-04-15T00:00:35Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Well.. you already measured the voltage! Do you want to move the sleep earlier?
created_at: 2025-04-15T00:01:23Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: `measured_r` per our style guide
created_at: 2025-04-15T00:02:39Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: What module is this and where is it located?
created_at: 2025-04-15T00:07:24Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Which instruments? Please mention
created_at: 2025-04-15T00:08:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
+
+
+Install the required libraries : 
+```shell 
+(.venv-htf)$ pip install pyvisa pyvisa-py pyusb
+```
+
+Then run the script : 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 5866.153540919828
+:PASS
+
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+Cool! This test was done with a resistor rated at $5.6\text K\Omega$ $\pm 5 \%$ so our measured value of $5.87 \text K\Omega$ is fine.
+
+The final step is going to be to add a rule so that our test fails if we have a bad resistor. For that $5\%$ margin, it means we want values in the $[5.320\text K\Omega: 5.880\text K\Omega] $ range. 
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: The test K\ didn't render correctly
created_at: 2025-04-15T00:08:38Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
+
+
+Install the required libraries : 
+```shell 
+(.venv-htf)$ pip install pyvisa pyvisa-py pyusb
+```
+
+Then run the script : 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 5866.153540919828
+:PASS
+
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+Cool! This test was done with a resistor rated at $5.6\text K\Omega$ $\pm 5 \%$ so our measured value of $5.87 \text K\Omega$ is fine.
+
+The final step is going to be to add a rule so that our test fails if we have a bad resistor. For that $5\%$ margin, it means we want values in the $[5.320\text K\Omega: 5.880\text K\Omega] $ range. 
+
+OpenHTF provides a convenient feature called a Validator to check this easily. Since we want to check the value of our measurement, we define this validation rule inside of the measurement decorator. For now we're using one of the default validators but it is possible to make your own custom validators for more specific use cases.  
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+)
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+```
+
+And now if we run the test with a resistor that is out of range (here a 220 $\Omega$ resistor), we get a fail: 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 220.493
+:FAIL
+failed phase: resistor_test [ran for 3.38 sec]
+  failed_item: resistor_val (Outcome.FAIL)
+    measured_value: 220.493
+    validators:
+      validator: 5320 <= x <= 5880
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+Congratulations, you have a working OpenHTF test that can be deployed in a resistor manufacturing plant!
+
+
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Put just one blank line
created_at: 2025-04-15T00:09:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
+
+
+Install the required libraries : 
+```shell 
+(.venv-htf)$ pip install pyvisa pyvisa-py pyusb
+```
+
+Then run the script : 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 5866.153540919828
+:PASS
+
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+Cool! This test was done with a resistor rated at $5.6\text K\Omega$ $\pm 5 \%$ so our measured value of $5.87 \text K\Omega$ is fine.
+
+The final step is going to be to add a rule so that our test fails if we have a bad resistor. For that $5\%$ margin, it means we want values in the $[5.320\text K\Omega: 5.880\text K\Omega] $ range. 
+
+OpenHTF provides a convenient feature called a Validator to check this easily. Since we want to check the value of our measurement, we define this validation rule inside of the measurement decorator. For now we're using one of the default validators but it is possible to make your own custom validators for more specific use cases.  
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+)
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+```
+
+And now if we run the test with a resistor that is out of range (here a 220 $\Omega$ resistor), we get a fail: 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 220.493
+:FAIL
+failed phase: resistor_test [ran for 3.38 sec]
+  failed_item: resistor_val (Outcome.FAIL)
+    measured_value: 220.493
+    validators:
+      validator: 5320 <= x <= 5880
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+Congratulations, you have a working OpenHTF test that can be deployed in a resistor manufacturing plant!
+
+
+
+## Using Simulation mode
+
+As mentionned previously, this tutorial can also be done without actual hardware. For this we will use the fact that our plugs have been provided with a simulation mode. In this case, they will return random values when querying data. 
+
+This means we won't have any correlation between the applied voltage and the measured current, but at least we can check that our test sequence works. 
+
+Right now, we make the call to our plugs inside of the `@plug(dmm=MultimeterPlug)` decorator. However this does not allow us to use the fact that our MultimeterPlug object can be instantiated with the `simulate` argument which we will need: 
+
+```python
+class MultimeterPlug(BasePlug):
+    def __init__(self, simulate:bool = False)-> None:
+        self.simulate = simulate
+```
+
+To do so, we'll use the configurations built in OpenHTF : `from openhtf.util import configuration`.  
+Then we load the useful configuration parameters inside of a configuration object:
+
+```python
+CONF = configuration.CONF
+SIMULATE_MODE = CONF.declare("simulate", 
+                             default_value=False, 
+                             description="Set if the test setup is in simulation mode")
+CONF.load(simulate=True)
+```
+
+Finally, we create instances of the MultimeterPlug and PowerSupplyPlug that use this configuration. All that is left to do is to make sure we call these new plugs inside of our phase decorators:
+
+```python
+SimulatedMultimeterPlug = configuration.bind_init_args(MultimeterPlug, SIMULATE_MODE)
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Why are these plugs named as `Simulated...`? Whether they are simulated would depend on whether the simulate config was set.
created_at: 2025-04-15T00:12:50Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
+
+
+Install the required libraries : 
+```shell 
+(.venv-htf)$ pip install pyvisa pyvisa-py pyusb
+```
+
+Then run the script : 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 5866.153540919828
+:PASS
+
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+Cool! This test was done with a resistor rated at $5.6\text K\Omega$ $\pm 5 \%$ so our measured value of $5.87 \text K\Omega$ is fine.
+
+The final step is going to be to add a rule so that our test fails if we have a bad resistor. For that $5\%$ margin, it means we want values in the $[5.320\text K\Omega: 5.880\text K\Omega] $ range. 
+
+OpenHTF provides a convenient feature called a Validator to check this easily. Since we want to check the value of our measurement, we define this validation rule inside of the measurement decorator. For now we're using one of the default validators but it is possible to make your own custom validators for more specific use cases.  
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+)
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+```
+
+And now if we run the test with a resistor that is out of range (here a 220 $\Omega$ resistor), we get a fail: 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 220.493
+:FAIL
+failed phase: resistor_test [ran for 3.38 sec]
+  failed_item: resistor_val (Outcome.FAIL)
+    measured_value: 220.493
+    validators:
+      validator: 5320 <= x <= 5880
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+Congratulations, you have a working OpenHTF test that can be deployed in a resistor manufacturing plant!
+
+
+
+## Using Simulation mode
+
+As mentionned previously, this tutorial can also be done without actual hardware. For this we will use the fact that our plugs have been provided with a simulation mode. In this case, they will return random values when querying data. 
+
+This means we won't have any correlation between the applied voltage and the measured current, but at least we can check that our test sequence works. 
+
+Right now, we make the call to our plugs inside of the `@plug(dmm=MultimeterPlug)` decorator. However this does not allow us to use the fact that our MultimeterPlug object can be instantiated with the `simulate` argument which we will need: 
+
+```python
+class MultimeterPlug(BasePlug):
+    def __init__(self, simulate:bool = False)-> None:
+        self.simulate = simulate
+```
+
+To do so, we'll use the configurations built in OpenHTF : `from openhtf.util import configuration`.  
+Then we load the useful configuration parameters inside of a configuration object:
+
+```python
+CONF = configuration.CONF
+SIMULATE_MODE = CONF.declare("simulate", 
+                             default_value=False, 
+                             description="Set if the test setup is in simulation mode")
+CONF.load(simulate=True)
+```
+
+Finally, we create instances of the MultimeterPlug and PowerSupplyPlug that use this configuration. All that is left to do is to make sure we call these new plugs inside of our phase decorators:
+
+```python
+SimulatedMultimeterPlug = configuration.bind_init_args(MultimeterPlug, SIMULATE_MODE)
+SimulatedPowerSupplyPlug = configuration.bind_init_args(PowerSupplyPlug, SIMULATE_MODE)
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+)
+@plug(dmm=SimulatedMultimeterPlug)
+@plug(supply=SimulatedPowerSupplyPlug )
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+```
+
+
+> *Note* : In this example we're manually declaring and uploading a configuration parameter, but it is also possible to load these parameters from a yaml file or a dict.  
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Point the reader to read the configuration.py module doc for more details
created_at: 2025-04-15T00:14:25Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,174 @@
+""" my_plugs.py """
path: examples/Resistor_Tutorial/plugs.py
user: login: glados-verma
body: Docstrings is not very useful, either elaborate or remove.
created_at: 2025-04-15T00:14:40Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,174 @@
+""" my_plugs.py """
+
+import time
+import random
+from typing import Tuple
+
+import pyvisa
+
+from openhtf.plugs import BasePlug
+
+
+### To use pyvisa ###
+# Use rm.list_resources() to find the devices plugged in
+# this will return a list of the connected devices such as:
+# ('USB0::0x1AB1::0x0E11::DP8C1234567890::INSTR',
+#   'ASRL1::INSTR', 'ASRL10::INSTR')
+#
+# Then run device = rm.open_resource(
+#     'USB0::0x1AB1::0x0E11::DP8C1234567890::INSTR'
+#     )
+# to connect to the device.
+# You can check the device ID with psu.query("*IDN?")
path: examples/Resistor_Tutorial/plugs.py
user: login: glados-verma
body: What is `psu`, did you mean `device`?
created_at: 2025-04-15T00:15:24Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,174 @@
+""" my_plugs.py """
+
+import time
+import random
+from typing import Tuple
+
+import pyvisa
+
+from openhtf.plugs import BasePlug
+
+
+### To use pyvisa ###
+# Use rm.list_resources() to find the devices plugged in
+# this will return a list of the connected devices such as:
+# ('USB0::0x1AB1::0x0E11::DP8C1234567890::INSTR',
+#   'ASRL1::INSTR', 'ASRL10::INSTR')
+#
+# Then run device = rm.open_resource(
+#     'USB0::0x1AB1::0x0E11::DP8C1234567890::INSTR'
+#     )
+# to connect to the device.
+# You can check the device ID with psu.query("*IDN?")
+
+class MultimeterPlug(BasePlug):
+    """Plug for control of a multimeter with pyvisa.
path: examples/Resistor_Tutorial/plugs.py
user: login: glados-verma
body: Also mention that the simulate flag can be used to bypass hardware
created_at: 2025-04-15T00:16:06Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,174 @@
+""" my_plugs.py """
+
+import time
+import random
+from typing import Tuple
+
+import pyvisa
+
+from openhtf.plugs import BasePlug
+
+
+### To use pyvisa ###
+# Use rm.list_resources() to find the devices plugged in
+# this will return a list of the connected devices such as:
+# ('USB0::0x1AB1::0x0E11::DP8C1234567890::INSTR',
+#   'ASRL1::INSTR', 'ASRL10::INSTR')
+#
+# Then run device = rm.open_resource(
+#     'USB0::0x1AB1::0x0E11::DP8C1234567890::INSTR'
+#     )
+# to connect to the device.
+# You can check the device ID with psu.query("*IDN?")
+
+class MultimeterPlug(BasePlug):
+    """Plug for control of a multimeter with pyvisa.
+
+    Args:
+        BasePlug (Plug): OpeHTF Plug object.
path: examples/Resistor_Tutorial/plugs.py
user: login: glados-verma
body: Remove? I'm not used to the idea of calling the base class an "arg"...
created_at: 2025-04-15T00:16:51Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,174 @@
+""" my_plugs.py """
+
+import time
+import random
+from typing import Tuple
+
+import pyvisa
+
+from openhtf.plugs import BasePlug
+
+
+### To use pyvisa ###
+# Use rm.list_resources() to find the devices plugged in
+# this will return a list of the connected devices such as:
+# ('USB0::0x1AB1::0x0E11::DP8C1234567890::INSTR',
+#   'ASRL1::INSTR', 'ASRL10::INSTR')
+#
+# Then run device = rm.open_resource(
+#     'USB0::0x1AB1::0x0E11::DP8C1234567890::INSTR'
+#     )
+# to connect to the device.
+# You can check the device ID with psu.query("*IDN?")
+
+class MultimeterPlug(BasePlug):
+    """Plug for control of a multimeter with pyvisa.
+
+    Args:
+        BasePlug (Plug): OpeHTF Plug object.
+    """
+    def __init__(self, simulate:bool = False)-> None:
+        self.simulate = simulate
+        self.logger.info("Logging mode is %s", self.simulate)
+        self.rm = None
+        self.multimeter = None
+
+    def connect(self)->None:
+        """Connect to the device using pysisa. 
path: examples/Resistor_Tutorial/plugs.py
user: login: glados-verma
body: Typo `pysisa`
created_at: 2025-04-15T00:17:19Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,174 @@
+""" my_plugs.py """
+
+import time
+import random
+from typing import Tuple
+
+import pyvisa
+
+from openhtf.plugs import BasePlug
+
+
+### To use pyvisa ###
+# Use rm.list_resources() to find the devices plugged in
+# this will return a list of the connected devices such as:
+# ('USB0::0x1AB1::0x0E11::DP8C1234567890::INSTR',
+#   'ASRL1::INSTR', 'ASRL10::INSTR')
+#
+# Then run device = rm.open_resource(
+#     'USB0::0x1AB1::0x0E11::DP8C1234567890::INSTR'
+#     )
+# to connect to the device.
+# You can check the device ID with psu.query("*IDN?")
+
+class MultimeterPlug(BasePlug):
+    """Plug for control of a multimeter with pyvisa.
+
+    Args:
+        BasePlug (Plug): OpeHTF Plug object.
+    """
+    def __init__(self, simulate:bool = False)-> None:
+        self.simulate = simulate
+        self.logger.info("Logging mode is %s", self.simulate)
+        self.rm = None
+        self.multimeter = None
+
+    def connect(self)->None:
+        """Connect to the device using pysisa. 
+        Does nothing if using simulation mode.
+        """
+        if not self.simulate:
+            try:
+                self.rm = pyvisa.ResourceManager()
+                self.multimeter = self.rm.open_resource(
+                    "USB0::6833::8458::DM8A265201811::0::INSTR"
+                    )
+                self.logger.info("Connected to Multimeter")
+            except ConnectionRefusedError as error:
+                self.logger.warning(
+                    "Connection refused : %s, " \
path: examples/Resistor_Tutorial/plugs.py
user: login: glados-verma
body: The trailing backslash `\` isn't need as Python will auto concat the two strings.
created_at: 2025-04-15T00:17:51Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,174 @@
+""" my_plugs.py """
+
+import time
+import random
+from typing import Tuple
path: examples/Resistor_Tutorial/plugs.py
user: login: glados-verma
body: This is a deprecated type hint: https://docs.python.org/3/library/typing.html#typing.Tuple
Please use `tuple` directly instead.
created_at: 2025-04-15T00:19:13Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,65 @@
+"""main_test.py"""
path: examples/Resistor_Tutorial/main_test.py
user: login: glados-verma
body: Docstring isn't very useful, either elaborate or remove.
created_at: 2025-04-15T00:20:20Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
+
+
+Install the required libraries : 
+```shell 
+(.venv-htf)$ pip install pyvisa pyvisa-py pyusb
+```
+
+Then run the script : 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 5866.153540919828
+:PASS
+
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+Cool! This test was done with a resistor rated at $5.6\text K\Omega$ $\pm 5 \%$ so our measured value of $5.87 \text K\Omega$ is fine.
+
+The final step is going to be to add a rule so that our test fails if we have a bad resistor. For that $5\%$ margin, it means we want values in the $[5.320\text K\Omega: 5.880\text K\Omega] $ range. 
+
+OpenHTF provides a convenient feature called a Validator to check this easily. Since we want to check the value of our measurement, we define this validation rule inside of the measurement decorator. For now we're using one of the default validators but it is possible to make your own custom validators for more specific use cases.  
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+)
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+```
+
+And now if we run the test with a resistor that is out of range (here a 220 $\Omega$ resistor), we get a fail: 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 220.493
+:FAIL
+failed phase: resistor_test [ran for 3.38 sec]
+  failed_item: resistor_val (Outcome.FAIL)
+    measured_value: 220.493
+    validators:
+      validator: 5320 <= x <= 5880
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+Congratulations, you have a working OpenHTF test that can be deployed in a resistor manufacturing plant!
+
+
+
+## Using Simulation mode
+
+As mentionned previously, this tutorial can also be done without actual hardware. For this we will use the fact that our plugs have been provided with a simulation mode. In this case, they will return random values when querying data. 
+
+This means we won't have any correlation between the applied voltage and the measured current, but at least we can check that our test sequence works. 
+
+Right now, we make the call to our plugs inside of the `@plug(dmm=MultimeterPlug)` decorator. However this does not allow us to use the fact that our MultimeterPlug object can be instantiated with the `simulate` argument which we will need: 
+
+```python
+class MultimeterPlug(BasePlug):
+    def __init__(self, simulate:bool = False)-> None:
+        self.simulate = simulate
+```
+
+To do so, we'll use the configurations built in OpenHTF : `from openhtf.util import configuration`.  
+Then we load the useful configuration parameters inside of a configuration object:
+
+```python
+CONF = configuration.CONF
+SIMULATE_MODE = CONF.declare("simulate", 
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Interesting use of configuration - we do suggest that CONF should be used only for configuring plugs, but converting a hardware plug into a simulation is - unusual :)
You can keep this as-is, but my suggestion is to instead create a base class for your plug (using abc or typing.Protocol), and create two implementations, one with real hardware and one for simulation. Then, use plug placeholders in your `@plug` calls:
https://github.com/google/openhtf/blob/92a841d970776f182c2e12cdab263f60d1d4dc4d/openhtf/core/base_plugs.py#L192

Then, `with_plugs` can be used to specify which plug should be used to replace the placeholder. This way you'd create two tests, one real and one simulation. See this example: https://github.com/google/openhtf/blob/92a841d970776f182c2e12cdab263f60d1d4dc4d/examples/with_plugs.py#L111

I realize this is a big change so consider this comment optional.
created_at: 2025-04-15T00:24:43Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
path: examples/Resistor_Tutorial/resistor_tutorial.md
body: I ended up changing the name as the pylint rule doesn't allow a "_" character as 3rd character in file name. Has now been renamed to resistor_plugs.py
created_at: 2025-04-15T13:48:51Z
author_association: NONE
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
path: examples/Resistor_Tutorial/resistor_tutorial.md
body: No it makes sense here. We first need to apply the voltage, and then the current flowing in the circuit is going to react depending on the resistor value. We want to wait after setting the voltage and before reading the current. 

The `supply.set_voltage(input_voltage)` tells the power supply to set a certain voltage. It isn't a measurement.

Am I missing something obvious?
created_at: 2025-04-15T14:09:34Z
author_association: NONE
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
+
+
+Install the required libraries : 
+```shell 
+(.venv-htf)$ pip install pyvisa pyvisa-py pyusb
+```
+
+Then run the script : 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 5866.153540919828
+:PASS
+
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+Cool! This test was done with a resistor rated at $5.6\text K\Omega$ $\pm 5 \%$ so our measured value of $5.87 \text K\Omega$ is fine.
+
+The final step is going to be to add a rule so that our test fails if we have a bad resistor. For that $5\%$ margin, it means we want values in the $[5.320\text K\Omega: 5.880\text K\Omega] $ range. 
+
+OpenHTF provides a convenient feature called a Validator to check this easily. Since we want to check the value of our measurement, we define this validation rule inside of the measurement decorator. For now we're using one of the default validators but it is possible to make your own custom validators for more specific use cases.  
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+)
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+```
+
+And now if we run the test with a resistor that is out of range (here a 220 $\Omega$ resistor), we get a fail: 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 220.493
+:FAIL
+failed phase: resistor_test [ran for 3.38 sec]
+  failed_item: resistor_val (Outcome.FAIL)
+    measured_value: 220.493
+    validators:
+      validator: 5320 <= x <= 5880
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+Congratulations, you have a working OpenHTF test that can be deployed in a resistor manufacturing plant!
+
+
+
+## Using Simulation mode
+
+As mentionned previously, this tutorial can also be done without actual hardware. For this we will use the fact that our plugs have been provided with a simulation mode. In this case, they will return random values when querying data. 
+
+This means we won't have any correlation between the applied voltage and the measured current, but at least we can check that our test sequence works. 
+
+Right now, we make the call to our plugs inside of the `@plug(dmm=MultimeterPlug)` decorator. However this does not allow us to use the fact that our MultimeterPlug object can be instantiated with the `simulate` argument which we will need: 
+
+```python
+class MultimeterPlug(BasePlug):
+    def __init__(self, simulate:bool = False)-> None:
+        self.simulate = simulate
+```
+
+To do so, we'll use the configurations built in OpenHTF : `from openhtf.util import configuration`.  
+Then we load the useful configuration parameters inside of a configuration object:
+
+```python
+CONF = configuration.CONF
+SIMULATE_MODE = CONF.declare("simulate", 
path: examples/Resistor_Tutorial/resistor_tutorial.md
body: Thanks for the suggestions, I realize my current implementation probably doesn't follow the intended use for CONF. My goal was to quickly offer a functioning example for people who don't have hardware at hand, so I'm not sure I'm ready to rework the example as you suggest just yet.
created_at: 2025-04-15T14:34:01Z
author_association: NONE
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
+
+
+Install the required libraries : 
+```shell 
+(.venv-htf)$ pip install pyvisa pyvisa-py pyusb
+```
+
+Then run the script : 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 5866.153540919828
+:PASS
+
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+Cool! This test was done with a resistor rated at $5.6\text K\Omega$ $\pm 5 \%$ so our measured value of $5.87 \text K\Omega$ is fine.
+
+The final step is going to be to add a rule so that our test fails if we have a bad resistor. For that $5\%$ margin, it means we want values in the $[5.320\text K\Omega: 5.880\text K\Omega] $ range. 
+
+OpenHTF provides a convenient feature called a Validator to check this easily. Since we want to check the value of our measurement, we define this validation rule inside of the measurement decorator. For now we're using one of the default validators but it is possible to make your own custom validators for more specific use cases.  
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+)
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+```
+
+And now if we run the test with a resistor that is out of range (here a 220 $\Omega$ resistor), we get a fail: 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 220.493
+:FAIL
+failed phase: resistor_test [ran for 3.38 sec]
+  failed_item: resistor_val (Outcome.FAIL)
+    measured_value: 220.493
+    validators:
+      validator: 5320 <= x <= 5880
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+Congratulations, you have a working OpenHTF test that can be deployed in a resistor manufacturing plant!
+
+
+
+## Using Simulation mode
+
+As mentionned previously, this tutorial can also be done without actual hardware. For this we will use the fact that our plugs have been provided with a simulation mode. In this case, they will return random values when querying data. 
+
+This means we won't have any correlation between the applied voltage and the measured current, but at least we can check that our test sequence works. 
+
+Right now, we make the call to our plugs inside of the `@plug(dmm=MultimeterPlug)` decorator. However this does not allow us to use the fact that our MultimeterPlug object can be instantiated with the `simulate` argument which we will need: 
+
+```python
+class MultimeterPlug(BasePlug):
+    def __init__(self, simulate:bool = False)-> None:
+        self.simulate = simulate
+```
+
+To do so, we'll use the configurations built in OpenHTF : `from openhtf.util import configuration`.  
+Then we load the useful configuration parameters inside of a configuration object:
+
+```python
+CONF = configuration.CONF
+SIMULATE_MODE = CONF.declare("simulate", 
+                             default_value=False, 
+                             description="Set if the test setup is in simulation mode")
+CONF.load(simulate=True)
+```
+
+Finally, we create instances of the MultimeterPlug and PowerSupplyPlug that use this configuration. All that is left to do is to make sure we call these new plugs inside of our phase decorators:
+
+```python
+SimulatedMultimeterPlug = configuration.bind_init_args(MultimeterPlug, SIMULATE_MODE)
path: examples/Resistor_Tutorial/resistor_tutorial.md
body: That's true, my mistake. I did this as VScode ws giving me a warning when doing : `MultimeterPlug = configuration.bind_init_args(MultimeterPlug, SIMULATE_MODE)`. When calling the plug in the test phase declaration, it was not recognizing the new `MultimeterPlug` as having the proper type. See screenshot attached

![image](https://github.com/user-attachments/assets/5ad2b1c4-4ef5-435d-a35c-bb7131e3a92d)
created_at: 2025-04-15T14:40:18Z
author_association: NONE
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Sorry I totally misread the lines! Ignore me.
created_at: 2025-04-15T19:41:43Z
author_association: COLLABORATOR
diff_hunk: @@ -1,39 +1,33 @@
-"""main_test.py"""
-
 import time
 
-from plugs import MultimeterPlug, PowerSupplyPlug
-
+import openhtf as htf
 from openhtf.output.callbacks import console_summary
-from openhtf import Test
-from openhtf import measures, Measurement
-from openhtf import plug
-from openhtf.util import configuration
+from openhtf.util import configuration, units
 
+from resistor_plugs import MultimeterPlug, PowerSupplyPlug
path: examples/Resistor_Tutorial/main_test.py
user: login: glados-verma
body: Here also, import the module only, not names from the module
created_at: 2025-04-15T19:42:34Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
+
+
+Install the required libraries : 
+```shell 
+(.venv-htf)$ pip install pyvisa pyvisa-py pyusb
+```
+
+Then run the script : 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 5866.153540919828
+:PASS
+
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+Cool! This test was done with a resistor rated at $5.6\text K\Omega$ $\pm 5 \%$ so our measured value of $5.87 \text K\Omega$ is fine.
+
+The final step is going to be to add a rule so that our test fails if we have a bad resistor. For that $5\%$ margin, it means we want values in the $[5.320\text K\Omega: 5.880\text K\Omega] $ range. 
+
+OpenHTF provides a convenient feature called a Validator to check this easily. Since we want to check the value of our measurement, we define this validation rule inside of the measurement decorator. For now we're using one of the default validators but it is possible to make your own custom validators for more specific use cases.  
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+)
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+```
+
+And now if we run the test with a resistor that is out of range (here a 220 $\Omega$ resistor), we get a fail: 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 220.493
+:FAIL
+failed phase: resistor_test [ran for 3.38 sec]
+  failed_item: resistor_val (Outcome.FAIL)
+    measured_value: 220.493
+    validators:
+      validator: 5320 <= x <= 5880
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+Congratulations, you have a working OpenHTF test that can be deployed in a resistor manufacturing plant!
+
+
+
+## Using Simulation mode
+
+As mentionned previously, this tutorial can also be done without actual hardware. For this we will use the fact that our plugs have been provided with a simulation mode. In this case, they will return random values when querying data. 
+
+This means we won't have any correlation between the applied voltage and the measured current, but at least we can check that our test sequence works. 
+
+Right now, we make the call to our plugs inside of the `@plug(dmm=MultimeterPlug)` decorator. However this does not allow us to use the fact that our MultimeterPlug object can be instantiated with the `simulate` argument which we will need: 
+
+```python
+class MultimeterPlug(BasePlug):
+    def __init__(self, simulate:bool = False)-> None:
+        self.simulate = simulate
+```
+
+To do so, we'll use the configurations built in OpenHTF : `from openhtf.util import configuration`.  
+Then we load the useful configuration parameters inside of a configuration object:
+
+```python
+CONF = configuration.CONF
+SIMULATE_MODE = CONF.declare("simulate", 
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Yeah this is fine to leave as-is
created_at: 2025-04-15T19:52:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
+
+
+Install the required libraries : 
+```shell 
+(.venv-htf)$ pip install pyvisa pyvisa-py pyusb
+```
+
+Then run the script : 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 5866.153540919828
+:PASS
+
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+Cool! This test was done with a resistor rated at $5.6\text K\Omega$ $\pm 5 \%$ so our measured value of $5.87 \text K\Omega$ is fine.
+
+The final step is going to be to add a rule so that our test fails if we have a bad resistor. For that $5\%$ margin, it means we want values in the $[5.320\text K\Omega: 5.880\text K\Omega] $ range. 
+
+OpenHTF provides a convenient feature called a Validator to check this easily. Since we want to check the value of our measurement, we define this validation rule inside of the measurement decorator. For now we're using one of the default validators but it is possible to make your own custom validators for more specific use cases.  
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+)
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+```
+
+And now if we run the test with a resistor that is out of range (here a 220 $\Omega$ resistor), we get a fail: 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 220.493
+:FAIL
+failed phase: resistor_test [ran for 3.38 sec]
+  failed_item: resistor_val (Outcome.FAIL)
+    measured_value: 220.493
+    validators:
+      validator: 5320 <= x <= 5880
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+Congratulations, you have a working OpenHTF test that can be deployed in a resistor manufacturing plant!
+
+
+
+## Using Simulation mode
+
+As mentionned previously, this tutorial can also be done without actual hardware. For this we will use the fact that our plugs have been provided with a simulation mode. In this case, they will return random values when querying data. 
+
+This means we won't have any correlation between the applied voltage and the measured current, but at least we can check that our test sequence works. 
+
+Right now, we make the call to our plugs inside of the `@plug(dmm=MultimeterPlug)` decorator. However this does not allow us to use the fact that our MultimeterPlug object can be instantiated with the `simulate` argument which we will need: 
+
+```python
+class MultimeterPlug(BasePlug):
+    def __init__(self, simulate:bool = False)-> None:
+        self.simulate = simulate
+```
+
+To do so, we'll use the configurations built in OpenHTF : `from openhtf.util import configuration`.  
+Then we load the useful configuration parameters inside of a configuration object:
+
+```python
+CONF = configuration.CONF
+SIMULATE_MODE = CONF.declare("simulate", 
+                             default_value=False, 
+                             description="Set if the test setup is in simulation mode")
+CONF.load(simulate=True)
+```
+
+Finally, we create instances of the MultimeterPlug and PowerSupplyPlug that use this configuration. All that is left to do is to make sure we call these new plugs inside of our phase decorators:
+
+```python
+SimulatedMultimeterPlug = configuration.bind_init_args(MultimeterPlug, SIMULATE_MODE)
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: Oh right, you'll have to get them a different name in that case - something like BoundMultimeterPlug. Optional since the main purpose of this binding is to allow multiple bindings for the same plug.
created_at: 2025-04-15T19:53:54Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: LGTM
<!-- Reviewable comment -ONznSsH5nk6DEvO3_jz:bb6ywmx -->
<!-- Sent from Reviewable.io -->

created_at: 2025-04-16T18:14:16Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: LGTM
<!-- Reviewable comment -ONzn_C23IIsyKZ52CS6:bb6ywmx -->
<!-- Sent from Reviewable.io -->

created_at: 2025-04-16T18:14:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: LGTM
<!-- Reviewable comment -ONznbZX4e6gk7j_bdgs:bb6ywmx -->
<!-- Sent from Reviewable.io -->

created_at: 2025-04-16T18:14:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: LGTM
<!-- Reviewable comment -ONznc8P9ohFHyzT85-N:bb6ywmx -->
<!-- Sent from Reviewable.io -->

created_at: 2025-04-16T18:14:17Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: LGTM
<!-- Reviewable comment -ONznce19eveukmgpH-u:bb6ywmx -->
<!-- Sent from Reviewable.io -->

created_at: 2025-04-16T18:14:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: LGTM
<!-- Reviewable comment -ONznd9OCOyyaHpKgRfu:bb6ywmx -->
<!-- Sent from Reviewable.io -->

created_at: 2025-04-16T18:14:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
+
+
+Install the required libraries : 
+```shell 
+(.venv-htf)$ pip install pyvisa pyvisa-py pyusb
+```
+
+Then run the script : 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 5866.153540919828
+:PASS
+
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+Cool! This test was done with a resistor rated at $5.6\text K\Omega$ $\pm 5 \%$ so our measured value of $5.87 \text K\Omega$ is fine.
+
+The final step is going to be to add a rule so that our test fails if we have a bad resistor. For that $5\%$ margin, it means we want values in the $[5.320\text K\Omega: 5.880\text K\Omega] $ range. 
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: LGTM
<!-- Reviewable comment -ONzndfT5FKQrT5TEyHu:bb6ywmx -->
<!-- Sent from Reviewable.io -->

created_at: 2025-04-16T18:14:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
+
+
+Install the required libraries : 
+```shell 
+(.venv-htf)$ pip install pyvisa pyvisa-py pyusb
+```
+
+Then run the script : 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 5866.153540919828
+:PASS
+
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+Cool! This test was done with a resistor rated at $5.6\text K\Omega$ $\pm 5 \%$ so our measured value of $5.87 \text K\Omega$ is fine.
+
+The final step is going to be to add a rule so that our test fails if we have a bad resistor. For that $5\%$ margin, it means we want values in the $[5.320\text K\Omega: 5.880\text K\Omega] $ range. 
+
+OpenHTF provides a convenient feature called a Validator to check this easily. Since we want to check the value of our measurement, we define this validation rule inside of the measurement decorator. For now we're using one of the default validators but it is possible to make your own custom validators for more specific use cases.  
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+)
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+```
+
+And now if we run the test with a resistor that is out of range (here a 220 $\Omega$ resistor), we get a fail: 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 220.493
+:FAIL
+failed phase: resistor_test [ran for 3.38 sec]
+  failed_item: resistor_val (Outcome.FAIL)
+    measured_value: 220.493
+    validators:
+      validator: 5320 <= x <= 5880
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+Congratulations, you have a working OpenHTF test that can be deployed in a resistor manufacturing plant!
+
+
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: LGTM
<!-- Reviewable comment -ONzneCN2VWNlVgX415V:bb6ywmx -->
<!-- Sent from Reviewable.io -->

created_at: 2025-04-16T18:14:19Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
+
+
+Install the required libraries : 
+```shell 
+(.venv-htf)$ pip install pyvisa pyvisa-py pyusb
+```
+
+Then run the script : 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 5866.153540919828
+:PASS
+
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+Cool! This test was done with a resistor rated at $5.6\text K\Omega$ $\pm 5 \%$ so our measured value of $5.87 \text K\Omega$ is fine.
+
+The final step is going to be to add a rule so that our test fails if we have a bad resistor. For that $5\%$ margin, it means we want values in the $[5.320\text K\Omega: 5.880\text K\Omega] $ range. 
+
+OpenHTF provides a convenient feature called a Validator to check this easily. Since we want to check the value of our measurement, we define this validation rule inside of the measurement decorator. For now we're using one of the default validators but it is possible to make your own custom validators for more specific use cases.  
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+)
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+```
+
+And now if we run the test with a resistor that is out of range (here a 220 $\Omega$ resistor), we get a fail: 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 220.493
+:FAIL
+failed phase: resistor_test [ran for 3.38 sec]
+  failed_item: resistor_val (Outcome.FAIL)
+    measured_value: 220.493
+    validators:
+      validator: 5320 <= x <= 5880
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+Congratulations, you have a working OpenHTF test that can be deployed in a resistor manufacturing plant!
+
+
+
+## Using Simulation mode
+
+As mentionned previously, this tutorial can also be done without actual hardware. For this we will use the fact that our plugs have been provided with a simulation mode. In this case, they will return random values when querying data. 
+
+This means we won't have any correlation between the applied voltage and the measured current, but at least we can check that our test sequence works. 
+
+Right now, we make the call to our plugs inside of the `@plug(dmm=MultimeterPlug)` decorator. However this does not allow us to use the fact that our MultimeterPlug object can be instantiated with the `simulate` argument which we will need: 
+
+```python
+class MultimeterPlug(BasePlug):
+    def __init__(self, simulate:bool = False)-> None:
+        self.simulate = simulate
+```
+
+To do so, we'll use the configurations built in OpenHTF : `from openhtf.util import configuration`.  
+Then we load the useful configuration parameters inside of a configuration object:
+
+```python
+CONF = configuration.CONF
+SIMULATE_MODE = CONF.declare("simulate", 
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: LGTM
<!-- Reviewable comment -ONznemK6eXxe0Pw3E1O:bb6ywmx -->
<!-- Sent from Reviewable.io -->

created_at: 2025-04-16T18:14:19Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
+
+
+Install the required libraries : 
+```shell 
+(.venv-htf)$ pip install pyvisa pyvisa-py pyusb
+```
+
+Then run the script : 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 5866.153540919828
+:PASS
+
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+Cool! This test was done with a resistor rated at $5.6\text K\Omega$ $\pm 5 \%$ so our measured value of $5.87 \text K\Omega$ is fine.
+
+The final step is going to be to add a rule so that our test fails if we have a bad resistor. For that $5\%$ margin, it means we want values in the $[5.320\text K\Omega: 5.880\text K\Omega] $ range. 
+
+OpenHTF provides a convenient feature called a Validator to check this easily. Since we want to check the value of our measurement, we define this validation rule inside of the measurement decorator. For now we're using one of the default validators but it is possible to make your own custom validators for more specific use cases.  
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+)
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+```
+
+And now if we run the test with a resistor that is out of range (here a 220 $\Omega$ resistor), we get a fail: 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 220.493
+:FAIL
+failed phase: resistor_test [ran for 3.38 sec]
+  failed_item: resistor_val (Outcome.FAIL)
+    measured_value: 220.493
+    validators:
+      validator: 5320 <= x <= 5880
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+Congratulations, you have a working OpenHTF test that can be deployed in a resistor manufacturing plant!
+
+
+
+## Using Simulation mode
+
+As mentionned previously, this tutorial can also be done without actual hardware. For this we will use the fact that our plugs have been provided with a simulation mode. In this case, they will return random values when querying data. 
+
+This means we won't have any correlation between the applied voltage and the measured current, but at least we can check that our test sequence works. 
+
+Right now, we make the call to our plugs inside of the `@plug(dmm=MultimeterPlug)` decorator. However this does not allow us to use the fact that our MultimeterPlug object can be instantiated with the `simulate` argument which we will need: 
+
+```python
+class MultimeterPlug(BasePlug):
+    def __init__(self, simulate:bool = False)-> None:
+        self.simulate = simulate
+```
+
+To do so, we'll use the configurations built in OpenHTF : `from openhtf.util import configuration`.  
+Then we load the useful configuration parameters inside of a configuration object:
+
+```python
+CONF = configuration.CONF
+SIMULATE_MODE = CONF.declare("simulate", 
+                             default_value=False, 
+                             description="Set if the test setup is in simulation mode")
+CONF.load(simulate=True)
+```
+
+Finally, we create instances of the MultimeterPlug and PowerSupplyPlug that use this configuration. All that is left to do is to make sure we call these new plugs inside of our phase decorators:
+
+```python
+SimulatedMultimeterPlug = configuration.bind_init_args(MultimeterPlug, SIMULATE_MODE)
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: LGTM
<!-- Reviewable comment -ONznfGz8ddeVajdqNW0:bb6ywmx -->
<!-- Sent from Reviewable.io -->

created_at: 2025-04-16T18:14:19Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,295 @@
+## Introduction
+The goal of this tutorial is to familarize yourself with some OpenHTF basic functionnalities. We'll do so by trying to determine the resistance value of a resistor. This will include using a programmable power supply as well as a digital multimeter. 
+
+We will apply a voltage to the resistor, and measure the corresponding current in the circuit. Using Ohm's law $ V = R.I $, this should give us an estimate of the resistance value of the resistor : $R = V/I$  
+We will then be able to determine if the resistor if good or bad by comparing the measured value to a threshold.
+
+During this tutorial, you will learn how to use OpenHTF Plugs, OpenHTF Measurements and OpenHTF Configurations. 
+
+## Installs
+Before anything else, create a virtual environment for this project : `$ python -m venv .venv-htf` and activate it : `$ source .venv-htf/bin/activate`
+
+Then install the openhtf library : `(.venv-htf)$ pip install openhtf`
+
+
+## Writing your first test
+The first step of this tutorial is to create a `main_test.py` that will contain our test:
+
+```python
+# main_test.py
+
+from openhtf import Test
+
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+After execution of the script, you should see as an output in the console that the test has passed : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+That was easy enough! Now let's start actually testing things. For this, we are going to use the measurement feature of Openhtf. We will tell Openhtf that a certain value inside of a Phase is something we want to measure and the result of the Phase depends on the value that is measured. This is done using a decorator above the Test Phase definition: 
+
+## Making a Measurement
+
+```python
+# main_test.py
+
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+If you run the test, You should see that it fails : 
+
+```shell
+(.venv-htf)$ python main_test.py
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+That is because OpenHTF was told that the `resistor_test` Phase was supposed to make a measurement called `resistor_val`, but since we never declared that measurement's value in the `resistor_test` Phase OpenHTF plays it safe and returns a **FAIL** status.  
+
+> *Note* : You might have noticed that there isn't a lot of information explaining exactly why the test failed. We can change that by adding a few lines to use an output callback:
+
+```python
+# main_test.py
+
+from openhtf.output.callbacks import console_summary
+from openhtf import Test
+from openhtf import measures, Measurement
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    pass
+
+def main():
+    test = Test(resistor_test)
+    test.add_output_callbacks(console_summary.ConsoleSummary())
+    test.execute()
+
+if __name__ == "__main__":
+    main()
+```
+
+This gives us a more detailed output which is very helpful for debugging when developing tests : 
+```shell
+(.venv-htf)$ python main_test.py
+:FAIL
+failed phase: resistor_test [ran for 0.00 sec]
+  failed_item: resistor_val (Outcome.UNSET)
+    measured_value: UNSET
+    validators:
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+
+To define the measurement, we just need to add the line : `test.measurements["resistor_val"] = 10` inside of our `resistor_test` phase. We set it as an arbitrary value for now, but soon this will be given by an actual measurement.
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+def resistor_test(test):
+    test.measurements["resistor_val"] = 10
+
+```
+
+## Creating Plugs
+
+To move on with our test definition, we need to be able to communicate with lab instruments. In our case, this is going to be a multimeter and a power supply. OpenHTF is meant to be very modular so the boilerplate code that allows us to interact with each device is going to be implemented inside of a `Plug` object in a seperate script. All we need to do for testing is to import the Plugs we need within our test script, and then interact with the device using the Plugs API. 
+
+The goal is not to dive into how to develop Plugs, so we'll just provide the code for the multimeter and the power supply. The tutorial was developped with a Rigol DM858 and Rigol DP932E, but it also includes a simulation mode so you can run everything without the hardware. 
+
+
+To use your plugs in a test phase, import them in your script and then declare them inside of a decorator for your test phase : 
+
+```python
+from openhtf import plug
+from my_plugs import MultimeterPlug, PowerSupplyPlug
+
+import time
+```
+and then 
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    test.measurements["resistor_val"] = 10
+```
+
+All that's left to do is to connect to the instruments, set the voltage, read the resulting current through the circuit and calculate the resistor value: 
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        )
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+    supply.connect()
+    dmm.connect()
+    
+    input_voltage = 5 #in V
+    supply.set_voltage(input_voltage)
+    time.sleep(3) # add a short wait for the voltage and current to stabilize
+    current = float(dmm.read_current()[1])
+
+    measured_R = input_voltage/current
+    test.measurements["resistor_val"] = measured_R
+    print(f"R value is: {measured_R}")
+```
+
+At this point we can either run the tutorial with some actual hardware, or we can use the simulation mode. 
+
+We'll start off with actual hardware, but check out the [Simulation mode](#using-simulation-mode) as it includes some information on how to use OpenHTF's configuration settings.
+
+### Hardware connection
+To interact with the hardware, we'll be using pyvisa under the hood. This library provides an easy interfacing solution to most devices that accept SCPI commands. Learning how to use pyvisa is out of scope here however.
+
+Make sure to have your instruments wired properly, with the resistor and the ammeter in series.
+
+
+Install the required libraries : 
+```shell 
+(.venv-htf)$ pip install pyvisa pyvisa-py pyusb
+```
+
+Then run the script : 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 5866.153540919828
+:PASS
+
+
+======================= test: openhtf_test  outcome: PASS ======================
+```
+
+Cool! This test was done with a resistor rated at $5.6\text K\Omega$ $\pm 5 \%$ so our measured value of $5.87 \text K\Omega$ is fine.
+
+The final step is going to be to add a rule so that our test fails if we have a bad resistor. For that $5\%$ margin, it means we want values in the $[5.320\text K\Omega: 5.880\text K\Omega] $ range. 
+
+OpenHTF provides a convenient feature called a Validator to check this easily. Since we want to check the value of our measurement, we define this validation rule inside of the measurement decorator. For now we're using one of the default validators but it is possible to make your own custom validators for more specific use cases.  
+
+```python
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+)
+@plug(dmm=MultimeterPlug)
+@plug(supply=PowerSupplyPlug)
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+```
+
+And now if we run the test with a resistor that is out of range (here a 220 $\Omega$ resistor), we get a fail: 
+
+```shell
+(.venv-htf)$ python main_test.py
+R value is: 220.493
+:FAIL
+failed phase: resistor_test [ran for 3.38 sec]
+  failed_item: resistor_val (Outcome.FAIL)
+    measured_value: 220.493
+    validators:
+      validator: 5320 <= x <= 5880
+
+
+======================= test: openhtf_test  outcome: FAIL ======================
+```
+Congratulations, you have a working OpenHTF test that can be deployed in a resistor manufacturing plant!
+
+
+
+## Using Simulation mode
+
+As mentionned previously, this tutorial can also be done without actual hardware. For this we will use the fact that our plugs have been provided with a simulation mode. In this case, they will return random values when querying data. 
+
+This means we won't have any correlation between the applied voltage and the measured current, but at least we can check that our test sequence works. 
+
+Right now, we make the call to our plugs inside of the `@plug(dmm=MultimeterPlug)` decorator. However this does not allow us to use the fact that our MultimeterPlug object can be instantiated with the `simulate` argument which we will need: 
+
+```python
+class MultimeterPlug(BasePlug):
+    def __init__(self, simulate:bool = False)-> None:
+        self.simulate = simulate
+```
+
+To do so, we'll use the configurations built in OpenHTF : `from openhtf.util import configuration`.  
+Then we load the useful configuration parameters inside of a configuration object:
+
+```python
+CONF = configuration.CONF
+SIMULATE_MODE = CONF.declare("simulate", 
+                             default_value=False, 
+                             description="Set if the test setup is in simulation mode")
+CONF.load(simulate=True)
+```
+
+Finally, we create instances of the MultimeterPlug and PowerSupplyPlug that use this configuration. All that is left to do is to make sure we call these new plugs inside of our phase decorators:
+
+```python
+SimulatedMultimeterPlug = configuration.bind_init_args(MultimeterPlug, SIMULATE_MODE)
+SimulatedPowerSupplyPlug = configuration.bind_init_args(PowerSupplyPlug, SIMULATE_MODE)
+
+@measures(
+        Measurement("resistor_val")
+        .doc("Computed resistor value")
+        .in_range(5320, 5880)
+)
+@plug(dmm=SimulatedMultimeterPlug)
+@plug(supply=SimulatedPowerSupplyPlug )
+def resistor_test(test, dmm:MultimeterPlug, supply:PowerSupplyPlug) -> None:
+```
+
+
+> *Note* : In this example we're manually declaring and uploading a configuration parameter, but it is also possible to load these parameters from a yaml file or a dict.  
path: examples/Resistor_Tutorial/resistor_tutorial.md
user: login: glados-verma
body: LGTM
<!-- Reviewable comment -ONznfj29LEffYFkQcbz:bb6ywmx -->
<!-- Sent from Reviewable.io -->

created_at: 2025-04-16T18:14:20Z
author_association: COLLABORATOR

PR comments:
user: login: ghost
created_at: 2025-04-15T14:58:04Z
body: Thanks for all of your comments. I hope I was able to take all of them into account. Let me know if there's anything else that needs some work
author_association: NONE
user: login: ghost
created_at: 2025-04-16T16:46:44Z
body: Should be good now. I changed the folder name and the imports from resistor_plugs.py
author_association: NONE
user: login: glados-verma
created_at: 2025-04-16T18:05:36Z
body: Looks great! Thanks for working through all the comments.
author_association: COLLABORATOR