From ffb6f6745786c387808e2a37e40ce91c735e03ef Mon Sep 17 00:00:00 2001 From: Amy Tseng Date: Mon, 8 Dec 2025 11:09:02 +0800 Subject: [PATCH 1/3] Docs: Add tutorial for writing custom controllers --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 093c9551..808397b3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,7 +11,7 @@ Welcome to Mobly's documentation! :caption: Contents: mobly - + tutorial_custom_controller Indices and tables ================== From 53f1f3a027d40279be417ac7418d54b5a7dfcc8d Mon Sep 17 00:00:00 2001 From: Amy Tseng Date: Tue, 9 Dec 2025 15:12:17 +0800 Subject: [PATCH 2/3] Docs: Add tutorial for writing custom controllers --- docs/tutorial_custom_controller.md | 151 +++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 docs/tutorial_custom_controller.md diff --git a/docs/tutorial_custom_controller.md b/docs/tutorial_custom_controller.md new file mode 100644 index 00000000..5f38d7bd --- /dev/null +++ b/docs/tutorial_custom_controller.md @@ -0,0 +1,151 @@ +# Custom Controller Tutorial + +Mobly enables users to control custom hardware devices (e.g., smart lights, switches) by creating custom controller modules. This tutorial explains how to implement a production-ready custom controller. + +## Controller Module Interface + +A Mobly controller module must implement the following top-level functions: + +* **`create(configs)`**: Instantiates controller objects from the configuration. +* **`destroy(objects)`**: Cleans up resources when the test ends. +* **`get_info(objects)`**: Returns device information for the test report. + +## Implementation Example + +The following example demonstrates a custom controller for a **Smart Light**, featuring type hinting, input validation, and fault-tolerant cleanup. + +### 1. Controller Module (`smart_light.py`) + +Save this code as `smart_light.py`. + +```python +"""Mobly controller module for a Smart Light.""" + +import logging +from typing import Any, Dict, List + +# The key used in the config file to identify this controller. +MOBLY_CONTROLLER_CONFIG_NAME = "SmartLight" + +class SmartLight: + """A class representing a smart light device. + + Attributes: + name: String, the name of the device. + ip: String, the IP address of the device. + """ + + def __init__(self, name: str, ip: str): + self.name = name + self.ip = ip + self.is_on = False + logging.info("Initialized SmartLight [%s] at %s", self.name, self.ip) + + def power_on(self): + """Turns the light on.""" + self.is_on = True + logging.info("SmartLight [%s] turned ON", self.name) + + def power_off(self): + """Turns the light off.""" + self.is_on = False + logging.info("SmartLight [%s] turned OFF", self.name) + + def close(self): + """Simulates closing the connection.""" + logging.info("SmartLight [%s] connection closed", self.name) + + +def create(configs: List[Dict[str, Any]]) -> List[SmartLight]: + """Creates SmartLight instances from a list of configurations. + + Args: + configs: A list of dicts, where each dict represents a configuration + for a SmartLight device. + + Returns: + A list of SmartLight objects. + + Raises: + ValueError: If a required configuration parameter is missing. + """ + devices = [] + for config in configs: + if "name" not in config or "ip" not in config: + raise ValueError( + f"Invalid config: {config}. 'name' and 'ip' are required." + ) + + devices.append(SmartLight( + name=config["name"], + ip=config["ip"] + )) + return devices + + +def destroy(objects: List[SmartLight]) -> None: + """Cleans up SmartLight instances. + + Args: + objects: A list of SmartLight objects to be destroyed. + """ + for light in objects: + try: + if light.is_on: + light.power_off() + light.close() + except Exception: + # Catching broad exceptions ensures that a failure in one device + # does not prevent others from being cleaned up. + logging.exception("Failed to clean up SmartLight [%s]", light.name) + + +def get_info(objects: List[SmartLight]) -> List[Dict[str, Any]]: + """Returns information for the test result. + + Args: + objects: A list of SmartLight objects. + + Returns: + A list of dicts containing device information. + """ + return [{"name": light.name, "ip": light.ip} for light in objects] + +``` + +### 2. Controller Module (`smart_light.py`) + +To use the custom controller, register it in your test script. + +```python +from mobly import base_test +from mobly import test_runner +import smart_light + +class LightTest(base_test.BaseTestClass): + def setup_class(self): + # Register the custom controller + self.lights = self.register_controller(smart_light) + + def test_turn_on(self): + light = self.lights[0] + light.power_on() + + # Verify the light is on + if not light.is_on: + raise signals.TestFailure(f"Light {light.name} should be on!") + +if __name__ == "__main__": + test_runner.main() +``` + +### 3. Configuration File (config.yaml) +Define the device in your configuration file using the SmartLight key. +```yaml +TestBeds: + - Name: BedroomTestBed + Controllers: + SmartLight: + - name: "BedLight" + ip: "192.168.1.50" +``` From 81dd83aaeea74524ddf0b290d0ecb663d671c84d Mon Sep 17 00:00:00 2001 From: Amy Tseng Date: Mon, 22 Dec 2025 16:05:16 +0800 Subject: [PATCH 3/3] Add missing 'is_on' attribute to SmartLight docstring and remove redundant type information from attributes docstring --- docs/tutorial_custom_controller.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/tutorial_custom_controller.md b/docs/tutorial_custom_controller.md index 5f38d7bd..10a72552 100644 --- a/docs/tutorial_custom_controller.md +++ b/docs/tutorial_custom_controller.md @@ -4,11 +4,16 @@ Mobly enables users to control custom hardware devices (e.g., smart lights, swit ## Controller Module Interface -A Mobly controller module must implement the following top-level functions: +A Mobly controller module needs to implement specific top-level functions to manage the device lifecycle. + +**Required functions:** * **`create(configs)`**: Instantiates controller objects from the configuration. * **`destroy(objects)`**: Cleans up resources when the test ends. -* **`get_info(objects)`**: Returns device information for the test report. + +**Optional functions:** + +* **`get_info(objects)`**: Returns device information for the test report. If not implemented, no controller information will be included in the result. ## Implementation Example @@ -31,8 +36,9 @@ class SmartLight: """A class representing a smart light device. Attributes: - name: String, the name of the device. - ip: String, the IP address of the device. + name: the name of the device. + ip: the IP address of the device. + is_on: True if the light is currently on, False otherwise. """ def __init__(self, name: str, ip: str):