Home

node.js

Below you find all posts tagged with "node.js". You can always go back to the home page if you want to read more.

DIY WiFi Environment Sensor

DIY, Code, Home Automation, node.js
WiFi Environment Sensor Assembly

Es gibt einige Räume im Haus, deren Temperatur und Luftfeuchtigkeit ich gerne überwachen möchte. Im Wohnzimmer habe ich dazu ein Elgato Eve Room stehen aber das für viele Räume anzuschaffen ist mir erstens zu teuer und zweitens komme ich mit Bluetooth LE nicht durch die Decken. Mehrere HomeKit-Hubs aufzustellen (bspw. AppleTV) ist irgendwie auch keine Lösung und Elgato kommt leider auch nicht mit einem angekündigten Bluetooth Range Extender um die Ecke (angeblich da Apple das nicht freigibt) aber das wäre ingesamt vermutlich auch unbezahlbar.

Was bleibt? DIY! Lötkolben raus, IDE auf und einen Sensor Marke Eigenbau selber basteln.

Die Hardware

Nach ein bisschen Recherche bin ich als Sensor auf den Bosch BME280 gestoßen, der Temperatur, Luftfeuchtigkeit und Luftdruck messen kann. Den Sensor gibt es bspw. bei AliExpress für um die 3 € auf einem Breakout Board.

Da die Sensordaten natürlich auch irgendwie per HomeKit verfügbar sein sollen, gab es dazu noch ein NodeMCU, ebenfalls von AliExpress für ca. 2,50 €. NodeMCU ist ein Entwicklungsboard, das einen ESP8266 Chip zur Verfügung stellt und damit WiFi / WLAN Applikationen sehr einfach zu bauen und zu programmieren macht.

NodeMCU and BME280 Boards

Nach dem Anlöten von vier Pins an das BME280 Breakout Board kann man die beiden Hardwarekomponenten einfach nach folgendem Muster mit Jumper-Kabeln verbinden:

NodeMCU | BME280
3V      | VIN
G       | GND
D3      | SCL
D4      | SDA

Zusätzlich habe ich noch den D8 Pin des NodeMCU mit RST Pin des NodeMCU verbunden, um die Deep Sleep Funktionalität des Boards zu nutzen. Die Idee ist hier, dass das Board nicht permanent läuft und Strom konsumiert, sondern nach einer Messung für einen definierten Zeitraum einschläft und danach eine neue Messung startet. Durch die so erzielte Stromeinsparung könnte man den Sensor später auch mit einer Batterie betreiben aber das habe ich in diesem Projekt nicht umgesetzt.

Die Software

Bei der Software gibt es drei Komponenten, die ich nachfolgend beschreibe. Zum einen die Firmware für den ESP8266 Chip, dann einen Webservice zum speichern und verwalten der Sensordaten und zu guter letzt ein Plugin für Homebridge, um den Sensor in HomeKit verfügbar zu machen. Den Sourcecode für alle drei Komponenten findet ihr in einem GitHub Repository.

Firmware

Die Firmware für den ESP8266 Chip habe ich mit der Arduino IDE entwickelt. Dazu braucht es neben der IDE selbst noch den ESP8266 Arduino Core, der die Unterstützung des Chips bereitstellt. Zusätzlich gibt es auch Bibliotheken, um mit dem BME280 Sensor zu kommunizieren. Hierbei habe ich mich für den Adafruit Unified Sensor Driver und die Adafruit BME280 Library entschieden. Wie man die einzelnen Bibliotheken einbindet ist auf den jeweiligen Projektseiten gut beschrieben.

Für mein Board musste ich die I2C Adresse des BME280 noch anpassen. Die Adafruit Breakout Boards nutzen hier wohl standardmäßig die Adresse 0x77, meine Boards aus dem Reich der Mitte nutzen aber die Adresse 0x76. Das lässt sich aber einfach im Header-File Adafruit_BME280.h der Adafruit BME280 Library anpassen, indem man dort nach der Zeile #define BME280_ADDRESS (0x77) sucht und entsprechend anpasst (sofern nötig).

Um das Board per USB anzusprechen und die Firmware drauf zu laden, braucht es noch einen USB to Serial Driver. Bei mir unter macOS hat dieser Treiber sich als funktionierend herausgestellt, wie es auf anderen Betriebssystemen aussieht kann ich leider nicht sagen.

Damit ist alles vorbereitet und es kann an die Programmierung der ESP8266 Firmware gehen. Der komplette Sourcecode findet sich auf GitHub, ich möchte aber noch mal kurz auf den Ablauf eingehen:

Wenn das NodeMCU Board Strom bekommt, wird die setup() Methode aufgerufen. Darin wird zuerst eine Verbindung zum WiFi aufgebaut, die SSID sowie das Passwort sind dabei konfigurierbar. Als nächstes wird die Verfügbarkeit des BME280 geprüft und der Sensor mit einigen Parametern initialisiert. Man muss diese Konfiguration nicht zwingend vornehmen, da der Sensor auch ohne misst, für meinen Anwendungsfall haben sich aber die im Code hinterlegten Sampling-Raten als die zuverlässigsten und genauesten herausgestellt. Nachdem im nächsten Schritt eine Messung von Temperatur (t), Luftfeuchtigkeit (h) und Luftdruck (p) vorgenommen wird, werden die ermittelten Werte an den Webservice (siehe unten) geschickt. Zu guter Letzt wird das Board in den Tiefschlaf versetzt und wacht dann nach der festgelegten Zeit wieder auf und das Ganze beginnt von vorne.

Webservice

Die ermittelten Sensordaten der schönen neuen Hardware müssen natürlich auch irgendwo hin. Insbesondere, da ich mich dafür entschieden habe die Hardware nicht permanent Strom ziehen zu lassen sondern zwischendurch in den Tiefschlaf zu versetzen, braucht es natürlich eine Persistenzschicht. Darüber hinaus möchte ich später auch gerne eine Historie der Sensordaten abrufen können.

Um das zu realisieren, habe ich einen kleinen Webservice in Node.js geschrieben, der die Sensordaten entgegennimmt und in einer SQLite Datenbank speichert. In meinem Szenario läuft der Webservice auf einem Raspberry Pi, der auch schon meine bestehende Hombridge Installation beherbergt. Der Webservice stellt dabei REST Endpunkte zum Hinzufügen, Lesen und Löschen von Sensordaten zur Verfügung. Der komplette Sourcecode sowie eine API Beschreibung findet sich im entsprechenden GitHub Repository.

Homebridge Plugin

Um die Sensordaten in HomeKit verfügbar zu machen fehlt noch ein Plugin für Homebridge. Homebridge emuliert die HomeKit API und kann damit auch Geräte wie diesen Sensor mit entsprechender Funktionalität ausstatten und so bspw. auch über Siri steuerbar machen. Details zu Homebridge und zur Installation gibt es an anderer Stelle genug, ich gehe hier also nur auf das selbstgeschriebene Plugin ein.

Was das Plugin im Endeffekt macht, ist HomeKit ein neues Accessory zur Verfügung zu stellen, das die Services bzw. Charakteristiken Temperatur (Temperature) und Luftfeuchtigkeit (Humidity) anbietet. Da Luftdruck (Air Pressure) leider noch keine offizielle HomeKit Charakteristik ist, fällt der Wert leider erstmal unter den Tisch. Die Werte werden vom Plugin dann per GET Request vom Webservice (siehe oben) abgeholt und können in HomeKit Apps angezeigt oder per Siri abgefragt werden.

Derzeit wird nur der aktuellste Sensorwert ausgegeben, die Eve App von Elgato kann aber bspw. auch historische Daten anzeigen und das ist eine meiner nächsten Verbesserungsideen. Mit fakegato-history gibt es bereits ein Projekt, welches die Daten vermutlich entsprechend aufbereiten könnte. Teile des Projektes basieren dabei auch auf meinen Reverse Engineering Aktivitäten in dem Umfeld.

WiFi Environment Sensor Casing

Das Gehäuse

Es hat zwar auch einen gewissen Charme, wenn überall im Haus Platinen zu sehen sind, die Meinung teilt aber nicht jede(r). Zum Glück gibt es ja freundliche Kollegen, die einen 3D-Drucker besitzen und sich bereit erklären ein Gehäuse zu drucken. Nachdem ich mit Onshape also ein entsprechendes CAD-Model designed und verschickt hatte, konnte ich den Sensor bereits am nächsten Tag in ein schickes Gehäuse stecken. Danke, Manuel!

Wenn ich weitere Sensoren dieser Art baue, werde ich das CAD Model wohl noch mal überarbeiten, da es an einigen Ecken ein bisschen knapp bemessen war. Gepasst hat es aber auch so irgendwie.

WiFi Environment Sensor

Das Ergebnis

Wenn man die Arbeitszeit (Basteln, coden, CAD Model bauen, etc.) mal aussen vor lässt, habe ich mir mit diesem Projekt für ein paar wenige Euro einen sehr gut funktionierenden Raumklima-Sensor gebaut. Ein freundliches "Hey Siri, wie ist die Luftfeuchtigkeit im Keller?" sagt mir damit ab sofort, ob ich dort besser mal lüften sollte. In Kombination mit einem Außensensor lässt sich damit natürlich auch herausfinden, ob das wirklich sinnvoll ist oder ob es draußen so feucht ist, dass das Lüften eher kontraproduktiv wäre.

node-mailchimp 0.8.2 - Bugfixes and Mandrill API

node.js, Code

node-mailchimp has finally been updated to version 0.8.2 with some long overdue bugfixes and is available via npm and GitHub. Good things come to those who wait.

In addition to the bugfixes I added support for the Mandrill API which should be working fine but was not properly tested up to now. I would not mind if you play around with it. Mandrill is a new product from the MailChimp team that provides email as a service running on the MailChimp infrastructure. Mandrill support in node-mailchimp is not yet available via npm but only directly from the GitHub repository.

If you want to give it a try, have a look at the following example. Further information can be found on the GitHub project page.

First of all you need an instance of the Mandrill API wrapper:

var MandrillAPI = require('mailchimp').MandrillAPI;

try { 
    var mandrill = new MandrillAPI('Your API Key');
} catch (error) {
    console.log('Error: ' + error);
}

With the wrapper instantiated you can now use all methods described in the Mandrill API documentation. The method names in this wrapper reflect the method names of the API and are named category_method with dashes (-) in method names being replaced by underscores (_).

The API method send-template from the API category messages would thus be available as messages_send_template in this wrapper. The parameters of the methods are passed as an object, omitting the API key which was already set earlier.

var params = {
	template_name: 'Name of the template to send',
	template_content: 'An array of template content to send',
	message: 'Information on the message to send'
};

mandrill.messages_send_template(params, function (data) {
    if (data.error)
        console.log('Error: '+data.error+' ('+data.code+')');
    else
        console.log(data); // Do something with your data!
});

A second way to use the Mandrill API is by using the call method instead of the wrapped methods directly. The call method differentiates between the categories and methods of the Mandrill API more clearly and automatically converts dashes to underscores. The example above using the call method looks like the this:

var params = {
	template_name: 'Name of the template to send',
	template_content: 'An array of template content to send',
	message: 'Information on the message to send'
};

mandrill.call('messages', 'send-template', params, function (data) {
    if (data.error)
        console.log('Error: '+data.error+' ('+data.code+')');
    else
        console.log(data); // Do something with your data!
});

Any feedback on the Mandrill functionality as well as node-mailchimp in general is much appreciated. Just contact me via the GitHub project page or Twitter.

node-mailchimp 0.8.0 - Now with OAuth2 goodness

node.js, Code

The latest version of node-mailchimp is now available from npm and GitHub. Be sure to update to have fun with the brand new OAuth2 functionality. Yay!

Below you find a rundown on how the OAuth2 process is handled by node-mailchimp along with an example on how to use it.

First of all you have to require and instantiate the module with a couple of parameters. The parameters clientId and clientSecret are required parameters available from MailChimp, a detailed explanation on how to get these can be found in the MailChimp OAuth2 documentation.

var MailChimpOAuth = require('mailchimp').MailChimpOAuth;

var options = {
    clientId: 'Your MailChimp client id',
    clientSecret: 'Your MailChimp client secret',
    serverUri: 'http://www.example.com',
    redirectUri: 'http://www.example.com/successfulLogin.html'
};

var oauth = new MailChimpOAuth(options);

When instantiating the module a server is automatically set up that listens on HTTP requests from MailChimp. This server needs to be available from the internet at the URI specified by serverUri, more on that later.

We're using OAuth2 as we want the user to allow access to MailChimp without giving his credentials to us so we need to send him to the MailChimp server to authorize there. Using the following method of MailChimpOAuth we receive a special URI to the MailChimp login form the user needs to be redirected to in a browser.

oauth.getAuthorizeUri();

That URI also includes the serverUri specified as a parameter and exactly that’s the URI the user will be redirected to upon successful authorization. The answer from MailChimp to that URI includes a code we need later on. If you additionally specified the parameter redirectUri the user is redirected to that page in the browser so he doesn't see a blank page.

The code received is now used to request an access token from MailChimp which in turn is used to get some additional metadata for our connection. All of that happens automagically, though, you don't have to do anything except listening for an authed event issued by MailChimpOAuth once everything is finished.

oauth.on('authed', function (apiKey) {
});

As you can see the event receives the parameter apiKey which you can pass on to other API functionality. That API key isn't really an API key to be quite honest, MailChimp is treating the access token similar to an API key, as explained in their API docs, though.

That's it! Having the API key you can now do whatever you want with the API, for example reading a list of the last 25 campaigns like follows.

var MailChimpAPI = require('mailchimp').MailChimpAPI;

oauth.on('authed', function (apiKey) {

    try { 
        var api = new MailChimpAPI(apiKey, { version : '1.3', secure : false });
    } catch (error) {
        console.log('Error: ' + error);
    }

    api.campaigns({ start: 0, limit: 25 }, function (data) {
        if (data.error)
            console.log('Error: '+data.error+' ('+data.code+')');
        else
            console.log(JSON.stringify(data)); // Do something with your data!
    });

});

One more thing: It makes sense listening on the error event of MailChimpOAuth as well so you're hopefully able to tell what went wrong, if something goes wrong.

oauth.on('error', function (message) {
    console.log('Error: '+message);
});

Any feedback is much appreciated as version 1.0 is getting closer and I want a solid wrapper by then that not only implements all functionality available in the MailChimp API (it does that already) but is also a breeze to work with.

Navigating the Makernote hell

node.js, Code

Trying to enhance node-exif, my Exif library for node.js, I came across Makernotes which really do annoy me right now.

Makernotes are part of the Exif specification, a standard for storing information about an image inside the image file, especially used in JPEGs. Where Exif itself is well specified and documented, the Makernote part is everything but. The specification describes the Makernote as "a tag for manufacturers of Exif writers to record any desired information. The contents are up to the manufacturer, but this tag should not be used for any other than its intended purpose".

As you can imagine the part the contents are up to the manufacturer led to a lot of different specifications with most of them being proprietary. Hurray! Luckily most of the specifications have been reverse engineered by now, for example by Evan Hunter, and are available around the internet, no one seems to claim completeness, though.

As if the proprietary nature with missing specifications isn't enough the Makernotes bring a couple of other problems which I’m not going to elaborate on here. If you’re interested you can check the Exiv2 Makernote page for a start.

Anyway, after digging for information and writing some code to extract the Makernote data I now need to test if everything works and iron out some bugs. This is why I'm looking for pictures taken with cameras by the following manufacturers and hope you can help me out:

Agfa, Asahi, Canon, Casio, Contax, Epson, Fujifilm, Foveon, Kodak, Konica, Kyocera, Leica, Mamiya, Minolta, Nikon, Olympus, Panasonic, Pentax, Ricoh, Samsung, Sigma, Sony.

If you own a camera by one of the manufacturers, know someone who does or simply have a picture taken with one of these cameras around I’d really appreciate if you can drop me a line and/or the picture on Twitter or GitHub. Thanks!