NEWS
Virtual Devices
-
Ich habe einen numerischen Wert den ich auf einen virtuellen numerischen State schreibe.
Scheinbar liefert getState(…).val auch bei numerischen Werten einen string.
Die Lösung mit der convert Angabe läuft bei mir schon und funktioniert auch. Ich will das nur nicht bei allen devices einbauen. Daher suche ich eine zentrale Lösung.
Hier ist das Problem auch beschrieben: http://forum.iobroker.net/viewtopic.php?t=6819
-
Dort ging es darum, dass bei Änderungen direkt über Admin der falsche Typ genutzt wurde, was aber auch mittlerweile gefixt sein sollte.
Ich kann das verhalten zumindest bei mir so nicht feststellen, vielleicht lohnt eine Suche nach dem Verursacher des falschen Datentyps? Oder du musst noch js-controller oder Admin updaten?
Dieser Codeschnipsel
log (typeof getState("hm-rpc.0.LEQ0535278.2.POWER").val)
liefert:
` > 12:55:48.740 [info] javascript.1 Start javascript script.js.VirtualDevices.test12:55:48.741 [info] javascript.1 script.js.VirtualDevices.test: number `
Also geht bei getState nicht der Typ number verloren.Ich kann mir nur vorstellen, dass dein Eingangsstate entweder nicht vom Typ number ist (aber trotzdem eine Zahl, aber eben als String, enthält) oder irgendein Skript/Adapter in den korrekten number State trotzdem einen String schreibt.
Als temporären Fix könntest du das Skript so ändern, die mittlere Zeile ist neu, die anderen beiden findest du im Skript (ungetestet!):
if (newDelay !== undefined && newDelay !== null) writeObj.delay = newDelay; if (this.config.states[state].common.type === 'number') newVal = parseFloat(newVal); log(newVal + 'writing value ' + val + ' to ' + writeId + ' with delay ' + writeObj.delay, 'debug');
-
Der Fehler
> Wrong type of …: "string". Please fix, while deprecated and will not work in next versions.
tritt auf, wenn in einen State der nicht vom typ "string" ist, ein string geschrieben wird, woher auch immer. Analog für andere Datentypen (number => nicht-number, boolean => nicht-boolean, etc…).Mit Lesen hat das wenn nur indirekt zu tun.
-
Ich verwende zwave Sensoren.
Das auslesen des Temperaturwertes ist angeblich ein string:
log (typeof getState("zwave.0.NODE17.SENSOR_MULTILEVEL.Temperature_1").val); 20:39:38.999 [info] javascript.0 Start javascript script.js.test.dummy 20:39:38.999 [info] javascript.0 script.js.test.dummy: string
Der Sensor ist ein "FIBARO System FGMS001-ZW5 Motion Sensor":
{ "common": { "name": "Temperature", "type": "number", "role": "value.temperature", "read": true, "write": false }, "native": { "value_id": "17-49-1-1", "type": "decimal", "genre": "user", "label": "Temperature", "units": "C", "help": "", "node_id": 17, "class_id": 49, "instance": 1, "index": 1, "min": 0, "max": 0, "read_only": true, "write_only": false, "is_polled": false }, "acl": { "object": 1636, "owner": "system.user.admin", "ownerGroup": "system.group.administrator", "state": 1636 }, "_id": "zwave.0.NODE17.SENSOR_MULTILEVEL.Temperature_1", "type": "state" }
Alle Adapter sind aktuell:
ioBroker.zwave: 0.8.0
ioBroker.javascript; 3.4.0
ioBroker.js-controller: 1.1.3
-
Dann wird der Fehler im ZWave Adapter liegen, der Strings reinschreibt, statt Number.
-
Denke auch an einen Bug im zwave Adapter.
Hab mal ein Topic eröffnet dazu mit ein paar mehr Details:
-
Da ich das gleiche Problem hatte (da mir gerade wieder ein Fensterkontakt gecrasht ist),
hab ich nun die Sensoren und Schalter in virtuelle Devices verpackt.
Jedoch hab ich das Skript "VirtualDevice" noch ergänzt, um ggfs. einen eigenes common/native-Gespann ins Device zu bringen.
!
var obj; if (typeof this.config.copy == 'string') { obj = getObject(this.config.copy); } else if (typeof this.config.copy == 'object') { obj = this.config.copy; } else { obj = {common: {}, native: {}}; } !
Für meine Homematic-Schalter nutze ich folgendes Skript:
! ````
// virtuelle HM-Schalter
new virtualHomematicSwitch('Wohnzimmer_Lampe', 'hm-rpc.0.ABC1234567');
// virtuelle HM-IP-Schalter
new virtualHomematicIpSwitch('Schlafzimmer_Lampe', 'hm-rpc.1.CBA7654321', 4);
! // -----------------
! function virtualHomematicSwitch(name, deviceId) {
var config = {
namespace: 'Schalter',
name: name,
copy: {common: {"name":name,"role":"switch"}, native: {}},
states: {
'unreachable': {
common: {type: 'boolean', def: false, read: true, write: false, name: name+'.unreachable', role: 'indicator.unreach'},
read: {
[deviceId + '.0.UNREACH']: {}
}
},
'lowBat': {
common: {type: 'boolean', def: false, read: true, write: false, name: name+'.lowBat', role: 'indicator.battery'},
read: {
[deviceId + '.0.LOWBAT']: {}
}
},
'dutyCycle': {
common: {type: 'boolean', def: false, read: true, write: false, name: name+'.dutyCycle', role: 'value'},
read: {
[deviceId + '.0.DUTYCYCLE']: {}
}
},
'working': {
common: {type: 'boolean', def: false, read: true, write: false, name: name+'.working', role: 'indicator.working'},
read: {
[deviceId + '.1.WORKING']: {}
}
},
'state': {
common: {type: 'boolean', def: false, read: true, write: true, name: name+'.state', role: 'state'},
read: {
[deviceId + '.1.STATE']: {}
} ,
write: {
[deviceId + '.1.STATE']: {}
}} } } return new VirtualDevice(config);
}
! // -----------------
! function virtualHomematicIpSwitch(name, deviceId, switchState) {
var config = {
namespace: 'Schalter',
name: name,
copy: {common: {"name":name,"role":"switch"}, native: {}},
states: {
'unreachable': {
common: {type: 'boolean', def: false, read: true, write: false, name: name+'.unreachable', role: 'indicator.unreach'},
read: {
[deviceId + '.0.UNREACH']: {}
}
},
'lowBat': {
common: {type: 'boolean', def: false, read: true, write: false, name: name+'.lowBat', role: 'indicator.battery'},
},
'dutyCycle': {
common: {type: 'boolean', def: false, read: true, write: false, name: name+'.dutyCycle', role: 'value'},
read: {
[deviceId + '.0.DUTY_CYCLE']: {}
}
},
'working': {
common: {type: 'boolean', def: false, read: true, write: false, name: name+'.working', role: 'indicator.working'},
},
'state': {
common: {type: 'boolean', def: false, read: true, write: true, name: name+'.state', role: 'state'},
read: {
[deviceId + '.' + switchState + '.STATE']: {}
} ,
write: {
[deviceId + '.' + switchState + '.STATE']: {}
}} } } return new VirtualDevice(config);
}
! ````
Und für die Sensoren folgendes Skript:
!
// Homematic Tür/Fenster Sensoren new virtualHomematicShutterContact('Wohnzimmer_Fenster', 'hm-rpc.0.ABC1234567'); // Homematic Drehgriffkontakte new virtualHomematicRotaryHandleSensor('Schlafzimmer_Fenster', 'hm-rpc.0.CBA7654321'); ! // ----------------- ! function virtualHomematicShutterContact(name, deviceId) { var config = { namespace: 'Sensor', name: name, copy: {common: {"name":name,"role":"sensor"}, native: {}}, states: { 'unreachable': { common: {type: 'boolean', def: false, read: true, write: false, name: name+'.unreachable', role: 'indicator.unreach'}, read: { [deviceId + '.0.UNREACH']: {} } }, 'lowBat': { common: {type: 'boolean', def: false, read: true, write: false, name: name+'.lowBat', role: 'indicator.battery'}, read: { [deviceId + '.0.LOWBAT']: {} } }, 'state': { common: {type: 'boolean', def: false, read: true, write: false, name: name+'.state', role: 'state'}, read: { [deviceId + '.1.STATE']: {} } } } } return new VirtualDevice(config); } ! function virtualHomematicRotaryHandleSensor(name, deviceId) { var config = { namespace: 'Sensor', name: name, copy: {common: {"name":name,"role":"sensor"}, native: {}}, states: { 'unreachable': { common: {type: 'boolean', def: false, read: true, write: false, name: name+'.unreachable', role: 'indicator.unreach'}, read: { [deviceId + '.0.UNREACH']: {} } }, 'lowBat': { common: {type: 'boolean', def: false, read: true, write: false, name: name+'.lowBat', role: 'indicator.battery'}, read: { [deviceId + '.0.LOWBAT']: {} } }, 'state': { common: {type: 'boolean', def: false, read: true, write: false, name: name+'.state', role: 'state'}, read: { [deviceId + '.1.STATE']: { convert: function(value) { return value !== 0; } } } ! }, 'handle': { common: {type: 'number', def: 0, read: true, write: false, states: {0: 'closed', 1: 'tilted', 2: 'open'}, name: name+'.handle', role: 'state'}, read: { [deviceId + '.1.STATE']: {} } } } } return new VirtualDevice(config); } !
Für die HUE-Lampen habe ich das Skripot modifiziert, da ich zu jeder HUE-Lampe einen CUxD-Switch habe.
Hier wolten ich sowohl über Alexa, als auch HueApp, als auch ioBroker, als auch CUxD-Schalter
die komplette Synchronisation übernehmen; so daß alles per ioBroker synchronisiert wird.
! ````
// Hue-Lampen verknüft mit CUxD-Schalter
new virtualHueLamp("hue.0.hue_bridge.Stehlampe", "hm-rpc.2.CUX4001110.1.STATE");
! // -----------------
! function virtualHueLamp(lampId, switchId) {
var lampObj = getObject(lampId);
var lampName = lampObj.common.name.split('.').splice(-1)[0];var config = { namespace: 'Hue', name: lampName, copy: {common: {"name":lampName,"role":lampObj.common.role}, native: {type: lampObj.native.type, model: lampObj.native.modelid}}, states: { 'unreachable': { common: {type: 'boolean', def: false, read: true, write: false, name: lampName+'.unreachable', role: 'indicator.unreach'}, read: { [lampId + '.reachable']: { convert: function(value) { return !value; } } } }, 'level': { common: {type: 'number', def: 0, min: 0, max: 100, read: true, write: true, unit: '%', name: lampName+'.level', role: 'level.dimmer'}, read: { [lampId + '.bri']: { convert: function(value) { return Math.floor(value*100/254); }, before: function (device, value, callback) { if (value > 0) { setState(device.namespace+'.lastLevel', value, true); } callback(); } } }, write: { [lampId + '.bri']: { convert: function(val) { return Math.ceil(val*254/100); }, delay: 800 } } }, 'lastLevel': { common: {type: 'number', def: 100, min: 0, max: 100, read: true, write: false, unit: '%', name: lampName+'.lastLevel', role: 'value'}, read: {} }, 'command': { common: {type: 'string', def: '', read: true, write: true, name: lampName+'.command', role: 'command'}, write: { [lampId + '.command']: { delay: 800 } } } } } if (switchId) { config.states['switch'] = { common: {type: 'boolean', def: false, read: true, write: false, name: lampName+'.switch', role: 'state'}, read: { [switchId]: { after: function (device, value) { var level = getState(device.namespace + '.level').val; if (value === false && level > 0) { setState(device.namespace + '.level', 0); } else if (value === true && level <= 0) { var lastLevel = getState(device.namespace + '.lastLevel').val; setState(device.namespace + '.level', lastLevel); } } } } }
! config.states.level.read[lampId + '.bri'].after = function (device, value) {
if (value > 0 && getState(switchId).val === false) {
setStateDelayed(switchId, true, 200);
} else if (value <= 0) {
setStateDelayed(switchId, false, 200);
}
}
! config.states.command.write[lampId + '.command'].before = function (device, value, callback) {
var parsed;
try{
parsed = JSON.parse(value);
} catch(e) {
return;
}
if (parsed.level > 0 && getState(switchId).val === false) {
//if switch is off and level is greater 0 turn on switch
setStateDelayed(switchId, true, false, 800, true, function(){
callback(value, 3500);
});
} else if (parsed.level <= 0){
//if level is set to 0 turn off switch and set level 0
setStateDelayed(switchId, false, false, 800, true, function(){
callback(null, 0);
});
} else {
callback();
}
}
}if (lampObj.native && lampObj.native.type === 'Color temperature light' || lampObj.native.type === 'Extended color light') { config.states.ct = { common: {min: 2000, max: 6500, unit: 'K', step: 10, name: lampName+'.ct', role: 'level.color.temperature'}, read: { [lampId + '.ct']: { convert: function(val) { return Math.max(2000, Math.min(6500, Math.round(Math.pow(10,6)/val))); } } }, write: { [lampId + '.ct']: { convert: function(val) { return Math.max(153, Math.min(500, Math.round(Math.pow(10,6)/val))); }, delay: 1500 } } } } if (lampObj.native && lampObj.native.type === 'Color light' || lampObj.native.type === 'Extended color light') { config.states.xy = { common: {read: true, write: true, type: "string", name: lampName+'.xy', role: 'level.color.xy'}, read: { [lampId + '.xy']: {} }, write: { [lampId + '.xy']: { delay: 800 } } } } return new VirtualDevice(config);
}
! ````
Vielleicht gibt es noch weitere Ideen.
-
Ich habe das Skript jetzt seit einiger Zeit sehr erfolgreich im Einsatz.
Wie geht es nun weiter?
Plant jemand einen Adapter daraus zu erstellen?
Oder den Adapter ioBroker.wrapper damit zu ergänzen?
Oder gleich in ioBroker als zentrale Funktion aufnehmen?
Ich habe bislang keine Erfahrung mit der Adapter Entwicklung, würde mich aber beteiligen.
-
Von Adaptern habe ich (bisher) keine Ahnung.
Wüßte aktuell auch nicht, wie man sowas umsetzen kann.
Da ich den Adapter Broadlink2 im Einsatz habe, habe ich nun einige 433Mhz-Funksteckdosen ebenfalls als virtuelle Geräte eingerichtet,
so daß man mit dem STATE eines solchen Geräts auch Funksteckdosen ein/ausschalten kann.
Zuerst das globale Skript dazu, um einen Toggle zu erstellen.
Hier muß aber der Broadlink-Code angepaßt werden.
! ````
/*
VirtualBroadlinkToggle 0.1
! name - name of device
namespace - create state within this namespace (device ID will be namespace.name)
onCode - on-Code
offCode - off-Code
*/
! // generic virtual broadlink toggle
function VirtualBroadlinkToggle(namespace, name, onCode, offCode) {
this.namespace = 'virtualDevice.' + namespace + '.' + name;
this.name = name;
this.onCode = onCode;
this.offCode = offCode;// create virtual device this.createDevice(function () { this.createStates(function () { console.log('created virtual device ' + this.namespace); }.bind(this)); }.bind(this));
}
! VirtualBroadlinkToggle.prototype.createDevice = function (callback) {
log('creating object for device ' + this.namespace, 'debug');
! extendObject('javascript.' + instance + '.' + this.namespace, {
type: "device",
common: {"name": this.name, "role": "switch"},
native: {}
}, function (err) {
if (err) {
log('could not create virtual device: ' + this.namespace, 'warn');
return;
}
log('created object for device ' + this.namespace, 'debug');
callback();
}.bind(this));
}
! VirtualBroadlinkToggle.prototype.createStates = function (callback) {
"use strict";
log('creating states for device ' + this.namespace, 'debug');
var id = this.namespace + '.state';createState(id, {type: 'boolean', def: false, min: false, max: true, name: this.name+'.state', role: 'state'}, function (err) { if (err) { log('skipping creation of state ' + id, 'debug'); } else { log('created state ' + id, 'debug'); } this.connectState(id); callback(); }.bind(this));
}
! VirtualBroadlinkToggle.prototype.connectState = function (stateId) {
var trigger = {id: 'javascript.' + instance + '.' + stateId, change: 'any', ack: false};
on(trigger, function (obj) {
var code = obj.state.val ? this.onCode : this.offCode;
sendTo('broadlink2.0', 'send_code', 'RM:RMPROSUB-ID.CODE_'+code);
}.bind(this));
}
! ````Die virtuelle Geräte (die nur einen state haben) kann man dann wie folgt anlegen:
new VirtualBroadlinkToggle("Schalter", "Funksteckdose", "onCode", "offcode");
So kann man die Funksteckdosen auch ohne Skripting steuern und an/aus-schalten.
Für Alexa kann man dann die Datenpunkte ebenfalls nehmen, oder je nach Laune verwenden.
-
Moin Moin,
ich habe den Thread bereits mehrmals gelesen, aber so ganz schlau bin ich doch nich draus geworden.
Das Schema habe ich soweit verstanden und ich wollte das nun auch bei mir einsetzen, da ich viele Systeme nutze.
Homematic, Hue Color, XIAOMI, Milight, Harmony, sonoff, wm-bus.
Nun, es stehen hier mehrere Script im Thread, welche davon sollten jetzt genutzt werden, damit ich meine States einheitlich darstellen kann?
Die States würde ich auch gerne für mobile.ui sowie cloud nutzen. Hat den schöne, dass nur die Auftauchen die ich wirklich brauche.
Wer mal ein Thermostat von Homematic in mobile.ui drin hatte, der weiß was da alles so aufpoppt.
Gehört das auch unter Global? Oder steht dieses als einziges Unter global?
/* VirtualDevice v0.3 Structure of config: { name: 'name', //name of device namespace: '', //create device within this namespace (device ID will be namespace.name) common: {}, //(optional) .................
-
Das Sript, was Du meinst, gehört unter "global",
da damit die Erstellung der virtuellen Geräte aufgerufen werden.
Die anderen Skript stehen bei mir z.B. unter common.virtualDevices.
Die Skripte dort regeln dann, wie die virtuelle Geräte erzeugt werden.
Diese sind meist selbst zu schreiben um z.B. Anpassungen an sein
eigenes System vorzunehmen; oder zu erweitern etc. pp.
Die vorgestellen Skripte sind daher nun als Leitfaden zu verstehen,
was dann möglich ist.
-
Müssen die Skripte, die die Erstellung der Virtual Devices vornehmen nach der Erstellung weiterlaufen oder kann man die dann stoppen bzw. entfernen?
-
Müssen die Skripte, die die Erstellung der Virtual Devices vornehmen nach der Erstellung weiterlaufen oder kann man die dann stoppen bzw. entfernen? `
weiterlaufen
-
Auch wenn der letzte Beitrag fast schon 1 Jahr her ist...
Ich habe die Sache mit dem VirtualDevice nachvollzogen und habe das auch grundsätzlich hinbekommen. Ich möchte 2 Tint E14 Lampen gruppieren und steuern, die können Farbe und Warmweiß bis Kaltweiß. Die Farbsteuerung erfolgt mit Hexadezimalangabe, z.B. #FF0000 für rot.
Mit Scriupten kenne ich nicht quasi gar nicht aus.
Wie würde man in dem 'State' Abschnitt des Skriptsstates: { //welche States sollen erstellt werden? 'level': { //State Konfiguration common: {type: 'number', def: 0, min: 0, max: 100, read: true, write: true, unit: '%'}, read: { //von welchen States sollen Werte eingelesen werden? 'hue.0.hue_bridge.Wohnzimmer_Decke.bri': { convert: function (val) { //wert soll konvertiert werden return Math.floor(val * 100 / 254); } }, }, write: { //in welche States sollen Werte geschrieben werden? 'hue.0.hue_bridge.Wohnzimmer_Decke.bri': { convert: function (val) { //wert soll konvertiert werden return Math.ceil(val * 254 / 100); }, delay: 1500 // schreibe Werte erst nach 1,5 Sekunden in den Adapter (Puffer) }, } }, }
die entsprechende Variable in 'common' definieren und wie würde man die Variable (mit return) zurückschreiben ?
-
Entwickle aktuell einen Adapter der einige der Funktionalitäten dieses Skriptes abbilden soll.
Wer interesse hat: [Neuer Adapter] LinkedDevices -
gibts das script auch in GIT, nur zwecks Versionierung?
-
Nachtrag. Ich habe ein Skript für 2 Tint-Birnen erstellt, damit lassen sich beide als eine Gruppe ansteuern, sowie Farbe, Farbtemperatur und Helligkeit:
new VirtualDevice({ namespace: 'WC', name: 'Deckenlicht', //das Gerät wird unter javascript.0.virtualDevice.'namespace'.'name' erstellt states: { //welche States sollen erstellt werden? 'level.colorrgb': { //State Konfiguration common: {type: 'string', def: 000000, read: true, write: true}, read: { //von welchen States sollen Werte eingelesen werden? //Mehrere Geräte müssen innerhalb der Klammern der read-Funktion stehen. 'zigbee.0.000d6ffffe0200fe.color': { function (val) { return (val) ; } }, 'zigbee.0.d0cf5efffefefcaf.color': { function (val) { return (val) ; } }, }, write: { //in welche States sollen Werte geschrieben werden? //Mehrere Geräte müssen innerhalb der Klammern der write-Funktion stehen. //delay: xxx - schreibe Werte erst nach xxx msec. in den Adapter (Puffer) 'zigbee.0.000d6ffffe0200fe.color': { function (val) { return (val); }, delay: 500 }, 'zigbee.0.d0cf5efffefefcaf.color': { function (val) { return (val); }, delay: 500 }, } }, 'level.colortemperature': { //State Konfiguration common: {type: 'number', def: 0, min: 155, max: 555, read: true, write: true}, read: { //von welchen States sollen Werte eingelesen werden? //Mehrere Geräte müssen innerhalb der Klammern der read-Funktion stehen. 'zigbee.0.000d6ffffe0200fe.colortemp': { function (val) { return (val) ; } }, 'zigbee.0.d0cf5efffefefcaf.colortemp': { function (val) { return (val) ; } }, }, write: { //in welche States sollen Werte geschrieben werden? //Mehrere Geräte müssen innerhalb der Klammern der write-Funktion stehen. //delay: xxx - schreibe Werte erst nach xxx msec. in den Adapter (Puffer) 'zigbee.0.000d6ffffe0200fe.colortemp': { function (val) { return (val); }, delay: 500 }, 'zigbee.0.d0cf5efffefefcaf.colortemp': { function (val) { return (val); }, delay: 500 }, } }, 'level.dimmer': { //State Konfiguration common: {type: 'number', def: 0, min: 0, max: 100, read: true, write: true}, read: { //von welchen States sollen Werte eingelesen werden? //Mehrere Geräte müssen innerhalb der Klammern der read-Funktion stehen. 'zigbee.0.000d6ffffe0200fe.brightness': { function (val) { return (val) ; } }, 'zigbee.0.d0cf5efffefefcaf.brightness': { function (val) { return (val) ; } }, }, write: { //in welche States sollen Werte geschrieben werden? //Mehrere Geräte müssen innerhalb der Klammern der write-Funktion stehen. //delay: xxx - schreibe Werte erst nach xxx msec. in den Adapter (Puffer) 'zigbee.0.000d6ffffe0200fe.brightness': { function (val) { return (val); }, delay: 500 }, 'zigbee.0.d0cf5efffefefcaf.brightness': { function (val) { return (val); }, delay: 500 }, } }, } });
-
Hi ich hab das Gefühl ich bin zu blöd dafür.
Hab viele Codes ausporbiert jedoch tut sich bei mir einfach nichts.
```
Ich hab diesen Code ohne Fehler bei mir als Javascript Code ausgeführt und in meinen Objekten tut sich einfach gar nichts
Was mache ich falsch?
-
hi ich habe den Code mal nach TypeScript portiert, macht es einfacher diesen zu verwenden:
https://gist.github.com/many20/3a5b047bd221ec107153c0231b575ca3
//https://forum.iobroker.net/topic/7751/virtual-devices/2 /* VirtualDevice v0.3 {1} Structure of config: { name: 'name', //name of device namespace: '', //create device within this namespace (device ID will be namespace.name) common: {}, //(optional) native: {}, //(optional) copy: objectId, //(optional) ID of device or channel to copy common and native from onCreate: function(device, callback) {} //called once on device creation states: { 'stateA': { //State Id will be namespace.name.stateA common: {}, //(optional) native: {}, //(optional) copy: stateId, read: { //(optional) states which should write to "stateA" 'stateId1': { trigger: {ack: true, change: 'any'} //(optional) see https://github.com/ioBroker/ioBroker.javascript#on---subscribe-on-changes-or-updates-of-some-state convert: function(val) {}, //(optional) functions should return converted value before: function(device, value, callback) {}, //(optional) called before writing new state. has to call callback or new value will not be written delay: 0, //(optional) delay in ms before new value gets written after: function(device, value) {}, //(optional) called after new value has been written validFor: //(optional) ms, to ignore old data which did not change for a long time. }, ... }, readLogic: 'last' || 'max' || 'min' || 'average', //(optional) default: last (only last implemented) write: { //(optional) states which "stateA" should write to 'stateId1': { convert: function(val) {}, //(optional) functions should return converted value before: function(device, value, callback) {}, //(optional) called before writing new state. has to call callback or new value will not be written delay: 0, //(optional) delay in ms before new value gets written after: function(device, value) {}, //(optional) called after new value has been written }, 'stateId3': { convert: function(val) {}, //(optional) functions should return converted value before: function(device, value, callback) {}, //(optional) called before writing new state. has to call callback or new value will not be written delay: 0, //(optional) delay in ms before new value gets written after: function(device, value) {}, //(optional) called after new value has been written }, ... }, }, ... } } */ /* new VirtualDevice({ namespace: 'Hue', name: 'Wohnzimmer', //das Gerät wird unter javascript.0.virtualDevice.Hue.Wohnzimmer erstellt states: { //welche States sollen erstellt werden? 'level': { //State Konfiguration common: {type: 'number', def: 0, min: 0, max: 100, read: true, write: true, unit: '%'}, read: { //von welchen States sollen Werte eingelesen werden? 'hue.0.hue_bridge.Wohnzimmer_Decke.bri': { convert: function (val) { //wert soll konvertiert werden return Math.floor(val * 100 / 254); } }, }, write: { //in welche States sollen Werte geschrieben werden? 'hue.0.hue_bridge.Wohnzimmer_Decke.bri': { convert: function (val) { //wert soll konvertiert werden return Math.ceil(val * 254 / 100); }, delay: 1500 // schreibe Werte erst nach 1,5 Sekunden in den Adapter (Puffer) }, } }, } }); new VirtualDevice({ namespace: 'Hue', name: 'Wohnzimmer', //das Gerät wird unter javascript.0.virtualDevice.Hue.Wohnzimmer erstellt states: { //welche States sollen erstellt werden? 'level': { //State Konfiguration common: {type: 'number', def: 0, min: 0, max: 100, read: true, write: true, unit: '%'}, read: { //von welchen States sollen Werte eingelesen werden? 'hue.0.hue_bridge.Wohnzimmer_Decke.bri': { convert: function (val) { //wert soll konvertiert werden return Math.floor(val * 100 / 254); } }, }, write: { //in welche States sollen Werte geschrieben werden? 'hue.0.hue_bridge.Wohnzimmer_Decke.bri': { convert: function (val) { //wert soll konvertiert werden return Math.ceil(val * 254 / 100); }, delay: 1500, // schreibe Werte erst nach 1,5 Sekunden in den Adapter (Puffer) before: function (device, value, callback) { if (value > 0 && getState('zwave.0.NODE10.SWITCH_BINARY.Switch_1').val === false) { //if switch is off and value is greater 0 turn on switch and set long delay setStateDelayed(switchId, true, false, 1500, true, function () { callback(value, 3500); }); } else if (value <= 0) { //if level is set to 0 turn off switch and set level 0 setStateDelayed(switchId, false, false, 1500, true, function () { callback(0, 0); }); } else { callback(); } } }, } }, } }); */ interface NumberCommon { type: 'number'; def: number; min: number; max: number; read: boolean; write: boolean; unit: '%' | string; } //https://github.com/ioBroker/ioBroker.docs/blob/master/docs/en/dev/objectsschema.md interface Common { /** * (optional - (default is mixed==any type) (possible values: number, string, boolean, array, object, mixed, file). As exception the objects with type meta could have common.type=meta.user or meta.folder */ type?: 'number' | 'string' | 'boolean' | 'array' | 'object' | 'mixed' | 'file'; /** * (optional) */ min?: number; /** * (optional) */ max?: number; /** * (optional) - increase/decrease interval. E.g. 0.5 for thermostat */ step?: number; /** * (optional) - unit of the value E.g. C° */ unit?: string; /** * (optional - the default value) */ def?: any; /** * (optional - if common.def is set this value is used as ack flag, js-controller 2.0.0+) */ defAck?: any; /** * (optional, string or object) - description, object for multilingual description */ desc?: string | object; /** * (optional, string or object) - name of the device */ name?: string; /** * (boolean, mandatory) - true if state is readable */ read: boolean; /** * (boolean, mandatory) - true if state is writable */ write: boolean; /** * (string, mandatory) - role of the state (used in user interfaces to indicate which widget to choose, see below) * https://github.com/ioBroker/ioBroker.docs/blob/master/docs/en/dev/stateroles.md */ role: 'state' | 'text' | 'button' | string; /** * (optional) attribute of type number with object of possible states {'value': 'valueName', 'value2': 'valueName2', 0: 'OFF', 1: 'ON'} */ states?: Record<string, string>; /** * (string, optional) - if this state has helper state WORKING. Here must be written the full name or just the last part if the first parts are the same with actual. Used for HM.LEVEL and normally has value "WORKING" */ workingID?: string; /** * (optional) - the structure with custom settings for specific adapters. Like {"influxdb.0": {"enabled": true, "alias": "name"}}. enabled attribute is required and if it is not true, the whole attribute will be deleted. */ custom?: any; } interface VirtualDeviceConfigStateRead { trigger?: { ack: boolean; change: "eq" | "ne" | "gt" | "ge" | "lt" | "le" | "any" }; convert?: (val: iobJS.StateValue) => iobJS.StateValue; before?: (device: VirtualDevice, value: iobJS.StateValue, triggerId: string, callback: (newVal?: iobJS.StateValue, newDelay?: number) => void) => void; delay?: number; after?: (device: VirtualDevice, value: iobJS.StateValue, triggerId: string) => void; //validFor?: number; } interface VirtualDeviceConfigStateWrite { convert?: (val: iobJS.StateValue) => iobJS.StateValue; before?: (device: VirtualDevice, value: iobJS.StateValue, triggerId: string, callback: (newVal?: iobJS.StateValue, newDelay?: number) => void) => void; delay?: number; after?: (device: VirtualDevice, value: iobJS.StateValue, triggerId: string) => void; } interface VirtualDeviceConfigState { common?: Partial<Common>; native?: Record<string, any>; copy?: string; read?: Record<string, VirtualDeviceConfigStateRead>; //stateId readLogic?: 'last' | 'max' | 'min' | 'average'; write?: Record<string, VirtualDeviceConfigStateWrite>; //stateId } interface VirtualDeviceConfig { namespace: string; name: string; common?: Partial<Common>; native?: Record<string, any>; copy?: string; onCreate?: (device, callback) => void; states: Record<string, VirtualDeviceConfigState>; //stateId will be namespace.name.stateA } export declare interface VirtualDevice { config: VirtualDeviceConfig; namespace: string; name: string; createDevice: (callback: () => void) => void; createStates: (callback: () => void) => void; normalizeState: (state: string) => void; connectState: (state: string) => void; subRead: (trigger: iobJS.SubscribeOptions, readObj: VirtualDeviceConfigStateRead, state: string) => void; convertValue: (val: iobJS.StateValue, func?: (value: iobJS.StateValue) => iobJS.StateValue) => iobJS.StateValue; } //generic virtual device function VirtualDevice(this: VirtualDevice, config: VirtualDeviceConfig): void { //sanity check if (typeof config !== 'object' || typeof config.namespace !== 'string' || typeof config.name !== 'string' || typeof config.states !== 'object') { log('sanity check failed, no device created', 'warn'); return; } this.config = config; this.namespace = 'virtualDevice.' + config.namespace + '.' + config.name; this.name = config.name; //create virtual device log('creating virtual device ' + this.namespace) this.createDevice(function (this: VirtualDevice, ) { this.createStates(function (this: VirtualDevice, ) { log('created virtual device ' + this.namespace) }.bind(this)); }.bind(this)); } VirtualDevice.prototype.createDevice = function (this: VirtualDevice, callback: () => void): void { log('creating object for device ' + this.namespace, 'debug'); //create device object const obj = this.config.copy ? getObject(this.config.copy) : { common: {}, native: {} }; if ((obj.common as any).custom) delete (obj.common as any).custom; if (typeof this.config.common === 'object') { obj.common = Object.assign(obj.common, this.config.common); } if (typeof this.config.native === 'object') { obj.native = Object.assign(obj.native, this.config.native); } extendObject('javascript.' + instance + '.' + this.namespace, { type: "device", common: obj.common, native: obj.native }, function (err) { if (err) { log(err, 'warn'); log('could not create virtual device: ' + this.namespace, 'warn'); return; } log('created object for device ' + this.namespace, 'debug'); if (typeof this.config.onCreate === 'function') { this.config.onCreate(this, callback); } else { callback(); } }.bind(this)); } VirtualDevice.prototype.createStates = function (this: VirtualDevice, callback: () => void): void { "use strict"; log('creating states for device ' + this.namespace, 'debug'); const stateIds = Object.keys(this.config.states); log('creating states ' + JSON.stringify(stateIds), 'debug'); let countCreated = 0; for (let i = 0; i < stateIds.length; i++) { let stateId = stateIds[i]; this.normalizeState(stateId); const id = this.namespace + '.' + stateId; log('creating state ' + id, 'debug'); const obj = this.config.states[stateId].copy ? getObject(this.config.states[stateId].copy) : { common: {}, native: {} }; if ((obj.common as any).custom) delete (obj.common as any).custom; if (typeof this.config.states[stateId].common === 'object') { obj.common = Object.assign(obj.common, this.config.states[stateId].common); } if (typeof this.config.states[stateId].native === 'object') { obj.native = Object.assign(obj.native, this.config.states[stateId].native); } createState(id, obj.common, obj.native, function (err) { if (err) { log('skipping creation of state ' + id, 'debug'); } else { log('created state ' + id, 'debug'); } this.connectState(stateId); countCreated++; if (countCreated >= stateIds.length) { log('created ' + countCreated + ' states for device ' + this.namespace, 'debug'); callback(); } }.bind(this)); } } VirtualDevice.prototype.normalizeState = function (this: VirtualDevice, state: string): void { log('normalizing state ' + state, 'debug'); if (typeof this.config.states[state].read !== 'object') { this.config.states[state].read = {}; } if (typeof this.config.states[state].write !== 'object') { this.config.states[state].write = {}; } const readIds = Object.keys(this.config.states[state].read); for (let i = 0; i < readIds.length; i++) { const readId = this.config.states[state].read[readIds[i]]; if (typeof readId.before !== 'function') { this.config.states[state].read[readIds[i]].before = function (device, value, triggerId, callback) { callback() }; } if (typeof readId.after !== 'function') { this.config.states[state].read[readIds[i]].after = function (device, value, triggerId) { }; } } const writeIds = Object.keys(this.config.states[state].write); for (let i = 0; i < writeIds.length; i++) { const writeId = this.config.states[state].write[writeIds[i]]; if (typeof writeId.before !== 'function') { this.config.states[state].write[writeIds[i]].before = function (device, value, triggerId, callback) { callback() }; } if (typeof writeId.after !== 'function') { this.config.states[state].write[writeIds[i]].after = function (device, value, triggerId) { }; } } log('normalized state ' + state, 'debug'); } VirtualDevice.prototype.connectState = function (this: VirtualDevice, state: string): void { log('connecting state ' + state, 'debug'); const id = this.namespace + '.' + state; //subscribe to read ids const readIds = Object.keys(this.config.states[state].read); for (let i = 0; i < readIds.length; i++) { if (getState(readIds[i]).notExist === true) { //check if state exists log('cannot connect to not existing state: ' + readIds[i], 'warn'); continue; } const readObj = this.config.states[state].read[readIds[i]]; const trigger: iobJS.SubscribeOptions = readObj.trigger || {change: 'any'}; trigger.ack = true; trigger.id = readIds[i]; this.subRead(trigger, readObj, state); log('connected ' + readIds[i] + ' to ' + id, 'debug'); } //subscribe to this state and write to write ids const writeIds = Object.keys(this.config.states[state].write); const trigger: iobJS.SubscribeOptions = {id: 'javascript.' + instance + '.' + this.namespace + '.' + state, change: 'any', ack: false}; on(trigger, function (this: VirtualDevice, obj: { state: ReturnType<typeof getState> }) { "use strict"; log('detected change of ' + state, 'debug'); for (let i = 0; i < writeIds.length; i++) { const writeObj = this.config.states[state].write[writeIds[i]]; let val: iobJS.StateValue = this.convertValue(obj.state.val, writeObj.convert); const writeId = writeIds[i]; log('executing function before for ' + writeId, 'debug'); writeObj.before(this, val, trigger.id.toString(), function (this: VirtualDevice, newVal?: iobJS.StateValue, newDelay?: number) { if (newVal !== undefined && newVal !== null) val = newVal; let delay = writeObj.delay; if (newDelay !== undefined && newDelay !== null) delay = newDelay; log(newVal + 'writing value ' + val + ' to ' + writeId + ' with delay ' + delay, 'debug'); setStateDelayed(writeId, val, false, delay || 0, true, function () { log('executing function after for ' + writeId, 'debug'); writeObj.after(this, val, trigger.id.toString()); }.bind(this)); }.bind(this)); } }.bind(this)); log('connected ' + state + ' to ' + JSON.stringify(writeIds), 'debug'); } VirtualDevice.prototype.subRead = function (this: VirtualDevice, trigger: iobJS.SubscribeOptions, readObj: VirtualDeviceConfigStateRead, state: string): void { const func = function (this: VirtualDevice, obj: { state: ReturnType<typeof getState> }) { let val: iobJS.StateValue = this.convertValue(obj.state.val, readObj.convert); //@todo aggregations log('executing function before for ' + trigger.id.toString(), 'debug'); readObj.before(this, val, trigger.id.toString(), function (this: VirtualDevice, newVal?: iobJS.StateValue, newDelay?: number) { if (newVal !== undefined && newVal !== null) val = newVal; if (newDelay !== undefined && newDelay !== null) readObj.delay = newDelay; log('reading value ' + val + ' to ' + this.namespace + '.' + state, 'debug'); setStateDelayed(this.namespace + '.' + state, val, true, readObj.delay || 0, true, function () { log('executing function after for ' + trigger.id, 'debug'); readObj.after(this, val, trigger.id.toString()); }.bind(this)); }.bind(this)); }.bind(this); func({ state: getState(trigger.id.toString()) }); on(trigger, func); } VirtualDevice.prototype.convertValue = function (this: VirtualDevice, val: iobJS.StateValue, func: (value: iobJS.StateValue) => iobJS.StateValue): iobJS.StateValue { if (typeof func !== 'function') { return val; } return func(val); } //global function createVirtualDevice(config: VirtualDeviceConfig): VirtualDevice { return new VirtualDevice(config) }
ich benutzt mehrer DECT Thermostat von Fritz mit dem entspechenden Adapter, dafür habe ich eine Funktion geschrieben mit der ich schnell mehrere VirtualDevice erzeugen kann:
const createVirtualThermostat = (name: string, id: string, isGroup?: boolean) => createVirtualDevice({ namespace: 'thermostat', name: name, common: { name: id }, states: { name: { common: { type: 'string', role: 'text', read: true, write: false }, read: { [id + '.name']: {}, }, }, manufacturer: { common: { type: 'string', role: 'text', read: true, write: false }, read: { [id + '.manufacturer']: {}, }, }, id: { common: { type: 'string', role: 'text', read: true, write: false }, read: { [id + '.id']: {}, }, }, ...(!isGroup ? { productname: { common: { type: 'string', role: 'text', read: true, write: false }, read: { [id + '.productname']: {}, }, }, battery: { common: { type: 'number', role: 'state', read: true, write: false }, read: { [id + '.battery']: {}, }, }, batterylow: { common: { type: 'boolean', role: 'state', read: true, write: false }, read: { [id + '.batterylow']: {}, }, }, batterylow_homekit: { common: { type: 'number', role: 'state', read: true, write: false }, read: { [id + '.batterylow']: { convert: d => d ? 1 : 0, }, }, }, actualtemp: { common: { type: 'number', role: 'state', read: true, write: false }, read: { [id + '.tist']: {}, }, } } : {} ), targettemp: { common: { type: 'number', role: 'state', read: true, write: true }, read: { [id + '.tsoll']: {}, }, write: { [id + '.tsoll']: {}, }, }, windowopenactiv: { common: { type: 'boolean', role: 'state', read: true, write: false }, read: { [id + '.windowopenactiv']: {}, }, }, errorcode: { common: { type: 'number', role: 'state', read: true, write: false }, read: { [id + '.errorcode']: {}, }, }, error: { common: { type: 'boolean', role: 'state', read: true, write: false }, read: { [id + '.errorcode']: { convert: d => !!d, }, }, }, present: { common: { type: 'boolean', role: 'state', read: true, write: false }, read: { [id + '.present']: {}, }, }, operationmode: { common: { type: 'string', role: 'state', read: true, write: false }, read: { [id + '.operationmode']: {}, }, }, hkrmode: { common: { type: 'number', role: 'state', read: true, write: true }, read: { [id + '.hkrmode']: {}, }, write: { [id + '.hkrmode']: {}, }, }, } });
benutzten kann man das dann so:
createVirtualThermostat('temp1', 'fritzdect.0.DECT_099950242551'); createVirtualThermostat('temp2', 'fritzdect.0.DECT_133570009104'); createVirtualThermostat('temp3', 'fritzdect.0.DECT_133570329192'); createVirtualThermostat('temp4', 'fritzdect.0.DECT_133570402960'); createVirtualThermostat('temp5', 'fritzdect.0.DECT_133570404888'); createVirtualThermostat('temp6', 'fritzdect.0.DECT_133570405128'); createVirtualThermostat('temp7', 'fritzdect.0.DECT_140780177080'); createVirtualThermostat('temp8', 'fritzdect.0.DECT_140800051032'); createVirtualThermostat('temp9', 'fritzdect.0.DECT_140800051048'); createVirtualThermostat('temp10', 'fritzdect.0.DECT_140800052056'); createVirtualThermostat('grp-temp1', 'fritzdect.0.DECT_grpC58331-3B9ABD4E9', true);
-
@zerberus Danke für das TS Code. Habe das jetzt in Benutzung, so wie Du das für das DECT Thermostat verwendest. Ich nehme es für Dimmer, um auch schnell mehrere virtuelle Geräte anzulegen.
Was ich noch nicht hinbekommen habe, ist das after. z.B. bei deiner createVirtualThermostat() Funktion: nehmen wir mal an, immer wenn ich
targettemp
schreibe, dann soll nach dem Schreiben nochhkrmode
geschrieben werden.targettemp: { common: { type: 'number', role: 'state', read: true, write: true }, read: { [id + '.tsoll']: {}, }, write: { [id + '.tsoll']: { after: function (device, value) { if (value > 25) { setState(id + 'hkrmode', 2); } else { setState(id + 'hkrmode', 1); } }, }, }, },
So auf diese Art... das gleiche kommt dann auch noch für convert etc... D.h. in der Funktion, mit welcher man schnell mehrere virtuelle Geräte anlegt, brauche ich unter read: und write: auch noch convert, after etc.
Ein (paar) Beispiel(e) dafür wäre(n) super.