Have you ever felt the need for your APIs to share their resources? Like, say, you have a RasPi in living room, it has some cool things on its GPIO and is running some code that has some endpoints exposed so you can control those cool things. In your bedroom you have another RasPi with different things on GPIO or even some slave ESPs and it also has some API. Wouldn’t it be wonderful if these two RasPi’s shared the things attached to them? Shared with each other and even external APIs? Of course, you can always do this manually – and that is what APIs are for, but you can do this ridiculously easily with ZettaJS .
About Zetta
Zetta is basically a tool for building IoT networks. It’s open source and is backed (or was backed) by Apigee. You start a server (hub), define devices attached to it with scouts, link the server to other ZettaJS servers so that they share their devices and finally you add application logic for the shared devices interaction. The way Zetta does this is simple and elegant, but unfortunately it seems to have lost traction recently. Its documentation is scarce and it doesn’t support ES6. I was only able to launch my servers on Node.js 4. Nonetheless, Zetta has a huge potential for IoT. I’d put it into the same league as Node-RED, Johnny-Five and others like that and I really hope the few guys still maintaining it will make it more contemporary and up to date.What Shall We Build?
Before trying your first Zetta server, you may want to go through the hello world. When you’re done – lets try two RasPi hubs network. I had no bright ideas while writing this article, so I just grabbed my RasPi Zero with LIRC (from the previous article) and a NanoPi Neo with a NodeMCU (ESP-12) as a slave. I’ve attached an RC004 sound sensor to the NodeMCU and decided that my RasPi Zero would turn on the TV when I snap fingers in front of the sensor. Not very useful but, hey, its just to demonstrate what you can do with Zetta.The Prototype
Okay, you can take a look on how Raspberry Pi Zero with LIRC, looks like in the previous article. Its basically a Linux machine running LIRC with Node.js wrapper. The other hub – NanpoPi Neo is also just a Linux machine, with no special hardware. The NodeMCU setup, though, is something of interest. Here it is: I used a ‘base’ board to easily plug the pins and also to give the RC004 sensor 5V, as NodeMCU has only 3V out. The sensor works in analogue mode, so the pin is A0 (on the photo above you can see the pin attached to D0, but I later changed my mind to the analogue setup). Before experimenting with the sensor I had to tune up its sensitivity till I saw it reacting to noise with the on-board LED. When you power the board up just snap near it – if you see the sensor LED blink – you are good to go, no coding required here. This guide includes general sensor setup, I used sound, but you can substitute it with whatever sensor you need – photo, temperature, gas, etc.Zero
Repo: https://github.com/yentsun/zero-zettajs-hub Fire up your code editor – its time for some coding. First, lets add the TV device driver to Zero server. Our TV is basically a state machine device in Zetta terms. It has a state (turned on/off) and allowed transitions for each state. You can find the full device code here . Quoting the interesting parts:
TV.prototype.turnOff = function (done) {
this.state = 'off';
lirc.irsend.send_once('tv', 'key_power', function () {
console.log("tv power command sent");
done();
});
};
TV.prototype.turnOn = function (done) {
this.state = 'on';
lirc.irsend.send_once('tv', 'key_power', function () {
console.log("tv power command sent");
done();
});
};
Our device has two methods: turnOff
and turnOn
which do pretty trivial things: change device state and send the same LIRC key_power
command (as turning the TV on and off is the same IR pulse code “POWER”). The funny thing is state machine helps us track the state of the actual TV. Provided that we don’t touch the remote – we always be able see if our TV is on or off from the Zetta browser (http://browser.zettajs.io/#/overview?url={hub_url}
):
Oh! And we can also turn on/off the TV from that browser once we deploy our server.
Generally we would initialize a device with a scout but there is no need in our case, because our device sits in OS level, so its essentially already and always initialized.
Neo
Repo: https://github.com/yentsun/neo-johnny5-zetta-hub The Neo machine is a bit different – it has a NodeMCU device as a slave (Firmata+Johnny5) which is not part of the OS, so we need a scout for it:
SensorScout.prototype.init = function (next) {
var self = this;
console.log('waiting for NodeMCU');
var board = new five.Board({
port: new EtherPortClient({
host: '192.168.0.107', // IP address of the NodeMCU
port: 3030
}),
timeout: 1e5,
repl: false
});
board.on('ready', function () {
var device = self.discover(sensor);
var RC004 = new five.Sensor({
pin: "A0"
});
RC004.on("change", function () {
var noise = this.scaleTo(0, 500);
device._onData(noise);
});
});
next();
};
As you can see its nothing more than a Johhny5 board initialization with the general Johnny5 analogue sensor setup on ‘ready’ event. Once the board is ready, the scout calls discover
method, which returns the device instance. Once the sensor starts receiving data, the scout scales it and sends to the device driver.
The sensor driver contains basic config for our device (name, type) and defines the _onData
method which processes sensor data and detects a snap:
SnapDriver.prototype.init = function (config) {
config
.type('sound_sensor')
.monitor('snapped'); // <-- the exposed stream
};
SnapDriver.prototype._onData = function (noise) {
if (noise < 424) {
this.snapped++;
console.log('SNAP!!!');
}
};
It also exposes snapped
stream which can be read from an application later. I’m sure this could be done with plain event listeners, but I didn’t find anything like this in the docs, so I kept the ‘streams monitor’.
Link The Hubs
Now the crucial thing to do is to link our Zero and Neo hubs so that they can share their devices. This is done vialink()
method. Lets add it to both our hubs.
Zero address is 192.168.0.108
, so Neo will have this link:
zetta()
.name('Neo Hub')
.use(scout)
.link('http://192.168.0.108:1337/') // <-- link to Zero
.listen(1337, function () {
console.log('Zetta is running at http://127.0.0.1:1337');
});
Neo’s IP is 192.168.0.101, so in Zero hub code:
zetta()
.name('Zero Hub')
.use(TV)
.link('http://192.168.0.101:1337/') // <-- link to Neo
.listen(1337, function () {
console.log('Zetta is running at http://127.0.0.1:1337');
});
Now when we start both hubs we’ll see they are connected to each other (example for Neo):
Jan-18-2018 20:03:52 [server] Server (Neo Hub) Neo Hub listening on http://127.0.0.1:1337
Zetta is running at http://127.0.0.1:1337
Jan-18-2018 20:03:54 [http_server] Websocket connection for peer "Zero Hub" established.
Jan-18-2018 20:03:54 [http_server] Peer connection established "Zero Hub".
And something similar for Zero.
Add App Logic
Lets add some automation logic to our network. Its mostly done via applications that can be injected in servers like other middleware with theuse()
method. We want our TV turned on/off when we snap the sensor, so in https://github.com/yentsun/zero-zettajs-hub/blob/master/main.js:
var logic = function (server) {
var sensor = server.from('Neo Hub').where({type: 'sound_sensor'}); // find the sensor
var tv = server.where({type: 'tv'}); // find the TV device
server.observe([sensor, tv], function (s, tv) { // observe both
console.log('observing sensor...');
s.streams.snapped.on('data', function(m) { // listen to the 'snapped' stream. Remember `.monitor('snapped')`?
console.log('I heard a snap!', m);
if (tv.available('turn-on')) { // turn on or off depending on current available action
tv.call('turn-on');
} else if (tv.available('turn-off')) {
tv.call('turn-off');
}
});
});
};
and then just:
zetta()
.name('Zero Hub')
.use(TV)
.link('http://192.168.0.101:1337/')
.use(logic) // <-- inject the app here
.listen(1337, function () {
console.log('Zetta is running at http://127.0.0.1:1337');
});
I think this application could also be put inside Neo hub instead, but it shouldn’t really matter.