Close

From protobuf to text protocol

A project log for Servio

Open-source DC servomotor with extensive testing infrastructure.

veverakveverak 01/13/2025 at 20:370 Comments

One of the earlier decisions that had to be made was: what communication protocol should we use for servio?

The two options I've thought about were:

  1. Protobuf 
  2. Text based

I opted for protobuf as I had previous experience with it and seemed as simple solution. I liked the idea of having instant-support from multiple languages, as I do not want to write libraries for interfacing with the servomotor. The main downside of protobufs was that it would not be trivial for Arduino users to interface with the servo - it seems protobuf are not yet easy to use there.

I was 60% in favor of protobuf, 40% in favor of text-based. The main bummer with text-based protocol was that I was pretty aware that parsing of text is non-trivial.

Change

That decision was changed recently and Servio is from now on text-based, why?

  1. I worked for more than one year as developer of compiler front-end, with hand-written lexer and parser
  2. I switched to Mac in my personal life and found out that projects protobuf+nanopb setup just did not worked on Mac

It happend somehow like this: After putting some hours into struggle with making everything work on mac I asked myself: is it really worth it? as much as protobuf is cool is not perfect and evidently I have to waste time trying to make it work on any platform. In the meantime I managed to write more complex data parsing at work reliably, so writing custom lexer/parser is not THAT hard...

One weekend later and Servio got it's text protocol!

Note: I do realize that this might not be the most productive decision ever, but this is still hobby project so there has to be some fun in it.

Goals for text protocol

The content of text protocol should more or less just mirror the content of protobufs. The goals are as follows:

  1. It should be easy for users to write commands for the servo
  2. It should be easy for users to parse the responses

That is, we need benevolent and friendly parser of what users send to the servo, but at the same time we have to be quite picky/careful about what we reply to the users - have robust input parsing, but very strict and easy to parse output.

Input

I've picked cmdline-like syntax for the input system, telling servo to switch to position control with goal 0 is just following null-delimited string sent over UART:

mode position 0

 Asking servo for property position:

prop position

 Querying value of configurable variable current_loop_i:

cfg get current_loop_i

 The endgoal is for this to mirror API of most cmdline arguments in bash, as these should be trivial for users to write. This should satisfy the goal of this being simple for the user.

Output

All answers from the servo are _valid JSON_. The root item is always _array_ and first item is always _string_ of value "OK" or "NOK", in case the command should return data, they are added as more items in the array. The string is always null terminated.

Here is an example of exchanges between servo and client:

> mode position 0
< ["OK"]
> cfg get current_loop_i
< ["OK", 42.0]
> prop mode
< ["OK","position"]

I assume that on any platform it should be easy to get somehow decent JSON library, and frankly, for most scenarios hand-parsing this limited subset of json is also feasible.

The important part is that we do not say that output is just JSON, we also constrict its structure.

Testing

Given that this was pretty dramatic change, how was this ... verified?

Parsers/Lexers had the strong benefit that they are extremely easy to unit test - you just prepare bunch of strings as input and verify whenever the text was parsed correctly. 

What is worse is to make reliable component test. Internally Servio uses concept of "dispatcher" - component which:

  1. Takes input from the user
  2. Applies it to the system
  3. Provides response

I figured that there are no host-executable tests for this, as dispatcher interacts heavily with HW, and decided to rectify that... and frankly, it turned out to be easier than expected.

Servio already tried to abstract implementations from hardware specific drivers by using plain old interfaces, the task was just to implement mock-versions of various hardware connected to the servo:

        drv::mock::pwm_mot mot;
        drv::mock::pos     gp;
        drv::mock::curr    gc;
        drv::mock::vcc     gv;
        drv::mock::temp    tm;
        drv::mock::stor    sd;

Writing 6 mocks was not really hard, and all that was necessary after that is to setup the core facility of servio (already abstracted into "core") and unit-test usage of dispatcher against it.

The test cases following that looked something like this:

        test( "mode power 0.2", R"(["OK"])"_json );
        EXPECT_EQ( cor.ctl.get_power(), 0.2_pwr );
        test( "prop mode", R"(["OK", "power"])"_json );

Each call to `test` gets an input, shows it through the dispatching system and checks that expected answer matches. Following that is some simple checking whenever the message had expected effect on the system.

I guess the number of tests is not optimal yet, but it clearly shows that it is easy to get more :) 

Discussions