As some of you might already know I have worked on an Arduino extension for mbeddr. Because such systems run headless, you can't just use printf for displaying messages. These systems usually ship with a serial or USB port which can be used to communicate with the outside world. There are actually two ways to deal with this. First we could replace the printf-backing file handle with a handle that writes to the serial port. Second we can use mbeddr's own reporting infrastructure: this approach also gives us more flexibility because we could either write messages to the serial port or store critical errors in EPROM for further investigation.
Here I will talk about how to build your own reporting backend for mbeddr, in this case a backend that writes to the serial port.
First I will give you an overview about the reporting architecture of mbeddr. It consists of four main parts: MessagesDefinition, MessageDefinitionTable, theReportStatement and a ReportingStrategy.
Messages, as the name suggests, define messages. They have a name to reference them, a text that is written out when the message is reported and they may have parameters. These parameters are basically key-value pairs. A message also has additional attributes:
A collection of messages. Any MessagesDefinition belongs to a MessageDefinitionTable. It acts as a kind of namespace for messages.
The ReportStatement is used to report a message. It references a message and provides the actual values for parameters, if the message defines any. The parameters can be any value matching the type of its definition, for instance a local variable or a value obtained from an external sensor. The statement also provides so called checks which is a guard. Only if it evaluates to true will the message be reported.
The ReportingStrategy is used in the build configuration to configure which kind of reporting the project uses. In mbeddr there are already predefined reporting strategies: printf and nothing. As the name suggest the first one uses simple printf statements to report a message and the later does nothing with them.
Now that we have a overview about what we need let's start implementing our own reporting.
The first thing we need is a ReportingStrategy. To do so we create a new concept and name it SerialReportingStrategy:
The editor also looks straight forward, just a constant cell with the words serial reporting in it:
Now we can select this new strategy in the mbeddr build configuration:
So thats it for the visible part. We can select it but it doesn't really do anything, because there is no generator that generates the code to write the serial port. To change this we need to add a generator that reduces the concepts discussed above to C, in case serial reporting is selected.
First we create a new Generator in MPS:
As you can see the is applicable rule checks if the selected reporting in the build configuration is the SerialReportingStrategy; otherwise this generator will not do anything (hoping that another generator will reduce the reporting concepts to C).
Let's start with the easiest thing the generator should handle: disabled messages! We create a reduction rule that only applies to messages that have the active flag set to false and then abandons the input:
The reporting architecture of mbeddr contains two core elements: a MessageDefinition and a MessageDefinitionTable which combines multiple messages. Messages are always part of a message definition table. So we need to reduce the table first. The generator is also quite simple. It take a MessageDefinitionTable and calls the $COPY_SRCL$ for its messages. Which loops over all of the massages and tries to reduce them in turn:
To get all the messages to reduce we use the nonEmptyMessages method of the MessageDefinitionTable behavior:
Next we need to reduce the message definition itself. We do two things in this reduction rule. First we generate a counter variable for the message. This variable can be used in tests to check how often a message occurred. In normal C code this variable is not relevant. And second we generate a char* variable that is initialized with the message text for later usage when the message is fired.1
The property macros for the names are quite simple, they just generate a unique name for each variable so that we can reference them later. The macro for the initializer of the string is a bit more interesting. It generates its value from the name of the message and the text but also appends a \n at the end for line ending.
Now that we have the texts and the counters generated, we can start with the part that emits the code to post the message to the serial port. I our case we have a library that does all the details. All we need to do is call a function with a char* as a argument. The library is added via the make file, the corresponding header file is called serial.h. So the next thing to do is reducing the ReportStatements, but in order to keep the generator clean we implemented two different reduction rules one for a message with parameters and one for messages without parameters. Later you will see why. Here is what the generator for messages without parameters looks like:
The condition checks if there are no arguments and it does not contain an check guard. The reduction rule then defines some dummy variable and a dummy function. Inside the function an ArbitraryTextExpression is used to insert some text which is not a expressed with mbeddr semantics. In the inspector you see that it also specifies a header to include and a type.
Both variables use reference macros, and the message counter is also guarded with an if in the generator to avoid emitting this code in the case the message is not counted.
Next is the ReportStatement with properties. It requires some more work to be done because we can not use printf we have to use sprintf and store that result in a buffer before we can put it to the serial port:
The major difference here is that it first generates a buffer variable to use it with sprintf, then prints the message and after that loops over all properties of the message where it prints them to the buffer and finally writes them to the serial port. The size of the buffer is also calculated in the reduction rule. It takes the maximum length of the property name and adds 24 to which is the maximum length of a 64 bit integer printed as a string plus the characters added to make it look pretty. There is downside of this hard coded number, it will cause long strings to be corrupted.
As you may have noticed, the whole part of checks is missing. I omitted this because it would blow up the post and shouldn't be that hard to do for you if you are familiar with the generator. To prevent data corruption of long string parameters you can either prohibit the usage of anything else than a number as parameter by using a type system check rule or calculate the length at runtime. It's up to you!
The main part of this code was written while adding serial reporting support to the mbeddr.arduino project. You can have a look at the code there for further references.
Note: Due some limitations of older GCC versions char literals in C code would end up in the flash area of the memory which is a different address space. This is why the generator generates char* variables for each of messages. This done to either accomplish backward compatibility and to illustrate the difference between this generator and the printf generator. If I would have been lazy I could have simply copied the old generator and replaced the printf calls.