Hooking into hooks

From Yombo
Jump to: navigation, search

See also

Overview

One of the most powerful features of the Yombo Gateway is the hook system. This feature allows modules to tightly integrate into the core of the framework. During various events or activities, the Yombo framework can call various hooks, which are just python functions. For example, during startup, a module can supply a list of automation rules, or when an state changes, the module can get notifications. Various hooks are implemented by the gateway libraries as system hooks (for a list visit [file:///home/mitch/Yombo/yombo-gateway/docs/build/html/chapters/hooks.html Hooks @ Yombo Python API reference]) Modules are also able to implement additional hooks of their or simple make use of the existing system hooks. An example use case of a module implementing additional hooks: An Insteon module can ask if any other modules have any capabilities of transmitting Insteon commands through a USB/Serial/Network interface (an interface module for instance).

Two types of hooks

  1. System hooks
  2. Module hooks

System Hooks

System hooks are called by various Yombo Gateway libraries and have a leading and trailing underscore(_). For example, before, the states library call “_states_set_” when a state changes value.

Module Hooks

Module hooks are implemented by modules and have a different naming convention. The standard is:

hook_modulename_some_action

The “hook” part of the name lets developers know to replace this portion with the name of their module (see example below). “modulename” is the name of the module calling the hook. “some_action” is whatever name the developer chooses to call the hook.

Usage

Using existing hooks (either type) is easy; just name a function in the name of the hook you wish to use. For example, to get updates of all State set requests:

1     def _states_set_(**kwargs):
2         """
3         Called by the states library whenever a state is being set (added / updated).
4  
5         :param kwargs: A dictionary of arguments. In this case: key and value
6         """
7         key_changed = kwargs['key']
8         new_value = kwargs['value']
9         logger.debug("My module received a notification of a state change: {key} = {value}", key=key_changed, value=new_value)

This example will log all changes to any state keys.

Implementing new hooks

We will look at the X10 API module. This module is responsible for listening for any requests to handle X10 device commands, as well as sending any status updates for device state changes. This module has required that any modules implementing an X10 interface (USB, Serial, network, etc) to return two things: 1) priority of interface, lower number is higher, and 2) a reference to the method to call for sending commands to X10 devices.  Note: Before this can be completed, you will need to include this line at the top of your module:from yombo.utils import global_invoke_all

 1         """
 2         Sets up the module to start processng X10 commands. After this function is complete, the X10 API module will
 3         be ready to accept commands.
 4 
 5         **Hooks implemented**:
 6 
 7         * hook_x10api_interfaces : Expects a dictionary back with "priority" and "callback" for any modules that
 8           can send X10 commands to the power lines. Since only one module can perform this task, the module with the
 9           highest priority (highest number) will be used. *callback* is the function to call to perform this request.
10 
11         :param kwargs:
12         :return:
13         """
14         results = global_invoke_all('x10api_interfaces')
15         temp = {}
16         for component_name, data in results.iteritems():
17             temp[data['priority']] = {'name': component_name, 'callback':data['callback']}
18 
19         interfaces = OrderedDict(sorted(temp.items()))
20         self.interface_callback = None
21         if len(interfaces) == 0:
22             logger.warn("X10 API - No X10 Interface module found, disabling X10 support.")
23         else:
24             # if this else doesn't complete, the interface will be disabled without a self.interface_callback
25             key = interfaces.keys()[-1]
26             self.interface_callback = temp[key]['callback']  # we can only have one interface, highest priority wins!!

This is a complex example, but it's great for demonstration. Here's a break down if what's happening:

  1. Ask all libraries and modules to run the hook "hook_x10api_interfaces". Any results will be collected and return to a dictionary.
  2. The results will contain a dictionary with the library or module name is the key, and any results will be the value of that key.
  3. We then sort the results to get the highest priority interface.
  4. We save the callback function to a local variable of the class.

On the flip side of this, lets look at a module that would respond to this request, the homevision module:

1         logger.debug("Registering Homevision with X10 API is priority 0 for X10 commands.")
2         return {'priority': 0, 'callback': self.x10api_send_command}

A breakdown of what is happening:

  1. A new method is defined with the proper hook name.  We replace "hook" with "homevision", the name of our module in lowercase.
  2. Return a dictionary with the priority of 0, and a reference to a function for the x10api module to call to send x10 commands to.