Introduction: How to Make a Tessel-style Software Module From Adafruit-style Hardware
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.
A version of this set of instructions will be kept up-to-date on Github here.
Step 1: Understanding Your Hardware
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!
- 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
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.
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
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.