JavaScript: Hardware mit Cylon.js steuern
Seite 3: Interaktion
Schnell reagiert
Die Beispiele zeigen bereits, wie einfach sich Befehle an Geräte versenden lassen. Auch das Reagieren auf Ereignisse ist möglich, wie eine schaltbare LED beweist:
Cylon.robot({
connections: {
arduino: { adaptor: 'firmata', port: '/dev/ttyACM0' }
},
devices: {
led: { driver: 'led', pin: 13, connection: 'arduino' },
button: { driver: 'button', pin: 2, connection: 'arduino' }
},
work () {
this.button.on('push', () => {
this.led.toggle();
});
}
}).start();
Möchte man neben der für das Ausführen vorgesehenen work-Funktion weitere Verhaltensaspekte kapseln, lässt sich das durch das Anhängen der gewünschten Funktionen an das Konfigurationsobjekt umsetzen. Sie stehen im Anschluss ebenfalls an der this-Referenz zur Verfügung:
Cylon.robot({
// ...
blink () {
this.led.toggle();
setTimeout(() => {
this.led.toggle();
}, 1 * 1000);
},
work () {
this.button.on('push', () => {
this.blink();
});
}
}).start();
Einen Roboter mit einer API ausstatten
Besonders interessant wird ein mit Cylon.js entwickler Roboter, wenn er nicht nur einem programmierten Fahrplan folgt, sondern in der Lage ist, mit seiner Umwelt zu interagieren. Er könnte zum Beispiel Sensordaten an eine Web- oder Cloud-Anwendung übertragen oder Anweisungen von ihr entgegennehmen und an lokale Aktoren übermitteln.
Erst durch derartige Interaktionsmöglichkeit lassen sich "Dinge" vernetzen und in größerem Stil zusammenschalten, beispielsweise als Schwarm. Da Cylon.js auf der serverseitigen JavaScript-Plattform Node.js aufbaut, ist es naheliegend, einen Roboter mit einer HTTP- oder Websockets-nutzenden API auszustatten. Für Ersteres ist zunächst das zusätzliche Modul cylon-api-http zu installieren:
$ npm install cylon-api-http
Die Schnittstelle lässt sich aktivieren, indem man die api-Funktion aufruft und ihr den Parameter http übergibt. Ohne die Angabe weiterer Parameter stellt sie unter https://localhost:3000 einen HTTP-Server zur Verfügung, wobei ein selbstsigniertes Zertifikat zum Einsatz kommt und auf Authentifizierung verzichtet wird:
Cylon.api('http');
Um Host und Port zu ändern, sind die entsprechenden Werte als Optionsobjekt zu übergeben:
Cylon.api('http', {
host: '192.168.1.100',
port: '4000'
});
Auf ähnliche Art lassen sich das SSL-Zertifikat austauschen und die Authentifizierung aktivieren. Für das Zertifikat sind zwei Dateien im .pem-Format nötig: eine für den privaten Schlüssel und eine mit dem eigentlichen Zertifikat. Anzugeben ist jeweils der absolute Pfad der benötigten Datei:
Cylon.api('http', {
host: '192.168.1.100',
port: '4000',
ssl: {
cert: '/.../certificate.pem',
key: '/.../privateKey.pem'
},
auth: {
type: 'basic',
user: 'root',
pass: 'secret'
}
});
Alternativ lässt sich die API für den Roboter über Websockets oder das MQTT-Protokoll anbieten.
Standardmäßig stellt Cylon.js alle Funktionen und Eigenschaften eines Roboters, die nicht vom Framework selbst definiert wurden, über die Anwendungsschnittstelle zur Verfügung. Ist Letztere anzupassen, kann man die gewünschten Funktionen in einem commands-Objekt zusammenfassen. Dadurch exportiert das Programm nur noch jene Funktionen, die Bestandteil des Objekts sind. Alle übrigen behandelt es als privat.
Es ist wichtig, die this-Referenz zu erhalten, weshalb sich ein Aufruf der gewĂĽnschten Funktion per call anbietet, wie das folgende Beispiel zeigt:
Cylon.robot({
// ...
blink () {
this.led.toggle();
setTimeout(() => {
this.led.toggle();
}, 1 * 1000);
},
commands: {
blink () {
this.blink.call(this);
}
}
work () {
this.button.on('push', () => {
this.blink();
});
}
}).start();
Sobald sich das Verhalten eines Roboters dynamisch beeinflussen lässt, ist es sinnvoll, es durch den Einsatz von Unit-Tests zu überprüfen. Dazu ist der Roboter in einen Testmodus zu versetzen, was sich durch die Übergabe der Eigenschaft testMode mit dem Wert true an die Funktion config realisieren lässt. Wichtig ist, dass der Aufruf innerhalb des Tests stattfinden muss, bevor der eigentliche Roboter geladen wird:
const assert = require('assertthat');
const Cylon = require('cylon');
suite('my robot', () => {
let robot;
suiteSetup(() => {
Cylon.config({ testMode: true });
require('../lib/myRobot');
robot = Cylon.MCP.robots['MyRobot'];
});
suite('work', () => {
test('is a function.', done => {
assert.that(robot.work).is.ofType('function');
done();
});
// ...
});
});
Im Test-Code fallen zwei Zeilen auf, die von der regulären Node.js-Notation abweichen: Erstens gibt der Aufruf von require keine Referenz auf den Roboter zurück, zweitens erfolgt der Zugriff auf Letzteren über den Namen MyRobot an der Eigenschaft MCP. Das Kürzel steht für "Master Control Program" und bezeichnet den Prozess, der die unterschiedlichen Roboter lädt und ihre Dienste über eine API zur Verfügung stellt.
Damit das Ganze funktioniert, muss man dem zuvor definierten Roboter allerdings noch einen Namen zuweisen (im vorliegenden Fall MyRobot):
Cylon.robot({
name: 'MyRobot',
// ...
work () {
// ...
}
}).start();