Adapter-Entwicklung
In diesem Dokument werden einige Code-Beispiele gesammelt, welche häufig bei der Adapter-Entwicklung benötigt werden.
Gefahr
Diese Code-Beispiele sind NICHT für JavaScript-Code im JavaScript-Adapter gedacht! Dort werden teilweise andere Funktionen bzw. unterschiedliche Signaturen genutzt.
Eine Adapter-Klasse sollte immer von utils.Adapter
erben. Die Basis-Klasse ist hier zu finden.
const utils = require('@iobroker/adapter-core');
class MyAdapter extends utils.Adapter {
// ...
}
Nützliche Eigenschaften
this.host
this.FORBIDDEN_CHARS
Objekte
Wie Objekte genau aufgebaut sind und welche Eigenschaften sie unterstützten, ist auf der Seite Objekte dokumentiert.
Neues Objekt erstellen (im eigenen Namespace)
Gerenell ist es empfehlenswert, Objekte über die instanceObjects
in der io-package.json anzulegen. Falls beim Adapter-Start bekannst ist, welche Objekte angelegt werden müssen, ist dieser Weg zu bevorzugen. Alternativ können Objekte per Code angelegt werden:
await this.setObjectNotExistsAsync('exampleDevice', {
type: 'device',
common: {
name: 'Any name',
type: 'string',
role: 'text'
},
native: {}
});
Bemerkung
Es ist sinnvoll, den Namen aller Objekte (wenn möglich) direkt Mehrsprachig anzulegen! Dies wird z.B. vom Admin-Adapter und js-controller
bereits berücksichtigt.
await this.setObjectNotExistsAsync('exampleDevice', {
type: 'device',
common: {
name: {
en: 'Any name',
de: 'Jeder Name',
ru: 'Любое имя',
pt: 'Qualquer nome',
nl: 'Iedere naam',
fr: 'N\'importe quel nom',
it: 'Qualche nome',
es: 'Cualquier nombre',
pl: 'Jakiekolwiek imię',
uk: 'Ім\'я',
'zh-cn': '任何名字'
},
type: 'string',
role: 'text'
},
native: {}
});
Rückgabe:
{"id":"xxx.0.exampleDevice"}
Bemerkung
Je allgemeiner die Funktion, desto weniger Prüfungen werden durchgeführt. „setObject“ prüft dabei am wenigsten.
createDevice
createChannel
createState
setObjectNotExists
setObjectWithDefaultValue
setObject
Alle Funktionen gibt es asynchron und mit callback. Jeweils für Objekte im eigenen Namespace und fremde Objekte.
// set[Foreign]Object[Async]
// Erstellt (oder überschreibt) das Objekt
await this.setObjectAsync(id, obj, options);
this.setObject(id, obj, options, callback);
await = this.setForeignObjectAsync(id, obj, options);
this.setForeignObject(id, obj, options, callback)
// Wrapper für set[Foreign]Object
this.setObjectWithDefaultValue(id, obj, options, callback);
// set[Foreign]ObjectNotExists[Async]
// Erstellt das Objekt nur, wenn es nicht existiert
// Wrapper für setObjectWithDefaultValue
await this.setObjectNotExistsAsync(id, obj, options);
this.setObjectNotExists(id, obj, options, callback);
await this.setForeignObjectNotExistsAsync(id, obj, options);
this.setForeignObjectNotExists(id, obj, options, callback);
// Wrapper für setObjectNotExists mit type = 'device'
await this.createDeviceAsync(deviceName, common, _native, options);
this.createDevice(deviceName, common, _native, options, callback);
// Wrapper für setObjectNotExists mit type = 'channel'
await this.createChannelAsync(parentDevice, channelName, roleOrCommon, _native, options);
this.createChannel(parentDevice, channelName, roleOrCommon, _native, options, callback);
// Wrapper für setObjectNotExists mit type = 'state'
await this.createStateAsync(parentDevice, parentChannel, stateName, roleOrCommon, _native, options);
this.createState(parentDevice, parentChannel, stateName, roleOrCommon, _native, options, callback);
Bestehendes Objekt aktualisieren (im eigenen Namespace)
Wird ein Objekt aktualisiert, können geschützte Eigenschaften übergeben werden, welche nicht angefasst werden. Dazu zählt z.B. der Name des Objektes, welcher durch den Nutzer geändert werden kann.
await this.extendObjectAsync(deviceName, {
type: 'device',
common: {
name: {
en: 'Any name',
de: 'Jeder Name',
ru: 'Любое имя',
pt: 'Qualquer nome',
nl: 'Iedere naam',
fr: 'N\'importe quel nom',
it: 'Qualche nome',
es: 'Cualquier nombre',
pl: 'Jakiekolwiek imię',
uk: 'Ім\'я',
'zh-cn': '任何名字'
},
type: 'string',
role: 'text'
},
native: {}
}, { preserve: { common: ['name'] } } );
Alle Funktionen gibt es asynchron und mit callback. Jeweils für Objekte im eigenen Namespace und fremde Objekte.
// extend[Foreign]Object[Async]
await this.extendObjectAsync(id, obj, options);
this.extendObject(id, obj, options, callback);
await this.extendForeignObjectAsync(id, obj, options);
this.extendForeignObject(id, obj, options, callback);
Objekte lesen
Alle Funktionen gibt es asynchron und mit callback. Jeweils für Objekte im eigenen Namespace und fremde Objekte.
// get[Foreign]Object[Async]
await this.getObjectAsync(id, options, callback);
this.getObject(id, options, callback);
await this.getForeignObject(id, options, callback);
this.getForeignObject(id, options, callback);
await this.findForeignObjectAsync(id, type, options);
this.findForeignObject(id, type, options, callback);
const allObjects = await this.getAdapterObjectsAsync(); // Alle folder, device, channel und state Objekte
this.getAdapterObjects(callback); // Alle folder, device, channel und state Objekte
Objekt View
Möchte man viele Objekte auf einmal aus dem System abfragen, so eignet sich die Funktion getObjectView
. Mit dieser Funktion können alle möglichen Objekt-Typen (siehe Objekte) aus der Objekt-Datenbank abgefragt werden.
await getObjectViewAsync('system', 'instance', {
startkey: 'system.adapter.',
endkey: 'system.adapter.\u9999'
});
Alle Funktionen gibt es asynchron und mit callback. Jeweils für Objekte im eigenen Namespace und fremde Objekte.
await this.getObjectListAsync(params, options);
this.getObjectList(params, options, callback);
await this.getObjectViewAsync(design, search, params, options);
this.getObjectView(design, search, params, options, callback);
// Wrapper für getObjectView mit search "device" + namespace filter
await this.getDevicesAsync();
this.getDevices();
// Wrapper für getObjectView mit search "channel" + namespace filter
await this.getChannelsOfAsync(parentDevice, options, callback);
this.getChannelsOf(parentDevice, options, callback);
// Wrapper für getObjectView mit search "state" + namespace filter
await this.getStatesOfAsync(parentDevice, parentChannel, options);
this.getStatesOf(parentDevice, parentChannel, options, callback);
// Wrapper für getObjectView mit search "enum"
await this.getEnumAsync(_enum, options);
this.getEnum(_enum, options, callback);
await this.getEnumsAsync(_enumList, options);
this.getEnums(_enumList, options, callback);
// Wrapper für getObjectView
await this.getForeignObjectsAsync(pattern, type, enums, options);
this.getForeignObjects(pattern, type, enums, options, callback);
Objekt löschen (im eigenen Namespace)
Bemerkung
Der zugehörige State wird ebenfalls gelöscht (falls type = state)
await this.delObjectAsync(deviceName);
Objekt rekursiv löschen (im eigenen Namespace)
Unterstützt seit js-controller
Version 2.2.8
await this.delObjectAsync(deviceName, { recursive: true });
Alle Funktionen gibt es asynchron und mit callback. Jeweils für Objekte im eigenen Namespace und fremde Objekte.
// del[Foreign]Object[Async]
await this.delObjectAsync(id, options);
this.delObject(id, options, callback);
await this.delForeignObjectAsync(id, options);
this.delForeignObject(id, options, callback);
// Wrapper für delForeignObjectAsync (mit recursive = true)
await this.deleteDeviceAsync(deviceName, options);
this.deleteDevice(deviceName, options, callback);
// Wrapper für delForeignObjectAsync (mit recursive = true)
await this.deleteChannelAsync(parentDevice, channelName, options);
this.deleteChannel(parentDevice, channelName, options, callback);
// Wrapper für delForeignObjectAsync
await this.deleteStateAsync(parentDevice, parentChannel, stateName, options);
this.deleteState(parentDevice, parentChannel, stateName, options, callback);
Zustände (States)
Wie States genau aufgebaut sind und welche Eigenschaften sie unterstützten, ist auf der Seite Zustände (States) dokumentiert.
Wert schreiben (aktualisieren)
Bemerkung
Es ist darauf zu achten, dass der Datentyp des übergebenen Wertes zum definierten Datentyp auf dem Objekt passt.
await this.setStateAsync('myState', {val: newValue, ack: true});
Alternativ kann der neue Wert auch einzeln übergeben werden. Es ist empfohlen, immer ein komplettes State-Objekt zu übergeben, da dies ansonsten intern aufgebaut wird. Sollte newValue
(versehentlich) ein Objekt sein, wird es als „fertiges“ State-Objekt interpretiert, welchem dann wichtige Eigenschaften fehlen werden.
await this.setStateAsync('myState', newValue, true);
Alle Funktionen gibt es asynchron und mit callback. Jeweils für States im eigenen Namespace und fremde States.
// set[Foreign]State[Async]
await this.setStateAsync(id, state, ack, options);
this.setState(id, state, ack, options, callback);
await this.setForeignStateAsync(id, state, ack, options);
this.setForeignState(id, state, ack, options, callback);
Wert schreiben (ändern)
Bemerkung
Es ist darauf zu achten, dass der Datentyp des übergebenen Wertes zum definierten Datentyp auf dem Objekt passt.
await this.setStateChangedAsync('myState', {val: newValue, ack: true});
Alle Funktionen gibt es asynchron und mit callback. Jeweils für States im eigenen Namespace und fremde States.
// set[Foreign]StateChanged[Async]
await this.setStateChangedAsync(id, state, ack, options);
this.setStateChanged(id, state, ack, options, callback);
await this.setForeignStateChangedAsync(id, state, ack, options);
this.setForeignStateChanged(id, state, ack, options, callback);
Wert schreiben (binär)
Geänderte Signaturen seit js-controller
4.0.15 (setForeignBinaryState)
Um Binärdaten in States zu speichern, muss das Objekt vom Typ common.type = 'file'
sein. Für mehr Details siehe File Storage.
Alle Funktionen gibt es asynchron und mit callback. Jeweils für States im eigenen Namespace und fremde States.
// set[Foreign]BinaryState[Async]
await this.setBinaryStateAsync(id, binary, options);
this.setBinaryState(id, binary, options, callback);
await this.setForeignBinaryStateAsync(id, binary, options);
this.setForeignBinaryState(id, binary, options, callback);
Wert lesen
Um den aktuellen Zustand eines States zu bekommen, können einzelne Werte aus der Datenbank abgefragt werden:
Bemerkung
Sollte der State ein Alias sein, wird automatisch der State des Verknüpften Objektes zurückgegeben.
const state = await this.getStateAsync('myState');
const value = state ? state.val : undefined;
// ...
// oder
this.getState('myState', (err, state) => {
if (!err) {
// Das hat nich geklappt!
} else {
const value = state.val;
// ...
}
});
Rückgabe:
Es wird ein vollständiges State-Objekt zurückgegeben. Siehe Zustände (States).
Alle Funktionen gibt es asynchron und mit callback. Jeweils für States im eigenen Namespace und fremde States.
// get[Foreign]State[Async]
await this.getStateAsync(id, options);
this.getState(id, options, callback)
await this.getForeignStateAsync(id, options);
this.getForeignStates(id, options, callback);
Wert lesen (mehrere auf einmal)
const states = await this.getStatesAsync('pfad.im.eigenen.namespace.*');
Rückgabe:
Alle Funktionen gibt es asynchron und mit callback. Jeweils für States im eigenen Namespace und fremde States.
// get[Foreign]States[Async]
await this.getStatesAsync(pattern, options);
this.getStates(pattern, options, callback);
await this.getForeignStatesAsync(pattern, options);
this.getForeignStates(pattern, options, callback);
Wert lesen (binär)
Geänderte Signaturen seit js-controller
4.0.15 (getForeignBinaryState)
Um Binärdaten in States zu lesen, muss das Objekt vom Typ common.type = 'file'
sein. Für mehr Details siehe File Storage.
Alle Funktionen gibt es asynchron und mit callback. Jeweils für States im eigenen Namespace und fremde States.
// get[Foreign]BinaryState[Async]
await this.getBinaryStateAsync(id, options);
this.getBinaryState(id, options, callback);
await this.getForeignBinaryStateAsync(id, options);
this.getForeignBinaryState(id, options, callback);
Wert löschen
Bemerkung
Das zugehörige Objekt wird nicht gelöscht
Alle Funktionen gibt es asynchron und mit callback. Jeweils für States im eigenen Namespace und fremde States.
// del[Foreign]State[Async]
await this.delStateAsync(id, options);
this.delState(id, options, callback);
await this.delForeignStateAsync(id, options);
this.delForeignState(id, options, callback);
Auf Änderungen reagieren
Um Informationen nicht ständig aus der Datenbank abfragen zu müssen, können einzelne States, Objekte oder auch Dateien (Files) (siehe File Storage) abonniert werden. Der js-controller
informiert uns dann über Änderungen.
Zustände (States)
Abonnieren mit
subscribe[Foreign]States[Async]
Event-Handler registrieren
Änderungen im Event-Handler auswerten
class MyAdapter extends utils.Adapter {
/**
* @param {Partial<utils.AdapterOptions>} [options={}]
*/
constructor(options) {
super({
...options,
name: 'my-adapter',
});
this.on('ready', this.onReady.bind(this));
this.on('stateChange', this.onStateChange.bind(this));
}
async onReady() {
// Alle eigenen States abonnieren
await this.subscribeStatesAsync('*');
// Einen State aus einem anderen Namespace abonnieren
await this.subscribeForeignStatesAsync('0_userdata.0.beispiel');
}
/**
* @param {string} id
* @param {ioBroker.State | null | undefined} state
*/
onStateChange(id, state) {
if (state && !state.ack) {
// Verarbeitung bestätigen
this.setForeignState(id, { val: state.val, ack: true });
}
}
}
Alle Funktionen gibt es asynchron und mit callback. Jeweils für States im eigenen Namespace und fremde States.
// subscribe[Foreign]States[Async]
await this.subscribeStatesAsync(pattern, options);
this.subscribeStates(pattern, options, callback);
await this.subscribeForeignStatesAsync(pattern, options);
this.subscribeForeignStates(pattern, options, callback);
Objekte
Abonnieren mit
subscribe[Foreign]Objects[Async]
Event-Handler registrieren
Änderungen im Event-Handler auswerten
class MyAdapter extends utils.Adapter {
/**
* @param {Partial<utils.AdapterOptions>} [options={}]
*/
constructor(options) {
super({
...options,
name: 'my-adapter',
});
this.on('ready', this.onReady.bind(this));
this.on('objectChange', this.onObjectChange.bind(this));
}
async onReady() {
// Alle eigenen Objekte abonnieren
await this.subscribeObjectsAsync('*');
// Ein Objekt aus einem anderen Namespace abonnieren
await this.subscribeForeignObjectsAsync('0_userdata.0.beispiel');
}
/**
* @param {string} id
* @param {ioBroker.Object | null | undefined} obj
*/
onObjectChange(id, obj) {
if (obj) {
// Objekt geändert
} else {
// Objekt gelöscht
}
}
}
Alle Funktionen gibt es asynchron und mit callback. Jeweils für States im eigenen Namespace und fremde States.
// subscribe[Foreign]Objects[Async]
await this.subscribeObjectsAsync(pattern, options);
this.subscribeObjects(pattern, options, callback);
await this.subscribeForeignObjectsAsync(pattern, options);
this.subscribeForeignObjects(pattern, options, callback);
Dateien (Files)
Unterstützt seit js-controller
Version 4.1.0
Abonnieren mit
subscribeFiles[Async]
Event-Handler registrieren
Änderungen im Event-Handler auswerten
class MyAdapter extends utils.Adapter {
/**
* @param {Partial<utils.AdapterOptions>} [options={}]
*/
constructor(options) {
super({
...options,
name: 'my-adapter',
});
this.on('ready', this.onReady.bind(this));
this.on('fileChange', this.onFileChange.bind(this));
}
async onReady() {
await this.subscribeFiles(this.namespace);
}
/**
* @param {string} id
* @param {string} fileName
* @param {number} size
*/
onFileChange(id, fileName, size) {
}
}
Daten formatieren
Um Daten für den Endanwender lesbar zu formatieren, gibt es im Adapter verschiedene Funktionen, welche diese Aufgabe übernehmen:
formatDate
formatValue
Beide Funktionen beziehen sich im Standard auf die System-Einstellungen (es können ggf. eigene Formatierungs-Optionen übergeben werden).
Bei formatValue wird dieses Format als String (mit 2 Zeichen Länge!) übergeben. Dabei ist das erste Zeichen im String das 1000er-Trennzeichen und das zweite definiert das Komma. Eselsbrücke: Die Reihenfolge ist genauso, wie später im Ergebnis.
Beispiele:
this.formatValue(2.43425, 3); - Rückgabe: 2,434
this.formatValue(2.53425, 0); - Rückgabe: 3 (wird gerundet!)
this.formatValue(1000.123, 2, ‚.,‘); - Rückgabe: 1.000,12
this.formatValue(1000.123, 2, ‚,.‘); - Rückgabe: 1,000.12 (US-Schreibweise)
Timeout / Interval
Die Basis-Adapter-Implementierung erlaubt das verwalten von Timeouts und Intervallen. Das nutzen dieser Adapter-Funktionen stellt sicher, dass alle Timeouts und Intervalle beim Stop der Instanz korrekt abgebrochen/beendet werden.
Die Signaturen der Funktionen sind dabei identisch zum JavaScript-Standard.
this.setTimeout = (callback, timeout, ...args);
this.clearTimeout(id);
this.setInterval(callback, timeout, ...args);
this.clearInterval(id);
Lizenzen
Unterstützt seit js-controller
Version 4.0.15
Alle gültigen Lizenzen für einen Benutzer werden im Objekt system.licenses
abgelegt (intern Lizenz-Manager genannt). Gültige Lizenzen für den aktuellen Adapter können wie folgt abgefragt werden:
await this.getSuitableLicenses(all?: boolean, adapterName?: string);
Dabei wird der folgende Public Key des js-controller verwendet, um die Lizenz zu verifizieren (der Private Key liegt wahrscheinlich auf irgend einem Cloud-Server um die Lizenzen zu erstellen):
/opt/iobroker/node_modules/@iobroker/js-controller-adapter/build/cert/cloudCert.crt
Die Lizenzen werden als JWT (JSON Web Token) abgelegt.