Tutorial: Creating expressions with LEDs¶
🏁 Goal of this tutorial
By the end of this tutorial, you will know how to request different type of LEDs effects using the LEDs devices available on your robot. Additionally, you will see an example of how LEDs can be used for expressiveness purposes.
Pre-requisites¶
Make sure you are familiar with the robot’s LEDs API
Creating LED effects from Python¶
The script leds.py
, below, is a simple ROS 2 node that demonstrates
several LEDs effects on a ARI robot, to showcase how LEDs can be used to enhance
the robot expressiveness.
Note
Similar effects can be achieved on the other PAL robots; only the
devices
setting needs to be changed. Refer to LEDs API for the list
of available LEDs devices on your robot.
Important
The script is meant to be run either from a Docker image configured on the same ROS 2 network as your robot, or directly on the robot (using a SSH connection).
1import rclpy
2from rclpy.node import Node
3from rclpy.action import ActionClient
4from pal_device_msgs.action import DoTimedLedEffect
5from std_msgs.msg import ColorRGBA
6from builtin_interfaces.msg import Duration
7import numpy as np
8import math
9import time
10
11# LED devices IDs
12BACK = 0
13LEFT_EAR = 1
14RIGHT_EAR = 2
15RESPEAKER = 4
16
17MAX_PRIORITY = 255 # The maximum value for leds effect priority
18
19
20def to_duration_msg(d):
21 """ Converts floating point duration into ROS duration message. """
22 nano, sec = math.modf(d)
23 return Duration(sec=int(sec), nanosec=int(nano * 1e9))
24
25
26class LEDClient(Node):
27 def __init__(self):
28 super().__init__('led_client')
29
30 self._leds = ActionClient(
31 self, DoTimedLedEffect, '/led_manager_node/do_effect')
32 while not self._leds.wait_for_server(timeout_sec=1.0):
33 self.get_logger().info("waiting for the LED manager...")
34
35 def fixed_color(self, devices, color, duration=5.):
36 """
37 Sets the LEDs to a fixed color.
38 """
39 goal = DoTimedLedEffect.Goal()
40 goal.devices = devices
41 goal.params.effect_type = goal.params.FIXED_COLOR
42
43 goal.params.fixed_color.color = color
44
45 goal.effect_duration = to_duration_msg(duration)
46 goal.priority = MAX_PRIORITY
47
48 self.get_logger().info(
49 f"Setting a fixed color: {color} for {duration}s")
50 self._leds.send_goal_async(goal)
51 time.sleep(duration)
52
53 def progress_bar(self, devices):
54 """
55 Uses the LEDs to display a progress bar.
56 """
57 goal = DoTimedLedEffect.Goal()
58 goal.devices = devices # The devices performing the effect
59 goal.params.effect_type = goal.params.PROGRESS # The type of effect
60
61 # Setting values for the two progress
62 # color components
63 # first color --> complete
64 # second color --> uncomplete
65 blue = ColorRGBA(r=0., g=0., b=1., a=1.)
66 goal.params.progress.first_color = blue
67
68 red = ColorRGBA(r=1., g=0., b=0., a=1.)
69 goal.params.progress.second_color = red
70
71 # led offset set to 0. Progression
72 # will be displayed starting from
73 # the back led, counterclockwise
74 goal.params.progress.led_offset = 0.
75
76 goal.effect_duration = to_duration_msg(0.2)
77 goal.priority = MAX_PRIORITY
78
79 self.get_logger().info(f"iDisplaying a progress bar")
80
81 # Increment by 1% every 0.2 seconds.
82 for percentage in np.arange(0, 1.01, 0.01):
83
84 goal.params.progress.percentage = percentage
85 self._leds.send_goal_async(goal)
86
87 self.get_logger().info(f"Task completion: {percentage * 100}%")
88
89 # Sleeping slightly less then the effect duration
90 # to always have the next completion goal
91 # queuing when the previous one execution finishes
92 time.sleep(0.19)
93
94 def rainbow(self, devices, transition_duration=1, duration=6.):
95 """
96 Sets up and sends a rainbow effect
97
98 (that is, continous transition over the rainbow colours).
99 """
100
101 goal = DoTimedLedEffect.Goal()
102 goal.devices = devices
103 goal.params.effect_type = goal.params.RAINBOW
104
105 # transition_duration --> the time the effect will take to complete an
106 # iteration over the rainbow colours
107 goal.params.rainbow.transition_duration = to_duration_msg(transition_duration)
108
109 goal.effect_duration = to_duration_msg(duration)
110 goal.priority = MAX_PRIORITY
111
112 self.get_logger().info(f"Starting a rainbow")
113 self._leds.send_goal_async(goal)
114 time.sleep(duration)
115
116 def blinking(self, devices, color1, duration1, color2, duration2, total_duration=5.):
117 """
118 Sets up and sends blinking color leds effect.
119
120 It alternates two colors with custom temporisation.
121 """
122
123 goal = DoTimedLedEffect.Goal()
124 goal.devices = devices
125 goal.params.effect_type = goal.params.BLINK
126
127 goal.params.blink.first_color = color1
128 goal.params.blink.first_color_duration = to_duration_msg(duration1)
129
130 goal.params.blink.second_color = color2
131 goal.params.blink.second_color_duration = to_duration_msg(duration2)
132
133 goal.effect_duration = to_duration_msg(total_duration)
134 goal.priority = MAX_PRIORITY
135
136 self.get_logger().info(f"Start blinking")
137 self._leds.send_goal_async(goal)
138 time.sleep(total_duration)
139
140
141def main():
142 rclpy.init()
143 led_node = LEDClient()
144
145 green = ColorRGBA(r=0., g=1., b=0., a=1.)
146 yellow = ColorRGBA(r=1., g=1., b=0., a=1.)
147 turquoise = ColorRGBA(r=0., g=1., b=1., a=1.)
148
149 led_node.blinking([LEFT_EAR, RIGHT_EAR, BACK],
150 yellow, 1,
151 turquoise, 2)
152
153 led_node.fixed_color([LEFT_EAR, RIGHT_EAR, BACK], green)
154
155 led_node.progress_bar([LEFT_EAR, RIGHT_EAR])
156
157 led_node.rainbow([LEFT_EAR, RIGHT_EAR, BACK])
158
159
160if __name__ == '__main__':
161 main()
The code explained¶
First things first, we need to import the required packages.
1import rclpy
2from rclpy.node import Node
3from rclpy.action import ActionClient
4from pal_device_msgs.action import DoTimedLedEffect
5from std_msgs.msg import ColorRGBA
6from builtin_interfaces.msg import Duration
7import numpy as np
8import math
9import time
Then, we define some constant values used later in the code. This step increases code readability.
11# LED devices IDs
12BACK = 0
13LEFT_EAR = 1
14RIGHT_EAR = 2
15RESPEAKER = 4
16
17MAX_PRIORITY = 255 # The maximum value for leds effect priority
18
19
20def to_duration_msg(d):
21 """ Converts floating point duration into ROS duration message. """
22 nano, sec = math.modf(d)
23 return Duration(sec=int(sec), nanosec=int(nano * 1e9))
to_duration_msg(d)
is a utility function that converts a floating point
duration into a ROS duration message. This is useful as several of the LEDs
effects require a Duration
message to specify the effect duration.
Next, we create the LEDClient
class, which inherits from
a ROS Node
. This class will be used to manage the communication with the
LED Manager action server, which is responsible for executing the LEDs effects.
26class LEDClient(Node):
27 def __init__(self):
28 super().__init__('led_client')
29
30 self._leds = ActionClient(
31 self, DoTimedLedEffect, '/led_manager_node/do_effect')
32 while not self._leds.wait_for_server(timeout_sec=1.0):
33 self.get_logger().info("waiting for the LED manager...")
Fixed-color effect¶
The most basic effect we can request to the LEDs action server is the
fixed-color effect. This effect sets the LEDs to a fixed color for a
certain amount of time. The method fixed_color
does just that.
35def fixed_color(self, devices, color, duration=5.):
36 """
37 Sets the LEDs to a fixed color.
38 """
39 goal = DoTimedLedEffect.Goal()
40 goal.devices = devices
41 goal.params.effect_type = goal.params.FIXED_COLOR
42
43 goal.params.fixed_color.color = color
44
45 goal.effect_duration = to_duration_msg(duration)
46 goal.priority = MAX_PRIORITY
47
48 self.get_logger().info(
49 f"Setting a fixed color: {color} for {duration}s")
50 self._leds.send_goal_async(goal)
51 time.sleep(duration)
We first create a DoTimedLedEffect.Goal
object, which is the object
we will send to the action server to request the fixed-color effect.
We set the target LED devices that will execute the effect. For instance, ears’ LEDs, or back LEDs. Check the LEDs API for the list of available LEDs devices on your robot.
The color
parameter is a std_msgs.msg.ColorRGBA
message, which contains
the RGBA components of the color as floating point values between 0 and 1.
Then, we set the effect duration and priority, before sending the
goal
object to the action server. The call is asynchronous; in this
example, we block the process for the effect duration, to allow the effect to
be executed before the next effect is requested.
Progression effect¶
The progression or progress bar effect is a more complex effect that allows us to
show a progression state through the LEDs. This is useful, for instance, to
show the battery level while the robot is docked, or the progress of a task
being executed by the robot. The method progress_bar
implements this.
53def progress_bar(self, devices):
54 """
55 Uses the LEDs to display a progress bar.
56 """
57 goal = DoTimedLedEffect.Goal()
58 goal.devices = devices # The devices performing the effect
59 goal.params.effect_type = goal.params.PROGRESS # The type of effect
60
61 # Setting values for the two progress
62 # color components
63 # first color --> complete
64 # second color --> uncomplete
65 blue = ColorRGBA(r=0., g=0., b=1., a=1.)
66 goal.params.progress.first_color = blue
67
68 red = ColorRGBA(r=1., g=0., b=0., a=1.)
69 goal.params.progress.second_color = red
70
71 # led offset set to 0. Progression
72 # will be displayed starting from
73 # the back led, counterclockwise
74 goal.params.progress.led_offset = 0.
75
76 goal.effect_duration = to_duration_msg(0.2)
77 goal.priority = MAX_PRIORITY
78
79 self.get_logger().info(f"iDisplaying a progress bar")
80
81 # Increment by 1% every 0.2 seconds.
82 for percentage in np.arange(0, 1.01, 0.01):
83
84 goal.params.progress.percentage = percentage
85 self._leds.send_goal_async(goal)
86
87 self.get_logger().info(f"Task completion: {percentage * 100}%")
88
89 # Sleeping slightly less then the effect duration
90 # to always have the next completion goal
91 # queuing when the previous one execution finishes
92 time.sleep(0.19)
For this effect, we set two colors (here hardcoded to blue and red) that represent the completed and uncompleted parts of the progression bar.
Using the parameters percentage
and led_offset
, we can
control the progression bar behaviour:
percentage
represents the fraction of lights to set with the completion colour. In a process advancement fashion, it represents the process completion status.led_offset
represents from which light in the LEDs array the progression should start from. For instance, if set to 0, the progression will start from the back-most LED and will progress counterclockwise.
In this example, we set the progression bar to advance by 1% every 0.2 seconds,
by iterating over an np.arange
object. At each step, we set the
goal.params.progress.percentage
to the current percentage and send
the goal
object to the action server.
Rainbow effect¶
The next effect performed by the LEDs is the rainbow effect. This effect continuously changes the LEDs colour, ranging over the whole rainbow colours.
94def rainbow(self, devices, transition_duration=1, duration=6.):
95 """
96 Sets up and sends a rainbow effect
97
98 (that is, continous transition over the rainbow colours).
99 """
100
101 goal = DoTimedLedEffect.Goal()
102 goal.devices = devices
103 goal.params.effect_type = goal.params.RAINBOW
104
105 # transition_duration --> the time the effect will take to complete an
106 # iteration over the rainbow colours
107 goal.params.rainbow.transition_duration = to_duration_msg(transition_duration)
108
109 goal.effect_duration = to_duration_msg(duration)
110 goal.priority = MAX_PRIORITY
111
112 self.get_logger().info(f"Starting a rainbow")
113 self._leds.send_goal_async(goal)
114 time.sleep(duration)
This effect is implemented in a similar way to the fixed-color effect, setting
the goal.params.effect_type
to goal.params.RAINBOW
. The
goal.params.rainbow.transition_duration
parameter defines the speed of the color wheel.
Blinking effect¶
Finally, the last effect we will implement is the blinking effect. This effect alternates two colors with custom durations, creating a blinking effect.
116def blinking(self, devices, color1, duration1, color2, duration2, total_duration=5.):
117 """
118 Sets up and sends blinking color leds effect.
119
120 It alternates two colors with custom temporisation.
121 """
122
123 goal = DoTimedLedEffect.Goal()
124 goal.devices = devices
125 goal.params.effect_type = goal.params.BLINK
126
127 goal.params.blink.first_color = color1
128 goal.params.blink.first_color_duration = to_duration_msg(duration1)
129
130 goal.params.blink.second_color = color2
131 goal.params.blink.second_color_duration = to_duration_msg(duration2)
132
133 goal.effect_duration = to_duration_msg(total_duration)
134 goal.priority = MAX_PRIORITY
135
136 self.get_logger().info(f"Start blinking")
137 self._leds.send_goal_async(goal)
138 time.sleep(total_duration)
For this effect, we set the two colors to blink, their respective durations, and the total duration of the blinking effect.
Script execution¶
Finally, we define the main function that will create an instance of the
LEDClient
class and call the different methods to perform the
LEDs effects.
141def main():
142 rclpy.init()
143 led_node = LEDClient()
144
145 green = ColorRGBA(r=0., g=1., b=0., a=1.)
146 yellow = ColorRGBA(r=1., g=1., b=0., a=1.)
147 turquoise = ColorRGBA(r=0., g=1., b=1., a=1.)
148
149 led_node.blinking([LEFT_EAR, RIGHT_EAR, BACK],
150 yellow, 1,
151 turquoise, 2)
152
153 led_node.fixed_color([LEFT_EAR, RIGHT_EAR, BACK], green)
154
155 led_node.progress_bar([LEFT_EAR, RIGHT_EAR])
156
157 led_node.rainbow([LEFT_EAR, RIGHT_EAR, BACK])
You can use the code snippets for the different effects in your own scripts, adapting them to your needs. For instance, you can change the LEDs devices to use, the colors, the durations, and so on.
Important
While you can control the LEDs effect via the ROS action interface, as we have done in this tutorial, you can also use special markup expressions to easily perform LED effects while the robot is speaking. Refer to Multi-modal expression markup language to learn more.
Next steps¶
The node you have developed only includes some of the effects that can be performed with LEDs. We invite you to explore more of these effects, playing with them for a deeper understanding of how LEDs can enhance the robot’s expressiveness: LEDs API.
If you want to know more about building expressive interaction, check 😄 Expressive interactions.