Chapter 1. Connecting Worlds

JavaScript is unique in its flexibility to run in different environments. Though the language originated in web browsers, JavaScript today drives web applications, and runs in databases and robots, too. In the latter cases, people often use the terms JavaScript and Node.js (a JavaScript runtime environment) interchangeably.

Without JavaScript, connecting devices to networks would entail writing software in three different languages. Embedded devices in particular often require C or C++, while user interfaces in web browsers require web technologies. Besides embedded devices and user interfaces, writing middleware for communication within networks might require yet another programming language.

When working in different environments, JavaScript is an interesting choice—it’s becoming the universal programming language. This chapter begins with an overview of embedded devices and connecting everything, followed by an introduction to basic JavaScript and its different runtime environments. You will also learn about the background of Node.js.

Why the Internet of Things?

On a physical level, computers are pieces of silicon with hundreds of millions (up to billions) of transistors. The transistors act as switches, either to store state or to run binary operations. In contrast to mechanical switches, transistors act on voltages and electrical signals.

Transistors are getting smaller and smaller. People refer to this phenomenon as Moore’s law. To understand how it works and where we are going, let’s look at computers under a microscope.

Transistors are made from different layers of conducting, isolating, and semi-conducting materials. All layers are added on top of a silicon substrate. The electrical properties of the substrate get altered to form transistors. On top of the transistors, there is a metal layer that allows for the formation of circuits. Figure 1-1 provides a visual explanation of layers on a silicon wafer.

njes 0101
Figure 1-1. Transistors are built by combining layers of isolating, semiconducting, and conducting materials (Drawing by A. C. Redolfi)

The shape of transistors is defined with the help of photolithography. So-called “wafer steppers” project images of transistors on a silicon wafer. These machines are one of the main driving forces behind Moore’s law.

By using decreasing wavelengths of light, it was possible to shrink transistors from micrometers in size to tens of nanometers. (It is generally believed that transistors can be shrunk to subnanometer size.)

The increased miniaturization of electronics has resulted in an unprecedented quantity of computing devices in homes, workplaces, and public spaces. It has revolutionized the way we live, travel, learn, and work.

However, until now, most devices have had few ways to directly talk to each other. The idea of an Internet of Things, or IoT, predicts that this will change dramatically in the near future, and again revolutionize modern daily life. By connecting devices, engineers are able to create smarter environments for logistics, manufacturing, and healthcare.

Connecting devices requires engineers to solve problems within hardware, software, and network protocols. Let’s look closer at embedded devices and how they communicate.

Embedded Devices

Embedded devices can have many functions. For example, they can play music, track body motion, or identify a parcel in a truck. They often make use of one or more of the building blocks shown in Figure 1-2.

njes 0102
Figure 1-2. Embedded devices consist of many different building blocks

In many cases, their core is a microcontroller unit (MCU) or microprocessor unit (MPU) that is connected to some sensors or a control panel.

Embedded devices monitor an environment or perform autonomous tasks. For monitoring, they need sensors. When acting on the physical environment (e.g., with a motor), they require actuators.

In contrast to most computers, some embedded devices operate “headless” (i.e., without a graphic display). A fire alarm, for example, has no need for a display, but might need to communicate with a network.

Many embedded devices have power constraints and limited memory for doing computations. This is why programming for embedded devices often is done with lower-level programming languages and often feels difficult.

Some of the frustrations of embedded development can be avoided by using a high-level language such as JavaScript. Previously, memory constraints (among other concerns) made JavaScript a poor choice for embedded devices. But as you will see in this book, JavaScript is becoming an interesting tool for connecting devices to the Internet.

Embedded Internet

The main difference between a “normal” embedded device and an embedded device for IoT is connectivity.

By adding links between devices, consumers and companies get new possibilities to track health, coordinate activities, monitor logistics, or improve shopping experiences.

Note

There has been a recent buzz around connected devices and the IoT. It is often overlooked that devices with network support have been evolving for a number of years. Due to Moore’s law and demand for connected hardware like smartphones (driving down component costs), connecting a great number of devices is viable for the first time outside of special realms like medical devices or space vehicles.

Working with remote devices and devices in networks is different from working with a single device. To better capture problems and differences in “systems” of connected devices, experts have created a reference model for the IoT. A simplified version with four abstraction layers is shown in Figure 1-3.

Imagine a new kind of device that plays music but also goes beyond. On the lowest abstraction level, this music player might have sensors that track your activity, motion, and (let’s say) the weather. On this “edge” level, it is all about capturing data from sensors.

njes 0103
Figure 1-3. Practical examples of the abstraction layer depicted in the “Internet of Things Reference Model”

At the next level, the transport of data is essential—for example, to report progress after a workout. But gateways in a network could also help to synchronize your preferences for music when you enter a sports club or download music after a music concert.

On top of that level, some processing happens to filter data for certain events. Typically, a microcontroller or microprocessor could process events and trigger changes in a display or request new information from other places in a network.

Finally, at the highest level, no hardware can be found anymore. Here, the main goals are about analyzing and storing data. This typically involves working with databases, data centers, and monitoring approaches.

Protocols

Links and networks are essential in developing applications for the IoT, so let’s explore this road a bit further. The Internet is mostly based on very high-level specifications and protocols. Agreeing to these standards makes interoperation between networks possible.

Figure 1-4 provides a simple diagram of the primary elements of computer networks and how they’re interconnected. On an abstract level, computer networks consist of nodes and links. Nodes can be anything from servers, to laptops, to an embedded device. Links can be anything from cables up to wireless connections.

njes 0104
Figure 1-4. Simplified view of a computer network

As programmers, we are often interested in how devices and users talk in a network. When signals hop from node to node, they follow certain rules or protocols.

Protocols are built on multiple layers. On the top of the stack, there are application-level protocols:

Hypertext Transfer Protocol (HTTP)

HTTP was first created to make the transfer of documents across computers easier. It then evolved into a more general protocol to transfer state between devices.

The WebSocket protocol

Websockets can provide a communication channel between devices. The important advantage of websockets over HTTP is that you can keep a connection open for real time communication.

Besides choices in a protocol stack, the way in which links connect—or connectivity—is important in system design. For many systems, it is important to understand the physical constraints of cables or over-the-air connections. Here are a few types of connections:

Ethernet

Going back to the origins of computer networks, Ethernet cables played an important role in connecting computers. Ethernet generally provides stable and secure links between devices, compared to wireless connections. However, the disadvantage of using a cable is that it is often inconvenient and sometimes impossible to route to where you need it.

WiFi

In contrast to Ethernet, wireless networking, or WiFi, provides much more flexibility. Wireless connections have steadily been making progress over the last decade. The login and password for a secured WiFi connection can sometimes be a challenge for getting embedded devices online. Also, data rates can suffer or be limited to smaller spaces depending on the environment of a WiFi router.

USB and serial communication

In embedded development, the Universal Serial Bus (USB) provides a convenient, wired solution for data transfer between the host and an embedded device. Generally, USB is known for its plug-and-play experience. Similar to Ethernet, USB provides a stable and secure link, and can easily transport power to devices. In practice, USB is typically used to connect peripheral devices.

Bluetooth and BLE

In some cases, you only need a wireless connection with a range of a few meters (e.g., if you want to control a light switch from your smartphone). Bluetooth provides peer-to-peer connections. The original Bluetooth protocol had high power consumption, so a new standard—Bluetooth Low Energy (BLE)—was created. Both the original Bluetooth and BLE protocols are in use today, but BLE is more common for IoT applications.

We’ll look at more protocols and ways to connect devices throughout this book. Right now it is important to remember that dealing with the IoT requires us to think about many types of abstractions: networks, nodes, links, devices, signals, and software. As a result, you need to design “systems” rather than build a single device, server, or process.

To develop a system, you must consider its subsystems and their parts. You need to ask how these connect. This requires you to work with multiple environments at once. One of this book’s goals is to show how this can be done with JavaScript.

Examples and Use Cases

Before going into technology details, let’s briefly review some common applications for connecting devices. Many technologies are currently just emerging.

One of the visionaries for moving computers into the background of our lives was Mark Weiser (1952–1999), director of the legendary Xerox PARC research laboratory. As he wrote in the Scientific American article “The Computer for the 21st Century” in 1991:

Ubiquitous computing names the third wave in computing, just now beginning. First were mainframes, each shared by lots of people. Now we are in the personal computing era, person and machine staring uneasily at each other across the desktop. Next comes ubiquitous computing, or the age of calm technology, when technology recedes into the background of our lives.

With decreasing costs of hardware and new software technologies, we’ll be able to transform everyday objects into input devices, monitors, or displays. Consider the dining room shown in Figure 1-5, for example.

njes 0105
Figure 1-5. Imagine a dining room with connected devices (photo by Mickey Destr)

If you were to start conversations with windows, chairs, tables, or lighting, what would you ask them to do? How would you tell them to behave in the mornings? How about when you have dinner with friends?

Besides having a direct impact on the physical experience of a space, microcontrollers that are connected to networks and databases could influence security and health matters from remote places by sensing your motion and activities, identifying falls, or directly changing your environment in response to external factors. We will discuss this further in Chapter 14.

That may sound like science fiction, but there are already racetracks where you can follow the motion of race cars and obtain all kinds of information about their position in the race. In addition, some car manufacturers offer an API that allows car owners to access information related to energy consumption for their vehicles.

Building these systems will require new approaches to software and hardware development. Instead of long cycles where every building block is engineered in isolation, it is often important to build entire working systems quickly. You might be programming displays, blinking LEDs, and working with sensors (all in the same system), as shown in Figure 1-6. Oh, and did we mention that you need to deal with different constraints of hardware and software, too?

When working with systems, modularity plays an important role. This concept allows you to abstract components of the system so that you are not overwhelmed by trying to build from all sides at once. The term modularity is used in many contexts, from biology to architecture as well as software development. A good example of modularity is Arduino, which we will discuss in Chapter 2. The standard interface of an Arduino Uno allows you to plug and play, for example, different shields. Devices such as Tessel, discussed later, also take modularity to heart with single-purpose swappable sensors. Modularity plays an important role in JavaScript projects too.

njes 0106
Figure 1-6. A friendship detector with a nontraditional display. Slide taken from a talk by Patrick Kalaher of Frog Design.

JavaScript for Distributed Programming

By now, you have seen a number of examples on what the IoT means when working with multiple environments at the same time. Because it can be used at so many levels, JavaScript is a promising language to tackle the problems of the IoT.

JavaScript and the IoT

Let’s now review what makes JavaScript an interesting choice for IoT development.

First, JavaScript is in wide use. A large number of web developers are familiar with it. While the language began as scripting language for web browsers, its programming model has been widely adapted for other environments too, such as web application servers or mobile web browsers.

In addition, because there is a large programming community behind it, JavaScript is well documented and there is good standardization across different implementations. From this, a strong ecosystem with many open source libraries comes as an important bonus.

Generally, JavaScript engines are high performance. Applications running on Chrome V8 can be very fast. The power of this trend can be seen by recent efforts to play 3D games in a web browser. Developments such as SIMD.js even expose high-performance computation features.

To develop user interfaces, JavaScript can be used with web technologies such as HTML5. The combination of JavaScript and HTML5 is useful for developing UI “companion apps” for IoT devices.

Last, but not least, JavaScript is well suited to embedded device programming:

  • JavaScript supports asynchronous function calls and I/O

  • Asynchronous calls are useful for event-driven hardware programming

  • Node.js provides hooks to integrate linked libraries from code that is written in C or C++; take a look at https://nodejs.org/api/addons.html and https://github.com/nodejs/nan for more details

In the next section, we will briefly review some JavaScript basics. We’ll also look at running JavaScript in a web browser as well as with Node.js. The sections to come are mainly a refresher for those who are new to JavaScript.

Hello World with JavaScript

First appearing in 1995, JavaScript was initially aimed at “nonprofessional” programmers. When it was first specified, it took constructs from a number of other languages.

Basic syntax

JavaScript is an object-oriented programming language. Its syntax is C-like, but in many cases simpler. Assigning a value to a variable is done as follows:

var foo = "Hello World.";

With this assignment, you create a variable foo that references the String "Hello World". That variable is declared by adding the var keyword in the beginning.

Statements normally end with a semicolon. The semicolon can be replaced by a comma, if you do multiple assignments as follows:

var foo = "Hello",
    bar = " World.";

The String objects in the preceding snippet are declared with double quotes. It is also possible to use single quotes to declare strings, which is common in many libraries:

var foo = 'Hello World.';

Note that JavaScript is dynamically typed. This means that, unlike C or Java, you don’t have to define the type of variable before runtime. Some examples:

var foo = 1;
var bar = 1.21;

Looking at numbers, there are no integer types in JavaScript. All numbers are floats. If you work with large numbers and want to avoid dealing with rounding problems, you can use some of the BigInt libraries in Node.js.

Higher-level functions

Functions are an important class of JavaScript objects. Interestingly, functions can be bound to variables too. This allows us to pass around functions as arguments. For example:

var blink = function() {
  console.log('blink');
};
setInterval(blink, 1000);

By passing the function blink to another function setInterval, blink is called in 1-second intervals (1,000 milliseconds is equivalent to 1 second).

Functions have a “scope” to resolve the state of their inner code bodies. In older JavaScript versions, scope was only bound to a function, not to a block of code. This has changed in newer versions. Since ECMAScript 2015, lexical declaration bindings formed with let and const are block scoped.

To understand what this means, consider the following examples:

// function scope
var foo = 1;
var bar = function() {
   console.log(foo);
}
bar();

Lexical scope allows proper closures.

Closures are a concept from functional programming. During evaluation, the runtime first checks the environment of the bar function. When the variable foo is not found inside the function bar, the runtime looks further in the parameter list bindings. If the foo binding is not found in the parameter list bindings, the runtime looks to the outer environment (the lexical environment of a function) and there it finds a foo binding.

Compare this to the behavior of variables that are defined with let or const:

// block scope
var outside = 1;
const constNumber = 42;
function printBlock() {
  let inside = 3;
  console.log(outside, inside, constNumber);
}
printBlock();

If you play with the variables inside and outside, you’ll see how let and const help to preserve the value from the original scope.

Dealing with scope requires some practice, especially if you are new to JavaScript. Also, scope is generally static, except for this.

With this, you can reference context inside an object, as we will see next.

Objects and arrays

First, objects and associative arrays look the same:

// a simple robot object
var robot = {}
robot['hand'] = 'up';
robot.hand = 'up';

JavaScript uses the idea of object “prototype” to generate new objects. Object prototypes can be cloned with the new operator as follows:

function Robot() {};
var robot = new Robot();

Within objects, the this variable can be used to refer to the current object:

robot.raiseHand = function() {
  this.hand = 'up';
};

Depending where you are in your program, the reference to this can change. Often, we want to “bind” a this context to objects. In that way, we can call functions on objects without worrying about the calling context of a function.

JSON

The JavaScript object literal syntax is a key-value pair:

var robot = {
    hand: 'up',
    raiseHand: function() {}
}

When you want to transport JavaScript objects from one place to another (such as over an internet connection), JavaScript Object Notation (JSON) syntax for object properties is used:

{
    "hand": "up",
    "legs": "down"
}

JSON is a serialization-safe subset of JavaScript (i.e., there are no functions and all property names are double-quoted). JSON can be found in all kinds of software systems—in particular, it is increasingly used in web development to replace XML formats.

Reading about code examples gets boring quickly. Let’s now look at how to run JavaScript in the web browser and with Node.js.

JavaScript Runtime Environments

JavaScript can run in multiple environments. In this book, there are three environments of interest: web browsers, web application servers, and embedded devices. Let’s first look at the web browser, which is where many software developers first encounter JavaScript.

The Browser

JavaScript has its origins in web browsers. If you haven’t developed in a web browser before, have a look at the screenshot in Figure 1-7.

njes 0107
Figure 1-7. Working with JavaScript in a web browser

In most web browsers, you can inspect a web page by right-clicking and selecting the appropriate option. You’ll find a developer console where you can try out the JavaScript statements from above. If this is new for you, we recommend you try a few of the exercises found at JavaScript for Cats, a brief workshop for JavaScript in the browser built by Max Ogden.

Web browsers often come with an integrated debugger that allows you to set breakpoints. This is often a good idea to understand what is going on. We’ll look closer at JavaScript in the browser in Chapter 10.

The Server

The foundations of JavaScript on the server were built in 2008 when Google released the open source JavaScript engine V8. The V8 project is part of the Google Chrome web browser.

One year later, Ryan Dahl released Node.js. He added an event loop and low-level JavaScript APIs for the filesystem and drivers for hardware. And, thanks to V8, it can run on the world’s most important computing platforms, from servers to tablets and smartphones.

An important part of the Node.js ecosystem is based on npm, the Node Package Manager. Packages can be published to the npm registry, which includes more than 100,000 open source packages that you can download, modify, and use in your programs.

For example, if you needed to find a library to develop with I2C tags,1 you could search the npm website by typing “i2c” in the search bar, as shown in Figure 1-8.

njes 0108
Figure 1-8. Search results from the npmjs.com website

To install the library called “i2c” to your current directory with npm, you would enter into the console:

$ npm install i2c

This command downloads the library and its dependencies in a directory called node_modules.

Besides installing packages, npm can be used to set up a project manifest file with:

$ npm init

When starting with a fresh project, it is often a good idea to run this command first.

Some of the packages from npm can be installed globally as command-line tools. We’ll need a number of those tools, such as Browserify. To install a package globally, you would run:

$ npm install -g browserify

For some libraries, such as the serialport library, during the installation you’ll see something like this:

> node-pre-gyp install --fallback-to-build

The node commands end with gyp, which stands for “generate your projects.” The gyp tools were developed by the Chromium team to improve the process of building the JavaScript runtime on different platforms. This approach works for JavaScript libraries that have specific hardware dependencies. With Node-gyp, there is a wrapper for gyp to compile native C/C++ into a Node library. This allows Node projects to interface with very low-level hardware libraries on different platforms.

You should definitely also check out nan (Node Native Addons) if you need to bind code in C or C++ to JavaScript.

Embedded Devices

JavaScript’s asynchronous programming model and its large ecosystem also make it interesting for programming embedded devices. There are several options to work with JavaScript inside and outside of an embedded device.

Projects such as Espruino, Kinoma.js, and iotjs allow you to run a subset of JavaScript directly on a microcontroller. Espruino and Kinoma.js allow you to buy boards on which JavaScript runs natively.

A number of embedded devices support running scripting languages. Because of its compact size, some developers have explored using the Lua language on embedded devices. By combining the advantages of Lua and JavaScript, the Tessel 1 explored the idea of transpiling JavaScript to Lua—for an example, see the Colony-Compiler.

The newer Tessel 2 combines a microcontroller with a “system-on-chip” (SoC) which includes a microprocessor. This hybrid approach is very promising because it affords low-level interfaces (microcontroller) with high-level abstractions (microprocessor running Linux).

With this strategy, you can run JavaScript on an embedded device by installing the same Node.js runtime that web developers use for web applications or browser programming. All you need is some embedded Linux (as you will see later in the book, embedded Linux is available for a number of boards). This strategy is adopted by boards such as the Intel Edison, BeagleBone, Raspberry Pi, and Tessel 2.

The embedded Linux approach is interesting for a couple of reasons:

  • The runtime performance of the JavaScript V8 engine on an embedded device is very good compared to compiled code and to other high-level languages.

  • JavaScript libraries and developers can build upon existing know-how for network protocols, filesystems, drivers, and databases. Power and memory can be constraints; however, if you have WiFi, you will draw a fair amount of power regardless of the workload on an application processor.

  • As Moore’s law continues to make computing resources cheaper, small operating systems with support of JavaScript can directly run on battery-powered devices too. Within the next few years, we will hopefully see innovations to reduce power consumption and improve battery life.

At this stage, the main problem with using JavaScript on an embedded device is the size of the Node.js runtime, which requires a good deal of memory on the device. On a laptop or server, a simple web server can take up to 50 MB of disk space. Many embedded devices have less disk space, and even less RAM to respond to incoming requests.

Luckily, there are projects that aim to reduce the hunger for memory. One such project is JXcore, which makes it possible to run Node.js apps on a wide range of devices and platforms. Also, ChaiScript draws inspiration from the JavaScript syntax to bind powerful C++ code. With this you can add scripting functionalities to C++ code for embedded applications.

The Node.js API

Node.js comes with a number of different modules that are important when looking at hardware. See the Node.js API documentation for more information. The following sections provide an overview of buffers and streams.

Buffer

You will encounter buffers in many Node.js libraries related to hardware and network protocols. The idea of buffers is to provide some minimal “typing” to an array of bytes.

In computers, there are different ways to group bits into numbers. For example, for serial communication between devices, numbers are collected in memory “buffers.” In Node.js, a buffer object can manage memory content and values. But buffers also provide an easy way to convert numbers from a hex format to decimal representation, and vice versa.

A good way to learn about buffers is via the Node.js console. First, you create a new “memory” buffer that has some random values by default. From Node’s REPL (entered by typing node into the console), type:

> var buf = new Buffer(4);
<Buffer 50 0a 00 03>

In the preceding example, you have 4 bytes, or 32 bits, of memory to work with. Buffers are not initialized by default. To fill the buffer with empty values, there is the fill method:

> buf.fill(0);
<Buffer 00 00 00 00>

Buffers become more interesting when reading or writing values.

To write data, there are several options depending on the size of the buffer. A very useful form of data in a buffer are unsigned integers (uint). 8 bits can represent values from 0x00 to 0xFF which you can see below. With buf.writeUint8, you can write unsigned integers at a position in the buffer:

> buf.writeUint8(0x78, 2);
> console.log(buf)
<Buffer 00 00 78 00>

To read content from a buffer, you can then use the readUint8 function as follows:

> buf.readUint8(2);
120

The value 120 is the decimal representation of the hex value 0x78. The content of a buffer often represents characters that are human readable. To convert numbers to readable characters, you can apply the toString() method:

> console.log(buf.toString());
x

Note that you would see Unicode characters if you just looked at the return value of buf.toString(). Unicode characters are useful in the context of non-Western alphabets or special symbols such as emojis.

In hardware, the hex representation is sometimes the most interesting. To see the hex values of a buffer, you can add a 'hex' argument to toString():

> console.log(buf.toString('hex'))
00007800

To understand data from an embedded device, you often want to create a buffer with numbers in hex format. A buffer can help in these cases:

> var buf2 = new Buffer('deadbeef', 'hex');
> console.log(buf2);
<Buffer de ad be ef>

The buffer module provides more ways to manage large chunks of binary numbers (e.g., 16-bit values or numbers in big-endian or little-endian notation). For now, remember that buffers allow you to easily speak and explore Hexspeak. that helps to control digital blocks of embedded devices.

Streams

Node.js is not only known for efficient processing of events from different sources. Its relationship to data via JSON makes Node.js special compared to other programming environments too.

A fundamental concept of working with data and bytes in Node.js are streams. Streams help you to observe and manipulate the flow of data over time. They can be anything that relates to data—for example, raw bytes of music, data strings from a database, or web pages from a web server. Take a look at the documentation of incoming HTTP requests to see an important usage of streams.

Besides managing data in networks or databases, streams are nice to deal with user input too. Input from a user can be captured with a writable stream:

// import stream libraries
var stream = require('stream');
var Stream = stream.Stream;

// create new stream to capture data
var ws = new Stream();
ws.writable = true;

// define write behavior
ws.write = function(data) {
  console.log("input=" + data);
}

// when closing a stream
ws.end = function(data) {
  console.log("bye");
}

// combine stream from input to output
process.stdin.pipe(ws);

A simple test shows how this works:

$ node pipe_out.js
hello
input=hello

The writable stream handles “write” and “end” events from the standard input. This connection is made by piping standard input to the writable stream. You could also pipe the output of a file into the write stream. For example:

$ echo hello | node pipe_out.js
input=hello

bye

1 I2C is a serial communication protocol. See the NXP datasheets for further detail.

Get Node.js for Embedded Systems now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.