Introduction: How to Make a Tessel-style Software Module From Adafruit-style Hardware

About: An engineer, seamstress, cook, coder, and overall maker. Spent a summer at Instructables; got a degree in E: Neural Engineering at Olin College; made a microcontroller (tessel.io); now thinking about climate c…

Tessel is an internet-connected microcontroller with built-in Wifi and various other capabilities. It's designed for easy interaction through a series of modules, such as Bluetooth Low Energy, Servo, and others.

The company has created Node modules for each of the 14 modules that can be npm installed and used very easily. However, what do you do if you want to control something other than the modules?

This tutorial shows you how to plug in modules from the existing Adafruit and Seeed ecosystem, and release them back out into the open source ecosystem as Tessel-ready npm modules.

I’ve so far done this with a PIR motion detector and a pulse sensor.

For this Instructable, I'll use the PIR as an example: from this part to this npm module.

A version of this set of instructions will be kept up-to-date on Github here.

Step 1: Understanding Your Hardware

Before you start, it would be a good idea to check out Tessel's hardware docs and hardware api docs. These two documents will give you a good idea of Tessel's capabilities and available interfaces.

As a general rule, anything that can be used with an Arduino can also be used with a Tessel.

Tessel reads in a maximum of 3.3V, so if your part outputs more than that, don't fry the board!

Tessel can:

  • Read and write digital signals (3.3V when high; 0V when low)
  • Write PWM signals ("duty cycles")
  • Read analog signalsCommunicate over SPI (MISO, MOSI, and SCK)
  • Communicate over I2C (SDA and SLC)Communicate over UART (TX and RX)
  • Provide 5V power* (if Tessel is powered over USB. Please see Powering Tessel)
  • Provide 3.3V power (a digital pin set to output(1) (high))

For the PIR sensor, I needed one 3.3V digital signal pin and 5V of power. It will typically say what you need on the manufacturer's page, straight out or on a datasheet. Adafruit typically says these things in the description field of a product page, as does Seeed.

Step 2: Setting Up the Repo

Tessel’s modules all have the same basic directory. You can see a template for Tessel modules in general on our style guide here. You might want to check out the PIR repo set up for development here.

Here are notes on some of the key files:

index.js (template) This file is the driver for the hardware. Here’s the basic setup:

  • Require util and the event emitter, along with any other dependencies. These let you write event-driven APIs.
  • Make a constructor function that instantiates the hardware as an object. Its two arguments are “hardware” and a callback. The function should emit a “ready” event that returns the object when it is ready. For the PIR, it’s “ready” as soon as the object is instantiated. For something more complex, e.g. the Ambient module, it’s not “ready” until it verifies that it has the correct firmware version.
    • `hardware` specifies where the hardware is connected to Tessel. For modules, it’s a port. For external hardware, this will most likely be a port and a pin (e.g. tessel.port[‘GPIO’].pin[‘A3’]). You should probably also add error handling in case the wrong hardware type is passed in (e.g. just a port when you need a pin) or for specification of the wrong type of pin (you can see which pins are digital, analog and PWM in the examples here). You can check the PIR code for examples of this error handling.
    • `callback(err, obj)` should return an error if there are any errors connecting, or if there are no errors, should return the object as its second argument.
  • Functions: this is the fun part! What do you want as the API for your hardware? What’s useful? What do you want to expose? For the PIR motion detector, I only have one function, which reads the pin. Most of the useful information is better exposed as events for “movement”, “stillness”, and “change”.
  • use function: The standard require for a Tessel module is require(‘module’).use(tessel.port[‘PORT’]). The “use” function takes hardware specification and a callback, and passes them to the object constructor.exports: Export the object function and the use function.

package.json (template)Use `npm init` to generate most of this file.

Other items of note:

You do not need the tessel npm package as a dependency.

  • Add a “hardware” section. By default, Tessel pushes the entire directory so that any dependencies are included. With a “hardware” section, you can specify files and folders to ignore when pushing to embedded devices. For our modules, we list “./test” and “./examples”.

examples folder (template) You need at least one example. This should show basic functionality of the hardware. Mine waits for a ready event, then logs a message on the emission of “movement” and “stillness” events. When you require the module, refer to ‘../’ rather than the module name.

test folder (template) We use the node module “tinytap” for testing. Every functionality that can be tested without physical interaction should be testable with `tessel run test/*.js`.

README.md (template) Your readme is your documentation. For consistency with Tessel modules, check out the template. We use the node module “marktype” to make pretty, linkable API documentation.

Step 3: Connecting Your Hardware to Tessel

Connect your hardware based on the hardware and API documentation. Instructions for establishing SPI/UART/I2C are part of the API docs.

At the top of your README, write which pins should be connected to which between the Tessel and the external hardware. A picture of the setup would also be useful to include in the README.

Step 4: Establishing Communication

Set up something basic to make sure you can connect to the sensor. I like to start with example code: require Tessel, read the pin, see what kind of values we get.

If you’re working with more complex hardware, you might need to wake up the hardware with a wakeup sequence. This sort of thing will be documented in the part’s datasheet. If there is existing Arduino code for the hardware, this can be a good starting point to poke around and get an understanding of how to interface with the device.

Step 5: Writing an API

Once you’re able to connect and get some basic functionality out of the device, start writing your API.

Start with the object constructor and the use function in your index.js file. Make sure you can require the hardware and have it connect.

Now, draft up your API. How might people want to interface with this piece of hardware? How can you make it intuitive?

If you’d like feedback on a proposed API, feel free to post it to the RFC category of our forums. As a general rule, top priority is intuitive interaction. Second priority is exposing as much as you can.

Step 6: Writing Tests

Write tests as you go.

  • Initializing the object should return the object to the callback, return the object, and emit the object as a ready event.
  • Super thorough tests check to make sure errors are emitted when they should be.

Step 7: Writing an Example

The example named `yourModuleName.js` should be a simple "is it working" example.

Feel free to write other examples to show off different uses of the hardware and the API you've built!

Step 8: Writing the Docs

Please follow the template formatting here to write your README.

Document every method, event, and property.

Complete documentation is important! If you don't document a method, most people will never realize that method exists.

Step 9: Publishing

Publish your module to npm! If you've never done that, this is a good tutorial. Be sure to include 'tessel' in the keywords of your package.json so that people can find it!

Other places you might want to publish as well: