Scenarios ========= Mirage provides a simple way to modify the behaviour of a complex module. Indeed, some modules (such as :doc:`ble_mitm `, :doc:`ble_master ` or :doc:`ble_slave ` ) provides some complex behaviours and can be easily extended thanks to the scenarios. If a module provides a *SCENARIO* input parameter, it allows to modify its behaviour using a scenario. The module associates some **signals** to some specific methods, allowing the user to catch this signal and execute an alternative code when it is triggered. Writing a scenario ------------------- Mirage provides a command allowing to quickly generates a basic scenario : ``create_scenario`` This command can be called from the :doc:`Command Line Interface ` : :: $ mirage ~~> create_scenario [QUESTION] Scenario's name : test [SUCCESS] Scenario test successfully generated : /home/user/.mirage/scenarios/test.py Or it can be used with the direct execution mode : :: $ mirage --create_scenario [QUESTION] Scenario's name : test [SUCCESS] Scenario test successfully generated : /home/user/.mirage/scenarios/test.py It generates the following code : :: from mirage.core import scenario from mirage.libs import io,ble,bt,utils class test(scenario.Scenario): def onStart(self): return True def onEnd(self): return True def onKey(self,key): return True As you can see, three methods are provided : * ``onStart`` is called when the module execution starts * ``onEnd`` is called when the module execution stops * ``onKey`` is called when a key is pressed, the provided parameter is a string indicating the key These three methods are directly managed by Mirage, they are common to any modules allowing to use scenarios. However, some specific signals can be generated by the module. These signals and the associated parameters are described in the **Signals** section of the module's documentation. Every signal is associated to a module's method executing the default behaviour. In the following module source code, the signal *helloworld* is associated to the method ``sayHello`` : :: @module.scenarioSignal("helloworld") def sayHello(self,name): io.info("Default behaviour : hello, "+name) When this method will be called, the associated signal will be triggered, and can be caught in a scenario. If you want to customize the default behaviour of this ``sayHello`` method, you can add the following method to the scenario : :: def helloworld(self,name): io.info("Custom behaviour : goodbye, "+name) If this method returns nothing or returns True, the default behaviour will be executed right after the scenario's method execution. If the method returns False, only the scenario's method ``helloworld`` is executed. .. image:: scenarios.png :align: center You can get the module instance in a scenario by using the attribute ``module`` : :: def helloworld(self,name): emitter = self.module.emitter receiver = self.module.receiver io.info("Custom behaviour : goodbye, "+name) If you want to use a scenario with the module named ``test_module``, just provide your scenario's name as the *SCENARIO* input parameter of the module : :: sudo mirage test_module SCENARIO=test Integrating scenarios in a module ---------------------------------- If you develop your own module, it's quite easy to allow users to use scenarios in order to customize your module's behaviour. First of all, you need to add an input parameter *SCENARIO*, that will be used to provide a scenario name : :: self.args = { [...] "SCENARIO":""} If this input parameter contains an empty string, it means that your module should use its default behaviour, but if a name is provided the corresponding scenario should be loaded. Write the following piece of code at the beginning of your ``run`` method : :: if self.loadScenario(): io.info("Scenario loaded !") self.startScenario() [...] The `loadScenario `_ method checks if a scenario has been provided in the *SCENARIO* input parameter. It sets the boolean attribute ``scenarioEnabled`` to True if needed. This boolean allows you to easily check if a scenario is enabled, it can be really useful for ending the scenario execution. At the end of the code of ``run``, you can use it in combination with the method `endScenario `_ : :: [...] if self.scenarioEnabled: self.endScenario() By default, your module provides three main signals : * ``onStart``, triggered when the method ``startScenario`` is called. * ``onEnd``, triggered when the method ``endScenario`` is called. * ``onKey``, triggered when a key is pressed (there is a single parameter, *key*, which is a string indicating what key has been pressed However, you probably want to generate your own signals in order to allow your user to modify your module's behavour. In mirage, it can be done by adding the decorator ``@module.scenarioSignal(signalName)`` above one of your methods. For example, I have the following module : :: from mirage.core import module from mirage.libs import utils,ble class test_module(module.WirelessModule): def init(self): self.technology = "ble" self.type = "test" self.description = "Test module" self.args = { 'INTERFACE':'hci0', 'PARAM1':'value1', 'SCENARIO':'' } self.dependencies = ["ble_sniff","ble_scan"] @module.scenarioSignal("onHelloWorld") def hello(self,name): io.info("Hello, "+name) def run(self): if self.loadScenario(): io.info("Scenario loaded !") self.startScenario() for _ in range(5): utils.wait(seconds=1) self.hello("Romain") if self.scenarioEnabled: self.endScenario() return self.ok({}) As you can see, the ``hello`` method (using one parameter, *name*) has been associated to the signal ``onHelloWorld``. When the method ``hello`` is called, the signal ``onHelloWorld`` is triggered. If a scenario has been provided, Mirage tries to find a method called ``onHelloWorld``, and executes it if it is found (the parameters are also passed to the callback method). If this method exists and returns *False*, the normal behaviour implemented in the ``hello`` method is ignored, while it is executed normally if the scenario method returns *True*.