Introduction

Radler framework takes its inspiration from the Robot Operating System (ROS). In Radler framework, the sensors, controllers, and actuators are constructed from functional units called nodes. Each node executes independently with a period determined by a local clock and scheduling constraints. Radler supports a publish/subscribe architecture where nodes communicate by publishing on certain topics and subscribing to other topics.

Getting Started

Find the documentation at https://sri-csl.github.io/radler/

To install

To checkout repository:

git clone https://github.com/SRI-CSL/radler.git
cd radler
git submodule update --init --recursive

To get Radler working on a clean version of Ubuntu 14.04:

sudo apt-get install cmake python3-pip
sudo pip3 install tarjan pyyaml

To install ROS:

sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu trusty main" > /etc/apt/sources.list.d/ros-latest.list'
wget https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -O - | sudo apt-key add -
sudo apt-get update
sudo apt-get install ros-indigo-ros-base
(echo ; echo "# Setup for ROS" ; echo "source /opt/ros/indigo/setup.bash" ) >> ~/.bashrc
source ~/.bashrc

To compile and run

Compile

Radler generates files from the RADL file into a usual ROS catkin structure, then a call to catkin_make will generate the executables as usual. (e.g., see examples/pubsub/pubsub.radl):

mkdir -p /tmp/catkin_ws/src
./radler.sh --ws_dir /tmp/catkin_ws/src compile examples/pubsub/single_machine/pubsub.radl --plant plant --ROS
cd /tmp/catkin_ws
catkin_make

Run

Since pubsub defines a plant, Radler has generated a launch file to run the requested nodes. The simplest way of running it is to source the catkin workspace we just compiled and use roslaunch:

source devel/setup.bash
roslaunch pubsub pubsub.plant.host_computer.launch

You should see the output of the two nodes explaining that they are communicating. For more details see pubsub example documentation.

You can stop everything by typing Ctrl-C.

Radler usage

Commands

radler.sh

radler.sh is a simple bash script used to run the actual python Radler script radler/main.py

Radler script

The script may be used to handle object files with the subcommands obj_version and obj_compatible or to compile .radl description with the compile subcommand. The script has two levels of arguments. The arguments before the subcommand and the one after, specific to the subcommand.

Generic arguments

The full list of arguments is provided by the --help argument.

--ws_dir

This is used to specify where is the workspace directory. The workspace directory is where the files are generated. This is by default equal to src.

Compile subcommand

The full list of arguments for the compile subcommand is available as the --help argument to the subcommand (radler.sh compile --help).

The radl description file is a positional compulsory argument. It has to be a file with the ‘.radl’ extension.

--object_files

To compile .radl description which depends on other modules, one has to give the object files of those modules with this argument.

--object_dest

By default, the generated object file has the same name as its module, with the ‘.radlo’ extension. Moreover it is by default written at the root of the workspace directory. This command can be used to change the name and directory of the generated object file.

Examples

The following examples demonstrate the features of Radler.

  • Pubsub: How to write pub/sub on single/multi machine
  • House_thermo: Toy example of a distributed house heating system modeling
  • Controller_gateway: How to write a gateway to forward back and forth messages between Radler world and a local sandboxed ROS
  • Drone: AR.Drone demos using ardronelib sdk and ardrone_autonomy ROS driver
  • Raspberrypi: Raspberry Pi 2/B+ demo using GPIO
  • Android: Android demos via ROS-enabled Android application and Android NDK

Pubsub

This simple examples demonstrate how to write pubsub on single/multi machine.

To generate the files from the .radl description, do:

mkdir -p /tmp/catkin_ws/src
cd /path/to/radler
./radler.sh --ws_dir=/tmp/catkin_ws/src compile examples/pubsub/single_machine/pubsub.radl --plant plant --ROS
cd /tmp/catkin_ws
catkin_make

You can then run all of them (in different terminals for more clarity):

roscore
./devel/lib/pubsub/talker
./devel/lib/pubsub/listener

For multi_machine testing, use a different .radl file in multi_machine directory as below:

./radler.sh --ws_dir=/tmp/catkin_ws/src compile examples/pubsub/multi_machine/pubsub.radl --plant plant --ROS

For running on multi_machine setup, copy executables to the corresponding machines. Refer to plant section of .radl file for the IPs as below.

scp /tmp/catkin_ws/devel/lib/pubsub/talker 192.168.10.202:~
scp /tmp/catkin_ws/devel/lib/pubsub/listener 192.168.10.203:~

On machine 192.168.10.201:

roscore

On machine 192.168.10.202:

./talker

On machine 192.168.10.203:

./listener

House_thermo

Toy example modeling a distributed house heating system. It uses the same Radler features as the pubsub example.

Controller_Gateway

This example demonstrates how to write a gateway to forward back-and-forth messages between Radler world and a local sandboxed ROS.

Radler world is defined in the radl_files folder. It consists of .radl description containing three nodes. A device stub publishing an integer, an actuator stub subscribing to a float and a gateway node for an external controller node, forwarding device msgs to it and forwarding actuators msgs from it.

To complete the example, a stub of a sandboxed ROS controller node is also provided in the sandboxed_ros_controller folder.

There are basically two interesting files, the gateway.radl description of Radler system which defines the gateway node and the associated gateway.h file implementing the actual gateway.

To generate the files from .radl description, do:

mkdir -p /tmp/catkin_ws/src
cd /path/to/radler
./radler.sh --ws_dir /tmp/catkin_ws/src compile examples/controller_gateway/radl_files/gateway.radl --ROS

Then to compile everything and get ROS executables, do:

ln -s /path/to/radler/example/controller_gateway/sandboxed_ros_controller /tmp/catkin_ws/src
cd /tmp/catkin_ws
catkin_make

You can then run all of them (in different terminals for more clarity):

roscore
./devel/lib/gateway/device_node
./devel/lib/gateway/actuator_node
./devel/lib/gateway/controller_gateway
./devel/lib/sandboxed_ros_controller/sandboxed_ros_controller_node

Demos using AR.Drone on ROS

Demos are available for three types of AR.Drone drivers: Demo #1 with ardronelib (AR.Drone sdk), Demo #2 with ardrone_autonomy (ROS driver), and Demo #3 with ps_drone (Python).
Demos #1 and #2 consist of at least three nodes sridrone, led, and camera, and three topics navdata, led_anim, and camera_param. The sridrone node publishes navdata and subscribes to led_anim and camera_param to generate signals that control LED animation and video input. The led node subscribes to navdata and publishes led_anim to indicate unstable roll (LEFT_GREEN_RIGHT_RED or LEFT_RED_RIGHT_GREEN) or low battery level (BLINK_RED). The camera node subscribes to navdata and publishes camera_param to toggle the video feed between front/bottom cameras. Demo #1 additionally supports for three other nodes: key, timer, and landing. The key node provides drone flight control using keyboard. The timer node provides timer-based drone flight control, e.g., landing after 2 seconds of hovering over 50cm. The landing node provides altitude-based landing, e.g., landing if the altitude is less than 50cm.
Demo #3 consists of two nodes sridrone and led to perform object detection using TensorFlow. The sridrone node publishes object_detection and subscribes to led_anim to generate signals that indicate certain objects (e.g., person, dog, cat, or teddy bear) being detected.
sridrone_rqt_graph.png

This application uses code snippets from the following open source projects.

Some additional links:

For Demos #1 and #2, OpenCV is required to find line with the Hough transform. In .radl file, edit opencv_houghline to enable line detection.

git clone https://github.com/opencv/opencv.git
cd /path/to/opencv
mkdir build
cd build
cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local ..
make
sudo make install

Demo #1 with sridrone_ardronelib

Compile up-to-date ardronelib sdk.

git submodule update --init --recursive
cd examples/drone/sridrone_ardronelib/ardronelib
make
sudo make install --makefile=../Makefile INSTALL_PREFIX=/usr/local

Edit drone_ip in examples/drone/sridrone_ardronelib/sridrone_ardronelib.radl with your drone’s IP if needed.

DEFS
   drone_ip: string "192.168.1.xxx"

Run ROS master.

roscore

Run the sridrone_ardronelib example.

mkdir -p /tmp/catkin_ws/src
cd /path/to/radler
./radler.sh --ws_dir /tmp/catkin_ws/src compile examples/drone/sridrone_ardronelib/sridrone_ardronelib.radl --plant plant --ROS
cd /tmp/catkin_ws
catkin_make
cd /tmp/catkin_ws/devel/lib/sridrone_ardronelib
./sridrone
./led
./camera
./key
./timer
./landing

Demo #2 with sridrone_ardrone_autonomy

Download and install ROS ardrone_driver from https://github.com/AutonomyLab/ardrone_autonomy.git.

sudo apt-get install ros-indigo-ardrone-autonomy
sudo apt-get install ros-indigo-image-view

Run ROS master and ardrone_driver.

roscore
rosrun ardrone_autonomy ardrone_driver

Note. Use -ip ${Your Drone’s IP address} if your drone does not have the default IP address.

rosrun ardrone_autonomy ardrone_driver -ip 192.168.1.xxx

Run the sridrone_ardrone_autonomy example.

cd /path/to/radler
./radler.sh --ws_dir /tmp/catkin_ws/src compile examples/drone/sridrone_ardrone_autonomy/sridrone_ardrone_autonomy.radl --plant plant --ROS
cd /tmp/catkin_ws
catkin_make
cd /tmp/catkin_ws/devel/lib/sridrone_ardrone_autonomy
./sridrone
./led
./camera

You can also run ros image_view node to check the camera feed or record it.

rosrun image_view image_view image:=/ardrone/front/image_raw
rosrun image_view image_view image:=/ardrone/bottom/image_raw
rosrun image_view video_recorder image:="/ardrone/front/image_raw" _filename:="/tmp/video_front_camera.avi"

Demo #3 object detection with PS-Drone and TensorFlow

Install Python OpenCV2.

sudo apt-get install python-opencv

Download ps_drone.py and firstVideo.py from http://www.playsheep.de/drone to the same directory (e.g., ps_drone). Test video streaming from AR. Drone using firstVideo.py. A window named PS-Drone with front camera feed should show up. We tested on Ubuntu 14.04.

cd /path/to/ps_drone
python firstVideo.py

Install TensorFlow and related packages. For details, refer https://www.tensorflow.org/install/install_linux.

sudo apt-get install python-pip
sudo pip install -U pip
sudo pip install --upgrade setuptools
sudo pip install --upgrade --target=/usr/lib/python2.7/dist-packages tensorflow
sudo apt-get install --fix-missing python-matplotlib

Download models built with TensorFlow.

git clone https://github.com/tensorflow/tensorflow.git
cd tensorflow
git clone https://github.com/tensorflow/models.git

Download a Single Shot Multibox Detector (SSD) with MobileNet model from here, and untar to /path/to/tensorflow/models/object_detection directory.

tar zxvf ssd_mobilenet_v1_coco_11_06_2017.tar.gz -C /path/to/tensorflow/models/object_detection

Download and install Protobuf.

git clone https://github.com/google/protobuf.git
sudo apt-get install autoconf libtool
cd protobuf
./autogen.sh
./configure
make
sudo make install

Compile Protobuf libraries.

export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
cd /path/to/tensorflow/models
protoc object_detection/protos/*.proto --python_out=.

Add to PYTHONPATH.

export PYTHONPATH=/path/to/ps_drone:/path/to/radler/examples/drone/sridrone_pydrone_tf_obj_detection/src:/path/to/tensorflow/models:/usr/lib/python2.7/dist-packages:$PYTHONPATH

Compile up-to-date ardronelib sdk. Skip this step if you already installed ardronelib sdk for Demo #1.

git submodule update --init --recursive
cd examples/drone/sridrone_ardronelib/ardronelib
make
sudo make install --makefile=../Makefile INSTALL_PREFIX=/usr/local

Edit drone_ip in examples/drone/sridrone_pydrone_tf_obj_detection/sridrone_pydrone_tf_obj_detection.radl with your drone’s IP if needed.

DEFS
   drone_ip: string "192.168.1.xxx"

Run ROS master.

roscore

Compile the sridrone_pydrone_tf_obj_detection example.

mkdir -p /tmp/catkin_ws/src
cd /path/to/radler
./radler.sh --ws_dir /tmp/catkin_ws/src compile examples/drone/sridrone_pydrone_tf_obj_detection/sridrone_pydrone_tf_obj_detection.radl --plant plant --ROS
cd /tmp/catkin_ws
catkin_make

Create symbolic links to find model and label.

cd /tmp/catkin_ws/devel/lib/sridrone_sridrone_pydrone_tf_obj_detection
ln -s /path/to/tensorflow/models/object_detection/data .
ln -s /path/to/tensorflow/models/object_detection/ssd_mobilenet_v1_coco_11_06_2017 .

Run the example.

./sridrone
./led

Even though the models can detect multiple objects simultaneously, led_anim topic only considers the object with the highest score. Led lights are blinking green (or red) when person (or teddy bear) is detected with the highest score. When dog (or cat) is detected with the highest score, the right (or left) led lights turn red.

Demos using Raspberry Pi 2/B+

The demo consists of two nodes led, and button, and one topic button_status. The button node publishes button_status and the led node subscribes to it to turn on the led light when the button is pressed.
raspberrypi_rqt_graph.png

Some additional links:

Preparing your Raspberry Pi

On your host Linux machine, insert the microSD card that comes with Raspberry Pi to extract its disk image.

lsblk
sudo dd bs=4M if=/dev/sdb | gzip > raspbian.img.gz

Now eject the microSD card that comes with Raspberry Pi, and insert a larger microSD card to copy the disk image.

unzip --stdout raspbian.img.gz | sudo dd bs=4M of=/dev/sdb
sudo sync

Boot with a larger microSD to provide enough disk space for this demo (we recommend 16GBytes). Run the following command to expand the disk space.

sudo raspi-config

Preparing LED and Button

Understand GPIO on Raspberry Pi. We use GPIO 4 and 17 for the LED output and for the Button input, respectively.

We use GPIOClass from http://www.hertaville.com/introduction-to-accessing-the-raspberry-pis-gpio-in-c.html.

Installing Radler on Raspberry Pi

Install Python 3.4 on the Raspberry Pi.

sudo apt-get install libncurses5-dev libncursesw5-dev libreadline6-dev
tar -zxf /path/to/your/Python-3.4.3.tgz
cd Python-3.4.3
./configure --prefix=/usr/local/opt/python-3.4.3
make
sudo make install
export PATH=$PATH:/usr/local/opt/python-3.4.3/bin

Install tarjan and pyyaml on the Raspberry Pi.

sudo pip3.4 install tarjan
sudo pip3.4 install pyyaml

Install ROS Indigo on the Raspberry Pi.

Follow the instructions in Step 2 at http://wiki.ros.org/ROSberryPi.

We recommend increasing the swapfile size of Raspberry Pi to 800MBytes (default is 100MBytes) by editing CONF_SWAPSIZE in /etc/dphys-swapfile, and restart dphys-swapfile.

/etc/init.d/dphys-swapfile stop
/etc/init.d/dphys-swapfile start

CMake 2.8.12 or higher is required on the Raspberry Pi.

wget http://www.cmake.org/files/v3.2/cmake-3.2.2.tar.gz
tar xf cmake-3.2.2.tar.gz
cd cmake-3.2.2
./configure
make
sudo apt-get install checkinstall
sudo checkinstall
dpkg -i ~/cmake-3.2.2/cmake_3.2.2-1_armhf.deb

Run the demo on Raspberry Pi

Run ROS master on the Raspberry Pi.

export ROS_MASTER_URI=http://localhost:11311
roscore

Edit target_link_libraries in packagen.py (under ralder/radlr/cgen).

target_link_libraries({lib_target} {lib_static_libs} rt)

Run the raspberrypi example.

mkdir -p /tmp/catkin_ws/src
cd /path/to/radler
./radler.sh --ws_dir /tmp/catkin_ws/src compile examples/raspberrypi/raspberrypi.radl --plant plant --ROS
cd /tmp/catkin_ws
catkin_make
cd /tmp/catkin_ws/devel/lib/raspberrypi
sudo chown -v root.root led
sudo chmod 4755 led
./led
./button

Demos using Android

Demo #1 implements sensing (of GPS, compass, touch screen) and controlling (of speaker volume) of Android device via ROS-enabled Android application using Gradle-Android studio environment. This demo is sensing GPS, compass, and touch screen coordinate from Android device, and publishing those under android_gps, android_compass, and android_touch topics, respectively. Radler nodes (named as gps, compass, and touch on the workstation side) are subscribing to corresponding topics and publishing them in Radler world. The controller Radler node subscribes to topics from gps, compass, and touch Radler nodes, and controls volume of the Android ringtone by publishing ROS topic.

sensor_pubsub_rqt_graph.png

Demo #2 implements touchscreen event detection via getevent system command and displays on the window of terminal emulator application.

touch_detector_rqt_graph.png

Some additional links:

Demo #1 Sensing and controlling of Android device via ROS-enabled Android application using Gradle-Android studio environment

Our example included in android_core directory is based on the tutorial example (android_tutorial_pubsub) from https://github.com/rosjava/android_core.

Install Android studio, and import android_core project to compile/generate/install signed apk file via Android studio (recommended).

Alternatively, you can use Gradle command as below.

cd /path/to/radler/examples/android/android_core

Edit sdk.dir in local.properties.

sdk.dir=/path/to/android/sdk

Execute a build with the Wrapper.

sudo ./gradlew

If you are not using Android studio, you need to sign your unsigned apk android_tutorial_pubsub-release-unsigned.apk under android_tutorial_pubsub/build/output/apk, and install signed apk.

cd /path/to/radler/examples/android/android_core/android_tutorial_pubsub/build/output/apk
keytool -genkey -v -keystore debug.keystore -alias radler -keyalg RSA -keysize 2048 -validity 20000
jarsigner -verbose -keystore debug.keystore android_tutorial_pubsub-release-unsigned.apk radler
adb install  android_tutorial_pubsub-release-unsigned.apk

For ease of testing, USB tethering can be used. Note that USB tethering is not required for deployment. Enable USB tethering mode on your Android device. ifconfig on your workstation will show you the usb0 interface.

usb0  Link encap:Ethernet  HWaddr xx:xx:xx:xx:xx:xx
      inet addr:192.168.42.11  Bcast:192.168.42.255  Mask:255.255.255.0

Set environment variables on your workstation.

export ROS_MASTER_URI=http://192.168.42.11:11311
export ROS_HOSTNAME=192.168.42.11

Run ROS master on your workstation.

roscore

Edit master_ip in examples/android/sensor_pubsub/sensor_pubsub.radl if needed.

DEFS
   master_ip: string "192.168.42.11"

Run the sensor_pubsub example. Note that when you open a new terminal to run Radler nodes, set environment variables (i.e., ROS_MASTER_URI and ROS_HOSTNAME).

mkdir -p /tmp/catkin_ws/src
cd /path/to/radler
./radler.sh --ws_dir /tmp/catkin_ws/src compile examples/android/sensor_pubsub/sensor_pubsub.radl --plant plant --ROS
cd /tmp/catkin_ws
catkin_make
cd /tmp/catkin_ws/devel/lib/sensor_pubsub
./gps
./compass
./touch
./controller

When you start PubSubTutorial application on your Android device, enter Master_URI as your workstation IP (192.168.42.11 in this demo). You can now observe your compass and GPS coordinates on the top of the screen. When you touch the screen, the ringtone sound will be played with volume that is proportional to the ratio of current x-coordinate and screen width.

Demo #2 Touchscreen event detector using Android NDK

Prepare build environment to build native ROS nodes using the Android NDK as described in http://wiki.ros.org/android_ndk/Tutorials/BuildingNativeROSPackages. do_docker.sh takes some time to complete.

git clone https://github.com/ekumenlabs/roscpp_android.git
cd roscpp_android
./do_docker.sh

Copy do_radler.sh script to the Docker workspace for cross compilation of Radler nodes.

cp /path/to/radler/examples/android/do_radler.sh /path/to/roscpp_android/

Edit android_ip and master_ip in examples/android/touch_detector/touch_detector.radl if needed. The android_ip is your Android device’s IP (192.168.42.129 in this demo). The master_ip is your workstation’s IP (i.e., Ubuntu machine where you run ROS master; 192.168.42.11 in this demo). Refer to http://wiki.ros.org/ROS/EnvironmentVariables for further explanation.

DEFS
   android_ip: string "192.168.42.129"
   master_ip: string "192.168.42.11"

Set environment variables on your workstation.

export ROS_MASTER_URI=http://192.168.42.11:11311
export ROS_HOSTNAME=192.168.42.11

Run ROS master on your workstation.

roscore

Compile the touch_detector example.

cd /path/to/radler
./radler.sh --ws_dir=/path/to/roscpp_android/output/catkin_ws/src compile examples/android/touch_detector/touch_detector.radl --plant plant --ROS
sudo docker run --rm=true -t -v /path/to/roscpp_android:/opt/roscpp_android -v /path/to/roscpp_android/output:/opt/roscpp_output -i ekumenlabs/rosndk /opt/roscpp_android/do_radler.sh /opt/roscpp_output

Copy Radler nodes for the touch_detector example.

cd /path/to/roscpp_android/output/catkin_ws/devel/lib/touch_detector
adb push touch /data/data
adb push detector /data/data

Run touch Radler node on your Android device. On your workstation, connect to you Android device via ADB.

adb shell
su
mount -o rw,remount /
export ROS_MASTER_URI=http://192.168.42.11:11311
export ROS_HOSTNAME=192.168.42.129
cd /data/data
./touch

Run detector Radler node.

adb shell
su
export ROS_MASTER_URI=http://192.168.42.11:11311
export ROS_HOSTNAME=192.168.42.129
cd /data/data
./detector

Alternatively, run Radler node on your Android device. Download an Android application (.apk) for Terminal Emulator for Android (e.g., https://github.com/jackpal/Android-Terminal-Emulator), and run it on your Android device. On the terminal emulator, run the following commands.

su
export ROS_MASTER_URI=http://192.168.42.11:11311
export ROS_HOSTNAME=192.168.42.129
cd /data/data
./detector

Now you will see O on both windows (i.e., Android Terminal Emulator and ADB shell) when you touch your Android’s screen. Otherwise X will be displayed.

Radler language specification

File constraints

A radl description file is a text file, preferably encoded in utf8, with extension ”.radl”. Such a file is called in short a radl file.

Meta syntax rules

A radl file define a module whose name is the file name. A module is a set of value declarations. A declaration may be an alias, used to declare a value equal to another one but with a new name. The name of a value may be omitted, note that it’ll prevent the user to reference this value. The type of a value may also be omitted, note that if the type is ambiguous, the inference will choose one. When defining a class field, one may declare a new value or refer to another value by its identifier.

Grammar

module := decl*
decl := alias | NAME? (':' TYPE)? TYPEVALUE | NAME? (':' CLASS)? class_value
alias := NAME = identifier
class_value := '{' field * '}'
field := FIELDNAME value +
value := identifier | decl
identifier := root_name | identifier '.' NAME
root_name := NAME

Grammar terminals

The NAME terminal is a spaceless word beginning with a letter which may contain numbers and underscore (the regex [a-zA-Z][a-zA-Z0-9_]*). NAME is also restricted not to be a keyword, or begin with ‘radl’. The other terminals are defined by the language.py file, with declarations of the form

'type' TYPE 'REGEX' TYPEVALUE

and

'class' CLASS (FIELDNAME (TYPE|CLASS) ('*'|'+'|'?')?)*

Keywords

Keywords are all TYPE, CLASS and FIELDNAME plus the extra_keywords entry of language.py.

White spaces

The syntax requires all terminals to be whitespace separated. A whitespace is any number greater than 1 of space, tabulation and end of line.

Comments

After #, any character is discarded up to the end of line.

Identifier scoping rules

Scoping regions

A file MODULE.radl defines an implicit scoping region for its content whose name is MODULE. Sub-regions are defined by class values. A class value declaration defines one and only one nested scoping region, syntactically delimited by matching braces, whose name is the declaration NAME.

Local unicity of names

Inside each region (not comprising its sub-regions), NAME of each declarations are unique. Moreover, each module name MODULE is ensured to be unique.

Global unicity of qualified names

A qualified name (qname) is an identifier whose root_name is a module name. By unicity of module names and declaration names, a qname defines a value uniquely.

Resolution of identifiers to qualified names

When resolving an identifier, the first step is the resolution of its root_name, which is matched with the closest enclosing scoping region or in the last resort to another module name. At that point, the root_name is replaced by the qualified name of its matching region and the identifier becomes a qualified name.

Example

---- file: module1.radl ----
x : int32 0
s : struct { FIELDS x : int32 42 }
----------------------------

This file declares three values whose qnames are module1.x, module1.s and module1.s.x.

---- file: module2.radl ----
x : int32 0
s : struct { FIELDS
    x : int32 0
    s2 : struct { FIELDS
        y = x
        y2 = s.x
        y3 = module2.s.x
        z = module2.x
        a = module1.s.x
    }
}
----------------------------

This file declares nine values whose qnames are module2.x, module2.s, module2.s.x, midule2.s.s2, module2.s.s2.y, module2.s.s2.y2, module2.s.s2.y3, module2.s.s2.z, and module2.s.s2.a. When defining the alias y = x, the identifier x is resolved by looking for a name x defined in s2 (which doesn’t exist) then in the enclosing region s where it is found, thus resolving to module2.s.x. s.x in the declaration of y2 is resolved to the same qname by first resolving s to module2.s and replacing it to get module2.s.x. When resolving the identifiers module2.s.x, module2.x, and module1.s.x, the root_name is found to be a module name meaning that these identifiers are already qualified names.

Shadowing

While using qualified names for identifiers permit to overcome most possible shadowings, a region will shadow any module with identical name.

Example

--- file: module3.radl ---

module1 : struct { FIELDS
    y = module1.x
}
--------------------------

In this file, the resolution of module1.x will result in module3.module1.x since the closest enclosing region named module1 is the one defined in module3. Thus the definition of the struct named module1 shadows the actual module module1. Note that in this example, since module3.module1.x isn’t declared, the compiler will complain that it is missing.

Radler language

Logical level

The two main classes used to describe the logical level are node and topic. Their full definitions may be found in language.py. The most important elements are:

class node
    PUBLISHES publication *
    SUBSCRIBES subscription *
    CXX cxx_class
    PERIOD duration
    WCET duration
class topic
    FIELDS int8/uint8/int16/uint16/int32/uint32/int64/uint64/
           float32/float64/
           bool/struct/array/duration/time +

Nodes

When creating a node, Radler will construct one instance of the provided C++ class (the CXX field). The step method of this instance will be called at a fixed frequency defined by the node’s period (the PERIOD field). This step function needs to be proven to have a worst case execution time no greater than the one described here in the WCET field. At each call, the step function is provided with the messages received from its subscriptions and is required to write the messages it has to publish.

Topics

A topic (defined uniquely by its name) is a purely logical way to define point-to-point communications between one producer and multiple consumers. There can be exactly one node publishing to a topic while many nodes can subscribe to it.

External files and PATH

File reference

An external file is described by a string representing its path. This path may be absolute (for example “/etc/example.cfg”) or relative (“src/defs.h”). Relative paths are resolved relatively to the working directory of the scoping region. Absolute path files are not recommended, but may be useful in very specific cases.

Working directory of a region, PATH and MODULE_BASE_PATH

If a PATH field exists in a region, it is used to set the working directory of this region relatively to the parent region’s working directory. The module working directory is the directory of the module file except when it is redefined by the MODULE_BASE_PATH of the module_settings value.

Example

---- file: path1.radl ----
n: node {
    PATH "nodes/example"
    CXX { PATH "src" FILENAME "n.cpp" }
}
--------------------------

Considering that path1.radl is in “/tmp/pathexample”, the working directory inside the region n is “/tmp/pathexample/nodes/example” and the file referred by “n.cpp” is “/tmp/pathexample/nodes/example/src/n.cpp”

---- file: path2.radl ----
n: node {
    PATH "nodes/example"
    CXX { PATH "src" FILENAME "n.cpp" }
}
settings : module_settings {
    MODULE_BASE_PATH "/usr/pathexample"
}
--------------------------

By adding this module_settings value, “n.cpp” refers to “/usr/pathexample/nodes/example/src/n.cpp”.

Libraries

Any user code needing libraries has to be declared as using a library with the LIB field. This field allows two forms of libraries, cmake_library and static_library.

Static library

Static libraries are of the simplest form, gathering a set of source files in their CXX field while the library header files are found in the HEADER_PATHS paths.

Cmake library

The cmake library enables the use of arbitrarily complex libraries since it is a user defined cmake script. The CMAKE_MODULE field provide the cmake module name used to find it in the working directory. COMPONENTS are the required components from this module, used when calling the cmake find_module command. By default, it is expected that the module defines two variables named ${CMAKE_MODULE}_LIBRARIES and ${CMAKE_MODULE}_INCLUDE_DIRS defining respectively the libraries to be linked against and the include directories. Both variable names can be specified if their naming doesn’t follow this convention, respectively with the fields CMAKE_VAR_LIBRARIES and CMAKE_VAR_INCLUDE_DIRS.

Typing

Inference

When a value is type annotated, it is checked to be of this type. If no type annotation is provided, the kind of the value is extracted from the ones possible in the value declaration context. If there is an ambiguity, it chooses the first relevant one in the order of declaration in language.py.

Checking

User values are checked to be correct with their type with the function check_type found in language.py.

Subtyping

Subtyping allows for example to use a 16bits integer (int16) where a 32bits integer is required (int32). For now, subtyping is only done on sized_types by allowing a type to be used in a bigger version. sized_types and possible sizes are explicit in language.py. User values are checked to fit the type’s size with the function check_type_size.

User code

Compilation

Include directories

The generated code is setup so that user code is compiled with the necessary include folders, i.e., the specified library header folders and the working directory of the file references.

Compiler definitions

The user code is compiled with the following definitions:

  • IN_RADL_GENERATED_CONTEXT : The basic definition to detect if the code is compiled within Radler compilation chain.
  • RADL_NODE_NAME : The name of the current radl node.
  • RADL_NODE_QNAME : Which is set to the node qname (with dot separators, for example “modulename.nodename”).
  • RADL_MODULE_NAME : The name of the current radl module.
  • RADL_MODULE : A pointer to the current module ast root.
  • RADL_HEADER : The radl generated header to be included by the user code (defining input and output data types).
  • RADL_STATE : The radl default name to be used for the C state type (if not specified in the radl file).
  • RADL_STEP_FUN : The default name to be used for the C step function (if not specified in the radl file).
  • RADL_INIT_FUN : The default name to be used for the C init function (if not specified in the radl file).
  • RADL_FINISH_FUN : The default name to be used for the C finish function (if not specified in the radl file).

C++ node definition

Each node is a Mealy machine. The user provide a class which will be instantiated with the default constructor to generate an instance representing the state of the machine. Then, the step method of this instance will be called to execute one step of the machine. The signature of the step method is required to be:

void step(const radl_in_t*, const radl_inflags_t*, radl_out_t*, radl_outflags_t*)

The four argument types are structures defined in the generated header file.

The input structure radl_in_t

This structure has one field per subscription of the node. The field name is the radl name of the subscription. Each field is in turn a structure reflecting the topic of the subscription, whose fields are the name of the radl topic FIELDS.

The output structure radl_out_t

It is similar to the input structure except that is is used by the step function to publish its publications. To this effect, the step function has to fill the output structure.

The flag structures radl_in_flags_t, radl_out_flags_t

Similarly to the input and output structures, these have a field for each subscription (resp. publication). The type of each field is a radl_flags_t as described in radl_flags.h.

C node definition

This is the same as the C++ case, except that instead of a class, the user needs to provide 4 things. To describe them, we use the default names provided by radl, but those may be decided in the radl file.

  • A state type, that we call RADL_STATE here
  • An init function of signature : void RADL_INIT_FUN(RADL_STATE *)
  • A step function of signature : void RADL_STEP_FUN(RADL_STATE *, const radl_in_t*, const radl_inflags_t*, radl_out_t*, radl_outflags_t*)
  • A finish function of signature : void RADL_FINISH_FUN(RADL_STATE *)

The flags

Computation of the output flags

The main idea of flags is to have some Boolean metadata attached to messages, which by default propagate through nodes. To this effect, the default value of the output flags of each publication of a node are set to the logical OR of all the flags of its subscriptions, equivalent to the following pseudocode:

v = 0;
for each s in subscriptions:
    v = v OR in_flags->s
for each p in publications:
    out_flags->p = v

Then, the input flags are given to the step function as read-only while the preset output flags are provided to the step function to give it a chance to turn on or off the desired flags. So, if the step function doesn’t change the output flags, they will propagate the input flags.

Computation of the input flag radl_STALE

The so-called stale flag has the broad meaning that its associated value isn’t “fresh”. More precisely, it either means that the publisher of the value flagged it as stale (by automatic propagation or by choice) or that no new message arrived since the last call to the step function. In the latter case, the step function gets the same input value (the mailbox hasn’t changed) but it is flagged as stale. To check if a subscription s is stale, one simply calls radl_is_stale(in_flags->s) which returns a Boolean.

Computation of the input flag radl_TIMEOUT

The so-called timeout flag has the broad meaning that its associated value has violated the timing constraints. More precisely, it either means that the publisher of the value flagged it as timeout (by automatic propagation or by choice) or that we haven’t received a message since period of the publisher plus the maxlatency. In the latter case, timing constraints are exceeded and something unexpected is happening. To check if a subscription s is timeout, one simply calls radl_is_timeout(in_flags->s) which returns a Boolean.

Turning off/on output flags

The function radl_turn_on is used to turn on a flag. For example to turn on the stale flag of the publication p, we would do:

radl_turn_on(rald_STALE, &out_flags->p);

To turn off flags, the similar function rald_turn_off should be used.

Introspection

To allow more robust and generic user code, it is possible to access values defined in the radl description.

Modules root entries

The generic way of doing so is to use the generated module root ast access functions. For example if we want to read the radl value of qname module1.x, one will first call the radlast_module1 function to get to the module root entry.

Shortcuts

Instead of needing to know the current module name and call the corresponding root ast access function, one can use the provided macro RADL_MODULE. When writing a step function, one can directly access the node it is used in by using the RADL_THIS macro.

Field access

The generic rule is that fields of values are the names of the declared value inside this value. This name is in turn

  • a pointer to the value (the generic case)
  • the value itself (for a topic, a struct or inside them).

The DEFS field

To allow the user to define values which are not radl relevant, but that should be statically defined and shared, we added the DEFS field in the node and topic classes.

Example

Consider the following radl description:

---- file: introspec.radl ----
x : int32 1
t : topic { FIELDS
    a : int32 2
}
n : node {
    DEFS
        y : int32 3
        s : struct { FIELDS
            z : int32 4
        }
    PATH working_dir "nodes/example"
    CXX { PATH "src" FILENAME "n.cpp" }
}
----------------------------

In the user code, if one wants to get the value of x (1):

*(radlast_introspec()->x)

or

*(RADL_MODULE->x)

Note the indirection, since we are in the generic case, the field x is a pointer to its value. To access a, since it is inside a topic, we don’t need the indirection:

RADL_MODULE->t.a

Note that t is a topic so it is not a pointer, its field are accessed directly. This also applies to any other value, for example to read “nodes/example”, one will do:

*(RADL_MODULE->n->working_dir)

or, when writing the step function for n:

*(RADL_THIS->working_dir)

Note that in order to access this value, we gave it a name in the radl description. Finally, to sum it up, to access z when writing the step function of n:

RADL_THIS->s.z