OPEnS Lab Internet of Agriculture Wireless Sensor Actuator Platform
Switch branches/tags
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.
PushingBox Update PushingBox readme Dec 12, 2018

Project Loom: Library Overview

This is the primary location of the Project Loom code, consolidated into place with a single Arduino .ino sketch built with code selected by the preprocessor based on the configuration file.

Other readmes are provided for more specific content, while this document focuses on providing an overview of what Loom is and how the library works.

Additional Readmes

  • Arduino and Loom Setup – How to setup Arduino and Loom from scratch
  • Hardware – The processors, sensors, actuators, etc. that Loom uses / supports
  • Using Loom – How to use Loom once you have set it and the Arduino IDE up
  • Extending Loom – How to add support to the Loom Library
  • Hub Scripts – Documentation on the mini programs / scripts that Loom supports
  • Loom Translator – Documentation on the collection of Loom functions for converting between data types and organization
  • Max MSP – Details on using Max MSP data processors with WiFi enabled Loom devices
  • Limitations and Bugs – The processors, sensors, actuators, etc. that Loom uses / supports

Table of Contents

  1. Installation

  2. Device Support

  3. Communication Platforms

  4. Device Configuration

  5. Max/MSP

  6. Library Architecture

    1. Overall Structure
      1. Example Library Include Hierarchy
  7. Channels

    1. Implementation
    2. Additional Devices
  8. Device Identification Hierarchy

  9. SD Card Support

  10. Real-Time Clock Support

  11. Non-Volatile Flash and EEPROM memory

  12. Configuration File

  13. API

  14. Using the Loom Library


See our Arduino and Loom Setup Guide

Device Support

  • Adafruit Feather M0
  • Adafruit Feather 32u4 (limited support)

Communication Platforms

WiFi and LoRa have had the most attention in development and thus have the most support and options, especially WiFi, as that is currently the only to communicate with a computer running Max/MSP. Other platforms have been implemented however. Below is the table of device inputs and outputs currently implemented, planned, or in consideration (bold for well implemented, * if partially implemented or in need of testing to be considered well implemented, plaintext if planned but not implemented, ? if in consideration). The top section is device-device communication platforms, and the bottom section are platforms that do not necessarily transmit directly to another Loom device

Inputs Outputs Comments
WiFi WiFi Implemented
LoRa LoRa Implemented
nRF nRF Implemented
Ethernet Ethernet* Currently only used with hubs, expansion in progress
GSM GSM* Currently only one device, so outputs MQTT via GSM, but no GSM receiving yet
Google Sheets? Google Sheets (PushingBox)* Works as output
Adafruit IO* Adafruit IO* Needs proper integration
IFTTT? IFTTT? IFTTT currently only works as output though Adafruit IO
SD card? SD card Works as output, considering as input
Serial? Serial Works for output. Serial as input may or may not be useful (is how Fona is tested)
OLED Display a bundle on an Adafruit OLED featherwing / breakout

Device Configuration

The configuration of the code to upload and flash to devices is specified in the config.h file. This file presents the options that can be set or toggled to achieve a particular behavior. The preprocessor uses these definitions to select the necessary declarations, variables, and functions automatically.

Currently changes to the config need to be done manually in the file itself. An interface to aid the process is in progress.


The associated Loom Data Processors and control modules are made as patches in Max/MSP. The Max interfaces are not strictly necessary for using this library, but provide convenient features and means of interacting with your Loom devices / network.

Librarary Architecture

The Loom Library is an answer to the growing set of code to drive Loom's supported devices, hardware, and communication platforms. The set of possible combinations of device configurations makes it unfeasable for Loom developers and users alike to manage sketches with desired functionality. Instead, a configuration file effectively creates the desired sketch dynamically. A loom_preamble.h file uses that configuration to select entire files to include, each of which may also contain additional preprocessor statements.

Overall Structure

The Loom Library is effectively an aggregate of all of the functionality possible within the entirety of the supported devices, sensors, and actuators. The user then specifies the needs of their sketch inside the config.h. Based on the needs of the sketch, the requisite files, functions, and logic will be dynamically included such that only what is needed by the sketch is uploaded to the device. loom_preamble.h uses config.h to know which files to include, these files in turn then include the libraries they need. loom_common.h is also always present as it has the Loom_begin() function, which sets up all the modules and sensors being used by the sketch.

Example Library Include Hierarchy

The hierarchy of included files looks something like the following (example if building for Ishield device on Feather M0 WiFi) (actual files may have since changed, but the idea/hierarchy remain).

Note – bolded are files that are always necessary and thus included, independent of config.h. In angle brackets are non-Loom dependencies.

  • Ishield_Example.ino
    • Config.h
    • Loom_preamble.h (likely to be renamed)
      • <OSCBundle.h>
      • Loom_translator.h
      • <Adafruit_SleepyDog.h>
      • <SPI.h>
      • loom_analog.h
      • loom_neopixel.h
        • <Adafruit_NeoPixel.h>
      • loom_mpu6050.h
        • <I2Cdev.h>
        • <Wire.h>
        • <MPU6050_6Axis_MotionApps20.h>
      • loom_wifi.h
        • <WiFi101.h>
        • <WiFiUDP.h>
      • loom_flash.h
        • <FlashStorage.h>
      • loom_common.h

Message Router

Intro to Route and Dispatch of Message Router

The message router is called via:

bndl->route(configuration.packet_header_string, msg_router);

From the process_bundle function in the 'loom_interface.h' file. The message router checks any OSC messages that it receives, and if the message's address string matches one of the dispatch calls in the router, then the message gets forwarded to a function designed to handle / process messages with that address string. This usually entails reading the arguments to the message (which should be sent in an order / format known by the function that will handle the processing).

The dispatch calls in the message router take the following form:

msg.dispatch("/address", processing_function, addrOffset);

An example of a dispatch that takes a message that has an address of the device identifying string followed by "setID", and calls a function set_instance_num (which sets the device instance number) would be:

msg.dispatch("/SetID", set_instance_num, addrOffset);

The reason only the /SetID is provided and not something like /LOOM/Ishield7/SetID is because the aforementioned route function checks for message addressed to the device, which in this case is identified by /LOOM/Ishield7, and passes only the remaining part of the address to the message router. Messages that do not start with the device indentifying string are not sent to the message router.


The LOOM library and associated Max/MSP processors are designed to use channel based interfaces by default. Channel interfaces abstract device instance number, UDP port, and IP address into a single letter A, B, C, … H.

The channels are purely a user convenience, and the devices can operate without any notion of channels in the firmware and/or Max patches.


A common port is used for the communication of device settings in order to allow the Max patches to communicate with devices in a consistent manner if the instance number, UDP port, or IP (i.e. the channel information) address are unknown.

Common Port: 9440

Device Unique Channnel Data: 9441 – 9448

Channel A: Instance # = 1 UDP Port = 9441

Channel B: Instance # = 2 UDP Port = 9442

• • •

Channel H: Instance # = 8 UDP Port = 9448

IP Address: Automatically assigned to devices by one's router. Max interfaces will automatically fetch IP address of selected device. If not using the channels feature implemented in the Loom Max interfaces, then Loom Debug mode may need to be enabled, or the user may need to add custom code, in order to obtain the device's IP address.

Additional Devices

The Max interfaces presently support upto 8 concurrent channels, though the library itself poses no such restrictions and using custom Max interfaces can allow for the control of more devices.

Device Identification Hierarchy

In Loom, a device is uniquely identified by 4 elements, from broad to precise identification:

  • Family
  • Family number
  • Device type
  • Device number

The OSC messages and bundles send to and from devices are address/signed with their ID string, in the following format:


For example, Ishield 7 in the 3 'Loom' subnet would be:



A family is generally a complete network of a given functionality under a single person or group.

Family Number

A family number is used to distinguish subnets between a family. This is typically used to seperate devices into groups by purpose, location, etc. In Max/MSP, you will generally only be observing a single subnet at a time. Family numbers should be 0 or greater.

Device Type

The Loom Library supports an autoname feature to try to name your device if it fits into a common hardware / functionality set. These include devices like Ishields, multiplexers, servo controllers, etc. and name the device accordingly. Alternatively, you can use custom names for your devices if you want.

Device Number / Channel

A device number is used to distinguish the devices on the same subnet (family number) that have the same device type. Device numbers are sometimes also referred to as channels. Device numbers are expected to be between 1 and 8, inclusive (channels A-H).

WiFi UDP Ports

If using WiFi, the family number and device number will determine the UDP ports that the device will expect messages from. The device will check and send on 3 ports, one for global messages, one for subnet, and one for device specific messages. The calculation of the specific ports are shown below. The reason for different ports fro various devices/purposes is to reduce the number of messages a device may recieve that were not intended for it.

Device Targetting Scope

The message routing function of the Loom Library supports receiving messages addressed at a global, subnet, or device level. Max/MSP can send on any of the 3 formats as well.


Broadcast the OSC bundle to all devices. Those with the matching family name can see/react to the message. If using WiFi the message is sent on UDP port 9400.



Broadcast to a single subnet of a given family. Those with the matching family name and family number can see/react to the message. If using WiFi the message is sent on UDP port 9400+10*(family-num).



Target a message at a single device, i.e. the one with the matching family, family number, device type, and device number. If using WiFi the message is sent on UDP port 9400+10*(family-num)+(device-num).


LoRa Subnet

Loom also supports subnets with LoRa. This is implemented by having the receiver check the source of the received OSC bundle (from the device identification address), and filter out unwanted bundles (i.e. empty them).

The options are specified in the config.h file by lora_subnet_scope:

  • 1: subnet
  • 2: global/family
  • 3: global only (not recommended)
  • 4: any message.

(use 2 if unsure)

These achieve a similar subnet structure that is produced with UDP ports on the WiFi boards.

Diagram of Device Scope


SD Card Support

The Loom library extends the Arduino SD library to have more features, mostly tailored towards using common data structures used by the library.

Namely, support for printing:

  • Single elements (int, floats, c-strings, Strings)
  • Arrays (ints, floats, c-strings, Strings)
  • Bundles (single or multi-msg format)

Additionally, if used with MaxMSP, there is support for sending commands to enable / disable the logging of bundles the device receives (also works without Max).

Real-Time Clock Support

The Loom library supports two RTC devices:

Currently, interrupts (for waking the microprocessor from sleep) are only supported via the DS3231, with the PCF8523 mostly being used in conjunction with an SD card (as Adafruit Adalogger featherwing has PCF8523 and SD card holder) to provide timestamps.

The Loom Library has support for both devices for returning strings of the date, time, or weekday. Additional timer functions are provided for the DS3231.

RTC and Low Power Dependencies

NOTE: To use the DS3231 extended library with the Feather M0, the following line must be added to RTClibExtended.h:

#define _BV(bit) (1 << (bit))

Sleep Modes

Project Loom supports two sleep modes for the Feather M0 and one sleep mode for the Feather 32u4. Here are some details on the various modes:

Mode Supported board Current Draw
Idle_2 Feather M0 ~5 mA
Standby Feather M0 ~0.7 mA
SLEEP_FOREVER Feather 32U4 Untested

Standby Operation

Due to some incompatibilities between Standby mode and falling interrupts, a very particular scheme must be followed to use Standby mode on the Feather M0. The following code is an example of how standby mode can be set up on the M0 with a wakeup interrupt on pin 11:

void setup() {
    pinMode(11, INPUT_PULLUP);
    bool OperationFlag = false;
    delay(10000); //It's important to leave a delay so the board can more easily
                  //be reprogrammed

void loop() {
    if (OperationFlag) {

        // Whatever you want the board to do while awake goes here

        OperationFlag = false; //reset the flag

    attachInterrupt(digitalPinToInterrupt(11), wake, LOW);


void wake() {
    OperationFlag = true;
    detachInterrupt(digitalPinToInterrupt(11)); //detach the interrupt in the ISR so that
                                                //multiple ISRs are not called

Setting the Time from the Internet

Loom supports setting the RTC time based on the time obtained from an NTP server.

UTC Time

Loom supports the usage of either local or UTC time.

Non-Volatile Flash and EEPROM memory

For storing configurations, both the Feather M0 and the Feather 32u4 boards are capable of storing a struct in non-volatile memory. The functions related to non-volatile memory can be found in loom_flash.h, and are automatically compatible with your board. If you modify the contents of the configuration structure, you can save them with write_non_volatile(). If you need to read from flash memory, you can read into the configuration structure with read_non_volatile(). Do not excessively write, as each board supports about ~10000 flash writesin its lifetime; the cells in flash memory wear out over time.

Configuration File

The configuration file is used in conjunction with preprocessor statements to build the specified sketch, with as small a program memory usage as possible. Any options that can be set or toggled, or any specification of hardware (e.g. sensors and actuators) being used occur in this config.h.

Any options for custom additions to the library should be added only to the configuration file.

The config.h file needs to be included before the library (via loom_preamble.h) itself, so that the configuration can be used to define the necessary subset of the library.


'[]'s are used to indicate arguments that are optional or specific to a given platform

  • Receive Bundle void receive_bundle(OSCBundle *bndl, CommPlatform platform)
    • Receive a packet and put it in an OSC bundle if not already
    • CommPlatform options:
      • WIFI
      • LORA
      • NRF
  • Process Bundle void process_bundle(OSCBundle *bndl)
    • Process a bundle (presumably recieved via recieve_bundle) by performing message routing on it
  • Measure Sensors void measure_sensors()
    • Measure any enabled sensors
  • Package Data void package_data(OSCBundle *send_bndl)
    • Package measured data into an OSC bundle
  • Send Bundle void send_bundle(OSCBundle *send_bndl, CommPlatform platform)
    • Send a bundle (presumably built via package_data) to another Loom device / MaxMSP
  • Log Bundle void log_bundle(OSCBundle *send_bndl, LogPlatform platform [,char* file])
    • Export a bundle
    • Platform options
      • PUSHINGBOX (Google Sheets)
      • SDCARD (requires filename)
      • OLED
  • Additional Loop Checks void additional_loop_checks()
    • Function to catch remaining functionality that is not contained by the rest of the API

Further details on using the API can be found here

Using the Loom Library

Details on setting up Arduino IDE and Loom can be found here

Details on using Loom once setup (with examples) can be found here