Christopher Coté - Arduino, Elixir And Nerves: A Deep Dive Into... - Code BEAM SF 2018

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
okay small-group good how many people have used Arduino 'z before hardware okay so it's a pretty small group so the general idea of firmata so Arduino is a small hardware control board usually about 16 megahertz runs an Atmel chip right small amount of memory and you write C code right the idea of firmata is that it's a essentially a C server that is gonna run in your microcontroller and give you a serial connection or some sort of stream connection to your host machine whether that's a nervous device Raspberry Pi your laptop whatever it is and so you can kind of do an asynchronous command control structure with your with your microcontroller so high level fermata itself is based on the MIDI protocol very simple binary protocol basically a stream of bytes which makes it perfect for Erlang and elixir to parse we'll see some really if you get nothing else out of this I mean if you're not interested in Arduino or anything like that I think they're some really elegant code that hopefully inspires you to to write some good code so yeah based on the MIDI protocol MIDI is a protocol for really for musical devices right so again having a host machine a computer or something like that and giving feedback you know like a keyboard right knowing which key you're pressing or a drum machine knowing which pad you're pressing on sending that to your computer and it can play audio or synchronize different machines along with that so it kind of makes sense it Maps pretty well to the idea of a microcontroller right your keys or different things being your digital and analog pins and then it's also got an idea of so as an example firmata - midi here right so the the the hex symbol D 0 through D a MIDI that is right your channel + on or off right so is your key press and your keyboard you know up or down in firmata that actually Maps pretty well to our digital ports right is it high and low so for a Fermata in this sense we actually use the direct mapping to map channel pressure to our digital pins now that doesn't always work right there's a lot of so default so this is from our host right messages that we can send to the microcontroller right to our little server running there it's running at about every it's configurable but by default I think it's every 19 milliseconds it does a loop so that's how often it's poling all its digital pins all its analog pins all that stuff and also looking at incoming messages for configuring stuff or or doing different things so these are all the messages that we can send well not all of them but most of all the message that we can send from our host machine to that microcontroller are most people familiar with like looking at binary protocol layouts and stuff like that right so this is pretty simple we've got our start byte so i2c right I squared C is a protocol for inter connected or inter interconnect communications so I squared C I - C III C so a lot of different sensors actuators actually support this protocol and can daisy chain them up to about 250 4 different devices in a row so very nicely if you have a limited amount of pins you can actually you know you can actually set up quite a few devices so Fermata supports this out of the box obviously this doesn't map very well to MIDI so we actually use something called sysex messages which are built into the MIDI protocol to system extension messages we have servo configuration again using our sistex command here I squared C configuration extended analogs again we can we can set that sampling interval so by default it's 19 milliseconds we can adjust it to whatever we want query the capabilities of the board so each board is slightly different right we want to know how many digital pins we have how many analog pins we have how many are capable of pulse which modulation different things like that analog mapping query which pins are analog query the firmware name and version that's actually built into the MIDI protocol yes I will show you generally a USB just some sort of serial connection right so whatever your board is going to support so right now it's four by default in the Electra client it is a UART interface right Fermanagh on the microcontroller side the current versions can basically take anything that has a stream interface within the arduino world so that could be a network interface that could be Wi-Fi anything that essentially produces an uber enumerable in the wiring or C C++ code currently on the elixir side we're just we're defaulting to UART but it could be really interesting I'm actually playing around with implementing it over nine hundred fifteen megahertz radio spectrum right so a custom Arduino board with a 915 megahertz radio a little gateway on the Raspberry Pi and then you can have multiple devices kind of all over the place and use a wireless communication and it's pretty simple I mean that the stream interface on the C++ side is very very simple so so again a bunch of different message types right and it will go through some of the the Electra code and we'll see you know exactly how easy it is to map this to to our code and then incoming so messages that are going to come from our microcontroller right little C server running there and messages that it's gonna send back to us so again this text message is the firmware you know if we've asked for what you know our capabilities or the what type of firmware it is it'll give us that our pin state capability messages coming back I to see responses and some of the protocol stuff so again so the way these these are set up right the stuff on the right here is everything that map's directly to our MIDI protocol and then the system extension messages that we've added for format or well fermata has added so yeah kind of gone over this right so we're gonna have starts this X right is at zero then we're gonna have a six command typewriter which is essentially our sub command of what type of sysex message is I've been up to 1024 seven bit characters and then the end of a message right so just a big stream of binary characters so before we get into the code full disclosure I was not the original author of the Fermata elixir library am now the maintainer I do a lot of it with nerve stuff so he hadn't touched it since elixir 1.1 so I kind of took it over for him but he's he's responsible majority the architecture and parsing I have added I squared C support string data there's an idea so before it was just one process managed everything in coming from the Fermata board I added support right so sometimes you know you get a lot of a lot of things happening a lot of a lot of sensor data coming in so now we've defaulted to whoever asks for the analog pin reporting right whatever process calls into the board and says hey I want it I want data on this pin which will send every 19 milliseconds well now get those messages back rather than having to do it so an analog pin come process mapping architecture to help you kind of scale that a little bit more I'm at an ultrasonic sensor support and neopixel support just for well a project I'm working on and they're and they're pretty general-purpose so excuse me blow-dry and I'll go in hopefully if we have time how to add custom stuff to the Fermata protocol it's pretty easy so this is our general layout here I did this Fritzing diagram last night it's very sloppy excuse me but gives the basic idea so here's kind of your you know your system over over here the stream here really maps to this USB cable right and when I was talking like hey we could throw in a radio or you could get a Wi-Fi or Ethernet shield for your your your Arduino you can go that way as well so this is the Arduino side right I to see analog-digital server or custom stuff these both happen to be custom the ultrasonic up there on the right hand corner is actually a it uses it uses a digital pin and actually just measures how long it's high or low right so we send out a trigger this is s using ultrasonic sound to measure distance right so it it sends out a ping and measures how long it takes to get back and that's really just a digital pin going high and low interest thing about that is generally that would be a blocking operation honored the Arduino right you're gonna wait for it to come back because we're running this server and we're checking all these different pins we don't want to block an Arduino so there's a library that included called new ping which essentially keeps an array of all of our ultrasonic devices or all of our pinging devices and each write each loop that 19 milliseconds will kind of keep track of which ones it said pings out for and if it's gotten a result back for that and an updated so it does it a non-blocking way pretty simple but but works pretty efficiently and then neopixel again has a low low-level library that Adafruit has provided also non-blocking but each one of those pixels is addressable they make long strips they make big rings that you can do all sorts of really cool stuff with them and those are the two I have hooked up here so we've got our nerves you are right which is again is gonna be that main interface to our to our serial connection formatted up board which is in the Fermata client and then generally what you're gonna do is for your application so from out of example is our application you're kind of kind of you know you're gonna use the from out of that board in your in your init function I'll show you that to initialize the board and kind of handle all your logic there and then we're gonna map right our eye to see analog digital server customs to our own processes within within elixir or Erlang so let's go into the Fermata board right so this is our core library here this needs to be much bigger how's that is that legible kinda I always get this question I'm using fewer code which has support for ligatures so you can do arrows like like all the you get some really nice stuff you'll you'll see it when we get into it but fira code for anybody that is asking so our first thing here right we're gonna see this Fermata we're gonna use the behavior here or macro vermont it up protocol that mix in so really all this is is giving us our you know our different message types right we saw that that mapping on the first first slides there it's a report version right if we went back to that that's 0 or you know our hex f9 system reset is FF digital message 9 0 etc etc etc we're actually using a range here for the hex values which is pretty cool it's really nice little feature there and we'll use that again for the analog messages same thing so you can see how we're exactly mapping this now when we added custom features we basically just need to find a byte or a hex value that isn't taken right within our code I mean we're gonna control that back controller right that little server on the microcontroller so we just need to know which ones aren't taken yet so here for what I to see is built-in so that's there but I added this to the Fermata library as this string did only go down a little further so here we have the custom stuff our sonar and our neopixel I basically just found some bytes that weren't taken some hex values and use those for configuring our so nars what our data is going to return and then neopixel stuff there so let's go to our initialization function of this firmata dot board very simple we're gonna open our UART again so that's kind of baked in right now didn't use nerves that you are what I'd like to do is you know pass in whatever whatever protocol we're gonna have and just make it a behavior that you have to implement certain functions and it'll work so yeah we open it so here we're gonna you know we're gonna reset the board initially so every time we start this up we want to make sure we're in a fresh state on the microcontroller so we're gonna send a reset to our microcontroller and then we're gonna just to make sure that that worked we're gonna say give us our system status right and that's gonna give us a firmware version and different things like that so that's our f9 command so we just send those right away and that gives us a you know make sure we're in a nice a nice state to start running so that was the initialization so yeah so this is where I think some of the the the really nice fun code is right so we've got our you are open and the only thing this is the function that handles all data coming into our system right there's only one function handles any UART data coming in here and this is it right super simple now obviously this is calling out somewhere right we're capturing this stuff and passing it to our firm formatted a protocol that parse you can see this register doing a reduced enum reduce on that string of binary data that we got in right so we just got this big string of data right you doing any framing we're relying on which is its what it works very well but we're relying on just kind of the default timings of the nerves UART library to kind of end that string although with our you know with our beginning system message bytes you know well we know we'll only parse one message at a time or maybe we get a partial message but as soon as the new string comes in we're gonna clear that out so yes over the serial no it's it's very very just like just pushing bytes over the over the wire yeah so okay so we're doing a reduce here right we've got our data which is just a you know a binary here state that outbox state that parser and we're gonna capture that and call dot parse on on each of those and pass our outbox and our parser to each one of those all right sorry our data and our accumulator to each of those so let's go check out from added a protocol that parse okay so we're doing just a bunch of function head mashing here right so we've got a bunch of parse functions you can see will you know we're gonna again we're gonna use our mix in here which gives us our you know our hex bytes here to to match on so we're doing some just pipe binary pattern matching here so it gets a little tricky right so we're basically we're reducing on a string of bytes so basically each each iteration of this parse is getting one bite into it right so we're gonna say okay if we're if the first byte is report version let's let's take our our parser and let's let's create a named tupple right so we're gonna take we're gonna say okay this is report version right we're it's you know a direct mapping of our binary value here so we can use the tupple report version now we don't know what the rest of the data is yet right so again we're doing that reduced so the next byte in the string we're reducing on we're gonna call parse again but we've got our in our accumulator we've already got this this name couple with a report version right so now the next function when we call that we're gonna match on what any you know any byte here you know we're just gonna assume as major but as long as we have this report version in our in our parser name tupple it's gonna match on this one so now we're gonna update our name topo report version we've got our major bite right so we've we've now our two bites into our binary string now it's gonna call it again so write that reduce is calling protocol that parse again we've got our we've got our accumulator with report version major we've got the rest of our data so now next thing that's gonna get matched right is report version major now we're gonna assume that next byte is our minor value right so this is getting a report our firmware version so now we've got it now we've got a full message right that's all that that's all that's coming along with our report version message so now we're gonna put that into our outbox right and anything else so if we maybe we got multiple commands and that one string that we've had so we're gonna write we're gonna prepend that to our outbox and then empty our parser we're done parsing that message set so let's clear out our parser accumulator and now we've got an array so let's assume that's all we get back right so now we're done we've got an out box parser is empty but out box is an array with these different right these different parsed commands that we've gotten back or maybe just one we're gonna do an enum each and just send those to ourselves right so now I've got a name tupple with the values in them that we're gonna send to ourselves right so if we look at the next function right we're not sending a message to ourselves we're gonna handle info report version major minor and here we actually after we get our version we're just immediately gonna go get a capability response right and this is again really the only time you're gonna call this is on that startup procedure right as we reset we get firmware version so now let's just go get our capability response this one's kind of unique in that it's gonna make a call built-in but this is kind of the startup procedure here we're gonna update our state with our version here and then send info and send info sends it to the originating calling process right so that Fermanagh example dot board is gonna now get a message of of this after we've done our kind of what needs to happen in the built in core library so does this so this is this right i think this is some some really really elegant code a great way to handle binary parsing does anybody have questions on this is it it's a pretty pretty clear the next one right because most of these this stuff is handled with sysex messages would be doing a very similar thing with the sysex messages right so we got our start sysex byte now these are general purpose right these are our system extension messages so it's not part of our protocol right it's not part of the the MIDI protocol these can be anything we can extend these now there's some defaults built into Fermata which we know we're gonna do a very similar thing here right we're gonna get our start we're gonna match in our start sysex byte and put that into our parser oh sorry yes yeah so we're gonna match our starts to sex byte this always confusing me because actually the next one that gets called is after the enca sex but now we're just gonna we're gonna we're gonna keep write that reduce is gonna keep calling this guy until we hit and sysex right so we don't want to this can't move up above this because well actually it probably could or no because and since x would never get mash this would always match and then sysex wouldn't so this has to be first so call this we're gonna keep filling our parser with each byte each new byte that we get in and appending it to this sysex until we hit end sucess and now we do the same well not quite the same thing now we've got our sub command we've got our sis starts to sex the next byte is always our sub command for sysex and we have all our data so if you look at our start so this is essentially the exact same thing only just for the sysex right so it's just kind of its separation concerns right we have our main protocol messages and we have our sis ex messages so here this is going to be right we're always going to hit this this is gonna be our start sysex that next byte is always our command or sub command for that sex message and then our sis ex data right and here again you know we're using our mix in right where we've declared all our different types so we've got our firmware query capability response analog mapping response I to see we actually use the range oh no my bed don't use the range stove here but this gets a little more complicated right because we're now we're dealing with like arbitrary data this could be stuff that doesn't quite fit in our seven bit or seven byte data so we you know we'd use some some bit mashing here but fiddling to to map so this is actually mapping just this this data to our ASCII characters right 32 through 126 so firmware names can only be ASCII characters obviously you can see right there so firmware crate that's gonna give a firmware name and we can dive into this a little bit how am i doing a time what we got 20 minutes oh okay okay so this is where we're gonna you know we're gonna map all that stuff if we have that much time to go into all this stuff but here you here you can see we've added sonar data right we're gonna parse that I'll show you that so this is one of those custom types right sometimes we get bad data right these sonar these sonars are really cheap and sometime just send junky data so but again we're getting a value that is quite large right it can it's in microseconds so it can be like 3,000 microseconds right if it's it's a long distance that's not gonna fit in there so we get our least significant bit most significant bit there in seven there there's seven bits so we we're gonna we're gonna move that over and then add our least significant bit and that is going to give us actually like a float actually a float value of how many microseconds that or maybe it's an integer we'll see but gives us our value there and we also get our trigger out of that right so the six messages are really easy in the way from ATO generally does it is so your sis X command and then basically each byte after that is an argument that you're passing in or receiving back and then obviously if you know right if you know you're gonna be sending big data or big data sorry babe run term if you know you're gonna be sending you know data doesn't fit on your seaside for this custom stuff you're actually going to map it to least significant bit most significant bit something like that which we do for our sonar data because we know it's going to be larger than what's going to fit in there so let's dive into let's see where I am here did that yeah so the custom from out of additions I'm just gonna pull up my notes really here really quick here cuz I've got a little command to run sorry that it's a little transparent is that still kind of legible so this is just using the diff command so now we're going to look at the C code right and just what I've added to or the sorry the C++ code on the Arduino the standard Fermata what I've added so the stuff on the right here is what I've added to add the neopixel and the and the ultrasonic range sensors right so we've got that new ping library I mentioned which is a non-blocking ping library for C++ code and then some of the Adafruit stuff to support the neopixel which is great everything else on the left is standard from odda right so our i2c definitions like I said that's all built into two standard Fermata here we can see I'm gonna do my my setup for ultrasonic you'll see you know the the pin mode I've I believe that pin mode is is built into is built into formatting I forget what it is whether it's like a I think there's a a digital timer mode for a pin that you can set and I think that's what that is so were you saying hey this digital pin we're gonna use as a timing one so we just need to measure high and low on this thing right and we use gonna use the new ping library to that the sonar config right that's 0 6 2 we can see maps to our firm out of code here right so we just have to make sure those are gonna map up match up sonar data same thing Mac's own ours that's for the C code and then our neopixel stuff right so we're neopixel data registration or setting the neopixel right is 0 7 2 setting the brightness is 7 3 and registering what type of neopixel we have is 7 4 right so again mapping back and forth very easy you just find a byte that is it used and do that and we're going to support one neopixel for this one you could have more but you then your registration and configuration could be a little more complicated this is very simple and we're gonna get some pointers to our some neopixels in the matrix right you're not using the matrix right now but like I said so now there's just lots of C code oh I actually changed the sampling interval here the new ping library had a so just I was trying to like refine the the distance sensing so I was just kind of hard coding it here you could do that from there from out of code or from the elixir side right to adjust your your timing loops or your sampling interval like I said the default is 19 I found 51 to work really well with the the the ping library in the ultrasonic sensors and again so there's you know it's custom hardware there's gonna be some some tweaking going on here so keep going down again so we're just gonna set up some of our ping variables for that new ping library trigger pins how many counters we have lots and lots of C code yeah pin mode callbacks I to see config okay so I'm gonna go back up here really quick most of the stuff this is just one big switch statement right yeah a C++ sysex callback right so this is get cut this is what gets called anytime you send a sysex message into the microcontroller right into that server sysex call back in and the registrations down you register this with the Fermata library saying hey time you get a sysex message right call this function so we're just gonna see here we're gonna we're gonna switch on those bytes again you know not quite as elegant as our elixir pattern-matching right unfortunately well this is doing a lot of the low-level right this is doing all that low-level stuff that we do not want to have to write right from out of takes care of all this right I to see or I squared C configuration servos digital analog pin mappings right timing stuff and it's well tested supported by the community totally open-source right it's I really like using it if you really really really need to optimize I recommend still starting with this and then pick apart the pieces that you need right the code is well written I mean it's one big file right but the code is really well written and so when you're saying you know if you need to bring out your I squared C stuff just grab this you know optimize and customize whatever you need and throw that into your your your code this is really nice for prototyping and I think it gets you pretty far for production as well and it's it's it's a nice protocol okay so we got this one big switch statement [Music] now we're gonna add our stuff right so we're setting these six messages so we got our sonar config we're gonna do a configuration here for our sonar x' neopixel registration neopixel addressing and like I said right we so we passing those message with the sysex messages we're up screen a little bit no okay this next message is it's always just assuming like as long as you're not doing like big floats or large integers it's just assuming like each byte is R 0 R 1 R 2 R 3 so right and that's already given to us by from out it's already done all that parsing for us so you've got R 0 is our index red green blue right to set our sort of colors and then we just called neopixels set pixel color super easy brightness same thing we're just setting one value in after in our sis X command again unless we're sending a large value than we do you know do some of that bit fiddling to make it into 7 bits past our least significant bit and most significant bit to pack it into our protocol totally ignore that that is for a something else I'm doing and here because right the sonar library look wrote we need to every every one of those loops those 51 milliseconds as we've configured it here we need to you know check write this automatically will do our i2c stuff it'll do our digital pins or analog pins or servo controls we've added a new one where we need to go and check every iteration if every every loop if we've gotten results back from our pings and and send pings if we need it five minutes perfect and so we we do that and you can see where we right we get our result and we're doing that that uh that bit fiddling there to descend our least significant bit most significant bit because we've got a large got a large integer that's not going to be fit into one byte so we do that there and and then we write that data out back over the serial line to our elixir client so I kind of went over how we do you know how we map that to write that stuff in the C code to our Fermata client on the elixir side I don't think I need to go back into that right now that thing I showed earlier so this is actually what I'm gonna do a little demo here right that that general layout here this is how I've got it laid out here this is obviously our supervision tree from observer we've got a supervisor our example board which actually gonna call for mod a dot board and then we've got our UART process down at the very answer that's actually where you know that interface is happening I've got our distance or ultrasonic sensor our neopixel UART probe and wait for devices those are kind of customized for a project I'm working on so I've got a bunch of devices I'll try to be really quick got a bunch of devices USB devices that are plugged into nerves device they all use to say all you get back when you enumerate over your your you our interfaces is the FTDI right the the chipset they're all the same FTDI totally different makers they're all the exact same FTDI chip so luckily I control the firmware I you know I'm in controller firmware so I've added a little that firm out of firm out of firmata we saw in there the string you know the serial dot right is allowing for device registration so I can open up all these you arts after I numerate them get data back and say okay this is my formatic client this is my radio gateway this is my whatever so and wait for devices is what does that this is a hydroponic system I'm working on this using firmata and obviously a little more advanced and not quite as trivial here we're doing co2 getpid controllers for co2 temperature humidity nutrient dosing pH dosing we do lighting control light readings so we've got sensors for all of that sensors for all the the water quality and then we've got cloud connections you know encrypted MQTT all that kind of stuff and that's all so we've got a hardware custom renal board hardware control that is such a dumb client and and then this is our our observer in an elixir thank you I'll give you a little demo here so I got what two minutes Shh so I didn't go through any of this code unfortunately so it's waiting for device right this is where it's sending that Fermata from out of Ramada we've registered we should be doing some pulsing so all these messages all this stuff coming like controlling this neopixel it's actually coming from a lick sir right so we just got a process that's doing really tight loops controlling this we got a little follow example and so now I'm adjusting the brightness of this based on distance from the ultrasonic sensor here so you can imagine that being a wheel and then here's a little tech ometer right so you can imagine that being a wheel right and so all of this the it's sending you know the ultrasonic sending the data into our lecture process our distance processor that's broadcasting a message actually the neopixel is just looping and calling into the other process to get its state and then the neopixel process is just writing out to to the neopixel library using that sistex command there it works thank you Oh anyway here we can see the distance here if you're not standing right there term motto musical notation and so just it's super simple it's super simple yeah and works well over you know USB slow USB serial connection musician I don't know I don't know the Fermata designer I don't know why he chose MIDI OSC right is the open sound control that's like a newer networkable protocol I'm actually contemplating like moving to the hat but yeah I have no idea why they chose MIDI from out of thing yeah I think that mapping is close right yeah yeah and and it works well over like a slow right little serial connection Oh intersect okay yeah okay yeah so so the see right so on the microcontroller we're just running this really tight loop right I mean I mean imagine like an HTTP server if you're familiar with you know like web stacks right which is essentially just runs a really really fast loop and waits for data to come in and then parses it right so that's that's what it's right on the sea side we're just running this really tight loop on the elixir side we're either sending or receiving it's an asynchronous communication protocol right so either send a message into that C server and it just looks for a byte and then does its thing and maybe sends a response if it was required so I mean you can really think of it as like you know a response or request response cycle right but very asynchronous does that make sense no yeah but it's dumb right I mean it's very abstracted it's just like you've got a general you've got a standard protocol like HTTP right or say you say a a get request is very similar to our you know a one byte message that we're sending over a serial write over a serial connection so the similar idea yeah yes I don't know what the benefit of that would be right you don't have I like my laptop so you could write nerves device have or Raspberry Pi has I to see so you could you could write an elixir server to and then allow other device you know other devices to connect to your your Raspberry Pi and use that as a micro controller you could definitely do that I would actually probably recommend getting the I think you can do this is run arduino code on your Raspberry Pi so I would just get that our do we know the standard for mounted Ino file to compile to the Raspberry Pi and just run that on the Raspberry Pi that's what you really want to do but it'd be it'd be cool you know be fun thought experiment or write uh to write it give it a try [Applause]
Info
Channel: Code Sync
Views: 2,383
Rating: undefined out of 5
Keywords: Code Beam SF, 2018, elixir, nerves
Id: sTOOjfUrQ0A
Channel Id: undefined
Length: 41min 16sec (2476 seconds)
Published: Sat Mar 17 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.