How to generate a signal with CRAPPY ?

As in every new 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 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: 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: ramp

  • condition: same as the constant signal 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: 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 constant signal 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 ramp, with the possibility to repeat them. So we’ve already detailed how it works!

It has 6 keys :

  • type: 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: 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 Generator crappy block.

Generate a signal

Creating a Generator is as simple as that :

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

Note

The Generator class is a block, so it’s located in the folder blocks which is in 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 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 Generator stops the program.

Several options also allow to precise how the 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 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 Generator block with the Grapher block :

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

Note

For each signal to be plotted, the associated Generator should be linked to the 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()