Nobel (code-generator) creates a REST API for your Arduino board, based on a SWAGGER or RAML definition.
Nobel scaffolds an Arduino application that exposes a REST API. Then, you can write the logic for interacting with your physical devices inside methods that will be executed when the corresponding URL is invoked.
Considering the following Swagger code:
swagger: "2.0"
info:
version: "0.0.1"
title: NobelTestingAPI
paths:
/servo:
post:
description: |
Moves the servo to the specified angle.
parameters:
- name: angle
in: body
description: angle object
required: true
schema:
$ref: '#/definitions/Angle'
responses:
200:
description: Successful response
schema:
$ref: '#/definitions/Angle'
put:
description: |
Moves the servo buy Adding the specified angle (could be negative)
parameters:
- name: angle
in: body
description: angle object
required: true
schema:
$ref: '#/definitions/Angle'
responses:
200:
description: Successful response
schema:
$ref: '#/definitions/Angle'
get:
description: |
Returns the current servo angle
responses:
200:
description: Successful response
schema:
$ref: '#/definitions/Angle'
definitions:
Angle:
description: Task object
properties:
angle:
type: integer
description: task object name
required:
- angle
or the following RAML code
#%RAML 0.8
title: NobelTestingAPI
/servo:
post:
description: |
Moves the servo to the specified angle.
body:
application/json:
example: |
{"angle": 71}
put:
description: |
Moves the servo buy Adding the specified angle (could be negative)
body:
application/json:
example: |
{"angle": -10}
get:
description: |
Returns the current servo angle
responses:
200:
body:
application/json:
example: |
{ "angle": 71 }
Nobel generates a project with several files (following the Arduino specs for splitting a program). One of the files contains the Handlers, where you can write your own code. Associated to the RAML example:
// Handlers
void servoHandler(WebServer &server, WebServer::ConnectionType verb, String uriParams, String queryParams) {
switch (verb)
{
case WebServer::POST:
server.httpSuccess();
break;
case WebServer::PUT:
server.httpSuccess();
break;
case WebServer::GET:
server.httpSuccess();
break;
default:
server.httpFail();
}
}
- NodeJS
- NPM
- Arduino development environment (This is not needed to run Nobel, but will be needed to make something useful with it).
Installing Nobel is really simple.
- Open a terminal/command line
- type
npm install -g nobel
... That's all you need.
Nobel is a Command Line Interface (CLI), which means that you will be executing it from a terminal/command line (I don't see a clear value on building a GUI for this).
nobel -s [your_swagger_or_raml_file] -n [your_project_name]
The line above shows the minimum parameters set you need to specify in order to scaffold an Arduino Application using Nobel. The result is:
.
└── myArduinoRobot
├── A_functions.ino
├── B_definitions.ino
├── C_handlers.ino
├── D_initialization.ino
└── myArduinoRobot.ino
You can find a description of each file in the following sections.
Argument | Required | Description |
---|---|---|
-s --source | YES | The Swagger or RAML file describing the API to implement on the Arduino program. |
-n --name | YES | The name of your project. Nobel will create a folder with this name, and one main project file inside named like this. |
-t --target | NO | The target directory where the project will be placed. If not specified, the project will be created on the folder where you are running Nobel. |
-il --installLibraries | NO | If specified, it installs the required Arduino libraries on the folder you specify. Click here to figure out where this folder is. |
-h --help | NO | Really? Yes, the user manual will be printed in the terminal/command line |
- You just need to open the project with the Arduino IDE (you can use your preferred one, but all this project has been made using the Official Arduino IDE). Important: If you are installing libraries, you will need to restart the Arduino IDE for those to be recognized (I can't do anything about it. Arduino IDE has some tricky parts. Get over it ;)).
- Compile: If it's not working, something is wrong. Nobel is thought to scaffold a right-away working application (that won't do anything until you put your code on it). If anything goes wrong at anytime, create an issue
- Put your own code!
Coding in Arduino is really a lot of fun (despite of/becouse of/but/your choice) it presents some challenges. The main one here: Memory. There could be better ways of implementing the code Nobel creates, but I haven't found it yet (feel free of making your own suggestions. That won't hurt my feelings at all). Because of this, I needed to hardcode some values (yes, shame on me). You only need to worry about it if you need to manually add new resources. If not:
- [your_project_name].ino: Main file. It only includes the libraries required by all of the other files. No need to touch at all.
- A_functions.ino: Contains functions that will be used by the application to parse and route the URLs to the proper handlers (It is not that simple to put these into a library, thanks for asking).
- B_definitions.ino: You will probably need to define some variables that you will use later in a global way. Even though you will probably initialize these
on
D_initialization.ino
, you will use these inC_Handlers.ino
, and, since Arduino concatenates in alphabetical order (excepting for the main file), not declaring the variables here, will result in errors. - C_Handlers.ino: You will write your logic here!!! A function will be generated per each result, and a SWITCH CASE inside of it, to determine the HTTP verb being invoked (this could be changing in the future, supporting other code styles. For Example: One function per Resource-Verb combination)
- D_Initialization.ino: You need a place where everything starts. The
setup
andloop
functions (which Arduino calls when booting) are placed here for you to complete as needed.
Seeing the example is the best way of understanding how to code.
Note: The scaffolded application will use DHCP. You can find the code to change this in the D_Initialization.ino
file (setup
function)
Sorry! I'll try to improve this, but this far, for an MVP, it is what it is!
Don't bother reading this if you are NOT manually adding new resources.
- Since
sizeof
function is not properly working, I needed to hardcode the resources array length on the very first lin:const byte SIZE_RESOURCES = 5; // constant until resolve what's going on with sizeof
. Change it if you manually add or remove resources. - Since I'm using a FLASH_ARRAY (basically for placing big amounts of data in the program memory instead of the variables space), I can't freely use an array and pass pointers as parameters or return these. So, the array needs to be defined inside the function that will use it. ugh indeed.
FLASH_STRING_ARRAY(resources,
PSTR("/resourceA/"),
PSTR("/resourceB/"),
PSTR("/resourceC/"),
PSTR("/resourceD/"),
PSTR("/resourceE/")
// add more if needed
);
When adding one, you will also need to add the proper Handler in D_Initialization.ino
- Add a new handler:
void registerHandlers() {
handlers[0].method = &resourceAHandler;
handlers[1].method = &resourceBlHandler;
handlers[2].method = &resourceCHandler;
handlers[3].method = &resourceDHandler;
handlers[4].method = &resourceEHandler;
}
Important: The order is the way of matching a resource with it handler!!! It's much more efficient and doable than trying to do some kind of "reflection". So, make sure of adding the handlers in the same order than the resources on the previous step.
Now, a handler, is basically a pointer to a function (Nobel places these functions on C_Handlers.ino
). So, if you have &resourceAHandler
, you will need:
void resourceAHandler(WebServer &server, WebServer::ConnectionType verb, String uriParams, String queryParams) {
// Your code here (generally, start with a switch)
}
You can choose to define your API by using Swagger or RAML. It's up to you ;)
swagger: "2.0"
info:
version: "0.0.1"
title: NobelExampleAPI
paths:
/led:
post:
description: |
Turns the light on
responses:
200:
description: Successful response
delete:
description: |
Turns the light off
responses:
200:
description: Successful response
#%RAML 0.8
title: NobelExampleAPI
/led:
post:
description: Turns the light on
delete:
description: Turns the light off
and save it (the example asumes that it will be called swaggerExample.yaml
or ramlExample.raml
)
For Swagger
nobel -s swaggerExample.yaml -n ledController
For RAML
nobel -s ramlExample.raml -n ledController
For Swagger
nobel -s nobelExample.raml -n ledController -il [path_to_your_arduino_libraries_directory]
For RAML
nobel -s nobelExample.raml -n ledController -il [path_to_your_arduino_libraries_directory]
if you haven't ever installed the required libraries.
- Open the IDE.
Add this line:
byte ledPin = 9; // It's a good practice to use a variable to semantically represent your Arduino output.
Add this line in the void setup()
function
pinMode(ledPin, OUTPUT); // We are specifying that the pin 9 will be used as an output.
The function code should look like this:
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
ethStart();
registerHandlers();
webserver.setFailureCommand(&dispatch);
webserver.begin();
}
digitalWrite(ledPin, HIGH); // Turns the led on
digitalWrite(ledPin, LOW); // Turns the led off
Let's add both calls to the POST
and DELETE
methods, and the final code should look like this:
void ledHandler(WebServer &server, WebServer::ConnectionType verb, String uriParams, String queryParams) {
switch (verb)
{
case WebServer::POST:
digitalWrite(ledPin, HIGH);
server.httpSuccess();
break;
case WebServer::DELETE:
digitalWrite(ledPin, LOW);
server.httpSuccess();
break;
default:
server.httpFail();
}
}
By default, the generated code will make Arduino take an IP by using DHCP. Open a Serial Monitor to see the assigned IP. In order to test the app, use any program capable of sending HTTP messages via network. Postman extension for chrome is my favorite.
- POST http://[the_ip_DHCP_assigned]/led (should turn the led ON).
- DELETE http://[the_ip_DHCP_assigned]/led (should turn the led OFF).
You can simply Clone this repo, make the changes you want, and make a Pull Request.
If doing that, please be aware that this repo points to submodules and so, for testing purposes, you should make sure that the code on these is also cloned by running this command (after cloning): git submodule update --init --recursive
- If you are using Atom, please make sure that the following configuration is correct. If not, it will add or remove "\n" and spaces characters from your templates making the unit tests to fail
- Without any doubt, I would have quit the idea of creating this project if I didn't find a good HTTP server for Arduino. So, it wouldn't be possible at all without Webduino by sirleech. Thanks to him and his amazing job.
- 2 KB for all my variables? Where to put the resources array? In the Program Memory!!! That is possible thanks to the Flash module by mikalhart.