 This is impressive, I just wanted to ask everyone to move to the front, but I think it's not possible anymore. Yes, thank you all for joining my talk about fearless embedded rust. I'm Martin Mosler, I work for Zulka Engineering in Switzerland. And my company and I, we basically see the potential of rust in future development projects, especially in embedded projects. And that's why the company also supports me in supporting the community, for example, with rust meetups, giving the talk here. So thanks to my company, I'm here today. And I'm also building up internal rust knowledge at our company that we can do more projects in the future when they come. Today I talk about the private project that I have done in the last months. It's about IoT temperature measurement device. I will do some live demo here. I stripped it down a little bit that it fits into our time that we have available today. And I hope that we will also have some time at the end to, that you can ask some questions. So let's start by looking at the hardware that I have chosen for my project. I've looked at the internet, what boards are well supported by the community. And one very promising choice was the ESP32C3 chip on the DevKit M1 board. That's what I have available here on my desk, exactly the same setup that you can see on the screen. And I've chosen it, as I said, for all the resources that you can find on the internet. You can find articles, you find libraries, everything, what it was my impression at least, everything that I need to get started. It is based on a RISC-5 processor, which basically means I don't have to work with a proprietary tool chain. I can install Klang LLVM and get my code compiled for this architecture. It comes with Wi-Fi support, which was important to me. I wanted to send out my measurements to a server, collect it, and then display it. And the hope was, if Wi-Fi is integrated into the chip, I can easily get it working. And spoiler alert, it was really the case. It was just a few lines of code. And it's pretty inexpensive. All the components that you see on the screen are about 10 euro total if you find a cheap supplier. And what is also quite important is the availability. I mean, you probably know how difficult it was to get a Raspberry Pi over the last few years. And with this board, it was no problem to order them and get them shipped. And finally, there's also a lot of storage space available on this board. I only use up to one megabyte at the moment, but it gives me the possibility to have a software update over the air at the end and also to use it to store my data that I don't have to go online for each measurement again. So what do you think is the first application that we are going to develop with a new language? It's Hello World. And that's what we are going to do now. So we create a Hello World application, compile it, link it, deploy it to the target and run it. And you will see that this is a very simple task with all the support that we get from the community already. When you install the Rust tool chain, it comes with a lot of different tools. And one quite important tool is cargo. It's like a build tool. If you come from C++, it's like CMake. It's also a dependency management tool. Again, my comparison with C++ would be Conan, for example. And it's much more. You can even create your own additions to the tool. And in this case, I installed an addition called Generate. It allows me to create a new project based on a template project. And let's have a quick look. So we can do a cargo generate. And then I have already downloaded the template to my computer. So I can use a path instead of a link to GitHub. And it's called like this. And then you specify basically what kind of project you are creating. I chose cargo. I can also select CMake, but I don't see the reason for using CMake in a Rust project. Give it a name. Hello, word. Okay. I tried this before. Hello, Rustations. And then they ask me if I want to choose the default values. It's true. Then I can select the CPU that I'm using. I can select all the expressive CPUs here. I have to see three, as I said, the RISC-5 processor. And that's it. And if we look at the Hello, Rustations project, you see there are only a few files being generated. There's the main file with the Hello world application. And the cargo.toml, which is basically the project description that you need for building it. The other parts, we don't have to look at them at the moment, but basically they configure the cross compilation tool chain. And I will not build this one now, but I will go into my presentations directory. I have also currently Hello world here, but I extended the application with a few log messages. You can see here, this is the main function. When we built this application, it has already some kind of firmware installed, which will finally initialize the board and set everything up and at the end go into my main function. And this is all that we need for the Hello world. So we ignore this line. It's just fixing up some runtime issues. It initializes the logger, and then I can send different log messages out to somewhere. And we will see where somewhere is in a second. So I put in an info message, a warning message, and an error that you can see it. So what do I have to do to compile and install it and run it? I can do it all in one step with the cargo runs. It will implicitly build the application, link it and flash it. All the tools are installed. And I select that I want to do a release build. It will be a little bit smaller and it takes less time to flash it to the target for us. Yeah, my mistake. I have to connect the USB port to my USB cable to my VM. Here we go. So I can select now TTY USB 0. And it was already compiled. That was the reason why I chose my directory. Otherwise it would download all the dependencies and compile them for us. And this takes some time. While this is downloading the program to the flash, I will briefly go to my slides and show you what it takes to install the tool chain. We won't do this now. It's already done. There's a nice project called RustUp. On the website you find the command which you can use to install all the tools that you need to start developing Rust for yourself. And for the cost compilation of the expressive, you basically need to install some dependencies that the tools need. For my Fedora VM, I had to install these parts. I had to register the USB device with Udev so that it gets detected and finally install the cargo extensions like the cargo generate plugin that I just used or the flashing tool that is being used to program the flash on the target. For the meanwhile, it has compiled, it has flashed the application to the target and booted. And it switched basically to a serial console afterwards. We get all the log messages from our application in this nice terminal. You see it's color coded. That's pretty nice. We have an indication if it's an info message, a warning and an error with color coding so you can detect your errors pretty easily. It also has some time code. So it takes about 300 milliseconds to boot and initialize everything up. And then finally after 300 milliseconds it will enter the main function where our Hello World program was. But now we want to have a look at what we are going to develop. So in this demo I want to connect the temperature sensor. I have chosen this DS18B20. It uses a one wire protocol and I have chosen it because it just uses a few pins. It needs the power supply. It needs the ground and the communication pin. And the nice thing about this sensor is you can plug multiple of them in parallel. It's a bus system and you don't have to change any hardware if you want to have two temperature measurements or three or four. I also indicated basically that you can put an external power supply to it. So for my project I put in three AA batteries on the five world and the ground pins. And finally when I have this board deployed in my garden it will run up to three weeks now. So that's pretty good. There's still room for improvement. This will come over the next weeks. By the way this is a pretty nice website where I have drawn this schematic walkbie. It basically allows you also to write small programs there in different languages and simulate it on the internet. And I should also be able to upload my image to this website and then I can simulate my Rust application with a virtual device on the internet. But I haven't tried this for my application because it's getting a little bit more complicated than Hello World. And finally when I have deployed my application I see on a small web page the temperature curve over three days here. And I also installed basically a voltage divider to monitor the battery voltage because I don't want to deep charge my batteries when they are deployed in the garden. So we will now look at what it takes to do the measurement of the temperature and output it to our console. We will do a small optimization to save energy because without that the batteries run down within one day and I don't want to change the batteries every 20 hours because this will also be sometimes at night. And finally we will connect to Wi-Fi hotspot and send the measurement out into the cloud. So since I cannot type it fast I have prepared different versions. So I will check out of the first version which is the temperature measurement and I will also do in the background already a compilation and the programming to the flash because I want to save some time for explaining the code. So this is now the main function after I have applied my changes and again it's not that big it fits on the screen. Up to here it's the same. What we need to communicate with the temperature sensor is basically first we have to get the GPIO to configure the GPIO and also to create a class or an object that communicates over the one wire protocol. So I told you before that the temperature sensor is using the one wire bus. So these three lines basically indicate that we first take the peripherals from a singleton in the framework and it's really a singleton because if we take the ownership and here comes the first rust paradigm basically we take the ownership of the peripherals that means no other part of the application can get it. So it's strictly controlled the compiler will check it. With the peripherals we can then ask them to give us a certain pin here and we ask for GPIO 3 that's the pin that I have chosen I could have chosen anything else and I hand this GPIO 3 over to a constructor that creates an input output pin out of it and that means I hand over the ownership. Again no other part of my application can use GPIO 3 anymore. It's dedicated to this input output driver and it goes a little bit further so now we have configured with this constructor we have also configured the device already we hand over this driver now to the one wire protocol again ownership concept the GPIO 3 input output driver is handed over to the one wire bus protocol and nobody else can use this driver and reconfigure it to some other function anymore. So this is all that we need to communicate over the one wire bus then I created an endless loop here we have an embedded device and this is the main function you wouldn't do anything meaningful afterwards anyway so in this loop we do a temperature measurement and then we go to sleep for 10 seconds. You can briefly have a look if this is now up and running oh no it asks for the USB port. Okay so there's now also a Rust feature which is kind of called pattern matching you might know it from functional languages if you had some experience to that before I basically do the temperature measurement in a function and this function returns a result a result in Rust is a common return type if something can go wrong so one part of the result is either the value that you expect or it's an error and the error gives you further details what did go wrong and there are a lot of things that can go wrong for example the device could not be found there was a glitch on the bus that the reading was not correct and so instead of using if else statements here to process the result I'm now able to have different cases with this pattern matching I can say if the measurement was okay then take the measurement and send it out I can also say if there was an error but a specific error like no device found I can give out a warning that there's no device on the bus and for all other errors that catch all basically I want to catch this error in this variable arrow and I give it out as an error message because no device found might not be a hard error it might be just just a warning for us and the nice thing here with this pattern matching is if I remove the last line you see that an error appeared and the compiler told us already that I'm not covering all the cases that I should cover and this means with the last and the pattern matching you have to be comprehensive you have to cover all the cases that you are supposed to cover of course I can remove this line here with my specific no device found error it's still okay because it's still it will then be handled as a catch all error and output it as an error message so we will have a quick look at the send message first and maybe first I explain the measurement the measurement is a normal struct means it holds two values one is a device ID and one is a temperature as a float value I have marked the structure to be serializable and that's quite interesting because there's not much else needed to serialize this structure now in a format that I can send over the wire what format is still open and if I go back to the send function you see that I call celled JSON so it's the serialization deserialization library for JSON I call the function to string pretty to have nice indentation and line breaks and give it an object of the structure measurement and that's all that you need whatever structure you have now the serializable can be now serialized into JSON and I send out an info message for it going back to my measurement function that gets a little bit more complicated you might have seen that the call to this function looks a little bit different than the lines above it so I call the measure temperature function and hand over the one wire bus for the bus to work on but since we are in a loop we cannot transfer the ownership to this measurement function because the first time we entered the loop we would hand over the ownership of the bus to it and the second time we go through this iteration it's not available anymore and the compiler would complain about this so we have to use a different concept which is called borrowing so we borrow this bus to this function and when the function returned this main function is the owner again of it and we can use it further on because we not only want to read from the bus but also want to write to it it has to be a mutable object that we are sharing and Rust everything is constant by default and if you do some Rust development you will experience after some time that a lot of your code is actually immutable there are only a few cases where you really need mutability and if this is not the case for your applications when you switch to Rust and there might be something that you are doing a different way than you would normally do with Rust so in C++ everything is mutable by default but what I found out is that you almost never need a mutable variable and that's a pretty nice learning from there so let's go to the measure temperature function and I basically used another library for talking to the device and can start a simultaneous measurement as I told you you can have parallel temperature sensors connected to it and this will start a measurement of all of them it needs the one-wire bus to communicate over and some time provider or delay provider because the one-wire bus does some bit manipulation it pulls the GPIO up and down and of course this has to be done for some microseconds so we need an object that provides delays in microseconds that's the ETS and in the next line you see we have to wait a little bit because the analog digital converter takes some time to do the measurement and convert it into a binary result and this also needs a time provider but that's not so nice solution for the ESP we have two time providers one which provides delays greater than 10 milliseconds and the other one lower than 10 milliseconds and if you use the wrong one it will just return and you will not see anything about it so I learned it the hard way this will take 750 milliseconds and once the measurements are done we can look on the one-wire bus for some devices and if some device was found we take the device address create an object of the temperature sensor read the data and return the measurement so I think I told you about this result type this is how you return the result type you wrap it in a K state or in a K block and then the caller knows that this is actually what I wanted and everything went well if it didn't go well for example if we didn't find a device on the bus we will not go into the F-statement and fall through and finally return an error with no device found you might have seen the question mark at the end of some lines that's also a feature of Rust where we can do an early return in an hour case so for example start simultaneous temp measurement I don't get the help at the moment start simultaneous temp measurement also returns a result and the question mark at the end of the line means if it's okay then continue if it's an error do an early return and return the error to the calling function so we don't have to handle these cases with if-else statements as we would do in other languages quite often and we do it in this line we have here in the search device a question mark this can also go wrong the creation of the object can go wrong reading sensor data so all the error handling code is basically hidden behind this question mark and that's pretty nice so let's have a look at our application on the target it's in the background it already started and we see that approximately every 10 seconds we get a new measurement and as I told you the object is serialized to JSON and printed as an info message to the console and it's quite hot in here and you can see if I touch the temperature sensor it should go up hopefully not above 40 degrees Celsius let's see and hopefully I didn't cause a shortage so it's increasing according to my body temperature let's stop here so we do the measurement now in an endless loop that means the CPU is active all the time and this port takes about 20 to 30 mA current that means my batteries will last maybe something like 10 hours and then I have to recharge them but I can do this a better way because I don't want to do the temperature measurement continuously every few seconds it's enough for me to have it every 5 minutes every 10 minutes I can ask the CPU to go into deep sleep mode and it's also supported by the framework interesting that it takes so long to check out my versions okay so back in the source code there's the main function there's only a few changes basically that the loop has disappeared so we initialize the one wire bus then we do the measurement as before and then we don't loop anymore but we call a deep sleep function that I have implemented here for 9 seconds and what happens with the deep sleep is that everything gets powered down on the board except for the real-time clock the timers are configured and after 9 seconds the real-time clock will tell the CPU to wake up again and for the deep sleep it means it goes through the reset and boots from scratch so the function deep sleep is also pretty simple I specified as return value on an exclamation mark that's a special signal to the Rust compiler that this function will never return and this is the case for our deep sleep we print out a log message and then we call some unsafe code who has heard about unsafe code and Rust okay, thanks so unsafe code means basically that this code block will not be verified by the compiler for undefined behavior ESP deep sleep is actually a C function that comes from the framework and it's pretty simple to call a C function so the language bindings are working quite nicely but the compiler cannot check what's inside the C function so I have to put this into an unsafe block if I would remove it I get a warning from the compiler or an error that this is unsafe to do it and I should put a block around it and it's more or less just a marker for other developers if they see this code they know there's something happening inside that will not be checked by the compiler so the developers have to take special care that this is working nicely so let's run this okay, there was a glitch on the USB cable it's reconnected and while it's flashing I want to show you another thing that I didn't mention before so I was talking about the peripherals and that we take them taking the peripherals does not return a result because there's nothing that can go wrong but taking the peripherals is something that only works once so you get an optional result that means the first time you get some peripherals and the second time you would call this function you get no peripherals and that's the option that's a little bit different from the ROAS and we don't handle this now with the next question mark but we have to take the values out so when we call peripherals take we get some peripherals back and we have to extract the peripherals out of the sum and you can do this for example with the unwrap unwrap calls should not appear that often in your code and you should know when to use them because it means if there is none it will panic and reboot the CPU but we are sure in our application here that we can take the peripherals because we are actually the first one to take them a quick look and you see it already flashed the application to the board did a temperature measurement and then it goes to power down mode for 9 seconds and you can already see that it boots from scratch again and the timing starts by 0 and with this simple change I already got my temperature locker to run for up to 3 weeks and the shorter I get this basically the uptime the longer it will last and now we go to the final part which is sending the values out to the cloud which was fast and I will also directly start it so I explain in a second what I just did so the Wi-Fi code as it is part of the chip it is well supported by the framework that means there are not many steps that I have to do to initialize the Wi-Fi I basically initialize it with the right pins from the peripherals so I hand over the modem part to the Wi-Fi driver again ownership moves to this driver nobody else can use the Wi-Fi anymore unless we give it the Wi-Fi driver then we start it inside this function there is just a small state machine which powers up the Wi-Fi module and it waits until it is connected with the connection parameters and finally it will return when it is connected to the hotspot and then I do a hard-coded delay here which we probably want to replace by something more stable because I want to wait until the DHPC server has given us an IP address before I can send out anything we do the temperature measurement as before and afterwards we wait two seconds again again something to be replaced for productive code but we want to make sure that everything is sent out from the buffers and not being kept in the buffers and not sent over the Wi-Fi and then we stop the Wi-Fi and go to sleep mode that's actually something you have to make sure that the Wi-Fi module is powered down and everything is in correct state before you go to the deep sleep the send function has changed a little bit in the send function we still have the serialization to a JSON message we put it to the console but we also send it now to a UDP socket you see in this function it doesn't know anything about Wi-Fi it just uses the network working functions and connects to some IP address and port 1337 and sends our message over the wire one thing that I have used here is this environment macro so I asked the compiler to resolve the IP address at compile time so I don't want to check in the IP address of the server I have done the same thing for the SSID and the Wi-Fi password I don't want to have this available on github especially for my private ones and that's what we need for Wi-Fi communication we have a quick look at it it looks good, we get some log messages for the Wi-Fi it basically connected to the conference network here with the password and clear text on the log messages that's fine and then it sends out the message but where did it end up? let's have a quick look at the config file you see I have configured the environment variable here in the config file, you can also do it on the console for yourself and this is the IP address of a virtual machine at AWS that I have set up for this case let's have a quick look if the messages arrive so I do it a little bit bigger so we do a netcat listen to UDP 1337 and hopefully within 10 seconds we should now get a temperature reading from this room so who is counting? here it is so we have now a fully developed IoT temperature sensor in Rust there's not much code that I have developed it was about 100 lines of code of course I struggled a little bit with the borough checker and with the type checker but for good reasons at the beginning I used a different temperature sensor before I found this one and the external library that I have used it was not up to date it was not conforming to the interfaces of the embedded layers on the hull and so basically the compiler guided me with complex error messages through my fixing process it was not that easy it looks like C++ template error messages at the beginning until you get familiar but at the end the compiler was completely right that I was missing something, there was a type mismatch and it should be a tool for you and not something you should fight against so all these language features that some people are afraid of unsafe code like borough checker, lifetimes strong type checking they are tools and you might struggle with them it's a steep learning curve but once you have mastered it basically you know that your code is more correct than before okay just one thing that I wanted to show you about cargo so I told you that I used some external libraries for the temperature sensor and for the bus system with cargo you can also search for libraries so if I know that I'm looking for library for the one-wire bus I can ask cargo search and then it actually found in the registry the libraries that I used in this project so it's the one-wire bus itself and it's already the temperature sensor library as well and if I want to use this in my project I can just say cargo add and probably they complain because I use it already but it's as easy as this for fast prototyping you can directly use the cargo tool to find the libraries that you want to use you can add this to the project and the next time I do a cargo run it will download the library, compile it and link it to my application that's pretty nice but for productive code again you have to check the stability of this library if there are any CVEs available and if it's maintained okay that's what I wanted to show you in this demo I think we have something like five minutes left for questions if you have any and if you have any I was asked that you can go over to the microphone that the people online can listen to them is this possible? it's over there thanks for the talk does it work? hello? so the delay function was called freeartos.delayMS so is there a freeartos running and how big is your firmware image? yep very good observation so what I have used let's go back to the slides briefly so what I have used I generated the project based on the ESP-IDF template and IDF is the IoT development framework from Expressive and it's basically a pretty high abstraction layer that we have here so we have the Wi-Fi support baked in here we have all this timing functions in there and part of the IDF is freeartos so if I use this I have freeartos running and that's why I pre-compiled it, it takes some time you can also use if you want to go into more dangerous parts you can create a project from the ESP template and then you have to go really low level then you have to look for some external libraries for Wi-Fi support and then you have to do more initialization like turning off the watchdog at the beginning because watchdog is enabled by default that your application doesn't reset after a certain amount of time so I forgot to mention this at the beginning so we are using the IDF template which gives us a higher abstraction layer but also some nice features and for prototyping I would recommend this and one special feature that we get here is that we are able to use the standard library and without the IDF you wouldn't have much dynamic memory allocation for example so you have to pre-allocate everything for your own I hope this answers your question next please in your last slide you showed the search for the single wire libraries and things and it only showed the Dallas temperature sensor is this the only thing I get in the internet for the single wire or is it because you installed it no, that's basically all it found in the registry there might be there might be more if they are written in a different way but what you can also see it found the library for the DS18B20 and probably it's referencing to the one wire bus and some metadata so there is some kind of more extensive search than just comparing the string but that's all that I found online and I have to say they are not up to date at the moment so when I started this project two months ago they were working directly with the embedded hull, that's the other crate which comes automatically with the project but the embedded hull updated some interfaces and this library was not updated yet so I hope this will happen in the next days or weeks next please is it possible to use an ascent runtime like Tokyo on an embedded device instead of free RTOS yeah, that's also a very interesting part so the steps from here would be that I want to look into embedded frameworks we have embassy for example is a very popular one which provides an ascent framework then we can use the ascent programming style and it also gives us some kind of higher abstraction from the CPU so my hope is that we can write embedded applications based on embassy and then also be able to be more flexible in switching the hardware below it, that's something I have to try out and haven't yet but that's definitely one of the next steps so this is pretty simple my example just to show you what other things I wanted to look at another thing is UI there is a library called SLINT which you probably can use for e-ink displays up to complex color displays with touch screens and I wanted also to integrate the UI as more e-ink displays with this device okay, next question please so as we have seen in your example when dealing with legacy applications legacy libraries you end up with unsafe blocks and in more complex projects my fear is that you end up having a lot of unsafe sections basically losing a lot of the security benefits of Rust itself do you have any experience or any gut feeling on how much that really hurts in real life having those unsafe blocks or so I think I have used the unsafe block because I was interfacing with the library written in C that's probably the use case that you are thinking of if you are working on the level that I am working here that's pretty on a high application level you shouldn't have basically any unsafe blocks if you are not interfacing with other languages if you would use the ESP template without the IDF support you probably have a lot more because then you have to dereference some pointers to the memory map registers I have done a couple more applications in this context one is the back end collecting the data in the cloud and then I have REST API that I have developed so I don't have any unsafe code at all so my expectation is if we do productive code and have a certain abstraction layer from the hardware we shouldn't have any unsafe code anymore I mean what I have also done is I wrapped the unsafe code into a function so that the caller of the function doesn't have to use unsafe anymore because I tell the user I have developed this small function very deliberately I checked that it's working correctly you have an addition used string type in your measurement struct there which is the heap allocated type so basically does cargo have some kind of tool to inspect your memory usage basically on the stack or the heap that's something I actually don't know it's something I wanted to find out I hope that I get something like this with embassy the only thing that I have is basically the image size at the moment for the flashing but I have no idea how much of the RAM is being used this board has 300 kilobytes something like that then I have two comments to the last questions with respect to monitoring the flash usage there is for example cargo bloat which you can use for expecting your binary and get the individual sizes of the symbols there is also some stack analysis stuff around but not fully operational at the moment and a comment to the question before is that especially if you use the unsafe for going deep sleep the ESPE EDF is currently the Rust support is currently a pretty moving target and especially deep sleep support is currently on the way of getting standardised in the ESPE HAL which will then go down that you can use save code for going deep sleep in the ESPE EDF hall or in the bare metal HAL okay so then at the end I won't have any unsafe code anymore that's the target hi I have a question regarding the interrupt context because you mentioned that in the main function you initialised the peripheral and in typical embedded C when you go to interrupt you need to access the peripheral can you explain briefly how this is handled no no actually I haven't dealt with interrupts in Rust yet that's something to look at if you want to go really deeply embedded if you want to save a lot of flash footprint for example and memory usage this is more like an application prototype for rapid prototyping and I can't answer that today okay thanks welcome any more questions then thank you for attending this presentation I hope I could show you a little bit that Rust applications are not that difficult you can do some rapid prototyping if you use the correct frameworks and if somebody is interesting to trying it out there is there's the code available on github exactly as I have shown it you can contact me on LinkedIn if you have any questions and finally if somebody has an idea of what he wants to develop and has some time I have two boards with me come to me give me some feedback what you like what you didn't like what you want to do with these boards then you can take them with you and program your own embedded IoT device thanks a lot