Automatically Sending ROS2 Data to IoT Core with Greengrass
Connect ROS2 robots to the cloud with no changes to robot firmware using a custom Greengrass component.
- The robots do not need certificates, conversion nodes, or any custom code to convert topics.
- The Greengrass component can be configured from a central location with the topics it should subscribe to.
- The Greengrass component does no verification on the device publishing messages on its subscribed topics. That is, if an attacker published messages on the ROS2 network, the Greengrass component would treat them as if they came from a real robot. That means this solution is best used in a closed, protected system, or where the ROS2 network is secured.
- The component only has public messages available to it. If you use custom messages in your ROS2 applications, you will need to build these into the Greengrass component or provide them separately so the component can convert ROS2 messages to JSON correctly.
1
sudo apt update && sudo apt upgrade -y
1
2
3
4
sudo apt install -y unzip
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
1
aws sts get-caller-identity
1
curl https://get.docker.com/ | sh
1
2
3
sudo usermod -aG docker $USER
# To activate in current shell:
newgrp docker
1
2
docker --version
docker compose --help
1
2
3
sudo apt install -y pipx
pipx ensurepath
pipx install git+https://github.com/aws-greengrass/aws-greengrass-gdk-cli.git@v1.6.2
source ~/.bashrc
. Once complete, you can check the library is working by executing:1
gdk --version
1
2
3
git clone https://github.com/mikelikesrobots/gg-ros2-mqtt-converter
cd gg-ros2-mqtt-converter
git submodule update --init --recursive
1
2
aws ecr create-repository --repository-name aws-iot-robot-connectivity-samples-ros2
aws ecr create-repository --repository-name ros2-mqtt-converter
.env
file with the new value. These commands can also accomplish the same automatically:1
2
URI=$(aws ecr describe-repositories --query repositories[0].repositoryUri --output text | cut -d/ -f1)
sed -i -e "s/REPLACE_ME/$URI/g" .env
1
2
./build_all.sh
./publish_all.sh
components/io.github.mikelikesrobots.Ros2MqttConverter/docker-compose.yml
file:1
2
3
4
5
ros2-mqtt-converter:
image: "{ECR_REPO}/ros2-mqtt-converter:latest"
# ...
robot-1:
image: "{ECR_REPO}/aws-iot-robot-connectivity-samples-ros2:latest"
ros2-mqtt-converter
Docker image and three robots using the aws-iot-robot-connectivity-samples-ros2
. If we had a system with real data, we could strip these services from the Docker Compose and remove the related folder from the docker
folder.1
2
3
robot-1:
image: "{ECR_REPO}/aws-iot-robot-connectivity-samples-ros2:latest"
command: ros2 run telemetry_mqtt mock_telemetry_pub --ros-args -r __ns:=/robot1
_update_subscriptions
method on line 56 of converter.py
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def _update_subscriptions(self):
topic_tuples = self.get_topic_names_and_types()
available_topics = dict((key, val[0]) for key, val in topic_tuples)
for topic in self._config:
# topic already exists in both? Continue
if topic in available_topics and topic in self._dynamic_subscriptions:
self.get_logger().info("Subbed to {} already".format(topic))
continue
# if topic is subscribed but has no topic name, delete
elif topic not in available_topics and topic in self._dynamic_subscriptions:
self.get_logger().info("Destroying sub for {}".format(topic))
self.destroy_subscription(self._dynamic_subscriptions.pop(topic))
# if topic is not subscribed but should be, subscribe to it
elif topic in available_topics and topic not in self._dynamic_subscriptions:
self._subscribe_to(topic, available_topics[topic])
_subscribe_to
is called with the topic name and type:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def _subscribe_to(self, topic_name, topic_type):
logger = self.get_logger()
logger.info("Creating subscription to topic {}".format(topic_name))
pkg = ".".join(topic_type.split("/")[:-1])
_cls = topic_type.split("/")[-1]
logger.info("from {} import {}".format(pkg, _cls))
msg_type = getattr(__import__(pkg, globals(), locals(), [_cls], 0), _cls)
sub = self.create_subscription(
msg_type,
topic_name,
lambda msg: self._callback(topic_name, msg),
10
)
# self._subscriptions is in use by the base class
self._dynamic_subscriptions[topic_name] = sub
_callback
method with the topic name and message contents:1
2
3
4
5
6
7
8
9
10
11
12
def _callback(self, topic_name, msg):
mqtt_topic = "robots/" + topic_name
mqtt_topic = mqtt_topic.replace("//", "/")
ordered_dict = message_to_ordereddict(msg)
mqtt_msg = json.dumps(ordered_dict)
self.get_logger().debug("Publishing on topic {} with message {}".format(mqtt_topic, mqtt_msg))
binary_message = BinaryMessage(message=bytes(mqtt_msg, "utf-8"))
publish_message = PublishMessage(binary_message=binary_message)
self._ipc_client.publish_to_topic(topic=mqtt_topic, publish_message=publish_message)
json
package. This is sufficient to package and publish, so the node then publishes the request to publish over local pub/sub to Greengrass. From there, the Bridge and MQTT broker take the message and publish it to AWS IoT Core. This mechanism is described more in Testing the Component.1
sudo apt install -y default-jre
scripts/setup_greengrass.sh
script. This script downloads the Greengrass installer and runs it with some parameters that you may need to update, such as the region. Once complete, it retrieves the ARN of the Greengrass core device you just created, then creates a deployment for it that includes all of the components we need.scripts/greengrass_deployment_config.json
file. This defines a few components with some configuration:- Nucleus - needed for all Greengrass operations.
- Cli - not required in this instance, but useful to modify the installation locally.
- EMQX - an MQTT broker provided by AWS. This will be used to publish messages to AWS IoT Core.
- MQTT Bridge - a component that can be configured to pass messages between AWS IoT Core, the local MQTT broker, or Greengrass's pub sub mechanism. In this case, it is configured to forward any message received on local pub/sub that begins with
robots/
up to AWS IoT Core. - Docker Application Manager - required to run Docker containers, including the ROS2 MQTT Converter.
- ROS2 MQTT Converter - our custom component, configured to listen to the
mock_telemetry
topic of three robots.
- Robots 1, 2, and 3 in the Greengrass component publish mock telemetry messages.
- ROS2 MQTT Converter is subscribed to these topics. It receives the messages, converts them to JSON, and publishes them using Greengrass pub/sub.
- The MQTT Bridge subscribes to these messages and packs them into MQTT messages, then uses the MQTT Broker to publish to AWS IoT Core.
- The messages are received in AWS IoT Core.
1
./scripts/setup_greengrass.sh
#
topic and seeing the messages come through: