********************
Transforms Device
********************

.. index:: transforms, devices; transforms

Overview
====================

The transforms device, as the name implies, provides a means to
transform the content of channels.  Given a list of channels, a new
channel is created that is the result of a simple expression on the
input channels.

There are many possible uses for this type of functionality unit
conversion, input validation, creating terms that drive further logic,
filtering and more.

Configuration
====================

A typical transforms configuration block looks like this::

  - name: template_transforms
    driver: devices.transforms_device:TransformsDevice
    settings:
      - name: count_by_two
        channels:
          - template.counter
        expr: c[0] * 2

Settings
--------------------

The settings for the transforms device are a list of individual
transform specification blocks.  Each item in the list has the
following items.

name (required)
    The channel name that will be created by this transform.  The full
    name is the name of the driver instance declaration a period and
    the name of the individual transform.
    ex. ``template.transforms.count_by_two`` in the example given
    above.

channels (required)
    A list of channels from the channel database that will be the
    inputs to the ``expr`` item.

expr (required)
    A single Python expression.  The value of the expression will be
    the value of the channel that is created.  This expression will be
    evaluated every time one of the channels listed by the ``channels``
    item is published to the channel database.

    The expression may make use of Python built-in functions to
    compute its value. The values from the ``channels`` list are
    provided to the expression in a list ``c``.  The order of elements
    of ``c`` are retained in the order listed in the configuration file.

How the expression works
------------------------------

The expression is evaluated with the python function call "eval".  Eval
works by taking a string representing a valid python statement, with a
dictionary as input, and returning the result of that statement.  Using
the code snippet presented above, the command looks like:

eval("c[0] * 2", {c: [template.counter]})

The output of the statement will produce a value that is twice the value
of the input, template.counter.

How to test the expression
----------------------------

The expression can be tested in a test environment without using the Dia.
To test this, the user can use a python interpreter session to perform 
the same evaluations::

    def my_eval(s, inputs):
     d = {"c":[x for x in inputs]}
     return eval(s, d)
 
    print my_eval("c[0] + 123", [1])

  
    
The above is the equivalent to the following yaml::

  - name: transforms
    driver: devices.transforms_device:TransformsDevice
    settings:
      - name: add_123
        channels:
          - template.counter
        expr: c[0] + 123

When the input template.counter is equal to integer 1.

Another example::

    print my_eval("c[0] * 1.8 + 32", [50])

This would be the equivalent to the following yaml::

  - name: transforms
    driver: devices.transforms_device:TransformsDevice
    settings:
      - name: C_to_F
        channels:
          - input.celsius
        expr: c[0] + * 1.8 + 32


Advanced logic examples:
--------------------------

Because the eval() python function is used, more complicated, compound
statements can be made.  For instance, a branching statement can be used::

    "c[0] > 10 and c[0] or "Not valid""

The above compares the input to integer 10, and if greater than uses 
the input value, otherwise substitutes 0.  If the input was 15, the 
output is 15, if the input 7, then the output is "Not valid".

This behavior can be useful if the user wants to filter the input based 
on a measurable condition, and give either the value, or some 
identifiable error condition::

    "c[0] == True and 'Conditional is True' or 'Conditional is False'"

The above evaluates if the input is equal to True, and sets the output 
to "Conditional is True".  If the comparison is False, it returns 
"Conditional is False".

Multiple input examples:
----------------------------

In some circumstances the user wants to create a transform that has 
multiple inputs to create a single output.  Each input is assigned an 
index of c, so for example, the following yaml::

  - name: transforms
    driver: devices.transforms_device:TransformsDevice
    settings:
      - name: add_vals
        channels:
          - input.val1
          - input.val2
        expr: c[0] + c[1]


The above adds the two inputs together, with input.val1 being represented 
by c[0] and input.val2 being represented by c[1].  Additional inputs would
be represented by c[2], c[3], etc. in the order they are defined in the 
channels setting.

Multiple transforms in a driver:
-----------------------------------

Multiple transforms can be used in a single driver::

  - name: transforms
    driver: devices.transforms_device:TransformsDevice
    settings:
      - name: double
        channels:
          - template.counter          
        expr: c[0] * 2
        
      - name: triple
        channels:
          - template.counter          
        expr: c[0] * 3

      
The above yaml defines two output channels for the Transform device.

Creating chains of transforms:
---------------------------------

In some circumstances, its necessary to chain together transforms to 
aggregate data, or produce a summary or status line of the current system.
Transform drivers are flexible, and can be used to reference other channels
produced by the same driver::


  - name: t1
    driver: devices.transforms_device:TransformsDevice
    settings:
      - name: double
        channels:
          - template.counter
        expr: c[0] * 2
        
      - name: double_double
        channels:
          - t1.double
        expr: c[0] * 2
        

The above demonstrates a transform that doubles templace.counter, and will
then in turn double that output, resulting in a value that is 4 times
template.counter in the channel t1.double_double.

A more useful transform driver could aggregate the information, for use in
a presentation that pushes data, like the TCPCSV presentation::

  - name: t1
    driver: devices.transforms_device:TransformsDevice
    settings:
      - name: double
        channels:
          - template.counter          
        expr: c[0] * 2
        
      - name: triple
        channels:
          - template.counter          
        expr: c[0] * 3
        
      - name: csv_out
        channels:
          - t1.double
          - t1.triple
        expr: "','.join(str(x) for x in  c)"


The above shows how to comma separate the input channels into a single 
output channel that can be easily extended to include other inputs.  

.. note::  When using quotes in the expression, make sure to use single quotes, 
	   and surround them with double quotes in the yaml.
