Introducing ORNIS

Keywords: #ros2 #ornis #tui

Presenting ANOTHER entry in the “Definitely not a Backronym” series! This one is a bit different, being a project that is entirely software based.

TL:DR

I made a Terminal User Interface for ROS2. Is it good? No. Should you use it? No. Go use Foxglove Studio, it’s an open source WebApp, made by people mature enough to know that Terminal User Interfaces are limiting and dumb. If you’re still interested, you can check out the GitLab here. All of the features and technical details will be there. The rest of this post will just be rambling about the different design decisions I made, hurdles I ran into, and where I would like to take the project in the future.

Motivation

So why on earth would someone waste their time on a project like this?

  1. Attempting to help with ‘discover-ability’. Something that has always bothered me about ROS is that the state of the network (Nodes, services, etc) are essentially hidden until prompted. If I want to see what nodes have been booted, I need to use $ros2 node list, then filter through the result. Same with services and topics, introspection happens via a terminal command, which then needs to be sifted through. On its surface, this doesn’t sound too bad, but if you’re dealing with a commercial robot with a shitload of different nodes doing a lot of things, it gets very hard to introspect about the system.

  2. Trying to introduce some tooling that ‘feels good’ and is fun to use. I truly adore software that’s written for the fun of it. Things like cowsay or $telnet towel.blinkenlights.nl (Seriously, try it). We’ve seen this kind of attitude enter the robotics space as well, with people like Michael Reeves and Simone Giertz bringing a completely unreasonable amount of joy into my life with their creations. I think creating things for no reason other than “Because it’s fun” is reason enough, and I wanted to create a tool with that mantra in mind.

  3. I wanted to practice my C++, as well as understand ROS2 a little better. Outside of university, essentially all of my programming experience (Outside of some shell scripts) was robot specific, be it ROS, or an embedded processor running FreeRTOS. Things like motor control, path planning, reading sensors through i2c or spi were all I really had experience in developing. These are great skills, but they are different to what you learn by developing a large application. There are a ton of neat features in modern C++, I wanted to explore them a bit, and try something new.

  4. rosshow. I discovered this after I had already started development, but when I saw it, it absolutely lit a fire under me. It’s such a great example of how powerful the terminal can be when in the right hands. It’s a great example of a ’no bullshit’ piece of software. If you can $rostopic echo it, you can (Probably) $rosshow, and visualise it. I love it, and I plan on implementing the same visualisation functionality in ORNIS.

A story of pivots

The ORNIS of today is VERY different to what I had originally envisioned. It wasn’t even called ORNIS in the beginning. It was actually called ROSTUI, which is a bad name for two reasons. First (Which I didn’t even realise at the time) is that it deviates from the naming scheme of all of my other projects. The second reason is that it’s not even original. Yeah, that one may have pigeon in front of it, but it also used FTXUI as the , and was just a little too similar for my liking. I am happy I made the switch, however. I like ORNIS a LOT more.

Originally, ORNIS was compatible with both ROS1 and ROS2. It’d work through the magic of popen, which would either run something like $rosnode list or $ros2 node list depending on what version of ROS the user wants. Topics would be streamed through $ros2 topic subscribe, you can figure out the rest. The major appeal of this was that the ORNIS would have been entirely independent from ROS. There would have been no ROS dependencies for compilation, if you wanted to check the status of a robot running ROS, either ROS 1 or 2, you’d be able to do so without needing to compile.

Starting with FTXUI

In the beginning (Insert joke), I had chosen FTXUI as my visual library of choice. And, it worked pretty fantastically. Just before I decided to ditch it, ORNIS looked like this:

drawing

Looks pretty great. FTXUI’s modularity allowed me to extremely easily add windows, insert/remove text and create pop-up windows, with pretty sensible styling. It didn’t take me long to get this functioning prototype working, it only supported listing the various nodes/topics/services, but I was already starting to have second thoughts. I was beginning to feel very limited in how much “Creative control” I had. I didn’t like how I was kind of “stuck” into having the windows auto-resize and place to fit the window. There were a few other reasons I had, relating to how buttons worked, and popup window styling options, but I cannot remember them. I had already been searching for potential alternatives when I stumbled upon NotCurses. I remember being so goddamn confused. “Is that a PDF?”, “Who would write a PDF for a library?”, “Who would write an alternative to ncurses, and call it NotCurses?!”. I was equal parts intrigued and confused.

Once I saw the video showing off the capabilities of NotCurses, I knew I was doomed. Transparency support, sixels, animations… I was going to have to re-write, and re-write I did. It didn’t take me TOO long to bring ORNIS back up to roughly feature parity, and once I did, I was very pleased with the results. The code itself was certainly a lot more complicated, but I had so much more control. It was exactly what I wanted, and I am very excited to start working on streaming/plotting topics with NotCurses, I think viewing LiDAR/camera feeds in a terminal with sixel support will blow a few minds.

Dropping ROS1 (Or “How I learned how RCL worked”)

Once I had the basic UI layed out, it was time to begin testing topic streaming. The principle was pretty simple:

  1. Invoke our old friend popen() with some variant of $ros topic echo {topic_name}
  2. Keeping the pipe open, have a loop spinning with fgets() continuously grabbing the output of the pipe, and storing that into a buffer
  3. Add formatting, then draw the buffer. Sounds simple, and that’s probably why it worked horribly. It worked tolerably well for simple single value publishers at around ~5hz, but would still sometimes either drop a message, or grab half of one, then overlap the rest onto the next message.

You can fix a few of these issue by attempting to figure out some sort of ’end message’ character you can look for, so the streamer only displays a single message at a time.

But, I haven’t even mentioned how badly it broke down once I either a) Increased the publish rate, or b) increased the message size (Even something relatively simple like a stamped pose). The reason for this is pretty obvious, when you call something like rostopic echo {topic}, a rospy node spins up, then subscribes to the topic. This subscription then prints the topic by converting to the string, which my program then intercepted as a character array, re-converted to a string, then drew that string. Here’s the thing, converting a message to a string is slow, writing to an output is also slow. We were doing both of these things twice. If we can’t even stream short simple messages, there is no way on earth we’d be able to stream something like a camera feed.

So, our other option is to ditch our usage of the console, and actually integrate a ROS node of some sort into ORNIS, allowing us to create our own subscriber and introspect about the system. The biggest issues is actually at compile time, abstracting out the ros1/ros2 nodes wouldn’t be too difficult. However, if ORNIS was to support both ROS1 and ROS2, it would require the user to have both ROS1 and ROS2 available and sourced. I definitely don’t want to inflict that on potential users. I’m sure I’d be able to enable certain features depending on whether ROS1 or ROS2 were available to cmake, but that sounds like a LOT of effort to support something I won’t be using. Potentially, if anyone ever ends up trying to use ORNIS, and would like ROS1 support, I’d be happy to implement it.

After settling on the direction forwards, my first attempt was to use rclcpp as our interface. This worked great until I started looking into how I’d handle interacting with the underlying network, which is to say, read/send messages to topics that aren’t known at compile time. I’d be able to stream topics by using the Generic subscription, but that would prevent support many of the earlier versions of ROS2. Further, it doesn’t help me at all if I want to make service calls (Aside from looking at their source code and figuring out how they made a generic subscription).

So, after more digging, I stumbled upon this ros2_introspection by Davide Faconti (Big fan!), as well as some dynamic message introspection tools made by the OSRF as a proof of concept (I think). Now, neither supported service calls, but the underlying foundation was there, all I had to do was fill in the blanks. It took me a little while of banging rocks together until I sorted out the call and response, but I got it working in the end!

Why didn’t you just use python?

One part stubbornness, one part stupidity. Given python’s run-time nature, the message/service introspection would have been pretty simple to do. I probably would have achieved this current functionality level far quicker as well. BUT, I would have deprived myself of the chance to learn and experiment more with C++, which is my weapon of choice for programming. ORNIS allowed me to play with things like maps of classes, mutexes for requests between threads, as well as diving into how the message system in ROS2 works at a lower level than I’m familiar with.

Visual design

In case it wasn’t made immediately obvious by how this website looks, I understand very little about what makes something aesthetic. My current go-to strategy can be summarised as “Keep it consistent, keep it minimal”. It’s not great, but it gets the job done. I definitely tried my best to follow this mantra for the design of ORNIS. On boot, the user is greeted by the following screen:

drawing

Pretty simple. I’m not especially happy with either the colour scheme, nor the layout. I don’t like forcing a color scheme, I’d prefer to grab the colour scheme from the user’s terminal and use that instead. This will support being consistent with any theming the user might have going on.

As for the layout, I have beef with the ’tooltip’ bar at the top. The idea was to guarantee the user always had a way of knowing what key-presses were available, which would ideally stop the user from ever feeling ’lost’. While it does function, I feel that it fails to support both mouse and keyboard driven inputs, which is actually kinda hard when you start thinking about discover-ability. I’d like to make it a little more dynamic, and add some pop up prompts showing both clickable buttons, and potential keypresses that would allow the user to interact with the software (Which can be disabled, of course).

Transitions

I think there is something to be said about adding a bit of visual flare under the surface. Every window transition is animated in some way.

Calling a service

At the moment, service interaction is jank and I hate it. For things like the AddTwoInts/Floats it works well enough, just press tab to go between entries, type regularly, enter to send. It really gets ugly for the AddTwoStrings, where the message sub-field (data) is what we want to fill out. It still works well enough for the MVP, but I really want to streamline it a bit.

Subscribing to a topic

At the moment ORNIS only supports streaming the raw value of a topic. Adding support for proper visualisation (Graphs, videos, point clouds) is my current goal.

What’s next?

I still have a ton of things I’d like to implement. To name a few:

  1. Proper topic visualisation, ala rosshow. At the moment, you only get the raw data displayed, I’d like to provide a meaningful and intuitive way to actually see the data being thrown around.
  2. Action support. I don’t personally use Actions frequently, so it wasn’t a very high priority for me. It’ll be pretty easy for me to add it, the hard part of ORNIS has already been layed out, I just need to get around to doing it.
  3. Add support for multiple topic streams at once. The foundation is already there to allow the user to, for example: view a topic, while sending out a service call.
  4. I want to add a “graph view” for all of the currently launched nodes. In the same vein as Graphviz, with nodes linked by publishers/subscribers. I can see it being very useful (And fun) to be able to get an easy to digest visualisation of the node structure currently running on the robot.

From here, I want to be balancing my time between KIWI and ORNIS, the plan is to concurrently develop them both. This should allow me to dogfood ORNIS, and figure out where it falls flat across the spectrum of developing a robot.