In this notebook we will show how you can write simple motions thanks to primitive and how you can easily combine them into more complex behaviors.
We will create a robot for the rest of this tutorial. Here again you can use a real or simulated version.
In [1]:
import os
import poppytools
from pypot.robot import from_json
config_path = os.path.join(os.path.dirname(poppytools.__file__), 'configuration', 'poppy_config.json')
robot = from_json(config_path)
robot.start_sync()
In pypot, a primitive is basically a thread that have acess (both read and write) to the different motors and sensors of a robot.
You write them in the exact same way as you would write if you want to directly control your robot. For instance, using the sinus written in this notebook:
In [2]:
%pylab inline
In [3]:
amp = 30 # in degrees
freq = 0.3 # in Hz
duration = 10 # in s
# Our sinus will be send at 50Hz to match the refresh frequency.
# This is not at all mandatory, yet sending at a higher frequency will have no impact.
step = 1 / 50.
N = duration / step
t = linspace(0, duration, N)
pos = amp * sin(2 * pi * freq * t)
import time
for p in pos:
robot.head_z.goal_position = p
time.sleep(step)
You can easily make it a primitive:
In [4]:
from pypot.primitive import Primitive
class MySinusPrimitive(Primitive):
def run(self):
amp = 30 # in degrees
freq = 0.3 # in Hz
duration = 10 # in s
# Our sinus will be send at 50Hz to match the refresh frequency.
# This is not at all mandatory, yet sending at a higher frequency will have no impact.
step = 1 / 50.
N = duration / step
t = linspace(0, duration, N)
pos = amp * sin(2 * pi * freq * t)
for p in pos:
self.robot.head_z.goal_position = p
time.sleep(step)
Basically all you had to do was to wrapped the code in the run method of your primitive and change all the robot by self.robot.
You should be able to run your primitive juste by doing:
In [5]:
prim = MySinusPrimitive(robot)
prim.start()
And wait for it to end.
In [6]:
prim.wait_to_stop()
You can restart it as many times as you want. This will run the primitives 3 times:
In [7]:
for _ in range(3):
prim.start()
prim.wait_to_stop()
So what's the point? Well, primitive can be easily combined.
Let's re-write this primitve so you can choose the amplitude, duration, and frequency of your sinus at instantiation.
In [8]:
class MySinusPrimitive(Primitive):
def __init__(self, robot, freq, amp, duration):
Primitive.__init__(self, robot)
self.freq = freq
self.amp = amp
self.duration = duration
def run(self):
# Our sinus will be send at 50Hz to match the refresh frequency.
# This is not at all mandatory, yet sending at a higher frequency will have no impact.
step = 1 / 50.
N = self.duration / step
t = linspace(0, self.duration, N)
pos = self.amp * sin(2 * pi * self.freq * t)
for p in pos:
self.robot.head_z.goal_position = p
time.sleep(step)
Now we can create two sinuses with different settings:
In [9]:
s1 = MySinusPrimitive(robot, 0.25, 20., 10.)
s2 = MySinusPrimitive(robot, 1., 5., 20.)
You can run one at a time:
In [10]:
s1.start()
s1.wait_to_stop()
In [11]:
s2.start()
s2.wait_to_stop()
Or both in parallel:
In [12]:
s1.start()
s2.start()
s1.wait_to_stop()
s2.wait_to_stop()
You should observe that the motion actually made by the motor was the mean of the two sinuses. Ssomething like this:
Primitives are automatically combined through a PrimitiveManager. You can defined the way they are combined through a filter (which is numpy.mean by default). In future versions, we hope to provide users with more control on how primitives could be combined: e.g. using primitives hierarchy, having special filter per motors... (See this issue on our github for details).
Primitives come in two flavors:
LoopPrimitive are just Primitive with a predifined run that calls an update method at a predefined frequency.
For instance, we could re-write our sinus as a LoopPrimitive:
In [13]:
from pypot.primitive import LoopPrimitive
class MySinusLoopPrimitive(LoopPrimitive):
def __init__(self, robot, update_freq,
sin_freq, amp, duration):
LoopPrimitive.__init__(self, robot, update_freq)
self.freq = sin_freq
self.amp = amp
self.duration = duration
def update(self):
t = self.elapsed_time
p = self.amp * sin(2 * pi * self.freq * t)
self.robot.head_z.goal_position = p
Note that LoopPrimitive will run until stopped.
In [14]:
ls1 = MySinusLoopPrimitive(robot, 50.0, 0.25, 20., 10.)
ls1.start()
And stop it whenever you want.
In [15]:
ls1.stop()
You can also pause and resume primitives.
In [16]:
ls1.start()
time.sleep(5)
ls1.pause()
time.sleep(5)
ls1.resume()
time.sleep(5)
ls1.stop()
The code above will result in a motion looking like this:
Primitive are also useful for other things that motion. For instance, it's a very simple way to write a recorder. All the figures above were actually created using this recorder:
In [17]:
class MyRecorder(LoopPrimitive):
def setup(self):
self.pos = []
def update(self):
self.pos.append(self.robot.head_z.present_position)
def plot(self, ax):
t = linspace(0, self.elapsed_time, len(self.pos))
ax.plot(t, self.pos)
And using it like this:
In [18]:
r = MyRecorder(robot, 10.)
r.start()
s1.start()
s2.start()
s1.wait_to_stop()
s2.wait_to_stop()
r.stop()
ax = axes()
r.plot(ax)
In [ ]: