ArduinoChromeAppSerial

HTML5 Chrome Packaged Apps and Arduino:
bidirectional communication via serial

In this article I provide you all the code you need to create a Chrome Packaged App built in HTML/JQuery to send and get data (in JSON format) from Arduino using the chrome.serial API, enabling a bidirectional communication between them.

A Chrome Packaged App allows developers to write an HTML/JS/CSS web application that look and behave like desktop native apps (just like Phonegap does for HTML5 mobile hybrid apps).

The advantage of using this technology is that you can use many special API to access filesystem, usb, bluetooth, serial (used in this article) and a lot of other interesting features of your OS.

If you have never used them you should read the Getting Started guide and try to create an Hello World application.

GOAL

We need to enable a bidirectional communication among Arduino and an HTML app via Serial:

1) The HTML/Chrome App should turn ON / OFF a led by clicking a < button >

2) Arduino receives the command via serial, turn of/off the led and returns the led status to the ChromeApp (to notify that the command has been executed with success and update the HTML UI)

3) The Chrome App has a listener to the Serial and updates its UI showing a red (if led OFF) or green (if led ON) SVG circle.

See it in action in the following video:

CODE:

This time I have not created the example from scratch. In fact I cloned and modified one of the official Chrome App Samples: Serial LedToggle

and I made some small changes:

1) Now Arduino returns a JSON with the led status to the HTML
2) The ChromeApp works with JQuery (I needed it to quickly parse the JSON returned by Arduino)
3) I use an SVG element in the ChromeApp to display the current Led status

The whole procedure to work with Chrome Apps, Arduino and the Serial are out of the scope of this article but if you just did an Hello World with both technologies you should able to test the following scripts.

Anyway at the end of the article you can find the complete source code (with everything you need to package the Chrome App)

Arduino Sketch:

#define LED 13

void setup() {
  Serial.begin(9600);

  pinMode(LED, OUTPUT);

  // Turn Led off
  digitalWrite(LED, LOW);

  // Print the led Status to the serial in JSON format
  printLEDStatus();
}

int incomingByte = 0;
void loop() {

  // Check if there's a serial message waiting.
  if (Serial.available() > 0) {
    // If there is, read the incoming byte.
    incomingByte = Serial.read();

    // if "y" turn on the led, otherwise turn off
    if (incomingByte == 'y') {
      digitalWrite(LED, HIGH);
    } else if (incomingByte == 'n') {
      digitalWrite(LED, LOW);
    }

    printLEDStatus();
  }

  delay(1000);

}

// Send the Led status via serial in JSON format
void printLEDStatus () {
   // read current LED status
    int ledStatus = digitalRead(LED);

    // Create the JSON to send
    String json = "{\"ledStatus\": " + String(ledStatus) + "}";

    // Print JSON via serial
    Serial.println(json);

}

manifest.json:

{
  "name": "ChromeAppSnippet: from Angular to arduino",
  "version": "2",
  "manifest_version": 2,
  "permissions": ["serial"],
  "minimum_chrome_version": "23",
  "icons": {
    "16": "icon_16.png",
    "128": "icon_128.png"
  },
  "app": {
    "background": {
      "scripts": ["launch.js"],
      "transient": true
    }
  }
}

launch.js

chrome.app.runtime.onLaunched.addListener(function() {
  chrome.app.window.create('index.html', {
	id: "mainwin",
    bounds: {
      width: 320,
      height: 240
    }
  });
});

index.html

<button style="position: absolute; bottom: 0;">Toggle Led</button>


</pre>
<div id="buffer"></div>
<pre>
<script type="text/javascript" src="jquery-1.11.0.min.js"></script><script type="text/javascript" src="main.js"></script>

main.js

// Serial used from your Arduino board
const DEVICE_PATH = 'COM11'; // PC
//const DEVICE_PATH = '/dev/ttyACM0'; //MAC
const serial = chrome.serial;

/* Interprets an ArrayBuffer as UTF-8 encoded string data. */
var ab2str = function(buf) {
  var bufView = new Uint8Array(buf);
  var encodedString = String.fromCharCode.apply(null, bufView);
  return decodeURIComponent(escape(encodedString));
};

/* Converts a string to UTF-8 encoding in a Uint8Array; returns the array buffer. */
var str2ab = function(str) {
  var encodedString = unescape(encodeURIComponent(str));
  var bytes = new Uint8Array(encodedString.length);
  for (var i = 0; i < encodedString.length; ++i) {
    bytes[i] = encodedString.charCodeAt(i);
  }
  return bytes.buffer;
};


////////////////////////////////////////////////////////
////////////////////////////////////////////////////////

var SerialConnection = function() {
  this.connectionId = -1;
  this.lineBuffer = "";
  this.boundOnReceive = this.onReceive.bind(this);
  this.boundOnReceiveError = this.onReceiveError.bind(this);
  this.onConnect = new chrome.Event();
  this.onReadLine = new chrome.Event();
  this.onError = new chrome.Event();
};

SerialConnection.prototype.onConnectComplete = function(connectionInfo) {
  if (!connectionInfo) {
    log("Connection failed.");
    return;
  }
  this.connectionId = connectionInfo.connectionId;
  chrome.serial.onReceive.addListener(this.boundOnReceive);
  chrome.serial.onReceiveError.addListener(this.boundOnReceiveError);
  this.onConnect.dispatch();
};

SerialConnection.prototype.onReceive = function(receiveInfo) {
  if (receiveInfo.connectionId !== this.connectionId) {
    return;
  }

  this.lineBuffer += ab2str(receiveInfo.data);

  var index;
  while ((index = this.lineBuffer.indexOf('\n')) >= 0) {
    var line = this.lineBuffer.substr(0, index + 1);
    this.onReadLine.dispatch(line);
    this.lineBuffer = this.lineBuffer.substr(index + 1);
  }

};

SerialConnection.prototype.onReceiveError = function(errorInfo) {
  if (errorInfo.connectionId === this.connectionId) {
    this.onError.dispatch(errorInfo.error);
  }
};

SerialConnection.prototype.connect = function(path) {
  serial.connect(path, this.onConnectComplete.bind(this))
};

SerialConnection.prototype.send = function(msg) {
  if (this.connectionId < 0) {
    throw 'Invalid connection';
  }
  serial.send(this.connectionId, str2ab(msg), function() {});
};

SerialConnection.prototype.disconnect = function() {
  if (this.connectionId < 0) {
    throw 'Invalid connection';
  }
  serial.disconnect(this.connectionId, function() {});
};

////////////////////////////////////////////////////////
////////////////////////////////////////////////////////

var connection = new SerialConnection();

connection.onConnect.addListener(function() {
  log('connected to: ' + DEVICE_PATH);
  connection.send("hello arduino");
});

connection.onReadLine.addListener(function(line) {
  logJSON(line);
});

connection.connect(DEVICE_PATH);

function logJSON(ledstatus) {

  // Get the LED status from the Json returned by the Serial
  // 0 = off | 1 = on
  ledstatus = jQuery.parseJSON( ledstatus ).ledStatus ;

  // Set the circle color according with the LED status
  if (ledstatus == 0)
     $('#statusCircle').css('fill','red');
  else
    $('#statusCircle').css('fill','green');

  // Print led Status to HTML buffer area
  log(ledstatus)
}

function log(msg) {
  $('#buffer').append(msg + '
');
}

var is_on = false;
$('button').click(function() {

  is_on = !is_on;
  connection.send(is_on ? 'y' : 'n');
});

Chrome Extension Panel
Upload your extension using the Chrome Extension Dashboard (available typing chrome://extensions/)

More info about packaging Chrome apps are available in the official guide

SOME NOTES:

1) on Windows: close the Chrome App and wait few seconds before uploading a new sketch to the board otherwise you will receive the error “Serial already used”.

2) Update the DEVICE_PATH property (in main.js) with the serial name you’re using to connect Arduino.
On Windows it will be : COM3, COM6 and so on
On Mac should be something like /dev/ttyACM0

Download source code

Comments (9)

  1. fabio (reply)

    February 24, 2014 at 7:43 pm

    hello a taste, well I’m new to this topic but I am very interested, I would ask how complilar all files in arduino? I add the files. js with a text editor? as I start the program? . I hope you can help me and thank you very much in advance and very good video.
    regards

    1. Fabio Biondi (reply)

      February 24, 2014 at 9:54 pm

      Hi, I’m sorry you have problems but this script can be useful if you have already tried at least once Arduino or ChromeApps, otherwise it’s very difficult to follow.

      Anyway:
      the html/javascript files must be packaged as Chrome App using the chrome://extension panel. It doesn’t work inside Arduino but it’s just like a classic HTML5 app that run as desktop app (just like your Word or Photoshop.. more or less :P ).

      The sketch (the arduino C source code) must be uploaded into your arduino using the Arduino IDE via USB, just like any other Arduino sketch.
      The ChromeAPp, when started, will connect to the serial port and it will be able to send and receive info from the Serial.

  2. Mochamad Agung Tarecha (reply)

    April 22, 2014 at 1:30 am

    can serial port COM automatic detect when device is arduino ?
    const DEVICE_PATH = ‘COM11′; // PC

  3. Chris (reply)

    May 8, 2014 at 9:37 am

    can i send data to an arduino from an html over wifi?

    1. Fabio Biondi (reply)

      May 8, 2014 at 10:08 am

      Sure but it’s a different technique and you need different hardware (in fact most Arduino boards don’t support the wifi)

      The easiest way is use an Arduino YUN and it’s very very easy to use.
      Check the official doc or read these short articles where I describe some test I did:
      http://www.fabiobiondi.com/blog/2014/02/html5-arduino-yun-and-temboo-api-get-temperature-in-json-format-and-send-it-to-gmail/
      http://www.fabiobiondi.com/blog/2014/02/arduino-yun-angularjs-and-firebase-with-curl-realtime-data/

  4. Rashaad (reply)

    May 30, 2014 at 4:20 am

    Thanks, this helped me solve the problem with the serial. If anyone is having issues, make sure you are using Uint8Array instead of Uint16Array to store your message that will be sent to the arduino!

  5. Roberto (reply)

    June 20, 2014 at 5:40 pm

    Problems connecting on linux. I know that linux has more restrictions on serial ports. Do you know how to allow connections?

    Thanks!

  6. Stefano Capettini (reply)

    July 14, 2014 at 11:13 pm

    Hi! Great work, really! It’s a real fast start. Only one thing: the example is not correct. If you download the zip file everything is ok, but if you copy and paste what is wrote in this page the app doesn’t completely work. This is because the version of Arduino’s sketch and the main.js script is not the same. You can fix it replacing the line 108 and 109 with:
    if(ledstatus==110)ledstatus=0;
    if(ledstatus==121)ledstatus=1;
    Anyway, the solution in the zip file, the one that use the JSON formatting of the response, it’s fantastic.
    Great work, again, very compliment.

  7. Stefano Capettini (reply)

    August 5, 2014 at 10:17 am

    Hi, just a question: my Arduino sends repeatedly jSON formatted data on serial. Is there an eventListener that will be triggered when Arduino sends data? Thanks for your help!

Leave a Reply

Your email address will not be published. Required fields are marked *

Published on: 24 February
Posted by:
Discussion: 9 Comments