Tutorial: Signals

Signals and Slots

Signals and Slots is an eventing mechanism used by many of the available modules to notify the application of events and the data associated with that event (if applicable). The premise is that a signal is a connection point that generates events and data, and a slot is a connection point that receives events and data. The application is then responsible for connecting together various signals and slot in a many-to-many configuration (a slot may connect to multiple signals and a signal may have multiple connections) to achieve the desired result. In the API documentation, signals are simply referred to as an 'event'.

A 'slot' may either be a JavaScript function (a.k.a. method when it's part of an object), or a method on a module object that is capable of being a slot. Most module methods are slots unless there is a technical reason for one not to be, however in general does not make sense to use such methods as slots anyway due to the input data requirements or the behaviour of the method.

Slots in JavaScript

The most common connection to a signal is a JavaScript method. This allows the application to deal with the event in the most flexible way. A function or method may be connected to a signal using the connect method of the signal. For example, to handle a change event (miob.Input#event:stateChanged) on an input:

var hw = $wat.load('hw')[0];
hw.digInputs[0].stateChanged.connect(onInputChanged);

This will call 'onInputChanged' whenever the input changes state. As indicated in the documentation, stateChanged emits one parameter as part of the event, a bool indicating the new state. The application can then choose to handle this by adding a parameter to the slot definition:

function onInputChanged(newState) {
     console.log('The new state of the input is: ' + newState);
}

This example generates a message in the debug console every time an input changes.

If a function is part of an object (a method), then the object may also be specified in the connect call as the first argument. In this case, the 'this' pointer will be properly set up when the method is called:

function inputHandler(inputNumber) {
  this.num = inputNumber;
  this.handleChange = function(newState) {
    console.log('Input ' + this.num + ' has changed to ' + newState);
  };
  var hw = $wat.load('hw')[0];
  hw.digInputs[this.num].stateChanged.connect(this, this.handleChange);
}
var handler = new inputHandler(4);

The method notation of connecting may be used with a function in order to pass extra information to a function callback. The object passed into the connect call will be accessible using 'this' in the function during the callback. This is an easy way to provide extra information, but care must be taken not to call the function directly, as 'this' would refer to something else.

// do not call directly, only use as a callback
function genericCallback(newState) {
     console.log('Input ' + this.num + ' has changed to ' + newState);
};
var hw = $wat.load('hw')[0];
hw.digInputs[6].stateChanged.connect({num: 6}, genericCallback);

Slots in Modules

Connecting signals directly to methods in other module objects tends to be less flexible as the data cannot be manipulated before the slot receives it. However, if a connection can be made that properly does the job, making a direct connection can make the code clean and efficient.

Most methods in modules can also be slots and thus be connected to signals. However, since the modules are strongly typed, care must be taken to ensure that method is compatible with the signal. To be compatibale, a method must have an equal number or less arguments than the signal, and the order and type of the arguments that the method does have must be identical to the signal. If the two signatures do not match, then a run time error will be generated when connect is called and the connect will fail. Here are some examples of valid and invalid signatures:

// the signal signature
someSignal(string, number, string)
// valid slot signatures
slot1(string, number, string)
slot2(string, number)
slot3(string)
slot4()
// invalid slot signatures
slot5(number)
slot6(string, string)
slot7(string, number, string, number)

Check the documentation for the event and method to determine the signature.

Here are some examples of using direct connections between module objects (hw is the 'hw' plugin object and eip is the 'eip' plugin object with a memory map loaded):

// make an output reflect an input
hw.digInput[0].stateChanged.connect(hw.digOutput[0], hw.digOutput[0].setState);
// make a latch from 2 inputs and an output
hw.digInput[0].risingEdge.connect(hw.digOutput[0], hw.digOutput[0].set);
hw.digInput[1].risingEdge.connect(hw.digOutput[0], hw.digOutput[0].clear);
// by default, edges are not tracked, so enable
hw.digInputs[0].edgeMask = hw.digInputs[1].edgeMask = hw.digInputs[0].Edge.Rising;
// bind a variable in the memory map to the inputs (so it always reflects the current state of the inputs)
// DeviceInputs is defined in the memory map as a U16
hw.inputs.stateChanged.connect(eip.input.DeviceInputs, eip.input.DeviceInputs.setValue);
eip.input.DeviceInputs.value = hw.inputs.state;