The Ada IoT Stack consists of an lwIp (“lightweight IP”) stack implementation written in Ada, with an associated high-level protocol to support embedded device connectivity nodes for today’s IoT world. The project was developed for the Make With Ada 2017 competition based on existing libraries and ported to embedded STM32 devices.
Being a huge fan of IoT designs, I originally planned to work on a real device such an IoT node or gateway, while using the Ada language. I really enjoy writing programs but my roots are in hardware (I earned a B.S. in electronic engineering). Back then I was not really a programmer, but if you can master the intricacies of hardware I think that experience will eventually help make you a good programmer; I’m not sure it works the same way in the other direction.
With so many programming languages out there, it's difficult to gain experience with all of them. In my case I was not even aware of the Ada language until I found out about the contest. That got me to learn Ada: there is a saying that without deadlines nobody will finish on time, and the contest supplied both the motivation and a deadline. For me the best way to learn something is not to read a book chapter by chapter. Sometimes you need to read a “getting starting” guide to gain a basic knowledge, but after that if you don't have a problem to solve, your motivation might come to an end. I made my choice to continue, with the IoT project.
When I started the IoT project I soon realized that the Ada Drivers Library didn't provide a TCP/IP stack. The Etherscope software by Mr. Carrez from the 2016 Make with Ada contest provided the Ethernet connectivity and UDP protocol but not TCP. I started to look at how to implement the TCP/IP stack, but due to my lack of experience in Ada and the fact that contest was running for almost two months it would not have worked to do something from scratch. So I wrote to Mr. Chouteau (at AdaCore) asking for information about any Ada libraries that implement a TCP stack, and he referred me to a lwIp implementation in Ada and Spark 2014. It was on a Spark 2014 git repository that had not shown up on a Google search. As far as I could tell, the code was only tested on a Linux-flavor OS using a TAP interface. I spent a couple of weeks studying the code and getting it to work on my debian box, which was a little hacky at the end since the TAP C driver implementation (system calls) didn’t work as expected; I ended up coding the TAP interface by hand.
A TAP device operates at layer 2 of the OSI model, which means Ethernet frames. That makes sense here since lwIp implements networking from Ethernet datagrams. Then I realized that I could combine the Etherscope project with the lwIp implementation. After removing several parts of the Etherscope project to get only Ethernet Frames I was ready to feed the lwIp stack. It was not so easy, I spent some time porting the lwIp Ada code to the Ada version for embedded ARM. One obstacle I found was the use of xml files to describe the network datagrams and other structures in a way that's used by a program called xmlada to generate some Ada body files. These describe things like bit and byte positions of TCP flags or fields within the datagram. The problem was that the ARM version don't provide xmlada, so I end up copying the generated files in my project.
After quite some time I got the lwIp stack to work on my STM32F769I board. This was no easy task, especially because the STLink debugger is not so easy to work with. (For example semihosting is basically the only way to have debugger output in the form of "printf". This is really slow and basically interrupts the flow of program execution in a nasty way. The problem here is that the ST board doesn't provide a JTAG interface to the Cortex M4/M7 device, and the STlink on board doesn't have an SWO line connection.)
The TCP/IP stack was just the beginning; it was really nice to see it working, but quickly gets boring. The original lwIp implements a TCP echo server: you open a socket, connect and then anything you send is replied by the server, which is not very useful for IoT. So I felt I was not making real progress, at least toward something that would give the judges a tangible project to evaluate. Again I was in a rush, this time with more knowledge of Ada but, as before, without wanting to write something from scratch.
One day I found the Simple Components Ada code by Mr. Kazakov. I really got to love it, but the problem was that after reading it a little bit I felt similar to the day I assisted my first German language "unterricht" years ago. I decided to continue spending time reading it until I finally figured out how to start porting it to my lwIp implementation. The first thing I ported was the MQTT client code, because of its simplicity in terms of dependencies on other “Simple Components” classes if we can refer to them like that. One problem solved here was the change in paradigm. Simple Components used Ada GNAT Sockets, and my lwip basically uses a callback scheme:, two different worlds, but previous experience with sockets helps since you know what the code is doing and what it should do in the new environment. At the end, the MQTT port consumed more time than expected since I not only ported the Simple Components but I also added code from the existing MQTT lwIp implementation in C to make up for the lack of timers.
It was really difficult for me to figure out how the connection state can be recovered when the callback executes. For example when a connection is made, certain variables are initialized and kept in a data structure, but after the callback returns, the structure needs to be preserved so that it can be recovered by a connection event and used in the corresponding callback.
The MQTT client gave me the insights and the experience to continue working and to try the more complicated HTTP protocol, this time improving the callback association. The port of the HTTP server was where problems with the lwIp implementation start to arise. I am almost sure that the lwIp code was only tested in a specific TCP/IP echo-controlled environment; the problem is that a TCP connection can behave differently, or more precisely has different scenarios (e.g., when closing a connection), so I ended up "patching the code" to behave as closely as possible to the standard. I also fixed some memory allocation problems with the lwIp Pcb's of the original stack. Nonetheless if you decide to try the code please be aware that the code should be treated as a "development " version.
Ultimately there was not enough time to finish my IoT Node as I had initially intended. The good part is that I really enjoy solving this kind of problem.
Work in progress
I really was impressed with Ada, it has the power to do things that in other languages like C/C++ would be much too prone error-prone. The lack of a good debugger was offset by the increased productivity in writing code that had fewer bugs in the first place. I hope to have some time to continue working on this project in the near future.
To see Manuel's full project log click here.