Embedding Angular in the physical world

How to prototype your $3.2 billion dollar business on Angular in 20 minutes

Today’s article is based upon the ngConf talk presented January 2014. See the video at the end of this article

We’re flexing our imagination and extending the reach of our Angular application deep into hardware. In this post, we’re going to be looking at C++ code, the built-in language we need to use to program the Arduino. Although we will discuss how each component is built in C++, this article is not a C++ tutorial.

Completed app

We’re using a fantastic open-source hardware platform: the Arduino. It is a single-board microcontroller intended to make the application of interactive objects or environments more accessible. The hardware consists of an open-source hardware board designed specifically to allow uploading and reprogramming of the chip without a chip programmer.

In essence, the Arduino is the ultimate prototyping hardware interface.

The source code for this project is available in its entirety on GitHub at https://github.com/fullstackio/ng-diuno.

Why not a plug computer or the Raspberry Pi?

Although the Raspberry Pi and other small form-factor computers are incredible tiny computers, oftentimes, their power is too much for an embedded application. For instance, powering a watch with a Raspberry Pi is likely to cause the watch to run out of power within a few hours. Additionally, strapping a Raspberry Pi to our wrists is likely to be uncomfortable.

For products like wearables, it’s often a better choice to use custom-built hardware. At the prototype stage, it can become cumbersome and expensive just to get a physical prototype ready to test.

The Arduino gives us a great environment to build and prototype hardware that will eventually become products of their own. Alongside the Arduino, Angular itself is a fantastic prototyping tool, as well, as we can rapidly achieve impressive results in our web applications. The combination of these two technologies gives us the ability to rapidly craft a hardware-based product with incredibly functional visual interaction in little time.

Angular is suitable for both prototype and production applications.

Understanding hardware

When doing any work with hardware, we need to understand a few fundamentals about how to interact with the physical components.

At its root, the Arduino is an open-source hardware platform that enables us to easily write software to prototype components in an easily manageable way. Typical Arduinos include a Programmable Integrated Circuit (PIC) that can be easily programmed and re-programmed through USB. Most Arduinos can be powered through USB, wall sockets, or batteries.

All Arduino boards include sockets where we can place different components, LEDs, potentiometers, LCD screens, sensors, etc. These sockets are referred to as pins. There are several different types of pins available on an Arduino: There are digital pins that allow us to apply a static amount of current; there are analog pins that allow us to apply a dynamic amount of current; and there are special pins. For more information about the different pin types, check out the Arduino documentation

With these pins, there are only a few actions we’re interested in managing:

Turning a pin HIGH typically means we’re applying voltage to the pin, whereas turning a pin LOW typically means we’re removing voltage from the pin. Due to how electricity works, changing the state of the pin can change how our circuits behave. For instance, if we place an LED between the ground and a digital pin, when current is not allowed through the digital pin (i.e., the pin is said to be in a LOW state), the LED will not light up; however, when current is applied to the pin, electricity will want to flow through the LED to the ground.

What we’re building

We’re going to demonstrate how to build a prototype remotely controlled temperature monitor (like the NEST) using inexpensive and open-source hardware. The total cost of our prototype project is about $85.50 (without trying very hard to find more inexpensive hardware).

In today’s project, we’re not going to dive into actually changing the temperature in a room, as the scope is focused on getting Angular and the Arduino to play well together.

The video demo

If you are interested in further information, feel free to email us at: [email protected] with any questions.

Materials

In order to build our temperature sensor prototype, we’ll need to gather the following components:

We’ve assembled our shopping list on Amazon if you want to follow along with the equipment we’re using here.

For a full image of our schematic, check out the #schematic later in the article.

Designing our system

Our system will require several components to be able to deliver the application from the Arduino:

  1. An HTTP server on the Arduino
  2. A way to deliver the Angular app
  3. A method for communicating to the Angular app from the Arduino
  4. A method for communicating to the Arduino from the Angular app

In building our system, we’ll need to be able to communicate between the Arduino and the web browser. From the Arduino, we’ll need to deliver the Angular app to the browser requesting the Angular app. Additionally, we’ll need to be able to send statuses of pins through to the Angular app.

From the opposite side, we’ll need to be able to modify the pin states on the Arduino from the Angular app. When we click a button, we’ll want the Arduino to respond. In this article, we’ll explore how to manage both of these types of interactions using Angular.

Although we’re using Angular in this article, many of the techniques presented here to interact with the Arduino can be applied by any JavaScript framework.

Designing the Angular delivery

In order to deliver an Angular app through the Arduino, we’ll need to be able to send the app to the browser. We have several different options of how to deliver the source of our apps. We can either:

It’s impractical to inline our entire Angular application with the Arduino source code, as it will quickly fill up the entire programmable space (for instance, the Arduino UNO board has only 32kb of space for its control code). This approach only makes sense when delivering tiny JavaScript applications. We can rule this out as an approach to building our application.

Since we’re prototyping the application, we want to easily be able to modify the code and the running system as we write it. This requirement makes writing to an SD card less than ideal for writing our Angular app in prototyping phase.

When we’re ready to deliver the final system, we can store the entire application on an SD card if the system calls for it. For instance, if we have a product that we don’t want to expose to the entire web, we can store all necessary content locally on the SD card and be completely disconnected from the Internet.

Since we’re prototyping our temperature sensor, we will allow the browser to fetch its dependencies like normal, rather than store them entirely on the Arduino. That is, we’ll send HTML back to the browser that tells it to retrieve its dependencies off-site.

Schematic

The full schematic of our completed prototype will look like the following:

Arduino with Schematic

Getting started with the Arduino

Once we have an Arduino available, we’ll need to have a way to communicate to the Arduino over USB. We’ll use the Arduino IDE to apply software to the PIC. The Arduino IDE is freely available online through the Arduino homepage here. Any time that we refer to C code, we’re talking about the code that we’ll write in our Arduino IDE.

Let’s test our our Arduino to get started. Inside our Arduino IDE, copy and paste the following code:

1
2
3
4
5
6
7
8
9
10
11
12
int led = 13; void setup() { pinMode(led, OUTPUT); } void loop() { digitalWrite(led, HIGH); delay(1000); digitalWrite(led, LOW); delay(1000); }

This is the sample app called Blink that’s distributed with the Arduino IDE.

To test our Arduino, we’ll place an LED with the long side of the LED in the ground pin and the short side in the pin labeled 13.

Click the upload button.

The IDE will compile the code first. If there is nothing wrong with the code then it will upload it to the Arduino, connected by USB. If the Arduino successfully receives the new sketch, then we should see the LED connected in pin 13 blink on and off, waiting a second between each blink.

If the software doesn’t compile, check to make sure there are no stray marks in the code. Oftentimes, the problem is that we simply forgot a ; or added one too many }.

If the sketch doesn’t upload to the board, then check to make sure the cable is connected properly. Check to make sure the device shows up on the computer. We might need to change the programmer or the serial board (both of these can be found in the Tools menu bar item). Check to make sure the Serial port is set to the Arduino device (if on a Linux or OSX machine, these will show up as /dev/tty.xxxx where the xxxx is a name that indicates the a port).

Using the Arduino Uno, we use the programmer as AVR ISP.

Let’s create a new sketch that we’ll use to create our Arduino app. Add the following base content to our sketch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <SPI.h> #include <Flash.h> #include <OneWire.h> #define DEBUG 1 void setup() { // Our setup code goes here #if DEBUG Serial.begin(9600); #endif } void loop() { // Our loop code goes here }

We’ve included a DEBUG flag in our code so that, as we’re developing our Arduino app, we can compile the code with or without debugging statements easily.

When we want to include debugging statements, we’ll set the DEBUG flag to 1, as in above. When we’re ready to deploy the application, we can turn the DEBUG flag to 0 and reduce the size and latency of the application in practice.

Connecting to the net

In order to actually get our Arduino on the net, we have to use either an ethernet shield (or breakout board) or a wifi shield (or breakout board). When using the ethernet shield, we need to be connected to a router that’s accessible by a computer.

Ethernet shield

When using the ethernet shield, the code to get connected online is incredibly simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Set the MAC address static uint8_t mac[] = { 0xDD,0xAD,0xBE,0xAF,0xFD,0xED }; // Set an IP for our network byte ip[] = { 10, 0, 1, 67 }; void setup() { // ... #if DEBUG Serial.print(F("Setting up the Ethernet card...\n")); #endif Ethernet.begin(mac, ip); // ... }

Provided we’ve created an IP that’s available on the network, when we boot our Arduino it will be accessible by the IP we’ve set above.

We can also use DHCP to allow the network to give us an available IP. The only change we need to take care of in the code is to pass the Ethernet.begin() function only a single argument of the MAC address.

1
2
3
4
5
6
7
8
9
10
11
// Set the MAC address static uint8_t mac[] = { 0xDD,0xAD,0xBE,0xAF,0xFD,0xED }; void setup() { // ... #if DEBUG Serial.print(F("Setting up the Ethernet card...\n")); #endif Ethernet.begin(mac); // ... }

Note that using DHCP instead of setting a static IP will increase the size of the sketch dramatically. It’s usually better to set the IP as a static IP on the network to save on this space.

WiFi shield

If we want to use a wifi shield or breakout board instead of the ethernet shield, we’ll need to be aware of the size of our sketch. When using the Adafruit CC3000, we’ll need to include the Adafruit library.

Grab the library at http://learn.adafruit.com/adafruit-cc3000-wifi/cc3000-library-software and download it. The library can be installed as we described above.

The CC3000 is fairly heavyweight, in terms of the compiled library size, so when using it, we’ll need to make sure we have enough space to run our actual application.

To set up the CC3000 to fetch an IP from the network, the above code stretches out to:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#define ADAFRUIT_CC3000_IRQ 3 #define ADAFRUIT_CC3000_VBAT 5 #define ADAFRUIT_CC3000_CS 10 Adafruit_CC3000 cc3000 = Adafruit_CC3000(ADAFRUIT_CC3000_CS, ADAFRUIT_CC3000_IRQ, ADAFRUIT_CC3000_VBAT, SPI_CLOCK_DIV2); #define WLAN_SSID "myWireless" #define WLAN_PASS "p4ssw0rd" // Security can be WLAN_SEC_UNSEC, WLAN_SEC_WEP, WLAN_SEC_WPA or WLAN_SEC_WPA2 #define WLAN_SECURITY WLAN_SEC_WPA2 void setup() { if (!cc3000.begin()) { Serial.println(F("Unable to initialise the CC3000! Check your wiring?")); while(1); } /* Delete any old connection data on the module */ Serial.println(F("\nDeleting old connection profiles")); if (!cc3000.deleteProfiles()) { Serial.println(F("Failed!")); while(1); } char *ssid = WLAN_SSID; // Max 32 chars Serial.print(F("\nAttempting to connect to ")); Serial.println(ssid); if (!cc3000.connectToAP(WLAN_SSID, WLAN_PASS, WLAN_SECURITY)) { Serial.println(F("Failed!")); while(1); } Serial.println(F("Connected!")); // Wait for DHCP to complete Serial.println(F("Request DHCP")); while (!cc3000.checkDHCP()) { delay(100); // ToDo: Insert a DHCP timeout! } }

For the duration of this article, we’ll be using an ethernet shield to connect to the network.

Getting started with the Angular app

As we progress through our application, we’ll want to implement the Angular components and the Arduino (hardware) side. We’re going to be using the yeoman toolchain to write our Angular app.

Although we can use any toolchain to write our app, we do need a server running to fetch the Angular app. It’s possible to write the app using a tool like Lineman or even just using a basic file server.

To install Yeoman, ensure that Node and NPM are installed. If they are not, grab the installation package at: nodejs.org. Once you have them, install Yeoman like so:

1
$ npm install -g yo

Next, we’ll need to include the Angular generator, a generator that was written to create a professional Angular app.

1
$ npm install -g generator-angular

We’ll use this generator to create our Angular app. Let’s create a directory where we’ll store our Angular app, then run the generator inside the directory, as follows:

1
2
$ mkdir app && cd $_ $ yo angular myTemp

The generator will ask a few questions, then generate the application. For our purposes, the defaults are fine.

Once the generator is complete, we’ll run the Angular app using the command: grunt serve. This command will launch the server and open our web browser. Note the address that the browser opens as: http://localhost:9000. We’ll use this URL in a bit.

Running the HTTP server

Now that both of our systems are ready to go, we can start implementing our Arduino web server. Writing a full-blown HTTP server in c++ can be pretty complex. Luckily, the Arduino team has done a fantastic job of packaging up relevant TCP/UDP socket libraries in the built-in Ethernet.h library.

The simplest web server using the Ethernet.h library might look something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void loop() { // listen for incoming clients EthernetClient client = server.available(); if (client) { while (client.connected()) { if (client.available()) { char c = client.read(); if (c == '\n' && currentLineIsBlank) { client.println("HTTP/1.1 200 OK"); client.println("Content-Type: text/html"); client.println("Connection: close"); client.println(); client.println("<html><body><h1>Hi from the Arduino</h1></body></html>"); break; } if (c == '\n') { currentLineIsBlank = true; } else if (c != '\r') { currentLineIsBlank = false; } } } delay(1); client.stop(); } }

When trying to map HTTP request methods and URL routes, our code can become complex and ugly VERY quickly.

Rather than implement the web server ourselves, we’ll use a library, called TinyWebServer, that abstracts a lot of the ugliness away.

The TinyWebServer library allows us to create a map of request URLs and HTTP methods to point to C functions to handle the request. For instance, when writing our index route, (the base route that responds when we request the root: /), we’ll map the URL to the / route as a GET request and point it to a function we’ll write in C.

Implementing TinyWebServer

To start using the TinyWebServer library, we need to install it in our Arduino IDE. Luckily, installation is easy. Download the TinyWebServer from https://github.com/ovidiucp/TinyWebServer. In our Arduino IDE, we’ll click on the Sketch menu item and click Import Library->Add Library. We’ll need to find the library on our system and select it within the new dialog box.

We’ll also need to include the Ethernet.h library along with the TinyWebServer library. In the Arduino sketch that we created previously, let’s add the lines that create the mapping to our index route:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <SPI.h> #include <Flash.h> #include <TinyWebServer.h> #define DEBUG 1 // index handler boolean index_handler(TinyWebServer& web_server) { return true; } // We need to end the list of handlers // with a NULL object TinyWebServer::PathHandler handlers[] = { {"/", TinyWebServer::GET, &index_handler }, {NULL}, }; // Headers to retrieve. This list needs // to be closed with a NULL object const char* headers[] = {NULL}; // Create the TinyWebServer handler TinyWebServer web = TinyWebServer(handlers, headers); void setup() { // Our setup code goes here #if DEBUG Serial.begin(9600); #endif } void loop() { // Process the next request in the loop web.process(); }

With this updated code, we have our map of routes and HTTP method requests. When a request comes in, the TinyWebServer handler we created, called web, will process the request on the next loop cycle.

If there is a request, the web.process() function (in the loop() function above) will block until the request is finished being handled.

Note that the handlers and headers lists need to end with a NULL object.

Every function that we define as a handler for a route will take a single parameter: the address of the TinyWebServer request object created through the client HTTP request. They are expected to return a boolean value that defines whether or not the request should be closed. For every one of our requests, we’ll return true, which tells TinyWebServer to close the connection.

We can return false if we want to manage closing the connection manually. For instance, if we wanted to keep the connection around for handling Server sent events, for example, we would return false and manage the client connection through the raw Ethernet Client object.

Why headers[]

The TinyWebServer library is written to be efficient and do only as much work is needed to handle request mapping. Thus, in order to get any headers from our request, we need to tell our instance what headers we are interested in keeping around so that it only does enough work to handle those headers.

Sending back HTML

When a GET request comes in at the / route, the web process will call the index_handler function. From inside this function, we have access to the TinyWebServer object. This TinyWebServer object allows us to interact with the raw HTTP request at a high level.

To send data back from the HTTP request, we can use the print method on the web_server TinyWebServer object. Since we are sending HTML back to the client, we can simply write it directly to the response object.

To send back a simple, blank HTML page with a title, we can write it out using the following code:

1
2
3
4
5
6
7
boolean index_handler(TinyWebServer& web_server) { web_server.print(F("<html><head>")); web_server.print(F("<title>Temperature sensor</title>")); web_server.print(F("</head><body>")); web_server.print(F("</body></html>")); return true; }

We are using the F() function to tell the Arduino compiler to store the constant strings in flash memory, rather than in SRAM. Since there are orders of magnitude more space available in flash memory (otherwise known as the program space) on the Arduino, it’s usually a good practice to get in the habit of storing constant strings in Flash.

One limitation when using flash memory: We cannot manipulate variables in the flash memory, as it’s considered static memory.

When we compile and run this sketch on the Arduino and connect to it in our web browser, we’ll get a blank page with a title of “Temperature sensor.”

Getting Angular involved

Up until this point, we haven’t touched Angular. Let’s get Angular involved. Since we have control of the HTML that is passed back from the Arduino, we can use this flow to pass in an external script to be loaded by the browser. We’ll use a single script that will dynamically place the Angular app on the page.

First, we need to tell the C program where our external host that is hosting our Angular app. Since we’re developing our application alongside the Arduino, we can set this to be local to our host network.

In production, we’ll want to deploy our code to an external web server and use a DNS name to fetch the script.

It’s also possible to use a DNS name to develop locally by changing our router settings to point a DNS lookup to our local server.

We’ll use this host to inject a <script> tag in our HTML in the index router:

1
2
3
4
5
6
7
8
9
10
11
const char *HOST = "10.0.1.2:9000"; // ... boolean index_handler(TinyWebServer& web_server) { web_server.print(F("<html><head><title>Temperature sensor</title></head>")); web_server.print(F("<body>")); web_server.print(F("<script id=\"appscript\" src=\"http://")); web_server.print(HOST); web_server.print(F("/scripts/scripts.js\"></script>")); web_server.print(F("</body></html>")); return true; }

When we load our browser page with our updated index handler, the browser will attempt to load the HOST + /scripts/scripts.js file. In order to prototype locally, we’ll need to launch our Yeoman server using the grunt serve command and fetch the new page from the Arduino.

Embedding Angular

Now that we have control over the script that the DOM will load, how do we get Angular on the page?

We’re going to load everything dynamically onto the page. This method is akin to how Facebook provides its JavaScript SDK API to third-party sites. We’ll add our styles and scripts using the scripts.js file.

We’ll need to use this script to inject all of our dependencies. We’ll also add the ng-app directive to the <body> tag on the fly and set up a directive that will encapsulate our entire view.

In order to fetch the external scripts on our page, we’ll need to request the external libraries from the same host domain. We’ll use a regular expression to extract the host from our original script on the page:

1
2
3
4
5
6
7
8
9
'use strict'; (function() { var scriptTag = document.getElementsByTagName('script')[0]; var matches = scriptTag.src.match(/^http[s]?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i); var host = matches[0]; var body = document.getElementsByTagName('body')[0]; var head = document.getElementsByTagName('head')[0]; })();

Next let’s create two helper functions that will help us place a <link> tag and a <script> tag on our page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
'use strict'; (function() { // script tag var createLinkTag = function(src) { var link = document.createElement('link'); link.setAttribute('rel', 'stylesheet'); link.setAttribute('type', 'text/css'); link.setAttribute('href', host + '/' + src); head.appendChild(link); }; var createScriptTag = function(src, async) { var script = document.createElement('script'); script.setAttribute('src', host + '/' + src); script.async = async || false; body.appendChild(script); };

With these two functions, we can place our files on the page. First, we’ll set up our link tags on the head element (using the above function) and then our Angular dependencies. Finally, we’ll load our app-specific JavaScript files:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
'use strict'; (function() { // ... // Create the link styles createLinkTag('bower_components/sass-bootstrap/dist/css/bootstrap.css'); createLinkTag('styles/main.css'); // Setup the angular dependencies createScriptTag('bower_components/angular/angular.js'); createScriptTag('bower_components/d3/d3.min.js'); var arr = [ 'scripts/modules/arduino.js', 'scripts/app.js', 'scripts/services/d3.js', 'scripts/directives/mainview.js', 'scripts/directives/temperatureGauge.js' ]; // Load the app files arr.forEach(function(src) { createScriptTag(src); }); // ... })();

Lastly, we’ll need to dynamically assign the ng-app directive to the body element and attach the main-view directive:

1
2
3
4
5
6
7
8
9
10
11
12
'use strict'; (function() { // ... body.setAttribute('ng-app', 'myApp'); var app = document.createElement('div'); var main = document.createElement('div'); main.setAttribute('main-view', ''); app.appendChild(main); body.appendChild(app); })();

When our browser fetches this script, we’ll have a fully featured running Angular app.

We have a bit of cleanup that we need to do to ensure that the views can be read from the hosting server and are able to communicate to the hosted server.

First, we need to set the $sce, or the Strict Contextual Escaping service, to be able to load from the hosting URL. Without this step, we’ll get a very confusing error saying that the Angular app does not have the proper permissions to load the templates.

1
2
3
4
5
6
7
angular.module('myApp', []) .config(function($sceDelegateProvider, HOSTED_URL) { $sceDelegateProvider.resourceUrlWhitelist([ 'self', HOSTED_URL() + '**' ]); });

Secondly, we’ll need to inject the loaded URL from the <script> tag. We can do this a number of ways, but using a constant will ensure that the value is always set at the start of the Angular app:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
angular.module('myApp', [ 'fsArduino' ]) .constant('HOSTED_URL', function() { try { var scriptTag = document.getElementById('appscript'); var matches = scriptTag.src.match(/^http[s]?\:\/\/([^\/?#]+)(?:[#]?|$)/i); return matches[0]; } catch(e) { // Default, just in case return 'http://localhost:9000'; } }) .config(function($sceDelegateProvider, HOSTED_URL) { $sceDelegateProvider.resourceUrlWhitelist([ 'self', HOSTED_URL() + '**' ]); });

Interacting with the Arduino

Now that we have Angular loading through the Arduino HTTP server, we can start to interact with the board through our Angular app. Before we can actually start to interact with the Arduino from the Angular app, we’ll need to know the Arduino’s IP.

Since we know the IP from the Arduino code, we can use the same approach to inject the IP from inside the index handler using a <script> tag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
byte ip[] = { 10, 0, 1, 32 }; // ... boolean index_handler(TinyWebServer& web_server) { web_server.print(F("<html><head><title>Temperature sensor</title></head>")); web_server.print(F("<body>")); web_server.print(F("<script>window.ip=\"")); web_server.print(ip_to_str(ip)); web_server.print(F("\"</script>")); web_server.print(F("<script id=\"appscript\" src=\"http://")); web_server.print(HOST); web_server.print(F("/scripts/scripts.js\"></script>")); web_server.print(F("</body></html>")); return true; } // ... const char* ip_to_str(const uint8_t* ipAddr) { static char buf[16]; sprintf(buf, "%d.%d.%d.%d\0", ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3]); return buf; }

With the Arduino IP located on the window object, we can reach it from inside the Angular app.

The Arduino module

We’ll use a module called fsArduino to contain the interactions between our Angular app and the Arduino. Inside this module, we’ll set up bindings that allow us to interact with the Arduino without needing to know the underlying data structure of how we communicate with it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'use strict'; angular.module('fsArduino', []) .constant('ARDUINO_URL', 'http://' + window.ip) .factory('Arduino', function(ARDUINO_URL, $http) { var service = { getPins: function() { // Define getPins }, setPins: function(pins) { // Define setPins } }; return service; });

We’ll use this Arduino service to get status from, and to set pins on, the Arduino. Note: We’re using the constant() of ARDUINO_URL to store the Arduino’s IP.

Getting pin status

We can get the status of a pin using the service method getPins(), as we’ve defined above. Since we’re interacting with the Arduino over HTTP, we can simply use the $http service to make a standard HTTP call to our Arduino.

1
2
3
4
5
6
7
8
9
10
11
12
// ... var service = { getPins: function() { return $http({ method: 'GET', url: ARDUINO_URL + '/pins' }).then(function(data) { return data.data; }); }, // ...

Back on the Arduino side, we need to create a route that will receive this request and respond with the status of the pins.

Using the TinyWebServer library handlers array, we can set up our mapping to a C function to handle the request.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
boolean pins_handler(TinyWebServer& web_server) { web_server.send_error_code(200); web_server.send_content_type("application/javascript"); web_server.end_headers(); // Handle sending back JSON from the current // state of the pins return true; } TinyWebServer::PathHandler handlers[] = { {"/pins", TinyWebServer::GET, &pins_handler}, {"/", TinyWebServer::GET, &index_handler }, {NULL}, };

We now have a function that will respond to the web request when called, but we have no data being sent back yet. To do that, we’ll need to create a helper function that can respond with JSON back to the request.

Since we’re using C, a statically typed language, it’s pretty difficult to handle the schema-less JSON data type. Since JSON is just text, we can manually create the JSON as a string and send it back as a response.

To handle this functionality, we’ll create a new function that we’ll call pinsToString(). As a helper function, we’ve also wrapped the entire pin handling functionality into its own object. As the source code for this entire article is available, we won’t describe specifically how the pin object works. For more information, see the pins.h source at: https://github.com/fullstackio/ng-diuno/blob/master/arduino/sketch_dec30a/pin.h.

Iterating over our pins

Before we are able to send out the pin status, we’ll iterate over every pin that we’re interested in. As we need to keep track of the interesting pins, we’ll need to create a structure to hold onto all of the pins we want to query and modify.

1
2
3
4
5
6
7
const int numPins = 3; Pin pins[numPins] = { Pin(9, OUTPUT_T), Pin(8, OUTPUT_T), Pin(7, ONEWIRE), }; OneWire ds(7);

With our pins array, we can walk through each pin and interact with each individually. We’ll create a function that will handle updating the state of each pin:

1
2
3
4
// Iterate over pins and update each of their states void UpdatePinsState() { for(int i=0; i<numPins; i++){ pins[i].getState(); } }

With the UpdatePinsState() function, we will walk through and collect the status of each pin, storing the updated status on the pin object itself.

Finally, we can walk through the pins and write our JSON object back from the pinsToString() function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
boolean pins_handler(TinyWebServer& web_server) { web_server.send_error_code(200); web_server.send_content_type("application/javascript"); web_server.end_headers(); pinsToString(web_server); return true; } // pinsToString() iterates over the pins and // writes out the corresponding JSON object using // the web request bool pinsToString(TinyWebServer& web_server) { UpdatePinsState(); web_server.print(F("{\"pins\":[")); int len = numPins; for(int i=0; i<len; i++){ web_server.print(F("{\"pin\":")); web_server.print(pins[i].getPin()); web_server.print(F(",\"value\":")); web_server.print(pins[i].getState()); web_server.print(F("}")); if ((i+1) < len) web_server.print(F(",")); } web_server.print(F("]}")); return true; }

Modifying the pin state

Now, when interacting in the other direction, where we want the Angular app to modify the state of the pins, we’ll need to be able to send updated information back to the Arduino. As we previously mentioned, it’s difficult to work with a schema-less JSON object inside the strictly statically typed C language.

Although there are some great libraries developed specifically to handle JSON from C and the Arduino (aJson), the parsing functionality is not always accurate and is very brittle in practice.

Rather than depend upon the JSON being parsed into a single object, we can create our own protocol to talk between the two parties.

At the heart of it, JSON is simply a method of wrapping data in a meaningful structure that is easily understood by both the human eye and JavaScript. We can create our own protocol that can take the place of JSON, but will still communicate the same information.

For instance, we can turn the 24-byte-long JSON object:

1
2
{ pin: 7, action: 'getTemp' } // 24 characters long

into the 4-byte-long ASCII string:

1
p7a0

and still achieve the same result when the Arduino receives the information and parses it.

Using JavaScript, it’s fairly trivial to create a function that can turn our JSON string into the shorter, mini-protocol version of the request data. We can even still interact with the Arduino service as though we were sending the JSON through to the Arduino, using our Arduino service to modify the request.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
angular.module('fsArduino', []) .factory('Arduino', function(ARDUINO_URL, $http) { var actions = { 'getTemp': 0 }; var actionifyPins = function(pins) { var str = ''; for (var i = 0; i < pins.length; i++) { var p = pins[i]; str += 'p' + p.pin; if (typeof(p.mode) !== 'undefined') {str += 'm' + p.mode;} if (typeof(p.value) !== 'undefined') {str += 'v' + p.value;} if (typeof(p.action) !== 'undefined') {str += 'a' + actions[p.action];} } return str; }; // ...

Now, our actionifyPins function will turn the JSON objects we send it into the proper string for our Arduino to handle on the back end. We can use this function in our setPins() function from inside our Arduino service.

We’ll also send the length of the string along with our request as a header. This point is important: We’ll need to be explicit in handling the request on the Arduino.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var service = { getPins: function() { // ... }, setPins: function(pins) { var strAction = actionifyPins(pins); return $http({ method: 'POST', url: ARDUINO_URL + '/pins/update', data: strAction, headers: {'X-Action-Len': strAction.length} }).then(function(data) { return data.data; }); } }; return service; });

Handling the POST request on the Arduino

In order to handle the POST request on the Arduino, we’ll modify the handlers[] array again and add a new route to handle it. In this case, since we’re interested in the X-Action-Len header, we’ll need to add the header string to the headers[] array. This step tells the TinyWebServer that we’re interested in using it in our requests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
boolean digital_pin_handler(TinyWebServer& web_server) { // Handle POST request return true; } TinyWebServer::PathHandler handlers[] = { {"/pins/update", TinyWebServer::POST, &digital_pin_handler}, {"/pins", TinyWebServer::GET, &pins_handler}, {"/", TinyWebServer::GET, &index_handler }, {NULL}, } const char* headers[] = { "X-Action-Len", NULL };

In order to handle the POST request, we’ll need to fetch the length of the string we want to parse from the request. We can do that by requesting the header value from the request object.

1
2
3
4
5
boolean digital_pin_handler(TinyWebServer& web_server) { const char* action_str_len = web_server.get_header_value("X-Action-Len"); int len = atoi(action_str_len); // ... }

Given a known data length, we can pull off the full set of data from our request.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
boolean digital_pin_handler(TinyWebServer& web_server) { const char* action_str_len = web_server.get_header_value("X-Action-Len"); int len = atoi(action_str_len); char* data = (char*)malloc(len); if (data) memset(data, 0, len); get_request_data(web_server, len, data); // The request data is now available in the // data variable } void get_request_data(TinyWebServer& web_server, int length_str, char* str) { char ch; Client& client = web_server.get_client(); if(client.available()) { for(int i=0; i<length_str; i++){ ch = (char)client.read(); if (ch != NULL && ch != '\n') { str[i] = ch; } } } str[length_str] = '\0'; }

Now that we have the request data available to us in the data variable, we can iterate over the data and take actions based upon the parsed data:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
enum ActionTypes { GETTEMP }; boolean digital_pin_handler(TinyWebServer& web_server) { // ... int sLen = strlen(data); int pinInt, valueInt, actionInt; ActionTypes actionT; float currTemp; int i = 0; while(i < sLen) { if (data[i] == 'p') { // We're handling a new pin pinInt = (int)(data[i++] - '0'); Pin *p = select_pin(pinInt); while(data[i++] != 'p' && i < sLen) { switch(data[i]) { case 'v': // Handle setting the value break; case 'a': i++; // move to the next character actionInt = (int)(data[i] - '0'); actionT = (ActionTypes)(actionInt); switch(actionT) { case GETTEMP: currTemp = getTemp(ds); p->setCurrentValue(currTemp); break; } break; // ... } } } } free(data); web_server.send_error_code(200); web_server.send_content_type("application/json"); web_Server.end_headers(); pinsToString(web_server); return true; } // ... Pin *select_pin(uint8_t pinNumber) { for(int i=0; i<numPins; i++){ if (pins[i].getPin() == pinNumber) return &pins[i]; } return NULL; }

We did not include the entire source in this post for that action; essentially, we’re walking through each of the characters in our POST data string and taking action on each one. In the case of the above demo string p7a0, we’ll parse through to get the pin number using the p character and pulling off the number of the string and selecting the pin.

Until we reach a new p, we’re going to walk through the string and look for actions, values, or modes to set. We’ll come across the a characters and then attempt to map the action by number to the intended action.

In this case, since we’re using enum to set the action types, we can reasonably set the action to be the first in the ActionTypes enum:

1
2
3
enum ActionTypes { GETTEMP };

This works because the ‘getTemp’ action in our Arduino service (in JavaScript) is set as the index of 0:

1
2
3
var actions = { 'getTemp': 0 };

After that, the only component left missing is to send the response as a JSON object; we can use the pinsToString() method to handle sending back the JSON of the pins.

Polling the temperature

How do we get the temperature of the DS18S20 temperature sensor? Easy! We’ll use the following function that handles reading the sensor:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
float getTemp(OneWire sensor){ //returns the temperature from one DS18S20 in DEG Fahrenheit byte data[12]; byte addr[8]; float celsius, fahrenheit; if ( !sensor.search(addr)) { //no more sensors on chain, reset search sensor.reset_search(); return -1000; } if ( OneWire::crc8( addr, 7) != addr[7]) { Serial.println("CRC is not valid!"); return -1000; } if ( addr[0] != 0x10 && addr[0] != 0x28) { Serial.print("Device is not recognized"); return -1000; } sensor.reset(); sensor.select(addr); sensor.write(0x44,1); // start conversion, with parasite power on at the end delay(1000); byte present = sensor.reset(); sensor.select(addr); sensor.write(0xBE); // Read Scratchpad for (int i = 0; i < 9; i++) { // we need 9 bytes data[i] = sensor.read(); } sensor.reset_search(); byte MSB = data[1]; byte LSB = data[0]; int16_t raw = (data[1] << 8) | data[0]; raw = raw << 3; if (data[7] == 0x10) { raw = (raw & 0xFFF0) + 12 - data[6]; } celsius = (float)raw / 16.0; fahrenheit = celsius * 1.8 + 32.0; // float tempRead = ((MSB << 8) | LSB); //using two's compliment // float TemperatureSum = tempRead / 16; return fahrenheit; }

With that, we’re ready to fetch and send data to and from the Angular app to be handled by the Arduino.

Interface elements

Finally, we’ll want to show a meaningful interface that shows our data in a compelling manner. Enter D3.

D3 is a dynamic data library that powers many data visualizations on the web today. It’s a fantastic framework for building visualizations of any kind. It’s the perfect tool to make a dynamic interface such as a temperature gauge. We’ll use D3 to build our interface.

First, we’ll need to create a D3 service that will make the window.d3 object accessible as an Angular object. We’ll then inject this incredibly simple service in our temperatureGauge directive:

1
2
3
4
5
6
'use strict'; angular.module('myApp') .service('D3', function D3() { return window.d3; });

In our temperatureGauge directive, we can inject the D3 service and start using it immediately. We’ll add a template and append a D3 SVG object inside of it. As we don’t intend this post as a tutorial on D3, we’ve shown the code in its entirety below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
'use strict'; angular.module('myApp') .directive('temperatureGauge', function(D3) { return { template: '<div class="thermometer"><svg></svg></div>', replace: true, scope: { 'ngModel': '=' }, restrict: 'EA', link: function (scope, element, attrs) { var el = D3.select('.thermometer'), w = attrs.width || el.node().clientWidth, h = attrs.height || el.node().clientHeight, r = Math.min(w, h) / 2, pi = Math.PI; var svg = el.select('svg') .attr('width', w) .attr('height', h) .append('g') .attr('transform', 'translate(' + w/2 + ',' + h/2 + ')'); var pie = D3.layout.pie() .startAngle(-pi/1.2) .endAngle(pi/1.2); var arc = D3.svg.arc() .innerRadius(r * 0.7) .outerRadius(r * 0.9); svg.selectAll('path.degree') .data(pie(D3.range(90) .map(function() { return 1; }))) .enter() .append('path') .attr('class', 'degree') .attr('d', arc); var handle = svg.append('g'); handle.append('rect') .attr('class', 'handle') .attr('width', 50) .attr('height', 5) .attr('y', -2.5) .attr('x', r - 65); var scale = D3.scale.linear() .domain([65,85]) .range([-180, 0]); var display = svg.append('text') .text('72') .attr('y', 25); scope.$watch('ngModel', function(n) { if (!n) { return; } handle.transition().duration(1000) .attr('transform', 'rotate('+scale(n)+')'); display.datum(n) .transition() .duration(1000) .tween('text', function(d) { var i = D3.interpolate(this.textContent, d); return function(t) { this.textContent = Math.round(i(t)); }; }); }); } }; });

For more information on using Angular and D3 together, check out our book at https://leanpub.com/d3angularjs. We give an in-depth introduction to D3 and demonstrate the best practices for using it with AngularJS.

Conclusion

Above all else, we hope this post has inspired you to explore how we can use the power of Angular to engage with the physical world – we’re not limited to the browser.

If you want to become an AngularJS expert today, check out our book at ng-book.com. It’s the most feature-complete AngularJS book available on the market today. Catering to beginners and experts alike, ng-book will help you become an Angular expert.

Video

ngConf slides

Get the weekly email all focused on AngularJS. Sign up below to receive the weekly email and exclusive content.
We will never send you spam and it's a cinch to unsubscribe.

Download a free sample of the ng-book: The Complete Book on AngularJS

ng-book: The Complete Book on AngularJS is the canonical AngularJS book available today.

It's free, so just enter your email address and the PDF will be sent directly to your inbox. Mailchimp can take up to an hour to deliver the free sample chapter, but if you don't receive it within the hour, send us an email and we'll manually send them to you!

We'll send you updates about the book, when it updates and other free content.

We will never send you spam and it's a cinch to unsubscribe.

Comments

comments powered by Disqus