Skip to main content

Electric Imp Squirrel Style Guide

How Best To Present Your Code

This document is intended to help provide a consistent coding style across Electric Imp libraries and example code, as well as guide developers towards some best practices.

Not all applications have the same requirements, and this document should only be adhered to when it makes sense. If you are writing public code or examples that stray from the style guide, you should add comments indicating why (as you’ll inevitably be asked that question anyway).

However, code submitted to Electric Imp for inclusion among our libraries should adhere to the standards presented below.

Comments

Always use // comments and always have one space between // and the start of your comment.

Comment text should start with a capital letter, and you should aim to write in proper sentences.

Place comments on their own line, above the relevant code.

Good

// Number of times the device has booted
local count = 5;

Bad

local count = 5; // Number of times the device has booted

/*
  Bad
*/

Semicolons

Always use semicolons to denote the end of a statement.

This code:

x <- 3;
-3 == x ? server.log("1: " + x) : server.log("2: " + x);

is interpreted differently that this code:

x <- 3
-3 == x ? server.log("1: " + x) : server.log("2: " + x)

Semicolons should not be included at the end of block statements (if, for, function, class etc) unless it is part of an assignment.

Good

local someFunction = function() {
  // Function code...
};

Bad

function someFunction() {
  // Function code...
};

Indentations

Indentations should never be tabs (\t) and should always be four spaces.

Spaces

Parentheses following a keyword (if, for, foreach etc) should be separated by a single space.

Good

if (err != null) {
  // Process error
}

Bad

foreach(key in table) {
  // Loop code...
}

Always includes spaces for extra clarity in code:

Good

for (local i = 0 ; i < myArray.len() ; i++) {
  // Loop code...
}

Bad

for (local i=0;i< myArray.len();i++) {
  // Loop code...
}

Curly Braces

Always use curly braces, and never start curly braces on new lines.

Good

if ("key" in table) {
  data = table.key;
}

if (x == y) {
  server.log("It's a match");
} else {
  server.log("It's not a match");
}

Bad

// No curly braces...
if (x == y)
  server.log("it's a match");
else
  server.log("It's not a match");

// Curly braces start on new line...
if (x == y)
{
  server.log("It's a match");
}

Curly braces may be omitted for single line if statements when both the condition and the statement are short and simple (though using curly braces is still preferred).

OK

if (x == null) server.log("X is null");

Global Variables

Global variables should be declared with the <- operator.

Good

x <- 5;

Constants

Constants should be declared at the top of the code file (below #requires), all caps, with underscores between words.

Good

const API_KEY = "abc123";

Bad

const apiKey = "abc123";

Function And Variable Names

Variable and function names should always be lowerCamelCase and be self-descriptive.

Auxiliary variables (such as loop counters) may be named with single-letter names.

OK

// i is a loop variable
for (local i = 0 ; i < devices.len() ; i++) {
  // Do something with devices[i]...
}

Bad

// r is a bad variable name
local r = http.get(url).sendsync();

Anonymous Functions

Anonymous functions should be avoided whenever possible in order to increase testability of code.

Good

function httpRequestHandler(request, response) {
  response.send(200, "OK");
}

http.onrequest(httpRequestHandler);

Bad

http.onrequest(function(request, response) {
  response.send(200, "OK");
});

Closures

When closures are required, they should be created via a factory method.

Good

function responseHandlerFactory(x, y, z) {
  return function(response) {
    // Use x, y, z...
  };
}

http.get(url).sendasync(responseHandlerFactory(x, y, z));

Tables

When constructing a table, you should use JSON syntax, ie. { "key" : value } not { key = value }.

Good

local data = {
  "temp" : 32.5
};

Bad

local data = {
  temp = 32.5
};

Hardware Objects

Hardware objects (pin, i2c, spi, etc) should be assigned to variables before use.

Good

button <- hardware.pin1;
button.configure(DIGITAL_IN, buttonCallback);

Bad

hardware.pin1.configure(DIGITAL_IN, btnCb);

Digital and Analog Out pins should always be initialized using the optional second parameter of the configure method.

Good

led <- hardware.pin1;
led.configure(DIGITAL_OUT, 0);

Bad

led <- hardware.pin1;
led.configure(DIGITAL_OUT);
led.write(0);

Cloud Services

All HTTP requests should be made asynchronously.

All methods that wrap web services should take an optional callback that will be executed upon completion of the request. The callback must take at least two parameters: err and response.

  • err — An error string (or null if there was no error).
  • response — The HTTP response object.

The err parameter should be populated with a string describing the error when the request returns a non-2xx HTTP status code, or when there is an error in the response body.

If the callback method requires additional parameters, they must come after err and response (additional parameters could include pre-parsed data, etc).

Calls to decode data must always be wrapped in a try...catch block.

Methods external to a class should never be inside a try...catch block.

Classes

Class Names

Class names should be UpperCamelCase and self-descriptive.

If the class wraps a hardware component or web service, its name should be the component or service.

// Wraps the Twitter REST API
class Twitter {
  // ...
}

// Wraps the TI TMP102 and TMP112
class TMP1x2 {
  // ...
}

Constructors And Initialization

A class’ constructor should only be used to set properties and configure GPIOs. All other initialization code should take place in a public init() and/or reset() method.

Good

class DeviceName {
  // Commands
  static REG_RESET = "\x02";

  _i2c = null;
  _addr = null;
  _intPin = null;

  // Default address is 0xDE
  constructor(i2c, interruptPin, address = "\xDE") {
    _i2c = i2c;
    _addr = address;
    _intPin = interruptPin;
    _intPin.configure(DIGITAL_IN, _handleInterrupt.bindenv(this));

    init();
  }

  function init() {
    _i2c.write(_addr, "", REG_RESET);
  }

  function _handleInterrupt() {
    // Interrupt handling code
  }
}

Hardware Objects in Classes

I²C and SPI buses should be configured before being passed into the constructor. These are multi-use buses, so your class should be able to co-operate with other classes that make use of the same bus.

GPIO pins should be configured in the constructor.

Public Methods

Public methods and properties should the lowerCamelCase.

Good

function someFunction() {
  // Public function code...
}

Private Methods

Methods intended to be private should have an underscore prepended to the name.

Good

function _privateFunction() {
  // Private function code...
}

Properties

Classes should never encourage developers to directly access internal properties. Instead it should create get and set methods where required. Additionally, all non-static properties should be initialized to null and have default values set in the constructor.

class MyClass {
  _property = null;

  constructor(property) {
    _property = property;
  }

  function setProperty(p) {
    _property = p;
  }

  function getProperty() {
    return _property;
  }
}

Static properties must always be explicitly declared static.

class MyClass {
  // Good
  static _queue = [];

  // ...
}

Immutable Values

Immutable values, such as a register address and a URL, should be static and named in all uppercase as if they were constants.

class MyWebservice {
  // Good
  static API_BASE = "https://example.com/api/";
}

class MyDriver {
  // Good
  static REG_TEMP = 0x02;
}

Constants And Enums

Libraries should never use constants or enums as they are both stored in the global constants table.

Order Of Properties And Methods

Properties should appear before the constructor with static properties above non-static properties.

The constructor should always be the first method in a class.

Public methods should appear before private methods in the class declaration.

The beginning of the private methods section should have the following line.

// -------------------- PRIVATE METHODS -------------------- //

Callbacks

Callbacks in libraries should be invoked with imp.wakeup().

Good

imp.wakeup(0, function() {
  callback();
});

Error Handling

Libraries must always expose a way for developers to handle unexpected errors. They must never silently trap and throw away errors.

Whenever possible you should avoid throw-ing errors.

Good

function _handleResponse(response, callback) {
  try {
    data = http.jsondecode(response.body);
    if ("error" in data) {
      imp.wakeup(0, function() {
        callback(data.error, response);
      });
      return;
    }
  } catch (error) {
    callback(error, response);
    return;
  }

  callback(null, response);
}

Bad

// Developer doesn't get a chance to handle error
function _handleResponse(response, callback) {
  try {
    data = http.jsondecode(response.body);
      if ("error" in data) {
        server.error(data.error);
        return;
      }
    } catch (error) {
      server.error(error);
      return;
  }
  callback(null, response);
}

Error Strings

Error strings in classes should never be declared inline. Rather, they should be treated as an immutable value.

Good

class MyClass {
  static BAD_PARAM_ERROR = "Bad Parameter";;

  function doSomething(param, callback) {
    if (param < 0) {
      local err = BAD_PARAM_ERROR;
        imp.wakeup(0, function() {
           callback(err, null);
        });

        return;
    }

    imp.wakeup(0, function() {
      callback(null, param);
    });
  }
}

Bad

class MyClass {
  function doSomething(param, callback) {
    if (param < 0) {
      imp.wakeup(0, function() {
        callback("Bad Parameter", null);
      });
      return;
    }

    imp.wakeup(0, function() {
      callback(null, param);
    });
  }
}