Interfacing a TFT_320QVT LCD/Touchscreen/SD to a Teensy 3.0

I’m working on a project which needs a touchscreen LCD. After searching eBay for a while, I noticed that many vendors were selling basically the same 3.2″ 320×240 TFT with resistive touchscreen and SD card reader. Though there were slight variations in the silkscreens, they all had the same model number – TFT_320QVT. I bought mine from digitalzone88 for $12.29. TK0466-1-digitalzone88 TK0466-4-digitalzone88 The board uses a SSD1289 LCD driver IC, and runs on 3.3V. The 3.3V voltage is incompatible with the typical Arduino, which runs at 5V (some vendors have created shields to interface it to an Arduino Mega). However, I purchased the TFT_320QVT with the intention of interfacing it to a Teensy 3.0, which runs at 3.3V. I noticed that there were not enough through-hole I/O pins on Teensy 3.0 to simultaneously interface to the LCD, touchscreen, and SD card. This necessitated the usage of the Teensy 3.0′s additional I/O pins, accessible only via solder pads on its underside: teensy3b To facilitate access to the 14 solder pads, I attached some header pins to piece of stripboard, and soldered 40AWG wire-wrap wire to between the pads and headers: extenderThe Teensy 3.0 runs on PJRC’s specially modified version of Arduino – Teensyduino. The task was to find compatible Arduino libraries. First order of business was the LCD. I searched the web, and found that dawnmist had spent a considerable effort in modifying Henning Karlson’s UTFT to work with the Teensy 3.0. Unbeknownst to me at the time, dawnmist’s modified UTFT is actually bundled with Teensyduino!

Next, was to get the touch screen working. It turns out that the current version of Henning Karlson’s UTouch is compatible with the Teensy 3.0.

For the SD slot, I tried the version of the Arduino SD library that’s bundled with Teensyduino. Unfortunately, though the SD library works with the TFT_320QVT’s SD reader, it stops working if you instantiate a UTFT object in the sketch. I tried sdfatlib, and found that not only does it work w/ the Teensy 3.0, but it coexists fine with UTFT. The only catch I found is that it works only with SPI_HALF_SPEED. When I set it to SPI_FULL_SPEED, it stops working.

Here is the Teensy 3.0 running UTFT‘s demo sketch on the TFT_320QVT: utft

Below is the step-by-step procedure to getting the TFT_320QVT up and running with Teensy 3.0:

Step 1: Install the libraries

1. UTFT: Run the Teensyduino installer, and when the Libraries to Install dialog is displayed, check the box next to UTFT in the Choose Additional Libraries to Install combobox. (Note: Henning Karlson’s latest UTFT also works with Teensy 3.0, but my discussion below will show how to interface to the UTFT that’s bundled with Teensyduino 1.18).

2. UTouch: Download UTouch.rar. Use WinRar or 7-zip to extract the enclosed UTouch folder. From the Arduino IDE pull-down menu, use Sketch->Import Library->Add Library… to install the extracted UTouch folder. Alternatively, you can just copy Utouch/ to your arduinosketchbook/libraries directory.

3. sdfatlib: Download the latest version of sdfatlib. From the Arduino IDE pull-down menu, use Sketch->Import Library->Add Library… to install the downloaded sdfatlibyyyymmdd.zip file. You can alternatively just extract the SdFat/ folder into your arduinosketchbook/libraries directory.

Step 2: Wire it up

1. LCD: dawnmist‘s modified UTFT library that’s bundled with Teensyduino has a configuration file: arduinofolder\libraries\UTFT\hardware\arm\HW_Teensy3.h. Inside HW_Teensy3.h, there are 3 options for the LCD pin assignments: USE_B_D_PORTS, USE_C_D_PORTS, and USE_USER_PORTS. By default, the file has

#define PORTS USE_B_D_PORTS

enabled. USE_B_D_PORTS gives the best performance when your sketch needs to use SPI (which is needed for the SD card slot). USE_C_D_PORTS gives the fastest performance, but is incompatible with SPI. USE_USER_PORTS allows you to configure arbitrary pins (by changing the DB_0-DB_16 #defines), but results in the slowest performance. I elected to use the default USE_B_D_PORTS setting.

2. SD card reader: The SD card reader works via SPI, so it needs to use SD_DIN->DIN (MOSI – 11), SD_DO->DOUT (MISO – 12), SD_CLK->SCK (13), and one of the chip select pins, CS0-CS4 (10,9,20,21, or 15). I chose to use SD_CS->CS4 (15).

3. Touchscreen: The touchscreen uses 5 pins, T_DIN/T_DO,T_CS/T_CLK/T_IRQ, which can be assigned to any arbitrary free GPIO pins.

Below is a chart of my pin assignments:

Teensy_pin = TFT_320QVT_pin
0 = LCD_DB4
1 = LCD_DB5
2 = LCD_DB8
3 = LCD_LED_A (backlight)
5 = LCD_DB15
6 = LCD_DB12
7 = LCD_DB10
8 = LCD_DB11
9 = LCD_REST (RESET)
11 = SD_DIN (MOSI)
12 = SD_DO (MISO)
13 = SD_CLK (SCK)
14 = LCD_DB9
15 = SD_CS
16 = LCD_DB0
17 = LCD_DB1
18 = LCD_DB3
19 = LCD_DB2
20 = LCD_DB13
21 = LCD_DB14
22 = LCD_WR
23 = LCD_RS
24 = T_CLK
25 = LCD_DB7
26 = T_CS
27 = T_DIN
28 = T_DO
29 = T_IRQ
32 = LCD_DB6

LCD_RD needs to be pulled up to 3.3v

I have created a sketch, UTFT_UTouch_SdFat_teensy3, that simultaneously demonstrates the LCD, touchscreen, and SD card by modifying the UTouch demo sketch. Below are the lines which are critical to configuring it to work with the above pin connections:

#include <UTFT.h>
#include <UTouch.h>
#include <SdFat.h>

// bitmap file to load as background.
// must be 320x240 and in format output by ImageConverter565
char bkgRaw[] = "ade.raw";

uint8_t sdCS = 15; // SD_CS - chip select
SdFat sd;
SdFile inFile;

uint8_t lcdRS = 23;
uint8_t lcdWR = 22;
uint8_t lcdCS = 4;
uint8_t lcdReset = 9;   
uint8_t lcdBacklight = 3; // must be a PWM pin for variable brightness
uint8_t lcdBacklightBrightness = 255; // 0-255
UTFT myGLCD(SSD1289, lcdRS, lcdWR, lcdCS, lcdReset);

// Initialize touchscreen
uint8_t t_Clk = 24;
uint8_t t_CS = 26;
uint8_t t_DIn = 27;
uint8_t t_DOut = 28;
uint8_t t_IRQ = 29;
UTouch myTouch(t_Clk, t_CS, t_DIn, t_DOut, t_IRQ);

The file displays a bitmap, ade.raw, as the background. Before running the sketch, copy ade.raw to a FAT-formatted SD card, and insert it into the TFT_320QVT’s SD card slot.

You can also substitute your own bitmap file. To create a .raw file, first create a 240×320 pixel jpg, png, or GIF file. Run it through either imageconverter565.exe (bundled with UTFT) or the online ImageConverter 565 make sure to select Convert to .raw file and Target Platform Arduino (AVR).

Here’s what UTFT_UTouch_SdFat_teensy3 looks like when it’s running:
btntest

Many thanks to dawnmist, and the others who figured out how to get UTFT working with the Teensy 3.0.

Downloads:
UTFT_UTouch_SdFat_teensy3
UTFT_demo_tft_320qvt_teensy3

Links:
I obtained much of the information I needed from these pages:
dawnmist – screen working … finally
Teensy 3.0 – driving an SSD1289 with utft

Arduino suddenly freezing up? Here’s how to fix it.

Every once in a while, my Arduino IDE suddenly takes forever to launch, sticking at the splash screen for a long time. Even after it launches, it’s still basically unusable, because the pull-down menus also freeze up, and take an eternity to respond. It happens to me every time I download a new Arduino IDE, which is so infrequent that repeatedly forget why it happens, and how to fix it. After ripping my hair out for a while, I start googling until I find the fix.

If you’re experiencing this problem running Arduino on Microsoft Windows, chances are, you have a virtual serial port from a Bluetooth SPP device installed in your system. The problem is that the Arduino IDE is trying to enumerate your system’s serial ports in order to locate the attached Arduino devices, and the code takes a long time to timeout. Luckily, user eried in the Arduino forums figured out what was happening, and posted a fix in the thread: Road to solve the delay in Arduino IDE. He has graciously come up with a workaround, and shared it with us.

The fix is easy. Simply download his rar file, extract his new rxtxSerial.dll, and replace the version that’s currently in the same directory where your arduino.exe resides. I’ve also attached a zip archive of the file below, for those people who don’t want to install WinRAR in order to get the new rxtxSerial.dll. Thanks, eried!

Downloads: rxtxSerial-2.2_fixed_2009-03-17.zip

ITEAD Studio IBOX – Part 3: Installing Java and Processing

ITEAD Studio asked me how to install Processing 2.1.1 on Debian 7.0 running on the IBOX, because they want to use the IBOX as a Lampduino controller. This article describes the installation procedure. All of the commands must be typed into a shell Terminal.

1. Install openjdk and librxtx

# apt-get update
# apt-get install openjdk-7-jdk librxtx-java

2. Download and extract Processing for 32-bit Linux

# cd /opt
# wget http://download.processing.org/processing-2.1.1-linux32.tgz
# tar xzf processing-2.1.1-linux32.tgz

3. Remove the x86 Java bundled with Processing and link to OpenJDK

# cd processing-2.1.1
# rm -rf java
# ln -s /usr/lib/jvm/java-7-openjdk-armhf java

4. Modify the serial support for ARM

Unfortunately, Processing 2.1.1 no longer uses RXTXcomm.jar, so we have to copy the serial support from Processing 2.0.3, and then modify it for ARM:

# cd /opt
# wget http://download.processing.org/processing-2.0.3-linux32.tgz
# tar xzf processing-2.0.3-linux32.tgz
# rm -rf processing-2.1.1/modes/java/libraries/serial
# mv processing-2.0.3/modes/java/libraries/serial processing-2.1.1/modes/java/libraries
# rm -rf processing-2.0.3*
# cd processing-2.1.1/modes/java/libraries/serial/library
# rm RXTXcomm.jar
# cp /usr/share/java/RXTXcomm.jar .
# rm linux32/librxtxSerial.so
# cp /usr/lib/jni/librxtxSerial.so linux32

Congratulalions, you have a working copy of Processing! Start Processing by launching /opt/processing-2.1.1/processing

** Optional Steps Below **

Processing complains that it doesn’t like OpenJDK when it’s launched. If you want to accelerate the performance, and improve compatibility, you can follow the additional steps below and install the hard float version of Oracle Java 7 SE Embedded. The OpenJDK 7 that’s installed via apt-get ignores the A20′s hardware floating point unit, and instead uses software emulation.

5. Download Java SE Embedded 7 update 51

Unfortunately, Oracle makes you accept a license agreement, which requires cookies, making download a lot messier than just running wget.
Use a web browser to navigate to the Java SE Embedded Downloads page. Click the Accept License Agreement radio button near the top of the page, and then click the link corresponding to ARMv6/7 Linux – Headful EABI, VFP, HardFP ABI, Little Endian. The next page requires you to log into an Oracle account. If you don’t have one, you can create one for free. Save the file the /opt directory. The included web browser wasn’t working in my copy of ITEAD Debian 7.0. If you need a web browser to download Java, you can use iceweasel:

# apt-get install iceweasel
# iceweasel

6. Install Oracle Java SE Embedded 7

# cd /opt
# tar xzf ejre-7u51-fcs-b13-linux-arm-vfp-hflt-client_headful-18_dec_2013.gz

The .gz file will extract into a directory at /opt/ejre1.7.0_51.
7. Modify Processing to use Oracle Java SE Embedded 7

# ln -s /opt/ejre1.7.0_51 /opt/processing-2.1.1/java

When you launch Processing 2.1.1, it should no longer complain that it doesn’t like the version of Java that you’re using. I found that the hardware floating support Oracle Java 7 gives a big speedup in any code that has floating point computations.

Previous Article: ITEAD Studio IBOX – Part 2: Booting up Debian Linux 7.0

ITEAD Studio IBOX Part 2: Booting up Debian Linux 7.0

My IBOX came shipped with Android TV A20 pre-loaded in the NAND flash. I plugged a Microsoft keyboard and mouse into the USB ports, and used my Panasonic plasma TV as a HDMI display.  The boot screen is a bit confusing, because it implies that the device has WLAN support:

atv1

You can ignore the big WLAN banner on the screen, because the IBOX only has a RJ-11 100BT Ethernet port. The main screen has typical media center functions. Applications takes you into an Android launcher screen, Settings takes you to a typical Android settings screen. Going to the Applications screen shows that it has some typical apps pre-installed:

atv2

Most notable is the Google Play store support. I tried logging into Google Play and installing a few apps. They worked fine. However, I’ve heard that YMMV … some apps don’t work properly. Adobe Flash is also pre-installed, which is a plus. I did not play with it much, beyond some cursory tests, because I am more interested in running Debian Linux on the IBOX. ITEAD has a Debian 7.0 image, which runs off an SD card. Instead of downloading the version from ITEAD’s OS page, I used an alpha version built on 2014-03-27 which ITEAD sent me. To install it, first extract the  iteados-A20-debian-xfce-2.0-alpha-2014-03-27.img file from the downloaded.bz2 archive. I used WinRAR on my Windows machine to extract it. The image file needs to be copied to a 4GB or bigger micro SD card. Note that the .img file cannot be copied to a FAT-formatted SD card, because it contains an entire Linux filesystem. Instead, it must be raw-copied using a utility. In Windows, Win32 Disk Imager is a free utility that fits the bill:

w32diskimager

Simply select the image file and the SD card’s drive letter, and then click the Write button. After the image is successfully written, insert the microSD card into the IBOX, and power it up. If all goes well, you will first see two Linux Penguins in the upper left corner of the screen. After a while, the login screen should appear:

deblogin

Log in with user: root, password: root. While Android TV A20 was incompatible with my Dell LCD monitor (all I got was a blank screen), Debian 7.0 is working great. Unfortunately, I’m having the same problem with both my Dell LCD and my Panasonic plasma: when the screen blanks out after inactivity, I am not able to wake it up again. Therefore, I have to reboot it after every time the screen blanks. Hopefully, ITEAD will be able to tell me how to fix this.

Previous Article: ITEAD Studio IBOX – Part 1: First Look
Next Article: ITEAD Studio IBOX – Part 3: Installing Java and Processing

ITEAD Studio IBOX – Part 1: First Look

ITEAD Studio recently contacted me to let me know that they’re going to display my Lampduino project at the Shenzhen Makerfaire. I thought that was a pretty cool idea, and am honored to hear that. They also asked if I wanted to test out their IBOX mini multifunction single board computer, which is currently in its final days of its campaign on Indiegogo, having already raised over 4.5x its funding goal. The IBOX is designed with hackers in mind, and is driven by an Allwinner A20 ARM Cortex-A7 processor. Here are the salient hardware specs:

  • CPU Dual-core ARM Cortex-A7
  • GPU Mali 400 MP
  • DDR3 RAM 1GByte
  • NAND FLASH 4GByte
  • 4x USB
  • 1x HDMI
  • 1x optical S/PDIF
  • 1x 100BT Ethernet
  • 1x 7-24V DC power jack

ITEAD touts the IBOX as a very open hackers platform, capable of running a plethora of OS’s.  Currently, the following distros are available for booting from its onboard NAND flash:

  • Android TV A20 (from ITEAD)
  • Android 4.2 (from Cubietech)
  • Lubuntu (from Cubietech)

The following distros must be booted from the microSD:

  • Android SD bootable image (from LinkSprite)
  • Debian 7.0 (from ITEAD)
  • Cubian (from Liu)
  • Arch Linux (from archlinuxarm.org)

The IBOX comes in an anodized aluminum case that has a glossy plexiglas top:

top

Being a pre-production sample, I’ll excuse the fact that the top had a lot of superficial scratches on it, but I wonder if a matte surface would be better, since it scratches so easily. The left side of the unit contains a plethora of connectivity:

left

The small rectangle on the left labeled Uboot is a switch for entering the U-Boot bootloader. The right side contains a micro SD slot:

right

Behind the rectangular window is a 2-color status LED and an IR receiver for talking to remote controls. The back panel contains a 32-pin expansion interface connector:

back

The expansion connector is what really separates the IBOX from the typical ARM mini PC. It opens up the platform for hardware hackers, containing pins for UART, TWI, SPI, SATA, etc.  You can find the full pinouts on the Indiegogo page. The bottom of the IBOX is held on with 4 Philips screws:

bottom

A set of rubber feet would be a nice addition.  Removing the metal bottom cover reveals the bottom of the baseboard:

botpcb

The baseboard is a very tight fit into the case.  In order to remove it, you must first pry the it away far enough to clear the various ports from their cutouts in left side of the case, and then pry upwards from the front.  The baseboard is a modular backplane which contains all of the I/O connectors, as well as the socketed core board:

pcbtop

The core board is essentially the “brain” of the IBOX, containing the A20 processor, RAM, etc. Since the core board is socketed, it can be swapped out as newer, more powerful ones become available (e.g. A31, etc).  Here is a close-up of the A20 core board:

coreboard

Note that there are two pushbuttons, SW1 and SW2. I’m not sure of their purpose, but they are not accessible when the IBOX is assembled. The plexiglas top was attached to my IBOX with some rather messy clear silicone caulk:

inside

Again, I’ll excuse the mess because it’s a pre-production sample. I hope that the production units will have cleaner assembly.

So far, the IBOX looks like an interesting platform for building an energy efficient media center, file server, http server, etc. I’m looking forward to powering it up and putting it through its paces. Perhaps I will use it to replace my Raspberry PI that’s currently running XBMC. In my next article, I will boot up the IBOX, and test it out.

Resources:

Indiegogo campaign – the most information can currently be found on this page.
ITEAD blog – contains various informative posts on the IBOX
A20 core board schematic
IBOX baseboard schematic
IBOX baseboard design files
Case dimensions
distro downloads

Noritake 24×6 Character VFD Module

I’ve always thought VFDs were pretty cool. They used to be the rage in high end consumer electronics. Back in the mid 70′s, I built a VFD alarm clock. Recently, I got my hands on a modern VFD module to play with. The Noritake-itron SCK-Y100-24063-N14 is a very flexible 24×6 character VFD module in the same form factor of a 20×4 character LCD module. It is a member of Noritake’s CU-Y series VFDs.

vfdardu

Overview

  • 5V supply voltage
  • serial (asynchronous and synchronous) and 8-bit parallel communication
  • CMOS signal and RS-232 (+-15V) voltage compatible
  • jumper-selectable baud rate: 9600, 19200, 38400(default), 115200.
  • extensive built-in character sets: USA, European, Japanese (Katakana only), Multilingual – various fonts and symbols, Canadian and French, Nordic, WPC1252 – european fonts and symbols, Cyrillic, Latin, Portuguese, PC858 – european fonts and symbols
  • adjustable brightness
  • locally selectable brightness for highlighting (useful for implementing menus)
  • double width, and double width & height characters

The video below compares the Noritake 26×6 VFD to a 20×4 LCD. The characters are noticeably smaller on the VFD due to the higher density, but still quite readable.

This page shows some of the versatility of the Noritake VFD: Versatile Character Display CU-Y Series

The VFD comes pre-configured to operate in async serial mode at 38400 baud. It isn’t necessary to use a UART to talk to the VFD; any GPIO pins will suffice. A minimum of 2 pins are needed, for SIN (input) and SBUSY (output). A third GPIO pin can be connected to RESET (input). I hooked mine up to an Arduino UNO as follows:

D2 -> SIN
D3 -> SBUSY
D4 -> RESET

Here’s what it looks like from the front, running Noritake’s Arduino menu demo:

vfdfront

The lower contrast on the left side of the photo is due to my camera’s reflection – the glass is very reflective. Note the highlighting via localized variations in brightness. Here’s what it looks like with a blue filter on top (again, apologies for the reflections – it actually looks a lot better than this photo):

vfdblue

Though my photos are crap, the display is quite easy to read indoors. I wouldn’t recommend it for outdoor use, however, or anywhere that you expect direct sunlight.

Back view:

vfdback

The 10-pin  jumper block on the top center is used for configuration, and the 14-pin jumper block on the bottom right is used only for parallel mode.

The writing speed of this VFD is very fast. Running in async serial mode on an Arduino UNO at 38400 baud, I was able to output 120 characters in a mere 38ms, which is about 3x faster than LiquidTWI2 can muster, even after the I2C bus is tweaked (and over 100x faster than LiquidTWI2 w/o I2C bus frequency tweaking). Unfortunately, Noritake’s Arduino library doesn’t compile on a Teensy 3.x, because it contains AVR assembly code in a timing function, and calls _delay_us(), which isn’t implemented on the Teensy 3.x. On the other hand, it should not be hard to replace these two functions. To use the Noritake VFD with Arduino, first download the Arduino code library. From the Arduino IDE’s pull-down menu, use Sketch->Import Library…->Add Library... to import Arduino_Noritake_VFD_CUY.zip. Include the following headers into your sketch:

#include <CUY_Interface.h>
#include <CUY_Serial_Async.h>
#include <Noritake_VFD_CUY.h>

Two classes need to be instantiated:

CUY_Serial_Async interface(38400,2, 3, 4); // SIN,BUSY,RESET
Noritake_VFD_CUY vfd;

Here is what initialization looks like:

void setup() {
  delay(500);          // wait for device to power up
  vfd.begin(24, 4);    // 24x4 character module
  vfd.interface(interface); // select which interface to use
  vfd.isModelClass(Y100);
  vfd.CUY_init();      // initialize the module
}

Note that Noritake chose to implement only partial compatibility with the LiquidCrystal library.  So while vfd.print(s) is supported, vfd.setCursor(x,y) is not; one has to instead call vfd.CUY_setCursor(x,y). The Noritake_VFD_CUY class methods are declared in Noritake_VFD_CUY.h. Noritake includes a few sample sketches, which you can access from Arduino’s menu via File->Examples->CUY.

Noritake also provides a handy host program, which lets you configure and test the display without a microcontroller.

cue-y_373x350

To use the program, connect the VFD to a PC running Microsoft Windows via a Serial->USB adapter, such as an FTDI cable. Using the Serial->USB adapter opens up the possibility of using the VFD  as a USB auxilliary display for a PC.

The Noritake SCK-Y100-24063-N14 is a very cool device, and I’m looking forward to building a project with it.

Resource Links

SCK-Y100-24063-N14 Overview 
CU-Y: Y-Series Evaluation Software
Code Library
Arduino Library with examples
Sample C++ code and configuration/hookup
How to use custom fonts
How to use the built-in font tables
How to use the font magnification command
How to create a menu using the highlight effect

Logging Rainforest RAVEn/EMU-2 Data to ThingSpeak (node.js on Raspberry Pi)

Last year, I bought a Rainforest EMU-2 energy monitor.  It wirelessly talks via zigbee to the smart energy meter that SCE installed at my house a couple of years ago. Unfortunately, its logging capabilities are rather primitive. All it does is give you a crude bar graph of the current and previous day’s usage. SCE actually has fairly detailed logs on their website, which you can access without buying your own energy monitor, but it’s cumbersome to log into their website and navigate down to the pages. Fortunately, a nice feature of the EMU-2 is that it has a USB port which behaves exactly like the Rainforest RAVEn, so you can read out the data into a computer and log it. I decided to hook up the EMU-2 to a Raspberry Pi, and log the data to ThingSpeak with node.js.

Step 1: Install node.js on Raspberry Pi

Unfortunately, the version of node.js that installs via apt-get on the Adafruit Occidentalis distro that I run on my Raspberry Pi is rather old (v 0.6.19), and won’t work with our code. I had to manually install from the nodejs repository:

wget http://nodejs.org/dist/v0.10.22/node-v0.10.22-linux-arm-pi.tar.gz
tar xzf node-v0.10.22-linux-arm-pi.tar.gz
cp -R node-v0.10.22-linux-arm-pi/bin /usr/local
cp -R node-v0.10.22-linux-arm-pi/lib /usr/local
cp -R node-v0.10.22-linux-arm-pi/share /usr/local
echo export NODE_PATH=/usr/local/lib/node_modules:/usr/local/lib/node_modules/npm/node_modules >> ~/.bashrc
source ~/.bashrc

I’m using node.js v0.10.22 because it works, and that’s the version that I’m using on my Debian development machine. I tried to install the latest, v0.11.11, but ran into all sorts of errors.

Step 2: Install node.js libraries

My RAVEn logging script depends on some libraries, serialportthingspeakclient, and xml2js, which we can install via npm:

npm install -g serialport thingspeakclient xml2js

Step 3: node.js code

The RAVEn XML API is fairly simple. To make things even simpler, I found that stormboy had already written a node.js library to decode it and github: stormboy/node-raven. node-raven reads data from RAVEn and uploads it to an MQTT server, so I had to modify the code to upload to ThingSpeak. Below is my script:

/**
 * Reads energy data from a smart meter via a RAVEn RFA-Z106 dongle (http://www.rainforestautomation.com/raven) and uploads to ThingSpeak.
 hacked from stormboy's node-raven https://github.com/stormboy/node-raven
 by Sam C. Lin
 */

var serialport = require("serialport"),
ThingSpeakClient = require('thingspeakclient'),
xml2js = require('xml2js');

process.on('uncaughtException', function(err) {
    // handle the error safely
    console.log(err);
});

var TRACE = true;

// RAVEn's serial port
var ravenSerialPath = '/dev/ttyUSB0';

// thingspeak parameters
var channelId = YOUR-THINGSPEAK-CHANNELID;
var apiKey = 'YOUR-THINGSPEAK-WRITE-API-KEY';

var tsclient = new ThingSpeakClient();
tsclient.attachChannel(channelId, { writeKey:apiKey});

// date offset for RAVEn which presents timestamp as seconds since 2000-01-01
var dateOffset = Date.UTC(2000, 0, 1);

var dailyNet = 0;
var dailyNetSentDate = 0;

var Raven = function(serialPath) {
    var self = this;

    // configure the serial port that the RAVEn USB dongle is on.
    this.serialPort = new serialport.SerialPort(serialPath, {
        baudrate: 115200,
        databits: 8,
        stopbits: 1,
        parity: 'none',
        parser: serialport.parsers.readline("\r\n")
    });

    this.serialPort.on("open", function() {
        openHandler(self);
    });
};

/**
 * Get the connection status between the USB device and the power meter
 */
Raven.prototype.getConnectionStatus = function() {
    var queryCommand = "<Command><Name>get_connection_status</Name></Command>\r\n";
    this.serialPort.write(queryCommand);
};

/**
 * Get information about the device
 */
Raven.prototype.getDeviceInfo = function() {
    var queryCommand = "<Command><Name>get_device_info</Name></Command>\r\n";
    this.serialPort.write(queryCommand);
};

/**
 * Query the amount of energy used or fed-in.
 */
Raven.prototype.getSumEnergy = function() {
    var queryCommand = "<Command><Name>get_current_summation_delivered</Name></Command>\r\n";
    this.serialPort.write(queryCommand);
};

/**
 * Get the power currently being used (or fed-in)
 */
Raven.prototype.getSumPower = function() {
    var queryCommand = "<Command><Name>get_instantaneous_demand</Name></Command>\r\n";
    this.serialPort.write(queryCommand);
};

Raven.prototype.getMessage = function() {
    var queryCommand = "<Command><Name>get_message</Name></Command>\r\n";
    this.serialPort.write(queryCommand);
};

Raven.prototype.getTime = function() {
    var queryCommand = "<Command><Name>get_time</Name></Command>\r\n";
    this.serialPort.write(queryCommand);
};

Raven.prototype.getCurrentPrice = function() {
    var queryCommand = "<Command><Name>get_current_price</Name></Command>\r\n";
    this.serialPort.write(queryCommand);
};

Raven.prototype.close = function() {
    this.serialPort.close();
};

// handle serial port open
function openHandler (self) {
    var parser = new xml2js.Parser();
    var buffer = "";	// read buffer.

    if (TRACE) {
    	console.log('serial device open');
    }

    // add serial port data handler
    self.serialPort.on('data', function(data) {
	buffer += data.toString() + "\r\n";		// append to the read buffer
	if ( data.toString().indexOf('</') == 0 ) {		// check if last part of XML element.

	    // try to parse buffer
	    parser.parseString(buffer, function (err, result) {
		if (err) {
		    console.log("err: " + err);
		    console.log('data received: ' + buffer);
		}
		else if (result.InstantaneousDemand) {
		    var timestamp = parseInt( result.InstantaneousDemand.TimeStamp );
		    timestamp = new Date(dateOffset+timestamp*1000);
		    var demand = parseInt( result.InstantaneousDemand.Demand, 16 );
		    demand = demand < 0x80000000 ? demand : - ~demand - 1;
		    if (TRACE) {
			console.log("demand: " + timestamp.toLocaleString() + " : " + demand);
		    }
		    var tsData = new Object();
		    tsData = { field1: demand };
		    tsclient.updateChannel(channelId,tsData);
		}
		else if (result.CurrentSummationDelivered) {
		    var timestamp = parseInt( result.CurrentSummationDelivered.TimeStamp );
		    timestamp = new Date(dateOffset+timestamp*1000);
		    var used = parseInt( result.CurrentSummationDelivered.SummationDelivered, 16 );
		    var fedin = parseInt( result.CurrentSummationDelivered.SummationReceived, 16 );
		    var curDate = timestamp.getDate();
		    var net = used - fedin;

		    if (dailyNet == 0) {
			dailyNet = net;
			dailyNetSentDate = curDate;
		    }

		    if (TRACE) {
		    console.log("sum: " + timestamp.toLocaleString() + " : " + used + " - " + fedin);
		    }

		    var tsData = new Object();
		    tsData = { field2: net,field3: used,field4: fedin};

		    // only send daily net once a day
		    if (curDate !== dailyNetSentDate) {
			tsData.field5 = net - dailyNet;
			dailyNet = net;
			dailyNetSentDate = curDate;
		    }

		    tsclient.updateChannel(channelId,tsData);

		}
		else if (result.ConnectionStatus) {
		    if (TRACE) {
			console.log("connection status: " + result.ConnectionStatus.Status);
		    }
		}
		else {
		    if (TRACE) {
			console.dir(result);	// display data read in
		    }
		}
	    });
	    buffer = "";	// reset the read buffer
	}
    });
}

var raven = Raven(ravenSerialPath);

Before you can run the script, you must ravenSerialPath, channelId, and apiKey to match your own configuration. Node that the code contains several getters which I don’t use, because by default, my EMU-2 sends out the data via the USB serial port at regular intervals.

Step 4: ThingSpeak Channel Configuration

Next, you must create a new ThingSpeak channel with 5 fields:
tsraven

Step 5: Run our node.js script

node raven-log.js &

Below are the live data from my ThingSpeak channel: Rainforest RAVEn Logging Demo






I will be updating the code from time to time. You can always get the latest version from github: lincomatic/raven-thingspeak

Logging Nest Thermostat Data: Update 1

I have updated my Nest thermostat data logger. The changes are as follows:

  1. added fan status, heater status, and AC status.
  2. polling interval increased from 15 min to 1 min.
  3. data are pushed to ThingSpeak only if at least one field changes.

The new node.js code:

"option strict";
var util = require('util'),
    ThingSpeakClient = require('thingspeakclient');
    nest = require('unofficial-nest-api');  // get from npm

process.on('uncaughtException', function(err) {
    // handle the error safely
    console.log(err);
});

// nest parameters
var username = 'YOUR-NEST-LOGIN';
var password = 'YOUR NEST PASSWORD';

// thingspeak parameters
var channelId = YOUR-THINGSPEAK-CHANNEL-ID;
var apiKey = 'YOUR THINGSPEAK-WRITE-API-KEY';

// update interval in ms
var updateInterval = 1000*60;

var tsclient = new ThingSpeakClient();
tsclient.attachChannel(channelId, { writeKey:apiKey});

var setTemp = 0;
var curTemp = 0;
var curHum = 0;
var curFanState = -1;
var curHeaterState = -1;
var curACState = -1;

function trimQuotes(s) {
    if (!s || s.length === 0) {
        return '';
    }
    var c = s.charAt(0);
    var start = (c === '\'' || c === '"') ? 1 : 0;
    var end = s.length;
    c = s.charAt(end - 1);
    end -= (c === '\'' || c === '"') ? 1 : 0;
    return s.substring(start, end);
}

function merge(o1, o2) {
    o1 = o1 || {};
    if (!o2) {
        return o1;
    }
    for (var p in o2) {
        o1[p] = o2[p];
    }
    return o1;
}

function fetchData(data) {
    nest.login(username, password, function (err, data) {
        if (err) {
            console.log(err.message);
            //process.exit(1);
            return;
        }

	nest.fetchStatus(function (data) {
            for (var deviceId in data.device) {
		if (data.device.hasOwnProperty(deviceId)) {
                    var shared = data.shared[deviceId];
		    var date = new Date();
		    var time = date.getFullYear()+'/'+date.getMonth()+'/'+date.getDate()+'-'+date.getHours()+':'+date.getMinutes();
		    var cTemp = nest.ctof(shared.current_temperature);
		    var sTemp = nest.ctof(shared.target_temperature);
		    var cHum = data.device[deviceId].current_humidity;
		    var cFanState = (shared.hvac_fan_state == true) ? 1 : 0;
		    var cHeaterState = (shared.hvac_heater_state == true) ? 1 : 0;
		    var cACState = (shared.hvac_ac_state == true) ? 1 : 0;

                    console.log(util.format("%s %s [%s], cur temp: %d F cur humidity: %d %% set temp: %d fan: %s heat: %s AC: %s",
					    time,
					    shared.name, deviceId,
					    cTemp,
					    cHum,
					    sTemp,
					    cFanState ? 'on' : 'off',
					    cHeaterState ? 'on' : 'off',
					    cACState ? 'on' : 'off'
					   ));
		    if ((cTemp !== curTemp) || (cHum !== curHum) ||
			(sTemp !== setTemp) || (cFanState !== curFanState) ||
			(cHeaterState !== curHeaterState) ||
			(cACState !== curACState)) {
			var tsData = new Object();
			tsData.field1 = cTemp;
			tsData.field2 = cHum;
			tsData.field3 = sTemp;
			tsData.field4 = cFanState;
			tsData.field5 = cHeaterState;
			tsData.field6 = cACState;
			console.log("sending to thingspeak");
			tsclient.updateChannel(channelId,tsData);
		    }
		    curTemp = cTemp;
		    curHum = cHum;
		    setTemp = sTemp;
		    curFanState = cFanState;
		    curHeaterState = cHeaterState;
		    curACState = cACState;
		}
	    }
	});
    });
}

fetchData();
setInterval(fetchData,updateInterval);

The corresponding ThingSpeak Channel also needs to have 3 new fields added to it:

  1. Field 4: Fan State
  2. Field 5: Heater State
  3. Field 6: AC State

I am currently running the demo on a Raspberry Pi. As before, you can view my live channel on ThingSpeak: Nest Logging Demo

I will be updating the code from time to time.  You can always get the latest version from github: lincomatic/nest-thingspeak

Related Articles: Nest

Logging Nest Thermostat Data

I recently had a Nest Thermostat installed at my house for free by my gas company as part of a pilot program that they’re running. It’s a pretty cool device, though I wouldn’t spend $250 to buy one. I like the fact that it’s a lot easier to use than my previous smart thermostat, especially being able to program it from a computer or my smartphone.  One very interesting feature to me is that it tracks the relative humidity of my house. Unfortunately, the Nest app doesn’t let you keep a running log of your data, so I decided to hack together a solution today.

I used node.js to implement a data logger which queries some parameters from my Nest thermostat, and upload it to ThingSpeak at regular intervals. Specifically, my node.js script uploads my Nest’s current temperature, humidity, and the current set temperature to thingspeak every 15 minutes. Below is the node.js script:

"option strict";
var util = require('util'),
    ThingSpeakClient = require('thingspeakclient');
    nest = require('unofficial-nest-api');  // get from npm

// nest parameters
var username = 'YOUR-NEST-USERNAME';
var password = 'YOUR-NEST-PASSWORD';

// thingspeak parameters
var channelId = YOUR-THINGSPEAK-CHANNELID;
var apiKey = 'YOUR-THINGSPEAK-WRITE-API-KEY';

// update interval in ms
var updateInterval = 1000*60*15;

var tsclient = new ThingSpeakClient();
tsclient.attachChannel(channelId, { writeKey:apiKey});

function trimQuotes(s) {
    if (!s || s.length === 0) {
        return '';
    }
    var c = s.charAt(0);
    var start = (c === '\'' || c === '"') ? 1 : 0;
    var end = s.length;
    c = s.charAt(end - 1);
    end -= (c === '\'' || c === '"') ? 1 : 0;
    return s.substring(start, end);
}

function merge(o1, o2) {
    o1 = o1 || {};
    if (!o2) {
        return o1;
    }
    for (var p in o2) {
        o1[p] = o2[p];
    }
    return o1;
}

function fetchData(data) {
    nest.login(username, password, function (err, data) {
        if (err) {
            console.log(err.message);
            process.exit(1);
            return;
        }

	nest.fetchStatus(function (data) {
            for (var deviceId in data.device) {
		if (data.device.hasOwnProperty(deviceId)) {
                    var shared = data.shared[deviceId];
		    var date = new Date();
		    var time = date.getFullYear()+'/'+date.getMonth()+'/'+date.getDate()+'-'+date.getHours()+':'+date.getMinutes();

                    console.log(util.format("%s %s [%s], Current temp: %d F Current Humidity: %d %% Target temp: %d",
					    time,
					    shared.name, deviceId,

					    nest.ctof(shared.current_temperature),
					    data.device[deviceId].current_humidity,

					    nest.ctof(shared.target_temperature)));

		    var fields = { field1: nest.ctof(shared.current_temperature),field2: data.device[deviceId].current_humidity, field3: nest.ctof(shared.target_temperature)};
		    tsclient.updateChannel(channelId,fields);
		}
	    }
	});
    });
}

fetchData();
setInterval(fetchData,updateInterval);

In order to run my node.js script, you need to first use npm to install unofficial-nest-api and thingspeakclient. My script is actually just a hacked up version of the example that comes with unofficial-nest-api.

Note that you must first update the initialization of username and password with your Nest.com login credentials.

Also, you need to enter your channel id and write api key from thingspeak.com. Below are my thingspeak channel settings:

channel

You can view my channel live on thingspeak here: Nest Logging Demo

channelview

TP-WR703N: Adding Storage Space – EXTROOT

Once you have your TP-WR703N running OpenWrt, you’ll find that it doesn’t have a lot of free space for adding your own files. Here is the output from df from after installing attitude-adjustment:

root@OpenWrt:/# df -h
Filesystem Size Used Available Use% Mounted on
rootfs 1.1M 352.0K 736.0K 32% /
/dev/root 2.0M 2.0M 0 100% /rom
tmpfs 14.3M 56.0K 14.2M 0% /tmp
tmpfs 512.0K 0 512.0K 0% /dev
/dev/mtdblock3 1.1M 352.0K 736.0K 32% /overlay
overlayfs:/overlay 1.1M 352.0K 736.0K 32% /

Note how rootfs has only 736KB free.  Luckily, there’s an easy way to put your rootfs onto external storage (extroot), which lets you easily expand your root file system onto a USB flash drive. In the instructions below, I will illustrate how to implement the pivot overlay flavor of extroot.

First, format your USB flash drive with an ext4 filesystem. I used an old 512MB Crucial USB drive. If you are using Windows, MiniTool Partition Wizard Home Edition can easily create an ext4 filesystem on your USB flash drive.

Before you can mount the USB flash drive on your TL-WR703N, certain packages need to be installed.  ssh into your TP-WR703N, and issue the following commands:

opkg update
opkg install block-mount kmod-fs-ext4 kmod-usb-storage
reboot

After OpenWrt reboots, access it via LuCI in your web browser, and navigate to the System->Mount Points tab. You should see a disabled mount point for /dev/sda1 as below:

mount

Click the Edit button and then check Enable this mount and Use as root filesystem:

extroot

Click Save & Apply, and then reboot OpenWrt.

If you don’t have LuCI installed, you can alternatively directly edit /etc/config/fstab. Add the following lines to the file:

config mount
  option device /dev/sda1
  option fstype ext4
  option is_rootfs 1
  option enabled_fsck 0
  option enabled 1

After your system reboots, if all is well, your USB drive will become your root file system:

root@OpenWrt:/# df -h
Filesystem Size Used Available Use% Mounted on
rootfs 462.3M 2.3M 435.4M 1% /
/dev/root 2.0M 2.0M 0 100% /rom
tmpfs 14.3M 48.0K 14.2M 0% /tmp
tmpfs 512.0K 0 512.0K 0% /dev
/dev/sda1 462.3M 2.3M 435.4M 1% /overlay
overlayfs:/overlay 462.3M 2.3M 435.4M 1% /

Note how rootfs/overlayfs now have 435.4MB free. From LuCI, it looks like this:

overlay

 

Related Articles: TP-WR703N