======================================
How to generate a signal with CRAPPY ?
======================================

As in every new :ref:`CRAPPY<What is Crappy ?>` project, the first thing to do
is to import it. So the first line of code will simply be :
::

   import crappy

Then it's up to the user to determine what the program should do. Here we'll
simply generate a signal, that we'll be able to send as a command or to plot
afterwards.

So first let's choose what kind of signal we want.

Choose a signal
---------------

There are 8 different types of signal, all described in the
:ref:`generator path` folder.

.. note::
  Every signal must be a dictionary providing the parameters to generate it.

Dictionary :
  A dictionary consists in a collection of key-value pairs. Each key-value pair
  maps the key to its associated value. Here is the syntax :
  ::

    d = {<key>: <value>, <key>: <value>, ..., <key>: <value>}

  The order does not matter as each value is only associated with its key.

.. note::
  Each dictionary in the signal generator **MUST** have a ``type`` key.

  Every non cyclic path **MUST** have a ``condition`` key. (Cyclic paths
  **MUST** have ``condition1`` and ``condition2``.)

A condition can be :

- A delay (``str``, in seconds): ``'delay=x'``
- A condition on a label (``str``): ``'label<x'`` or ``'label>x'``. The label
  can be internal (``t(s)`` or ``cmd_label``) or be provided using a link.
- ``None`` for a signal that never ends
- A personalized condition: a function taking the dict of current labels as an
  argument and returning ``True`` if the path ends.

Here are described the 4 most common types of signal :

1. Constant signals
+++++++++++++++++++

The simplest signal, it only has 3 keys :

- ``type``: :ref:`constant`
- ``value``: Whatever constant ``int`` or ``float`` value.
- ``condition``: The condition that will indicate the end of the signal. For
  example a position to reach or a time to wait are commonly used conditions.

Example :
  To get a constant signal sending the value 5 for 10 seconds, one should
  write :
  ::

    Signal1 = {'type': 'constant', 'value': 5, 'condition': 'delay=10'}

2. Ramp signals
+++++++++++++++

An other quite simple signal that just has 4 keys :

- ``type``: :ref:`ramp`
- ``condition``: same as the :ref:`constant signal<1. Constant signals>`
  condition, it will indicate the end of the signal when it's reached.
- ``speed``: the slope of the ramp, in units per second.
- ``cmd``: the starting value of the ramp. `Optional key`. (If not specified,
  the starting value will be the previous value.)

Example :
  To get a ramp signal increasing by 2 (mm/s for example) until it reaches 30
  (mm), one should write :
  ::

    Signal2 = {'type': 'ramp', 'speed': 2, 'condition': 'x(mm)>30')

.. note::

  Of course ``x(mm)`` must be a label containing the real-time position of
  whatever we are controlling. It should be provided to the Generator block
  through a link.

3. Sine signals
+++++++++++++++

Now a sine signal, that has 6 keys :

- ``type``: :ref:`sine`
- ``freq``: the frequency of the signal
- ``amplitude``: the amplitude of the signal
- ``offset``: adds an offset to the signal, the default offset is 0.
  `Optional key`.
- ``phase``: adds a pahse to the signal, in unit of radians. The default phase
  is 0. `Optional key`.
- ``condition``: same as the :ref:`constant signal<1. Constant signals>`
  condition, it will indicate the end of the signal when it's reached.

Example :
  To get a sine with a frequency of 0.5, an amplitude of 2, an offset of 1 and
  that stops after 25 seconds, one should write:
  ::

    Signal3 = {'type': 'sine', 'freq': .5, 'amplitude': 2, 'offset': 1,
    'condition': 'delay=25'}

  Now to get a cosine, with the same parameters as the ``Signal3``, then one
  should write:
  ::

    from math import pi
  
    Signal4 = {'type': 'sine', 'freq': .5, 'phase': pi/2, 'amplitude': 2,
    'offset': 1, 'condition': 'delay=25'}

.. note::
  The number pi first has to be imported from the python module ``math``.

4. Cyclic ramp signals
++++++++++++++++++++++

This type of signal is simply the combination of two :ref:`ramp`, with the
possibility to repeat them. So we've already detailed
:ref:`how it works<2. Ramp signals>`!

It has 6 keys :

- ``type``: :ref:`cyclic ramp`
- ``condition1``: the condition to reach to stop the first ramp.
- ``speed1``: the slope of the first ramp
- ``condition2``: the condition to reach to stop the second ramp.
- ``speed2``: the slope of the second ramp
- ``cycles``: number of repetitions of the two ramps. Can be 1. If 0, it will
  loop forever.

Example :
  To get a signal that goes up at a speed of 0.1 (mm/s) until it reach 5 (mm),
  then goes down to 2 (mm) at a speed of 0.1 (mm/s), and is repeated 3 times,
  one should writev:
  ::

    Signal5 = {'type': 'cyclic_ramp', 'condition1': 'x(mm)>5',
    'speed1': 0.1, 'condition2': 'x(mm)<2', 'speed2': -0.1, 'cycles': 3}

Apart from these 4 main types of signals, there's another one that can prove
very useful.

5. Custom signals
+++++++++++++++++

This type allows to import any signal from a .csv file (hence the name
`custom`).

It only has 2 key:

- ``type``: :ref:`custom`
- ``filename``: the path of the ``.csv`` file.

.. warning::

  The file must contain 2 columns: The first one with the time, and the second
  one with the value to send.

.. note::

  It will try to send at the right time every timestamp with the associated
  value.

Example :
  Really needed ?
  ::

    Signal6 = {'type': 'custom', 'filename': 'my_custom_signal.csv'}

One the signal has been created, it's ready to be generated using a
:ref:`Generator` crappy block.

Generate a signal
-----------------

Creating a :ref:`Generator` is as simple as that :
::

  OurGenerator = crappy.blocks.Generator([Signalx])

.. note::

  The :ref:`Generator` class is a block, so it's located in the folder
  :ref:`blocks<Blocks>` which is in :ref:`crappy<What is Crappy ?>`:
  ``crappy.blocks.[...]``

    ``Signalx`` can be replaced with the name of a signal that's already been
    created, or directly with the explicit dictionary of the signal to be
    generated.

And here it is! Actually, that's not all. A :ref:`Generator` block in crappy
must contain a list of dictionaries (hence the list: ``[]``).

Great, other signals can be added!
::

  OurGenerator = crappy.blocks.Generator([Signal1, Signal2, Signal3, Signal4,
  Signal5])

.. note::

  Once the end of a signal has been reached, the next one in the list begins
  immediately. Once the end of the list have been reached, the :ref:`Generator`
  stops the program.

Several options also allow to precise how the :ref:`Generator` should work :

- ``cmd_label`` renames the output signal. The default name is 'cmd'. This
  feature is mostly useful when the program contains several Generators.
- ``freq`` imposes the generator output frequency. The Generator will output
  commands at the given frequency even if that implies missing signal points.
- ``repeat`` if ``True``, the generator loops endlessly on the list and never
  ends the program.

Example :
  To generate ``Signal1`` at 500 points per second and name it 's1', and also
  generate ``Signal2`` and ``Signal3`` without imposing a frequency and name it
  's2', one should write:
  ::

    OurGenerator1 = crappy.blocks.Generator([{'type': 'constant',
    'value': 5, 'condition': 'delay=10'}], cmd_label='s1', freq=500)

    OurGenerator2 = crappy.blocks.Generator([Signal2, Signal3], cmd_label='s2')

As simple as that ! Now let's try plotting the signals.

Plot a signal
-------------

To do so, first create a :ref:`Grapher` crappy block :
::

  crappy.blocks.Grapher((`Here everything that should be plotted on the graph`),
  Here the graph settings`)

Example :
  To plot ``Signal1``, ``Signal2`` and ``Signal3`` at a frequency of 2 points
  per second on the same graph, and Signal1 only at a frequency of 10 points per
  second on another graph, one should write :
  ::

    Graph1 = crappy.blocks.Grapher(('t(s)', 's1'), ('t(s)', 's2'), freq=2)

    Graph2 = crappy.blocks.Grapher(('t(s)', 's1'), freq=10)

.. note::
  Of course it won't work if all the signals haven't been generated before.

Finally, the last step is to link the :ref:`Generator<Generate a signal>` block
with the :ref:`Grapher<Plot a signal>` block :
::

  crappy.link(`name_of_the_Generator`, `name_of_the_Grapher`)

.. note:: 

  For each signal to be plotted, the associated :ref:`Generator` should be
  linked to the :ref:`Grapher`.

Code Example
------------

::

  import crappy

  # First: a constant value (2) for 5 seconds
  path1 = {'type':'constant','value':2,'condition':'delay=5'}
  # Second: a sine wave of amplitude 1, freq 1Hz for 5 seconds
  path2 = {'type':'sine','amplitude':1,'freq':1,'condition':'delay=5'}
  # Third: A ramp rising a 1unit/s until the command reaches 10
  path3 = {'type':'ramp','speed':1,'condition':'cmd>10'}
  # Fourth: cycles of ramps going down at 1u/s until cmd is <9
  # then going up at 2u/s for 1s. Repeat 5 times
  path4 = {'type':'cyclic_ramp','speed1':-1,'condition1':'cmd<9',
      'speed2':2,'condition2':'delay=1','cycles':5}

  # The generator: takes the list of all the paths to be generated
  # cmd_label specifies the name to give to the signal
  # freq : the target frequency in points/s
  # spam : Send the value even if it's identical to the previous one
  #   (so that the graph updates continuously)
  # verbose : display some information in the terminal
  gen = crappy.blocks.Generator([path1,path2,path3,path4],
      cmd_label='cmd',freq=50,spam=True,verbose=True)

  # The graph : we will plot cmd vs time
  graph = crappy.blocks.Grapher(('t(s)','cmd'))

  # Do not forget to link them or the graph won't be able to plot anything !
  crappy.link(gen,graph)

  # Let's start the program
  crappy.start()

Another example
---------------

::

  import crappy
  # In this example, we would like to reach different levels of strain
  # and relax the sample (return to F=0) between each strain level

  speed = 5/60 # mm/sec

  path = [] # The list in which we'll put the paths to be followed

  # We will loop over the values we would like to reach
  # And add two paths for each loop: one for loading and one for unloading
  for exx in [.25,.5,.75,1.,1.5,2]:
    path.append({'type':'constant',
      'value':speed,
      'condition':'Exx(%)>{}'.format(exx)}) # Go up to this level
    path.append({'type':'constant',
      'value':-speed,
      'condition':'F(N)<0'}) # Go down to F=0N

  # Now we can simply give our list of paths as an argument to the generator
  generator = crappy.blocks.Generator(path=path)

  # This block will simulate a tensile test machine
  machine = crappy.blocks.Fake_machine()
  # The generator must be linked to the machine in order to control it
  crappy.link(generator,machine)
  # And the machine must be linked to the generator because we added
  # conditions on force and strain, so the generator needs to access these
  # values coming out of the machine
  # Remember : links are one way only !
  crappy.link(machine,generator)

  # Let's add two graphs to visualise in real time
  graph_def = crappy.blocks.Grapher(('t(s)','Exx(%)'))
  crappy.link(machine,graph_def)

  graph_f = crappy.blocks.Grapher(('t(s)','F(N)'))
  crappy.link(machine,graph_f)

  # And start the test
  crappy.start()
