Adventures with an Onion Omega2Pro
Introduction #
I love development boards and single board computers for how versatile they are. Among development boards I have a few favorite ones; one of which is a Tessel 2, and the other being an Onion Omega 2. Both of these boards run a distribution of Linux based on the OpenWRT project. The Onion Omega 2 is newer than the Tessel 2 and has more RAM, but it’s surprising how similar these boards are.
I like the mission of both boards. They strive to make building products easy, by greatly lowering the barrier to entry to build stuff because you can bring your own runtime. Don’t like writing C / C++ ? use Python, Rust, JavaScript (on Node) with full access to GPIO, I2C and SPI peripherals. This is what makes the IoT ecosystem far more approachable.
The Omega2 Pro #
Recently Onion announced a new development board called the Omega2Pro. The reason why this board intrigued me was that it had a lot more storage out of the box, and that it had 512MB
of Memory (128 MB
RAM and 384 MB
of flash memory for swap). This was going be very useful for a project I had in mind.
The Project #
When building new things, one of my go to programming languages is JavaScript. When building web apps, I have the luxury of using full blown development tools built in to the browser (Chrome DevTools). However, when using Node.js running on the Omega2Pro, I can’t use DevTools and all it’s goodness. I have to typically resort to logging extensively and I lose the ability to look at stack / heap allocations or to take heap snapshots etc. I desperately wanted to get DevTools to work with Node.js running on my Omega2Pro.
The good news is that the Node.js runtime (based on V8) has support for the Chrome DevTools protocol (which in turn is used by the developer tools frontend) starting release 6.15
. So as long as I used a reasonably new version of Node, I would be okay. All I had to do was to compile a version of Node.js from source to run on the Omega2Pro using their toolchain with the inspector
feature turned on.
The Process #
Building Node.js from source is something I had never done. So I spent a few days to understand some of the moving parts in the Node.js build system (node-gyp
). Building Node.js is not that hard, as long as you have the right setup. The compiler would have to target the mips
architecture (because the Omega2Pro runs a MIPS based chipset). This meant that I was going to have to cross compile Node.js for MIPS, given I was using an Intel core-i7 Ubuntu 18.04 (x64)
. For this, I had to setup the Onion Omega2Pro’s toolchain. Omega2Pro’s toolchain is open source. I used the openwrt-18.06
branch to set things up. I broadly followed these instructions, which I am summarizing below
Install pre-requisites to build the toolchain
sudo apt-get update sudo apt-get install -y build-essential vim git wget curl subversion build-essential libncurses5-dev zlib1g-dev gawk flex quilt git-core unzip libssl-dev python-dev python-pip libxml-parser-perl default-jdk
Clone the tool chain repository
git clone git@github.com:OnionIoT/source.git git checkout openwrt-18.06
Prepare the toolchain
sh scripts/onion-feed-setup.sh git checkout .config
Onion Omega2Pro’s toolchain is based on the OpenWRT projects’ toolchain. One of the fundamental concepts of the OpenWRT build system is the concept of
packages
andfeeds
. Feeds represent a collection of packages. Everypackage
declares aMakefile
. So when we setup the feeds (by callingsh scripts/onion-feed-setup.sh
) we are adding Onion’s package feeds to the OpenWRT tool chain. The provided scripts take care of indexing the feeds also calledinstalling
afeed
in OpenWRT parlance. We also restore the contents of the.config
file by runninggit checkout
on it; as the scriptonion-feed-setup.sh
modifies its contents.Build the toolchain
make -j # Use make -j1 V=s (for verbose logging in case something bad happens)
This takes a couple of hours. So read a book or watch a movie.
Onion Omega2Pro’s default feeds’ include a Node.js package (v8.10
). Unfortunately for me, this did not include the support for Chrome DevTools protocol. I also wanted to use the latest LTS (long term support) release of Node which is version 8.14
.
Thankfully the internet is awesome, and the good folks at nxhack/openwrt-node-packages already publish a feed with ready to use Node.js packages for OpenWRT. This tracks the latest stable version of Node 8. So I decided to use that. All I had to do was:
Update
feeds.conf
to include this line# Add this line to feeds.conf in the `source` directory. src-git node https://github.com/nxhack/openwrt-node-packages.git
Update the
feeds
index# Adds the new `node` feed ./scripts/feeds update node # Removes the existing `feeds/packages/node` package rm ./package/feeds/packages/node # Removes the existing `feeds/packages/node-*` packages rm ./package/feeds/packages/node-* # Install (or commit) to the feed index ./scripts/feeds install -a -p node
Now I had the latest
node
package in thesource/feeds/node/node
folder . (Remember this is the version that does not include support for Chrome Dev Tools, but is the version we want). Looking into the make file atsource/feeds/node/node/Makefile
, I realized that this build fetched the source code fornode
(8.14
) and invoked thenode-gyp
build system with theconfigure
command. This was the file that I was going to introduce my changes in to add support for Chrome DevTools. All I was going to have to do, is change a few build flags.Before I got ahead of myself, i wanted to sanity check the build setup. For that, I just ran a build for the updated Node feed, without any of my changes. This was trickier than I thought. Even though, I added the packages to the index, but I had no idea which commands to use, to get things to compile. It took me a while & lots of Googling; but I read the OpenWRT wiki and figured out what all the variables in the
Makefile
refer to, and the process of adding and compiling a package.# The command takes the following format # From the `source` folder all you need is to call # make package/<command> <package_name> where # `command` is one of [compile, install]. make package/compile node/node make package/install node/node
The build succeeded. However, I had no idea where the output binary was. To locate that I had to resort to
find . -name "node*"
I finally found something in
source/build_dir/target-mipsel_24kc_musl/node-v8.14.1/out/Release/node
.To test the built version of Node, all I had to do now was to copy this binary to an Omega2Pro, and to run it.
# I created a folder called Tools cd source/build_dir/target-mipsel_24kc_musl/node-v8.14.1/out/Release scp node root@omega-xxxx.local:/overlay/Tools/node
Here
overlay
is the mount point for the writeable filesystem on the Omega2Pro. For more information onoverlayfs
, read this.I finally ran
./node
in an ssh session.ssh root@omega-xxxx.local cd /overlay/Tools chmod +x /overlay/Tools/node ./node # I saw node running in REPL mode after a couple of seconds. >
These steps might simple in retrospect. Getting to this point actually involved a ton of trial and error from my end and series of sleepless nights. To summarize my progress so far, I had correctly setup the Omega2Pro toolchain, and had cross compiled a the latest version of Node 8 LTS from source.
Now, the Fun Part #
Now, all I had to do to get Chrome DevTools to work was to add support for the Chrome DevTools inspector protocol. I read this wiki and I figured out the next steps.
- Node provides support for Chrome DevTools using an optional feature called
v8-inspector
. - For
v8-inspector
to work, we need to build Node with ICU (libicu
). There are 4 options you can use when usingnode-gyp
. These are:none
small-icu
system-icu
full-icu
none
is what gets passed in when building Node.js without making any modifications to the Makefile
. I think this is being done to minimize the size of the eventual binary.
Building with
libicu
presents a small challenge.libicu
needs to be packaged with a tool calledicupkg
which itself needs to be compiled for thehost
CPU. We also needgcc
support to build32 bit
binaries. For that you will need to run these commands.sudo dpkg --add-architecture i386 sudo apt-get install gcc-multilib g++-multilib
Now we are ready to build Node.js with
libicu
. Update theMakefile
. The section that we need to update isCONFIGURE_ARGS
. Notice that the--with-intl
parameter takesnone
for default. I am changing that that tosmall-icu
.diff --git a/node/Makefile b/node/Makefile index f8a68a2..e60c891 100644 --- a/node/Makefile +++ b/node/Makefile @@ -119,7 +119,7 @@ define Package/node/config choice prompt "ICU Selection" default NODEJS_ICU_NONE help Select i18n features @@ -171,7 +171,7 @@ CONFIGURE_ARGS:= \ $(if $(CONFIG_NODEJS_10),,$(if $(CONFIG_NODEJS_11),,--shared-openssl)) \ $(if $(CONFIG_NODEJS_DEBUG),--debug) \ $(if $(CONFIG_NODEJS_ICU_SMALL),, \ - --with-intl=$(if $(CONFIG_NODEJS_ICU_SYSTEM),system-icu,none)) \ + --with-intl=$(if $(CONFIG_NODEJS_ICU_SYSTEM),system-icu,small-icu)) \ $(if $(findstring mips,$(NODEJS_CPU)), \ $(if $(CONFIG_SOFT_FLOAT),--with-mips-float-abi=soft)) \ $(if $(findstring +neon,$(CONFIG_CPU_TYPE)),--with-arm-fpu=neon) \
Here is the gist which should make it very clear.
Notice in the above script, I did the simplest thing possible. I could have also set an environment variable, but this seemed easier. All we need to do now, is to rebuild Node.js with these new flags. Let’s also copy this to the same
overlay
directory as before.make package/compile node/node make package/install node/node cd source/build_dir/target-mipsel_24kc_musl/node-v8.14.1/out/Release scp node root@omega-xxxx.local:/overlay/Tools/node
Testing & Demos #
To test this lets run
./node
in an ssh session.ssh root@omega-xxxx.local cd /overlay/Tools chmod +x /overlay/Tools/node ./node # I saw node running in REPL mode after a couple of seconds again. >
So far so good. Looks like the rebuilt version of Node.js still works.
For the demo, we need to run a simple Node.js program with the
inspector
enabled. For that I wrote a very simple JavaScript program.(function() { console.log('Ok. Ready'); debugger; console.log('Hello World'); setInterval(() => { console.log('Tick'); }, 1000); })();
All we need to do is to run
node --inspect-brk=0.0.0.0:9229 index.js
This tells
node
to run but wait for a debugger to attach before it executes the script. We are asking the debugging protocol server to run on all network interfaces, and on port9229
. This will be useful, when connecting DevTools to this instance of the inspector.We now need to configure DevTools to discover and connect to this instance of the inspector. For that we should first go to,
chrome://inspect
and enable network targets. I also added my Omega2Pro’s IP address. Notice I am using the same port9229
.
- Finally this is what the debugging experience looks like
- A full debugger is a game changer. Hope you enjoyed reading the article, and congratulations on making it this far.