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.
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.Optional functions:
get_info(objects): Returns device information for the test report. If not implemented, no controller information will be included in the result.The following example demonstrates a custom controller for a Smart Light, featuring type hinting, input validation, and fault-tolerant cleanup.
smart_light.py)Save this code as smart_light.py.
"""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: 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): 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]
smart_light.py)To use the custom controller, register it in your test script.
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()
Define the device in your configuration file using the SmartLight key.
TestBeds: - Name: BedroomTestBed Controllers: SmartLight: - name: "BedLight" ip: "192.168.1.50"