So, hi. My name is Alexi. I'm here today to talk about some kind of fun projects.
To introduce myself a bit more, I am working at Brooklyn, similarly to Luca.
I am doing embedded stuff. Obviously, I mostly work with embedded Linux platforms.
I'm doing canal developments.
My company is focused on all these kind of things, and especially on upstreaming and using open source.
And so, when I'm not working on those embedded Linux things, I also do a bit of hacking on some other platforms.
And so, that's actually what I want to talk to you about today.
So, the project I want to talk about is a kind of game that I have developed with a few friends.
And this is a blind test game. So, it comes with a set of hardware.
And the concept is pretty simple. There's a song playing. You have to guess the song.
And to be able to compete, you have some physical buzzers. So, this is this kind of button here.
This is handmade with some custom electronics software and all.
You have a web interface. Everyone has its own score on the screen. You have the music playing, a countdown, and everything.
So, we have developed this kind of thing with some friends. It works. We have a working implementation.
And this is a nice kind of project to have a little sandbox to play with some other technologies.
And especially with this buzzer thing. So, what I'm calling buzzer is the physical button here.
And this one is very simple. In the end, this is an ESP32, those inexpensive chips with some Wi-Fi in there.
And I have a few peripherals to use. I have a buzzer button. This is basically a button that I have to detect.
Some LED and some Wi-Fi connection to what I'm calling an NBC here. This is a controller.
Access acting as an access point. This is nothing more than a Raspberry Pi.
And so, the goal of this project here for this part is to create the whole firmware for this button to connect to this controller.
And notify the controller about any time someone presses the button so that the player can play.
So, as I mentioned, there is already a working implementation. We've been playing with friends with colleagues, even.
And here the challenge is to rewrite this. It is written in C. And since the firmware is kind of small, it is perfectly fine to take some time to rewrite it in rest and to experiment in rest.
But more specifically, in no STD rest, as I am doing some MD Linux, I am more interested in no STD here.
That means that I won't have all the fancy tools and types that I have with the standard rest library.
So, that's what I've been doing in the recent weeks. I have now a working implementation again, but with rest.
And I'm here today just to let you know about my feedback, where did I struggle, what did work well, what did not.
And just a bit of disclaimer, this one fixed all the bugs in the firmware. This won't make it blazingly fast. This is not the goal here.
This is mostly about learning some rest and playing with rest.
All right, so let's do it. The first step is to switch from our C environment to ready to work rest environments.
And this is pretty simple. Before doing that, I need to know what I need to write my firmware.
Here's the main feature is to be able to use some peripherals on the platform and to use the Wi-Fi interface embedded in the chip.
To do that, we have an ecosystem ready to use an open source one. This is ESP HL maintained by Spressif if I'm not wrong.
And in there, more specifically, we have ESP radio. This is the one which will let us use the Wi-Fi stack in the chip.
It comes with a few constraints, obviously, to be able to use this ESP radio stack. We need ESP HL, but we need it with some specific features which are considered unstable.
So that's a few things that we have to enable in our project configuration.
We need some alok crate. I have mentioned no STD. We are already bringing in some alok, but we need it to be able to use some Wi-Fi.
We need the ESP HL's glue and so on. And why we are at it? Let's bring some other crates, for example, to be able to write some ACing code.
In this case, I brought an embassy because this is pretty well supported with ESP HL.
And so I will be able to split all my architecture between different tasks, for example.
So let's start with that. We need to bring up our environments. This is pretty straightforward.
I have to install the rest of the chain with RESTAP. From there, I can install this ESP Generator.
This is a template tool that will just create the basic skeleton fire projects.
It is highly configurable. You can decide to bring embassy or not to bring alok or not.
You can even do something very minimal without even using one file with this.
It will generate everything. You can switch to this directory and bam, you are ready to run some code.
What does it look like? So I have taken the basic example generated by this ESP Generator.
I won't focus too much about the details on all the code I will show. It will be way too long.
But basically here, we already have a main task here, which is receiving a spawner.
From this spawner, I will be able to spawn all my tasks in my code.
We have a few no SDD details here. We see on top no SDD. We are asking not to search for a main function.
We have some specific linking to perform. This is already on-door for us.
In our main task, we start getting a handle on the peripheral.
This is some nice rest abstraction to have a single-tone on all the big mutable hardware we have on our platform.
Some basic initialization. We see some example about manipulating Wi-Fi.
Then some infinite loop in which we start seeing some weights intact.
That's how I will be playing with a single.
Before diving into the implementation, just a word about embassy.
So what's exactly embassy? This is a runtime, an async runtime.
When you want to perform some async code, you need a runtime providing, for example, an executor.
So embassy is one of those.
It comes with a few attributes here that allows me to declare some functions as task.
So those tasks will be able to run concurrently to my main function, for example.
So I will be able to write concurrent code.
For example, here I have a dummy task, which will print a message every five seconds.
While the rest of the code is running.
All of this is a cooperative scheduling.
All right, we have the basic tooling. We can start implementing things.
So the very first thing I want to do is to make sure that my button, my buzzer,
is able to connect to my controller, the embassy thing, acting as a Wi-Fi access point.
And to do that, let's not dig too much into the code, but basically I can retrieve some Wi-Fi objects.
I can declare a configuration in which I will set some SSAD password.
And then I have some API to wait for the connection.
First, wait for the controller to be started.
And then wait for the connection.
You see that here, I am already moving all of this into a dedicated task.
So I can define a task that will involve the monitoring of my Wi-Fi connection.
I have also this small task here, that is kind of monitoring.
I am using embassy next, which is a stack providing TCP, for example.
And so I need something to run the stack in a dedicated task.
So that's why I have this net task on top.
On the main function, I can start sponing my task.
So you see that I receive this sponer here.
And I am using it to start my task.
So my Wi-Fi task, my embassy net stack task, and so on.
And in the main task, I can wait for the connection task to have finished finish this job.
So for example, waiting to have received a 9pv4 configuration.
All right, simple, and we are connected.
Is it maybe not?
Why that?
The first time I tried to run this kind of code, it crashed miserably and systematically.
And things were quite weird, because we have the panic message pointing and some ESP runs this, whatever it means.
Function code would be a Cisco table, not implemented.
And a kind of broken backtrace.
And the weird thing is that it happens systematically.
But it happens only when I try to connect the button to my custom access point.
If I try to connect it to my ISP router, for example, at own, it works.
So fortunately, for this, it is reproducible with the basic ESP HL example.
That's the test worth doing, because maybe I've done something stupid in my code.
But here, no, I can reproduce it with some basic example.
So the next logic thing to do is to call for help in ESP HL.
So I have started feeling a bug, giving as many details as I could.
And I've started discussing with who I assume to be as perceived employees.
People maintaining this ESP HL crates.
And we did a few back and forth, and it allowed me to understand few things about this ESP chip that I did not get at all.
This is what's happening.
I have my custom firmware in the flash.
I have some run code.
And what happened is that from time to time, my firmware is calling into some run code function.
That's not outstanding.
That's something that's perfectly fine in embedded systems.
That happens.
I suspect that this is done for some Wi-Fi features from the Wi-Fi stack.
But the thing I did not suspect is that the run code can also call some functions into my flash.
And this is where things will go wrong.
This is kind of smart.
If you do not provide a Cisco table to the run code, it should for bugs to some default things.
But there was a table defined in ESP HL.
And this table was not filled.
There were some entries in there that just caused a straight panic.
So that's the panic message we have seen.
So after understanding things, those things, after doing a few tests on my own.
But not understanding a thing about what was writing.
I've let the adults do the code and make some commits.
So they have implemented a get-reads in this school table.
My luck re-entrant free run trunks.
And from there, finally my browser is able to connect my custom access point.
So if one of you has this GitHub under, I owe you a bear.
Thank you.
Okay, so we have a basic wireless connection.
Unfortunately, this is fixed on the main branch on ESP HL.
ESP HL has not been released yet with those fixes.
But fortunately, cargo is able to handle that.
So you can open your cargo.tomail.
Create this patch.crites.io thingy here.
And you set some specific remotes.
And once your crates have been updated upstream, you can get rid of this.
All right, we have a Wi-Fi connection working.
I have a small TCP server running on my controller.
This is actually a WebSocket server.
So let's try to connect to this server.
I am using the embassy net crates.
This one provides object like TCP sockets, which are very useful.
So I can create this TCP sockets.
Set some time-out keep alive.
Set will be on the automatically via the stack.
Set the target.
So we have the IP address of my controller here.
And try to connect.
And then in my main loop, I can check for any data that is coming from the controller.
And so.
Pretty easy, right?
Is it?
Once again.
We have to split things a bit.
And we won't be able to just handle everything in the main task.
Why that?
Because we are breaking this in rest.
And so we have some ownership to rest always.
This TCP socket object can be owned by only one task.
So what I'm planning to do at this step is to move this socket into a dedicated stack, a dedicated task.
And if anyone wants somewhere in my firmware to send a message,
it can't access directly to the socket.
It is owned already by another task.
So I have to set up this kind of channels thinking between my tasks to exchange commands to send or data and statuses received by the button.
So let's try to implement that.
I create the ethernose task, socket task.
In there.
And you see here that as parameters, I'm providing channels.
It is, in fact, only one side of a channel that I'm providing.
The other side will be used by all the tasks.
And I have the main loop.
We see here some nice constructs based on the icing framework I am using.
I am doing select on two different features.
In fact, all the icing functions I am using right now are features.
They return features.
And so I have to pull those features.
And to do that, I can use select, for example, to pull on multiple features.
And by doing that, the first features that we trigger, will make the select return.
And it will return and either this either enum, if I remember correctly.
We'll let me know about which future triggered first.
So from there, in my task, I am pulling both the TCP socket for incoming data.
And the TX channel, in case someone want to send data through this socket.
And then the corresponding code on the main side.
If I want to send a message, I use the channel.
All right, simple.
Nope.
This is where I've started to wrestle with a dark beast called the compiler.
If, like me, you are a C developer only sings rust from far.
This is where you will start having goosebumps.
Because there is some lifetime issues and things like that.
It made me scratch my head quite some time.
So what's happening basically here?
I am moving things between tasks.
Some things have reference to other things.
And so rest wants to know about left times of those references to make sure that it is valid.
So let's try to navigate in all of this.
Here, I am being told that I am just missing references.
All right, let's try to apply something here.
Applying anonymous left times.
So that's this code underscore thing.
And not enough.
Now I'm being told, okay, there is a left time.
So that you are passing to a task must have a static left time.
Which means it must live for all program duration.
Fine.
I can change the prototype and good static.
Not too enough.
The object you are passing are not static.
Yeah, obviously I am creating them in the main task.
Okay.
So let's create global viable that will not leave long enough.
Not good enough.
And from there, I started to stop understanding.
We have those phantom data thingy here.
I am not strong enough in rest.
But I've searched some embassy examples, some ESPHL examples.
And apparently the good thing to do here is to wrap your data structures in some static cells,
which will allocate memory for this data at real time.
But you can still declare this data initialized this data at random.
So this is what is done in this TX in it, RX in it.
And from there, yes, we can compile.
We did not even test the code yet.
Okay.
So we assume to have working TCP connection.
We don't have a better able to send a buzz notification yet.
We have to read a button.
And fortunately this one is quite simple.
We can benefit from ESPHL.
We just need to configure and read a GPIO.
And the nice thing here is that it handles everything for us.
This wait for falling head here.
It automatically configures the relevant interrupts.
Wait for the interrupt.
These are both the interactive ones.
It has triggered.
And that's it.
And from there, I can send them a set for the dedicated channel.
So that it goes through my TCP socket.
Well, it's almost straightforward.
You have to undo some bouncing on the GPIO, for example.
All right.
We have a button.
We are missing one thing.
There is an LED in our button.
This LED is useful, for example, to blink in a specific color.
So that each player can remember about its thing.
The LED I have chosen because I had some in my code.
It's some WS2812.
This is not an LED where you just apply a voltage on the pin.
This needs a specific serial protocol to be manipulated.
And fortunately on ESPHL32, we have a specific peripheral called Aranti.
This one is used for remotes, for TVs, for doors, and all.
And you can disable a modulation on this and use it as a controller for your specific LED protocol.
And I do not even have to write it on my own.
I can just check on crates.io.
Find these smart LEDs crates, and it tells us everything.
Please give me an ARMT envelope.
And then I will give you API to drive your LED.
So that's why we are doing here.
We are instantiating this ARMT peripheral here.
And feeding it to the crates.
Then I have this LED object, and I can use some right traits on it
to apply a specific color.
Boom, done.
All right.
We are missing one thing.
We need to exchange messages with the controller.
We have set up the medium.
We need to define the protocol.
And here the protocol is JSON.
We exchange JSON message.
Anytime there is a buzz, I send a JSON message.
Anytime the controller wants to set a color on my button.
I receive a JSON message.
And so that would be very nice.
Not to have to select this ARMT messages on our own.
And that's where Rust is very good.
We have this set of crates here.
This is amazing.
How does it work?
This is a general crate providing traits.
And a standard ARMT laser, this ARMT laser for famous protocols.
JSON, C-BOR, CSV, and so on.
You can even write your own password.
But you have multiple crates that you can use.
You have the main one, giving the main traits.
You have a JSON for example, which is the one implementing the actual password.
But here this one depends on STD.
I don't want to write STD code.
I don't want to build STD in my family.
I can somehow disable STD in there.
But then I need to enable LOC.
Sure.
We have LOC.
But I would like to try without LOC.
And so there is this set of JSON code that is working without STD without LOC.
It is slightly less powerful, but perfectly enough for what I want to do.
To use it, cargo add the crates.
And this is amazing.
I have my JSON on the left.
I just have to write the corresponding strict and use the corresponding attributes in my code.
So I declare that the pattern can have a type.
It can have GTAs.
In those details, I have integrals, floats and all.
You see here, those derived macros here, setting the using the macros from STD.
And then when I receive a message on my TCP socket, I just have to use this from STD.
With this nice little turbo-fish thing, saying that I want to deserialize a message LED pattern.
And boom, I have a strict.
I have all the details of the command inside there.
If I want to send a message, I build a structure with a relevant field.
And I call it the other way around.
To slice, please store it in a buffer.
And I can push this buffer into my socket.
Final detail.
Some messages are slightly different, but that would be a shame to redefine my messages just for a small difference.
For example, I can ask to switch the LED off.
I don't have details in this case.
No problem.
I can deserialize options in rest.
I am turning my details field here into option.
And so when I do from slice, I may or may not have some details.
And then it's up to me to perform the relevant behavior on this.
So, after wrestling with lifetimes and compiler issues, this one was very nice.
All right.
I even have a few minutes to perform a small demo.
I have the buzzer. I have the controller here.
I will pour it up.
The controller.
First demo was nice enough to even make a t-shirt in sync with my talk.
So, let's play the controller.
So, this one will host all the logic for the game.
Meaning the Wi-Fi access points.
The small web server, TCP servers, the game interface and all.
I will open a console here.
I will switch to my project directory.
So, here I have all my rest code.
The base has been generated with ESP Generates.
We won't take a look at the code.
This is not the interesting part.
What I will rather do is take my buzzer here.
Plug USB-C.
So, the chip I have selected as USB-C, which is nice.
Plug the other end on my computer where I can.
So, I hope everything is booty.
I will type Kaggle Run.
So, this one will refresh my ESP chip.
And it will also start monitoring the logs.
I made it on the serial line through USB.
So, that's really useful in there.
I will even provide this small ESP log equal my crate to have the debug logs output.
So, let's do this.
No serial.
Oh.
Brace for the demo effect.
Okay.
So, the flash was super fast because it is comparing what is already flashed.
And I had the proper firmware.
And so, here we see the logs in my thing here.
We have to push a button here to make the controller start.
And the first log we are seeing are coming from the dedicated Wi-Fi task.
For example, trying to connect to my controller.
So, it is not started yet.
It should be in a few seconds.
In the meantime, I will try to open a browser.
And access nbc.local slash admin.
So, this is a web page or state on the controller.
We see that the browser has managed to connect.
And so, we have some logs from my JSON passing module here.
Telling that it has received some patterns command.
And so, I will try to show at the same time if I erase you in the game.
Here, and I try to pair the button.
You will see that when I press the button.
There are multiple things happening on the browser side.
The browser has sent an identifiable message.
And we have received some specific pattern.
Please start doing a wave pattern so that the team knows about that the browser is ready.
And so, from there, we can start to play.
And thanks to this whole tooling, I can monitor finally and debug finally all the messages
exchange between my browser and the controller.
So, that's it for the demo.
We won't have time to play a whole game obviously.
Two clothes on this.
I have nvp working.
There are a few things that I want to clarify on this project.
The binary is quite fat currently.
And that's a shame for a no SD program.
We have to improve rest in geometric use,
error management and all.
If you are interested in the project, you have the GitHub link here.
Do not hesitate to take a look to criticize the code or to send PS.
Thank you.