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)
orcmd_label
) or be provided using a link.None
for a signal that never endsA 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
: constantvalue
: Whatever constantint
orfloat
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
: rampcondition
: 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
: sinefreq
: the frequency of the signalamplitude
: the amplitude of the signaloffset
: 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 rampcondition1
: the condition to reach to stop the first ramp.speed1
: the slope of the first rampcondition2
: the condition to reach to stop the second ramp.speed2
: the slope of the second rampcycles
: 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
: customfilename
: 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
ifTrue
, 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 generateSignal2
andSignal3
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
andSignal3
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`)
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()