Skip to content
  • Home
  • Recent
  • Tags
  • 0 Unread 0
  • Categories
  • Unreplied
  • Popular
  • GitHub
  • Docu
  • Hilfe
Skins
  • Light
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Skripten / Logik
  4. Virtual Devices

NEWS

  • Weihnachtsangebot 2025! 🎄
    BluefoxB
    Bluefox
    21
    1
    892

  • UPDATE 31.10.: Amazon Alexa - ioBroker Skill läuft aus ?
    apollon77A
    apollon77
    48
    3
    9.0k

  • Monatsrückblick – September 2025
    BluefoxB
    Bluefox
    13
    1
    2.4k

Virtual Devices

Scheduled Pinned Locked Moved Skripten / Logik
48 Posts 21 Posters 18.9k Views 11 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • Z Offline
    Z Offline
    Zerberus
    wrote on last edited by Zerberus
    #41

    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);
    
    J 1 Reply Last reply
    0
    • Z Zerberus

      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);
      
      J Offline
      J Offline
      j1s2e3
      wrote on last edited by
      #42

      @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 noch hkrmode 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.

      1 Reply Last reply
      0
      • P Pman

        Ich weiß, es gibt schon Adapter wie Scene und andere, um Geräte zu gruppieren usw.

        Ich möchte mit diesem Skript deutlich weiter gehen und damit eine Vielzahl an Problemen beseitigen, welche bei mir beim Arbeiten mit ioBroker häufig auftreten. Eigentlich würde sich das Skript sogar gut eignen, um daraus einen Adapter zu bauen, allerdings fehlt mir dafür ein GUI. Das Skript ist ganz neu und evtl. nicht frei von Bugs, funktioniert bei mir aber sehr zuverlässig.

        Damit das Script lauffähig ist muss der Javascript Adapter "Nicht auf alle Zustände beim Start abonnieren" ausgeschaltet haben und "Erlaube das Kommando setObject" muss eingschaltet sein.

        Problem:

        Bei der Arbeitsteilung zwischen Adaptern, History, Vis usw. kommt es häufig zu Unklarheiten, an welcher Stelle überhaupt gewisse Funktionalität erbracht werden soll. So hat z. B. ein Dimmer in Homematic einen Datenpunkt "LEVEL", welcher Werte von 0-100 annehmen kann. Bei HUE ist es "bri" mit 0-254 und bei Z-Wave sind es 0-99. Bei Hue habe ich vor einiger Zeit in den Adapter den Wert "level" mit der Homematic-Logik hinzugefügt, diesen gibt es in HUE eigentlich nicht. Und eigentlich halte ich es für falsch, solche Logiken in Geräte-Adapter einzubauen, diese sollten die Daten so zur Verfügung stellen, wie es die Geräte tun. Spätestens bei Vis würde ich aber gerne jede Lampe mit Werten 0-100 steuern. Manche Widgets versuchen auch mit möglichst vielen Werten klar zu kommen. Oft reicht das nicht und ich stehe wieder vor dem Problem, dass ich kein passendes Widget finde, um eine gewisse Funktion auszuführen. Dimmer sind hier nur ein Beispiel, wo ich Geräte verschiedener Hersteller auf einen Standard bringen möchte. Wer schonmal versucht hat, den Betriebsmodus eines Homematic-Thermostat mit einem Knopf zu steuern, weiß wovon ich rede. Das Thermostat besitzt nämlich gleich 5 States um den Betriebsmodus zu wechseln und einen weiteren um diesen anzuzeigen. Hier kommt dieses Skript als Vermittler ins Spiel.

        Skriptbeschreibung:

        Das Skript eignet sich, um Geräte zu virtualisieren bzw. ganz neue Geräte zu erstellen. Soll heißen, es werden States in einem Namespace unter javascript.x.virtualDevice.* erstellt. Jeder dieser States kann Werte von mehreren Geräten erhalten und auch wieder in mehrere andere Geräte schreiben. Dabei können bedarfsweise Konvertierungen oder andere Funktionen ausgeführt werden. Das Skript ist so universell gehalten und dabei vergleichsweise einfach zu steuern, dass ich damit alle meine Probleme lösen konnte. Wenn man schon viel mit ioBroker gearbeitet hat, werden einem die Vorteile einer Geräte-Virtualisierung schnell klar. Besonders bei der Arbeit mit History und Vis sowie der Verwaltung von Zugriffsrechten bietet es eine Menge Vorteile, weswegen ich mittlerweile alle Geräte virtualisiert habe. So geht mir nie eine History verloren, wenn ich einen Aktor austausche und ich muss auch nichts an den Views in Vis ändern.

        Beispiel:

        Hier erstelle ich eine "Kopie" einer Hue-Lux Lampe, also nur dimmbar, keine Farben. Dabei benötige ich nur den Dimm-Wert, alle anderen States aus dem Adapter brauche ich für mein virtuelles Gerät nicht. Um auf das oben genannte Beispiel einzugehen nutze ich hier den 'bri'-Wert aus dem HUE-Adapter:

        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)
                        },
                    }
                },
            }
        });
        

        Damit erstelle ich ein Gerät unter javascript.0.virtualDevice.Hue.Wohnzimmer, welches eine Datenpunkt level hat. Dieser Datenpunkt nimmt Werte zwischen 0 und 100 entgegen und konvertiert diese zu 0-254 und umgekehrt.

        Man sieht hier auch, dass man unter level.read und level.write weitere States einfügen kann, so dass gleich mehrere Lampen gesteuert werden. Diese müssten auch nicht alle vom HUE-Adapter kommen und können ganz unterschiedliche Werte erwarten.

        Es ist aber noch mehr möglich:

        Ich habe im Wandschalter für die Hue-Lampe ein Z-Wave Relay verbaut. So kann ich die Hue-Lampe klassisch an der Wand ein und ausschalten, aber auch über ioBroker vom Aus-Zustand einschalten. Ich möchte aber nicht erst umständlich das Relay schalten und dann die Hue-Lampe. Ich möchte, dass das Relay ganz automatisch einschaltet, wenn ich level auf einen Wert größer 0 setze. Dazu löse ich vor dem Schreiben von level in den Hue-Adapter eine Funktion aus:

        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();
                                }
                            }
                        },
                    }
                },
            }
        });
        

        Ich hoffe anhand dieses Beispiels werden einige Funktionen des Skripts deutlich. Bei Lampen mit Farbtemperatur habe ich den ct-Wert in Kelvin konvertiert, so dass ich die mir bekannte Weißtemperatureinheit eingeben kann, z. B. 2700k für Warmweiß.

        Homematic-Thermostate habe ich zusammen mit Stellantrieb in ein einziges virtuelles Gerät zusammengefügt und dabei die Moduswahl auf einen Datenpunkt reduziert.

        Skript

        `/*
        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)
            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
                        },
                        ...
                    },
                },
                ...
            }
        }
        */
        
        //generic virtual device        
        function VirtualDevice(config) {
            //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.createStates(function () {
                    log('created virtual device ' + this.namespace)
                }.bind(this));
            }.bind(this));
        }
        
        VirtualDevice.prototype.createDevice = function (callback) {
            log('creating object for device ' + this.namespace, 'debug');
            //create device object
            var obj = this.config.copy ? getObject(this.config.copy) : {common: {}, native: {}};
            delete obj.common.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('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 (callback) {
            "use strict";
            log('creating states for device ' + this.namespace, 'debug');
            var stateIds = Object.keys(this.config.states);
            log('creating states ' + JSON.stringify(stateIds), 'debug');
            var countCreated = 0;
            for (var i = 0; i < stateIds.length; i++) {
                let stateId = stateIds[i];
                this.normalizeState(stateId);
                var id = this.namespace + '.' + stateId;
                log('creating state ' + id, 'debug');
                var obj = this.config.states[stateId].copy ? getObject(this.config.states[stateId].copy) : {
                    common: {},
                    native: {}
                };
                delete obj.common.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 (state) {
            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 = {};
            }
        
            var readIds = Object.keys(this.config.states[state].read);
            for (var i = 0; i < readIds.length; i++) {
                var readId = this.config.states[state].read[readIds[i]];
                if (typeof readId.before !== 'function') {
                    this.config.states[state].read[readIds[i]].before = function (device, value, callback) {
                        callback()
                    };
                }
                if (typeof readId.after !== 'function') {
                    this.config.states[state].read[readIds[i]].after = function (device, value) {
                    };
                }
            }
            var writeIds = Object.keys(this.config.states[state].write);
            for (i = 0; i < writeIds.length; i++) {
                var writeId = this.config.states[state].write[writeIds[i]];
                if (typeof writeId.before !== 'function') {
                    this.config.states[state].write[writeIds[i]].before = function (device, value, callback) {
                        callback()
                    };
                }
                if (typeof writeId.after !== 'function') {
                    this.config.states[state].write[writeIds[i]].after = function (device, value) {
                    };
                }
            }
            log('normalized state ' + state, 'debug');
        }
        
        VirtualDevice.prototype.connectState = function (state) {
            log('connecting state ' + state, 'debug');
            var id = this.namespace + '.' + state;
        
            //subscribe to read ids
            var readIds = Object.keys(this.config.states[state].read);
            for (var 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;
                }
                var readObj = this.config.states[state].read[readIds[i]];
                var trigger = 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
            var writeIds = Object.keys(this.config.states[state].write);
            var trigger = {id: 'javascript.' + instance + '.' + this.namespace + '.' + state, change: 'any', ack: false};
            on(trigger, function (obj) {
                "use strict";
                log('detected change of ' + state, 'debug');
                for (var i = 0; i < writeIds.length; i++) {
                    let writeObj = this.config.states[state].write[writeIds[i]];
                    let val = this.convertValue(obj.state.val, writeObj.convert);
                    let writeId = writeIds[i];
                    log('executing function before for ' + writeId, 'debug');
                    writeObj.before(this, val, function (newVal, newDelay) {
                        if (newVal !== undefined && newVal !== null) val = newVal;
                        var 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);
                        }.bind(this));
                    }.bind(this));
                }
            }.bind(this));
            log('connected ' + state + ' to ' + JSON.stringify(writeIds), 'debug');
        }
        
        VirtualDevice.prototype.subRead = function (trigger, readObj, state) {
            var func = function (obj) {
                var val = this.convertValue(obj.state.val, readObj.convert);
        
                //@todo aggregations
        
                log('executing function before for ' + trigger.id, 'debug');
                readObj.before(this, val, function (newVal, newDelay) {
                    if (newVal !== undefined && newVal !== null) val = newVal;
                    if (newDelay !== undefined && newDelay !== null) writeObj.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);
                    }.bind(this));
                }.bind(this));
            }.bind(this);
            func({state: getState(trigger.id)});
            on(trigger, func);
        }
        
        VirtualDevice.prototype.convertValue = function (val, func) {
            if (typeof func !== 'function') {
                return val;
            }
            return func(val);
        }`[/i][/i][/i][/i][/i][/i]
        
        J Offline
        J Offline
        j1s2e3
        wrote on last edited by
        #43

        @pman Ich habe dein Skipt im "einfachen" Einsatz. Funktioniert sehr gut. Danke!

        Jetzt habe ich einen Anwendungsfall, wo ich erstmal an meine Grenzen stosse. Javascript ist nicht meine Welt, sondern Embedded Linux.

        Folgendes Setup:

        Ich habe einen Enocean RGBW Controller. Der geht soweit in ioBroker. Der hat folgende Datenpunkte:
        R, G, B, W jeweils 0-1023 als high resolution Dimmwert. 0=aus 1023=max. Helligkeit. Dazu ein DP CMD. Schreibt man dort 0(R), 1(G), 2(B) oder 3(W) rein, dann wird der in R, G, B oder W hinterlegte Dimmwert am LED Strip eingestellt.

        Ich habe jetzt erstmal einen virtuelles Gerät angelegt, welche die absoluten Dimmwerte 0-1023 in Prozentwerte 0-100% konvertiert. Das geht.

        Jetzt versuche ich einen DP "ON", welcher nur im virtuellen JS-Gerät existiert, anzulegen. Das klappt erstmal auch.

        So sieht das Skript aktuell aus:

        let id_03_woz_A5_FRGBW71L = "enocean.0.ffb2fd86";
        
        function roundToTwo(num) {
            return +(Math.round(num + "e+2")  + "e-2");
        }
        
        new VirtualDevice({
            namespace: 'enocean_0',
            name: '03_woz_A5_FRGBW71L',
            states: {
                'R': {
                    common: {type: 'number', def: 0, min: 0, max: 100, read: true, write: true, unit: '%'},
                    read: {
                        [id_03_woz_A5_FRGBW71L + ".R"]: {
                            convert: function (val) {return roundToTwo((val * 100) / 1023);}
                        }
                    },
                    write: {
                        [id_03_woz_A5_FRGBW71L + ".R"]: {
                            convert: function (val) {return Math.round((val * 1023) / 100);}
                        }
                    }
                },
                'G': {
                    common: {type: 'number', def: 0, min: 0, max: 100, read: true, write: true, unit: '%'},
                    read: {
                        [id_03_woz_A5_FRGBW71L + ".G"]: {
                            convert: function (val) {return roundToTwo((val * 100) / 1023);
                            }
                        }
                    },
                    write: {
                        [id_03_woz_A5_FRGBW71L + ".G"]: {
                            convert: function (val) {return Math.round((val * 1023) / 100);}
                        }
                    }
                },
                'B': {
                    common: {type: 'number', def: 0, min: 0, max: 100, read: true, write: true, unit: '%'},
                    read: {
                        [id_03_woz_A5_FRGBW71L + ".B"]: {
                            convert: function (val) {return roundToTwo((val * 100) / 1023);}
                        }
                    },
                    write: {
                        [id_03_woz_A5_FRGBW71L + ".B"]: {
                            convert: function (val) {return Math.round((val * 1023) / 100);}
                        }
                    }
                },
                'W': {
                    common: {type: 'number', def: 0, min: 0, max: 100, read: true, write: true, unit: '%'},
                    read: {
                        [id_03_woz_A5_FRGBW71L + ".W"]: {
                            convert: function (val) {return roundToTwo((val * 100) / 1023);}
                        }
                    },
                    write: {
                        [id_03_woz_A5_FRGBW71L + ".W"]: {
                            convert: function (val) {return Math.round((val * 1023) / 100);}
                        }
                    }
                },
                'ON': {
                    common: {type: 'boolean', def: false, read: true, write: false},
                    read: {
                        [id_03_woz_A5_FRGBW71L + ".R"]: {
                            convert: function (val) {return val == 0 ? false : true;}
                        },
                        [id_03_woz_A5_FRGBW71L + ".G"]: {
                            convert: function (val) {return val == 0 ? false : true;}
                        },
                        [id_03_woz_A5_FRGBW71L + ".B"]: {
                            convert: function (val) {return val == 0 ? false : true;}
                        },
                        [id_03_woz_A5_FRGBW71L + ".W"]: {
                            convert: function (val) {return val == 0 ? false : true;}
                        }
                    }
                }
            }
        });
        

        Wie kann ich die Werte für R, G, B und W von ON logisch auswerten, kombinieren?

        D.h. der virtuelle DP "ON" soll true sein, wenn R, oder G, oder B, oder W größer 0 ist, und false wenn R, G, B und W 0 sind, also keine LED leuchtet?

        So wie oben, werden zwar die DP R, G, B, W vom Enocean Gerät gelesen, richtig nach true/false konvertiert, aber nicht logisch miteinander verküpft. Ich habe keine Idee, wie ich das im Kontext deines Skriptes anstellen soll???

        [id_03_woz_A5_FRGBW71L + ".R"]: {
            convert: function (val) {return val == 0 ? false : true;}
        },
        

        Wie bekommen ich den Returnwert von diesem Statement in eine Variable z.B. is_R, ditto für G, B und W, um dann sowas wie if (is_R || is_G || is_B || is_W) set ON true; else set ON false;

        P 1 Reply Last reply
        0
        • J j1s2e3

          @pman Ich habe dein Skipt im "einfachen" Einsatz. Funktioniert sehr gut. Danke!

          Jetzt habe ich einen Anwendungsfall, wo ich erstmal an meine Grenzen stosse. Javascript ist nicht meine Welt, sondern Embedded Linux.

          Folgendes Setup:

          Ich habe einen Enocean RGBW Controller. Der geht soweit in ioBroker. Der hat folgende Datenpunkte:
          R, G, B, W jeweils 0-1023 als high resolution Dimmwert. 0=aus 1023=max. Helligkeit. Dazu ein DP CMD. Schreibt man dort 0(R), 1(G), 2(B) oder 3(W) rein, dann wird der in R, G, B oder W hinterlegte Dimmwert am LED Strip eingestellt.

          Ich habe jetzt erstmal einen virtuelles Gerät angelegt, welche die absoluten Dimmwerte 0-1023 in Prozentwerte 0-100% konvertiert. Das geht.

          Jetzt versuche ich einen DP "ON", welcher nur im virtuellen JS-Gerät existiert, anzulegen. Das klappt erstmal auch.

          So sieht das Skript aktuell aus:

          let id_03_woz_A5_FRGBW71L = "enocean.0.ffb2fd86";
          
          function roundToTwo(num) {
              return +(Math.round(num + "e+2")  + "e-2");
          }
          
          new VirtualDevice({
              namespace: 'enocean_0',
              name: '03_woz_A5_FRGBW71L',
              states: {
                  'R': {
                      common: {type: 'number', def: 0, min: 0, max: 100, read: true, write: true, unit: '%'},
                      read: {
                          [id_03_woz_A5_FRGBW71L + ".R"]: {
                              convert: function (val) {return roundToTwo((val * 100) / 1023);}
                          }
                      },
                      write: {
                          [id_03_woz_A5_FRGBW71L + ".R"]: {
                              convert: function (val) {return Math.round((val * 1023) / 100);}
                          }
                      }
                  },
                  'G': {
                      common: {type: 'number', def: 0, min: 0, max: 100, read: true, write: true, unit: '%'},
                      read: {
                          [id_03_woz_A5_FRGBW71L + ".G"]: {
                              convert: function (val) {return roundToTwo((val * 100) / 1023);
                              }
                          }
                      },
                      write: {
                          [id_03_woz_A5_FRGBW71L + ".G"]: {
                              convert: function (val) {return Math.round((val * 1023) / 100);}
                          }
                      }
                  },
                  'B': {
                      common: {type: 'number', def: 0, min: 0, max: 100, read: true, write: true, unit: '%'},
                      read: {
                          [id_03_woz_A5_FRGBW71L + ".B"]: {
                              convert: function (val) {return roundToTwo((val * 100) / 1023);}
                          }
                      },
                      write: {
                          [id_03_woz_A5_FRGBW71L + ".B"]: {
                              convert: function (val) {return Math.round((val * 1023) / 100);}
                          }
                      }
                  },
                  'W': {
                      common: {type: 'number', def: 0, min: 0, max: 100, read: true, write: true, unit: '%'},
                      read: {
                          [id_03_woz_A5_FRGBW71L + ".W"]: {
                              convert: function (val) {return roundToTwo((val * 100) / 1023);}
                          }
                      },
                      write: {
                          [id_03_woz_A5_FRGBW71L + ".W"]: {
                              convert: function (val) {return Math.round((val * 1023) / 100);}
                          }
                      }
                  },
                  'ON': {
                      common: {type: 'boolean', def: false, read: true, write: false},
                      read: {
                          [id_03_woz_A5_FRGBW71L + ".R"]: {
                              convert: function (val) {return val == 0 ? false : true;}
                          },
                          [id_03_woz_A5_FRGBW71L + ".G"]: {
                              convert: function (val) {return val == 0 ? false : true;}
                          },
                          [id_03_woz_A5_FRGBW71L + ".B"]: {
                              convert: function (val) {return val == 0 ? false : true;}
                          },
                          [id_03_woz_A5_FRGBW71L + ".W"]: {
                              convert: function (val) {return val == 0 ? false : true;}
                          }
                      }
                  }
              }
          });
          

          Wie kann ich die Werte für R, G, B und W von ON logisch auswerten, kombinieren?

          D.h. der virtuelle DP "ON" soll true sein, wenn R, oder G, oder B, oder W größer 0 ist, und false wenn R, G, B und W 0 sind, also keine LED leuchtet?

          So wie oben, werden zwar die DP R, G, B, W vom Enocean Gerät gelesen, richtig nach true/false konvertiert, aber nicht logisch miteinander verküpft. Ich habe keine Idee, wie ich das im Kontext deines Skriptes anstellen soll???

          [id_03_woz_A5_FRGBW71L + ".R"]: {
              convert: function (val) {return val == 0 ? false : true;}
          },
          

          Wie bekommen ich den Returnwert von diesem Statement in eine Variable z.B. is_R, ditto für G, B und W, um dann sowas wie if (is_R || is_G || is_B || is_W) set ON true; else set ON false;

          P Offline
          P Offline
          Pman
          wrote on last edited by
          #44

          War schon etwas länger nicht im Forum aktiv, das Skript scheint noch aktiv genutzt zu werden (läuft bei mir auch im 24/7 Einsatz!).
          Um auf die letzte Frage einzugehen @j1s2e3 :

          Dafür reicht die convert Funktion nicht aus, du kannst aber before nutzen, dort hast du als Parameter nicht nur den val sondern auch das virtualDevice selber und eine callback funktion, der dein berechneter Wert übergeben wird, erst dann wird geschrieben. Ein etwas umständlicher Weg wäre es, in der Funktion einfach per getState() (aus dem Javascript Adapter) die jeweils anderen Werte abzufragen und dann den ON wert zu berechnen. Da du aber auch Zugriff auf das virtuelle Gerät bekommst kannst du beliebige Werte direkt im Gerät-Objekt speichern.
          Beispiel (nicht getestet, wirst aber die Idee dahinter verstehen):

          new VirtualDevice({
                      namespace: 'enocean_0',
                      name: '03_woz_A5_FRGBW71L',
                      onCreate: function (device, callback) {
                          device.myStorage = {
                              R: 0,
                              G: 0,
                              B: 0,
                              W: 0,
                          };
                          callback();
                      },
                      states: {
                          //... rgbw states
                          'ON': {
                              common: {type: 'boolean', def: false, read: true, write: false},
                              read: {
                                  [id_03_woz_A5_FRGBW71L + ".R"]: {
                                      before: function (device, value, callback) {
                                          device.myStorage.R = value;
                                          callback(device.myStorage.R || device.myStorage.G || device.myStorage.B || device.myStorage.W);
                                      }
                                  },
                                  [id_03_woz_A5_FRGBW71L + ".G"]: {
                                      before: function (device, value, callback) {
                                          device.myStorage.G = value;
                                          callback(device.myStorage.R || device.myStorage.G || device.myStorage.B || device.myStorage.W);
                                      }
                                  },
                                  [id_03_woz_A5_FRGBW71L + ".B"]: {
                                      before: function (device, value, callback) {
                                          device.myStorage.B = value;
                                          callback(device.myStorage.R || device.myStorage.G || device.myStorage.B || device.myStorage.W);
                                      }
                                  },
                                  [id_03_woz_A5_FRGBW71L + ".W"]: {
                                      before: function (device, value, callback) {
                                          device.myStorage.W = value;
                                          callback(device.myStorage.R || device.myStorage.G || device.myStorage.B || device.myStorage.W);
                                      }
                                  }
                              }
                          }
                      }
                  });
          
          1 Reply Last reply
          0
          • P Offline
            P Offline
            Pman
            wrote on last edited by Pman
            #45

            Mit Bezug auf die letzte Frage zum Skript möchte ich euch nicht eine neuere Version des Skripts vorenthalten, welche dieses und weitere Probleme einfacher lösen kann. Unter dem Skript noch ein Beispiel, wie man die neue Logik-Funktion nutzen kann.
            Achtung: Grundsätzlich sollte das Skript mit der alten Version kompatibel sein, allerdings werden die Geräte nun standardmäßig unter "0_userdata.0.virtualDevice" angelegt. Um dies rückgängig zu machen siehe im Skript Zeile 57.
            Wie immer: vorher dem Update ein Backup machen.

            /*
            VirtualDevice v1.0
             
            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) function 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
                            },
                            ...
                        },
                        logic: { //(optional), default type: last
                            type, //can be one of: 'last' || 'number.sum' || 'number.max' || 'number.min' || 'number.average' || 'string.concat'
                            parameters //(optional) depends on type: number {decimals: 1} / string {separator: " "} / 
                        }, 
                        write: {
                            //(optional) states which "stateA" should write to 
                            'stateId1': {
                                convert: function(val) {}, //(optional) function 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
                            },     
                            ...
                        },
                    },
                    ...
                }
            }
            */
            
            //generic virtual device        
            function VirtualDevice(config) {
                //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.globalNamespace = '0_userdata.0'; //old: 'javascript.' + instance + '.';
                this.sandboxNamespace = 'virtualDevice.' + config.namespace + '.' + config.name;
                this.namespace = this.globalNamespace + '.' + this.sandboxNamespace;
                this.name = config.name;
                this.data = {};
                
            
                //create virtual device
                log('creating virtual device ' + this.sandboxNamespace)
                this.createDevice(function () {
                    this.createStates(function () {
                        log('created virtual device ' + this.namespace)
                    }.bind(this));
                }.bind(this));
            }
            
            VirtualDevice.prototype.createDevice = function (callback) {
                log('creating object for device ' + this.sandboxNamespace, 'debug');
                //create device object
                var obj = this.config.copy ? getObject(this.config.copy) : {common: {}, native: {}};
                // @ts-ignore
                delete obj.common.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(this.namespace, {
                    type: 'device',
                    common: obj.common,
                    native: obj.native
                }, function (err) {
                    if (err) {
                        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 (callback) {
                "use strict";
                log('creating states for device ' + this.sandboxNamespace, 'debug');
                var stateIds = Object.keys(this.config.states);
                log('creating states ' + JSON.stringify(stateIds), 'debug');
                var countCreated = 0;
                for (var i = 0; i < stateIds.length; i++) {
                    let stateId = stateIds[i];
                    this.normalizeState(stateId);
                    var id = this.sandboxNamespace + '.' + stateId;
                    log('creating state ' + id, 'debug');
                    var obj = this.config.states[stateId].copy ? getObject(this.config.states[stateId].copy) : {
                        common: {},
                        native: {}
                    };
                    // @ts-ignore
                    delete obj.common.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);
                    }
                    // @ts-ignore
                    if (!obj.common.type) {
                        // @ts-ignore
                        obj.common.type = 'mixed';
                    }
                    // @ts-ignore
                    if (!obj.common.role) {
                        // @ts-ignore
                        obj.common.role = 'state';
                    }
                    // @ts-ignore
                    if (!obj.common.name) {
                        // @ts-ignore
                        obj.common.name = stateId;
                    }
                    extendObject(this.globalNamespace + '.' + id, {
                        type: 'state',
                        common: obj.common,
                        native: obj.native
                    }, function (err) {
                        if (err) {
                            log('skipping creation of state ' + id, 'debug');
                        } else {
                            log('created state ' + id, 'debug');
                        }
                        setTimeout(() => {this.connectState(stateId)}, 1000);
                        countCreated++;
                        if (countCreated >= stateIds.length) {
                            log('created ' + countCreated + ' states for device ' + this.namespace, 'debug');
                            callback();
                        }
                    }.bind(this));
                }
            }
            
            VirtualDevice.prototype.normalizeState = function (stateId) {
                log('normalizing state ' + stateId, 'debug');
                if (typeof this.config.states[stateId].read !== 'object') {
                    this.config.states[stateId].read = {};
                }
                if (typeof this.config.states[stateId].write !== 'object') {
                    this.config.states[stateId].write = {};
                }
            
                this.data[stateId] = {read: {}, write:{}, val: undefined};
                var readIds = Object.keys(this.config.states[stateId].read);
                for (var i = 0; i < readIds.length; i++) {
                    this.data[stateId].read[readIds[i]] = null; 
                    var readId = this.config.states[stateId].read[readIds[i]];
                    if (typeof readId.before !== 'function') {
                        this.config.states[stateId].read[readIds[i]].before = function (device, value, callback) {
                            callback()
                        };
                    }
                    if (typeof readId.after !== 'function') {
                        this.config.states[stateId].read[readIds[i]].after = function (device, value) {
                        };
                    }
                }
                var writeIds = Object.keys(this.config.states[stateId].write);
                for (i = 0; i < writeIds.length; i++) {
                    this.data[stateId].write[writeIds[i]] = null; 
                    var writeId = this.config.states[stateId].write[writeIds[i]];
                    if (typeof writeId.before !== 'function') {
                        this.config.states[stateId].write[writeIds[i]].before = function (device, value, callback) {
                            callback()
                        };
                    }
                    if (typeof writeId.after !== 'function') {
                        this.config.states[stateId].write[writeIds[i]].after = function (device, value) {
                        };
                    }
                }
                log('normalized state ' + stateId, 'debug');
            }
            
            VirtualDevice.prototype.connectState = function (stateId) {
                log('connecting state ' + stateId, 'debug');
                let id = this.sandboxNamespace + '.' + stateId;
            
                //subscribe to read ids
                let readIds = Object.keys(this.config.states[stateId].read);
                for (let i = 0; i < readIds.length; i++) {
                    //check if state exists
                    getObject(readIds[i], function (err, obj) {
                        // @ts-ignore
                        if (err || obj.type !== 'state') {
                            log('cannot connect to not existing state: ' + readIds[i] + '|' + err + '|' + JSON.stringify(obj), 'warn');
                            return;
                        }
                        var readObj = this.config.states[stateId].read[readIds[i]];
                        var trigger_temp = readObj.trigger || {change: 'any'};
                        trigger_temp.ack = true;
                        trigger_temp.id = readIds[i];
                        this.subRead(trigger_temp, readObj, stateId);
                        log('connected ' + readIds[i] + ' to ' + id, 'debug');
                    }.bind(this));
                }
            
                //subscribe to this state and write to write ids
                var writeIds = Object.keys(this.config.states[stateId].write);
                var trigger = {id: this.namespace + '.' + stateId, change: 'any', ack: false};
                // @ts-ignore
                on(trigger, function (obj) {
                    "use strict";
                    log('detected change of ' + stateId, 'debug');
                    for (var i = 0; i < writeIds.length; i++) {
                        let writeObj = this.config.states[stateId].write[writeIds[i]];
                        let val = obj.state.val;
                        try {
                            val = this.convertValue(obj.state.val, writeObj.convert);
                        } catch (e) {
                            log('device "' + this.name + '" caused an error in write convert function:' + e, 'warn');
                            continue;
                        }
                        let writeId = writeIds[i];
                        log('executing function before for ' + writeId, 'debug');
                        try {
                            writeObj.before(this, val, function (newVal, newDelay) {
                                if (newVal !== undefined && newVal !== null) val = newVal;
                                var delay = writeObj.delay;
                                if (newDelay !== undefined && newDelay !== null) delay = newDelay;
                                log('writing value ' + val + ' to ' + writeId + ' with delay ' + delay, 'debug');
                                this.data[stateId].val = val;
                                setStateDelayed(writeId, val, false, delay || 0, true, function () {
                                    log('executing function after for ' + writeId, 'debug');
                                    writeObj.after(this, val);
                                }.bind(this));
                            }.bind(this));
                        } catch (e) {
                            log('device "' + this.name + '" caused an error in a write function:' + e, 'warn');
                            continue;
                        }
                    }
                }.bind(this));
                log('connected ' + stateId + ' to ' + JSON.stringify(writeIds), 'debug');
            }
            
            VirtualDevice.prototype.subRead = function (trigger, readObj, stateId) {
                //function to process read states
                var onRead = function (obj) {
                    let val = obj.state.val; 
                    try {
                        val = this.convertValue(obj.state.val, readObj.convert);
                    } catch (e) {
                        log('device "' + this.name + '" caused an error in read convert function:' + e, 'warn');
                        return;
                    }
                    log('executing function before for ' + trigger.id, 'debug');
                    try {
                        readObj.before(this, val, function (newVal, newDelay) {
                            if (newVal !== undefined && newVal !== null) val = newVal;
                            if (newDelay !== undefined && newDelay !== null) readObj.delay = newDelay;
                            log('reading value ' + val + ' into ' + this.namespace + '.' + stateId, 'debug');
                            this.data[stateId].read[trigger.id] = val; 
                            let aggregatedVal = this.aggregateRead(stateId, trigger.id);  
                            this.data[stateId].val = aggregatedVal;              
                            setStateDelayed(this.namespace + '.' + stateId, aggregatedVal, true, readObj.delay || 0, true, function () {
                                log('executing function after for ' + trigger.id, 'debug');
                                readObj.after(this, aggregatedVal);
                            }.bind(this));
                        }.bind(this));
                    } catch (e) {
                        log('device "' + this.name + '" caused an error in a read function:' + e, 'warn');
                        return;
                    }
                }.bind(this);
            
                //subscribe to state
                on(trigger, onRead);
            
                //get state once initially
                getState(trigger.id, function (err, state) {
                    if (!err && state) {
                        onRead({state: state});
                    }
                });
            }
            
            VirtualDevice.prototype.convertValue = function (val, func) {
                if (typeof func !== 'function') {
                    return val;
                }
                return func(val);
            }
            
            VirtualDevice.prototype.aggregateRead = function (stateId, readId) {
                var aggregatedVal = this.data[stateId].read[readId];
                if (!this.config.states[stateId].logic || !this.config.states[stateId].logic.type) {
                    return aggregatedVal;
                }
                switch(this.config.states[stateId].logic.type) {
                    case 'last':
                        //use value of state that triggered this function
                        aggregatedVal = this.data[stateId].read[readId];
                        if (this.config.states[stateId].logic.parameters && typeof this.config.states[stateId].logic.parameters.decimals === 'number') {
                            aggregatedVal = Number(aggregatedVal.toFixed(this.config.states[stateId].logic.parameters.decimals));
                        }
                        break;
                    case 'number.sum':
                        var values = Object.values(this.data[stateId].read).filter(value => typeof value === 'number');
                        aggregatedVal = values.reduce((accumulator, current) => accumulator + current);
                        if (this.config.states[stateId].logic.parameters && typeof this.config.states[stateId].logic.parameters.decimals === 'number') {
                            aggregatedVal = Number(aggregatedVal.toFixed(this.config.states[stateId].logic.parameters.decimals));
                        }
                        break;
                    case 'number.min':
                        var values = Object.values(this.data[stateId].read).filter(value => typeof value === 'number');
                        aggregatedVal = Math.min(...values);
                        if (this.config.states[stateId].logic.parameters && typeof this.config.states[stateId].logic.parameters.decimals === 'number') {
                            aggregatedVal = Number(aggregatedVal.toFixed(this.config.states[stateId].logic.parameters.decimals));
                        }
                        break;
                    case 'number.max':
                        var values = Object.values(this.data[stateId].read).filter(value => typeof value === 'number');
                        aggregatedVal = Math.max(...values);
                        if (this.config.states[stateId].logic.parameters && typeof this.config.states[stateId].logic.parameters.decimals === 'number') {
                            aggregatedVal = Number(aggregatedVal.toFixed(this.config.states[stateId].logic.parameters.decimals));
                        }
                        break;
                    case 'number.average': 
                        var values = Object.values(this.data[stateId].read).filter(value => typeof value === 'number');
                        aggregatedVal = values.length ? values.reduce((accumulator, current) => accumulator + current) / values.length : 0;
                        if (this.config.states[stateId].logic.parameters && typeof this.config.states[stateId].logic.parameters.decimals === 'number') {
                            aggregatedVal = Number(aggregatedVal.toFixed(this.config.states[stateId].logic.parameters.decimals));
                        }
                        break;
                    case 'string.concat':
                        var values = Object.values(this.data[stateId].read).filter(value => typeof value === 'string');
                        var separator = " ";
                        if (this.config.states[stateId].logic.parameters && typeof this.config.states[stateId].logic.parameters.separator === 'string') {
                            separator = this.config.states[stateId].logic.parameters.separator;
                        }
                        aggregatedVal = values.join(separator);
                        break;
                    default:
                        //use value of state that triggered this function
                        aggregatedVal = this.data[stateId].read[readId];
                        break;
                }
                return aggregatedVal;
            }
            

            Als Beispiel für die neue Funktion bietet sich ein Gruppierungsgerät an, das die Werte mehrerer Geräte zusammenfasst.
            In diesem Fall habe ich meine Thermostate zusammengefasst. valveOpen zeigt z.B. die Anzahl der Ventile mit einem Wert > 0 an, maxTemp zeigt die höchste Temperatur an usw.

            new VirtualDevice({
                "namespace": "Heizung.Gruppe",
                "name": "alle",
                "states": {
                    "valveOpen": {
                        "common": {
                            "type": "number",
                            "min": 0,
                            "def": 0,
                            "read": true,
                            "write": false,
                            "unit": "Valves",
                            "role": "value.counter"
                        },
                        "read": {
                            "0_userdata.0.virtualDevice.Heizung.Wohnzimmer.valve": {
                                "convert": function(value) {
                                    return value ? 1 : 0
                                }
                            },
                            "0_userdata.0.virtualDevice.Heizung.Badezimmer.valve": {
                                "convert": function(value) {
                                    return value ? 1 : 0
                                }
                            },
                            "0_userdata.0.virtualDevice.Heizung.Schlafzimmer.valve": {
                                "convert": function(value) {
                                    return value ? 1 : 0
                                }
                            }
                        },
                        "logic": {
                            "type": "number.sum"
                        }
                    },
                    "valveClosed": {
                        "common": {
                            "type": "number",
                            "min": 0,
                            "def": 0,
                            "read": true,
                            "write": false,
                            "unit": "Valves",
                            "role": "value.counter"
                        },
                        "read": {
                            "0_userdata.0.virtualDevice.Heizung.Wohnzimmer.valve": {
                                "convert": function(value) {
                                    return value ? 0 : 1
                                }
                            },
                            "0_userdata.0.virtualDevice.Heizung.Badezimmer.valve": {
                                "convert": function(value) {
                                    return value ? 0 : 1
                                }
                            },
                            "0_userdata.0.virtualDevice.Heizung.Schlafzimmer.valve": {
                                "convert": function(value) {
                                    return value ? 0 : 1
                                }
                            }
                        },
                        "logic": {
                            "type": "number.sum"
                        }
                    },
                    "minTemp": {
                        "common": {
                            "type": "number",
                            "unit": "°C",
                            "def": 0,
                            "min": -10,
                            "max": 50,
                            "read": true,
                            "write": false,
                            "role": "value.temperature"
                        },
                        "read": {
                            "0_userdata.0.virtualDevice.Heizung.Wohnzimmer.temperature": {},
                            "0_userdata.0.virtualDevice.Heizung.Badezimmer.temperature": {},
                            "0_userdata.0.virtualDevice.Heizung.Schlafzimmer.temperature": {}
                        },
                        "logic": {
                            "type": "number.min"
                        }
                    },
                    "maxTemp": {
                        "common": {
                            "type": "number",
                            "unit": "°C",
                            "def": 0,
                            "min": -10,
                            "max": 50,
                            "read": true,
                            "write": false,
                            "role": "value.temperature"
                        },
                        "read": {
                            "0_userdata.0.virtualDevice.Heizung.Wohnzimmer.temperature": {},
                            "0_userdata.0.virtualDevice.Heizung.Badezimmer.temperature": {},
                            "0_userdata.0.virtualDevice.Heizung.Schlafzimmer.temperature": {}
                        },
                        "logic": {
                            "type": "number.max"
                        }
                    },
                    "avgTemp": {
                        "common": {
                            "type": "number",
                            "unit": "°C",
                            "def": 0,
                            "min": -10,
                            "max": 50,
                            "read": true,
                            "write": false,
                            "role": "value.temperature"
                        },
                        "read": {
                            "0_userdata.0.virtualDevice.Heizung.Wohnzimmer.temperature": {},
                            "0_userdata.0.virtualDevice.Heizung.Badezimmer.temperature": {},
                            "0_userdata.0.virtualDevice.Heizung.Schlafzimmer.temperature": {}
                        },
                        "logic": {
                            "type": "number.average",
                            "parameters": {
                                "decimals": 1
                            }
                        }
                    },
                    "test": {
                        "common": {
                            "type": "string",
                            "read": true,
                            "write": false,
                            "role": "value"
                        },
                        "read": {
                            "0_userdata.0.virtualDevice.Heizung.Wohnzimmer.temperature": {
                                "convert": function(value) {
                                    return value + '°C'
                                }
                            },
                            "0_userdata.0.virtualDevice.Heizung.Badezimmer.temperature": {
                                "convert": function(value) {
                                    return value + '°C'
                                }
                            },
                            "0_userdata.0.virtualDevice.Heizung.Schlafzimmer.temperature": {
                                "convert": function(value) {
                                    return value + '°C'
                                }
                            }
                        },
                        "logic": {
                            "type": "string.concat",
                            "parameters": {
                                "separator": " | "
                            }
                        }
                    }
                }
            });
            
            Johannes TäuberJ 1 Reply Last reply
            0
            • P Pman

              Mit Bezug auf die letzte Frage zum Skript möchte ich euch nicht eine neuere Version des Skripts vorenthalten, welche dieses und weitere Probleme einfacher lösen kann. Unter dem Skript noch ein Beispiel, wie man die neue Logik-Funktion nutzen kann.
              Achtung: Grundsätzlich sollte das Skript mit der alten Version kompatibel sein, allerdings werden die Geräte nun standardmäßig unter "0_userdata.0.virtualDevice" angelegt. Um dies rückgängig zu machen siehe im Skript Zeile 57.
              Wie immer: vorher dem Update ein Backup machen.

              /*
              VirtualDevice v1.0
               
              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) function 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
                              },
                              ...
                          },
                          logic: { //(optional), default type: last
                              type, //can be one of: 'last' || 'number.sum' || 'number.max' || 'number.min' || 'number.average' || 'string.concat'
                              parameters //(optional) depends on type: number {decimals: 1} / string {separator: " "} / 
                          }, 
                          write: {
                              //(optional) states which "stateA" should write to 
                              'stateId1': {
                                  convert: function(val) {}, //(optional) function 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
                              },     
                              ...
                          },
                      },
                      ...
                  }
              }
              */
              
              //generic virtual device        
              function VirtualDevice(config) {
                  //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.globalNamespace = '0_userdata.0'; //old: 'javascript.' + instance + '.';
                  this.sandboxNamespace = 'virtualDevice.' + config.namespace + '.' + config.name;
                  this.namespace = this.globalNamespace + '.' + this.sandboxNamespace;
                  this.name = config.name;
                  this.data = {};
                  
              
                  //create virtual device
                  log('creating virtual device ' + this.sandboxNamespace)
                  this.createDevice(function () {
                      this.createStates(function () {
                          log('created virtual device ' + this.namespace)
                      }.bind(this));
                  }.bind(this));
              }
              
              VirtualDevice.prototype.createDevice = function (callback) {
                  log('creating object for device ' + this.sandboxNamespace, 'debug');
                  //create device object
                  var obj = this.config.copy ? getObject(this.config.copy) : {common: {}, native: {}};
                  // @ts-ignore
                  delete obj.common.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(this.namespace, {
                      type: 'device',
                      common: obj.common,
                      native: obj.native
                  }, function (err) {
                      if (err) {
                          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 (callback) {
                  "use strict";
                  log('creating states for device ' + this.sandboxNamespace, 'debug');
                  var stateIds = Object.keys(this.config.states);
                  log('creating states ' + JSON.stringify(stateIds), 'debug');
                  var countCreated = 0;
                  for (var i = 0; i < stateIds.length; i++) {
                      let stateId = stateIds[i];
                      this.normalizeState(stateId);
                      var id = this.sandboxNamespace + '.' + stateId;
                      log('creating state ' + id, 'debug');
                      var obj = this.config.states[stateId].copy ? getObject(this.config.states[stateId].copy) : {
                          common: {},
                          native: {}
                      };
                      // @ts-ignore
                      delete obj.common.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);
                      }
                      // @ts-ignore
                      if (!obj.common.type) {
                          // @ts-ignore
                          obj.common.type = 'mixed';
                      }
                      // @ts-ignore
                      if (!obj.common.role) {
                          // @ts-ignore
                          obj.common.role = 'state';
                      }
                      // @ts-ignore
                      if (!obj.common.name) {
                          // @ts-ignore
                          obj.common.name = stateId;
                      }
                      extendObject(this.globalNamespace + '.' + id, {
                          type: 'state',
                          common: obj.common,
                          native: obj.native
                      }, function (err) {
                          if (err) {
                              log('skipping creation of state ' + id, 'debug');
                          } else {
                              log('created state ' + id, 'debug');
                          }
                          setTimeout(() => {this.connectState(stateId)}, 1000);
                          countCreated++;
                          if (countCreated >= stateIds.length) {
                              log('created ' + countCreated + ' states for device ' + this.namespace, 'debug');
                              callback();
                          }
                      }.bind(this));
                  }
              }
              
              VirtualDevice.prototype.normalizeState = function (stateId) {
                  log('normalizing state ' + stateId, 'debug');
                  if (typeof this.config.states[stateId].read !== 'object') {
                      this.config.states[stateId].read = {};
                  }
                  if (typeof this.config.states[stateId].write !== 'object') {
                      this.config.states[stateId].write = {};
                  }
              
                  this.data[stateId] = {read: {}, write:{}, val: undefined};
                  var readIds = Object.keys(this.config.states[stateId].read);
                  for (var i = 0; i < readIds.length; i++) {
                      this.data[stateId].read[readIds[i]] = null; 
                      var readId = this.config.states[stateId].read[readIds[i]];
                      if (typeof readId.before !== 'function') {
                          this.config.states[stateId].read[readIds[i]].before = function (device, value, callback) {
                              callback()
                          };
                      }
                      if (typeof readId.after !== 'function') {
                          this.config.states[stateId].read[readIds[i]].after = function (device, value) {
                          };
                      }
                  }
                  var writeIds = Object.keys(this.config.states[stateId].write);
                  for (i = 0; i < writeIds.length; i++) {
                      this.data[stateId].write[writeIds[i]] = null; 
                      var writeId = this.config.states[stateId].write[writeIds[i]];
                      if (typeof writeId.before !== 'function') {
                          this.config.states[stateId].write[writeIds[i]].before = function (device, value, callback) {
                              callback()
                          };
                      }
                      if (typeof writeId.after !== 'function') {
                          this.config.states[stateId].write[writeIds[i]].after = function (device, value) {
                          };
                      }
                  }
                  log('normalized state ' + stateId, 'debug');
              }
              
              VirtualDevice.prototype.connectState = function (stateId) {
                  log('connecting state ' + stateId, 'debug');
                  let id = this.sandboxNamespace + '.' + stateId;
              
                  //subscribe to read ids
                  let readIds = Object.keys(this.config.states[stateId].read);
                  for (let i = 0; i < readIds.length; i++) {
                      //check if state exists
                      getObject(readIds[i], function (err, obj) {
                          // @ts-ignore
                          if (err || obj.type !== 'state') {
                              log('cannot connect to not existing state: ' + readIds[i] + '|' + err + '|' + JSON.stringify(obj), 'warn');
                              return;
                          }
                          var readObj = this.config.states[stateId].read[readIds[i]];
                          var trigger_temp = readObj.trigger || {change: 'any'};
                          trigger_temp.ack = true;
                          trigger_temp.id = readIds[i];
                          this.subRead(trigger_temp, readObj, stateId);
                          log('connected ' + readIds[i] + ' to ' + id, 'debug');
                      }.bind(this));
                  }
              
                  //subscribe to this state and write to write ids
                  var writeIds = Object.keys(this.config.states[stateId].write);
                  var trigger = {id: this.namespace + '.' + stateId, change: 'any', ack: false};
                  // @ts-ignore
                  on(trigger, function (obj) {
                      "use strict";
                      log('detected change of ' + stateId, 'debug');
                      for (var i = 0; i < writeIds.length; i++) {
                          let writeObj = this.config.states[stateId].write[writeIds[i]];
                          let val = obj.state.val;
                          try {
                              val = this.convertValue(obj.state.val, writeObj.convert);
                          } catch (e) {
                              log('device "' + this.name + '" caused an error in write convert function:' + e, 'warn');
                              continue;
                          }
                          let writeId = writeIds[i];
                          log('executing function before for ' + writeId, 'debug');
                          try {
                              writeObj.before(this, val, function (newVal, newDelay) {
                                  if (newVal !== undefined && newVal !== null) val = newVal;
                                  var delay = writeObj.delay;
                                  if (newDelay !== undefined && newDelay !== null) delay = newDelay;
                                  log('writing value ' + val + ' to ' + writeId + ' with delay ' + delay, 'debug');
                                  this.data[stateId].val = val;
                                  setStateDelayed(writeId, val, false, delay || 0, true, function () {
                                      log('executing function after for ' + writeId, 'debug');
                                      writeObj.after(this, val);
                                  }.bind(this));
                              }.bind(this));
                          } catch (e) {
                              log('device "' + this.name + '" caused an error in a write function:' + e, 'warn');
                              continue;
                          }
                      }
                  }.bind(this));
                  log('connected ' + stateId + ' to ' + JSON.stringify(writeIds), 'debug');
              }
              
              VirtualDevice.prototype.subRead = function (trigger, readObj, stateId) {
                  //function to process read states
                  var onRead = function (obj) {
                      let val = obj.state.val; 
                      try {
                          val = this.convertValue(obj.state.val, readObj.convert);
                      } catch (e) {
                          log('device "' + this.name + '" caused an error in read convert function:' + e, 'warn');
                          return;
                      }
                      log('executing function before for ' + trigger.id, 'debug');
                      try {
                          readObj.before(this, val, function (newVal, newDelay) {
                              if (newVal !== undefined && newVal !== null) val = newVal;
                              if (newDelay !== undefined && newDelay !== null) readObj.delay = newDelay;
                              log('reading value ' + val + ' into ' + this.namespace + '.' + stateId, 'debug');
                              this.data[stateId].read[trigger.id] = val; 
                              let aggregatedVal = this.aggregateRead(stateId, trigger.id);  
                              this.data[stateId].val = aggregatedVal;              
                              setStateDelayed(this.namespace + '.' + stateId, aggregatedVal, true, readObj.delay || 0, true, function () {
                                  log('executing function after for ' + trigger.id, 'debug');
                                  readObj.after(this, aggregatedVal);
                              }.bind(this));
                          }.bind(this));
                      } catch (e) {
                          log('device "' + this.name + '" caused an error in a read function:' + e, 'warn');
                          return;
                      }
                  }.bind(this);
              
                  //subscribe to state
                  on(trigger, onRead);
              
                  //get state once initially
                  getState(trigger.id, function (err, state) {
                      if (!err && state) {
                          onRead({state: state});
                      }
                  });
              }
              
              VirtualDevice.prototype.convertValue = function (val, func) {
                  if (typeof func !== 'function') {
                      return val;
                  }
                  return func(val);
              }
              
              VirtualDevice.prototype.aggregateRead = function (stateId, readId) {
                  var aggregatedVal = this.data[stateId].read[readId];
                  if (!this.config.states[stateId].logic || !this.config.states[stateId].logic.type) {
                      return aggregatedVal;
                  }
                  switch(this.config.states[stateId].logic.type) {
                      case 'last':
                          //use value of state that triggered this function
                          aggregatedVal = this.data[stateId].read[readId];
                          if (this.config.states[stateId].logic.parameters && typeof this.config.states[stateId].logic.parameters.decimals === 'number') {
                              aggregatedVal = Number(aggregatedVal.toFixed(this.config.states[stateId].logic.parameters.decimals));
                          }
                          break;
                      case 'number.sum':
                          var values = Object.values(this.data[stateId].read).filter(value => typeof value === 'number');
                          aggregatedVal = values.reduce((accumulator, current) => accumulator + current);
                          if (this.config.states[stateId].logic.parameters && typeof this.config.states[stateId].logic.parameters.decimals === 'number') {
                              aggregatedVal = Number(aggregatedVal.toFixed(this.config.states[stateId].logic.parameters.decimals));
                          }
                          break;
                      case 'number.min':
                          var values = Object.values(this.data[stateId].read).filter(value => typeof value === 'number');
                          aggregatedVal = Math.min(...values);
                          if (this.config.states[stateId].logic.parameters && typeof this.config.states[stateId].logic.parameters.decimals === 'number') {
                              aggregatedVal = Number(aggregatedVal.toFixed(this.config.states[stateId].logic.parameters.decimals));
                          }
                          break;
                      case 'number.max':
                          var values = Object.values(this.data[stateId].read).filter(value => typeof value === 'number');
                          aggregatedVal = Math.max(...values);
                          if (this.config.states[stateId].logic.parameters && typeof this.config.states[stateId].logic.parameters.decimals === 'number') {
                              aggregatedVal = Number(aggregatedVal.toFixed(this.config.states[stateId].logic.parameters.decimals));
                          }
                          break;
                      case 'number.average': 
                          var values = Object.values(this.data[stateId].read).filter(value => typeof value === 'number');
                          aggregatedVal = values.length ? values.reduce((accumulator, current) => accumulator + current) / values.length : 0;
                          if (this.config.states[stateId].logic.parameters && typeof this.config.states[stateId].logic.parameters.decimals === 'number') {
                              aggregatedVal = Number(aggregatedVal.toFixed(this.config.states[stateId].logic.parameters.decimals));
                          }
                          break;
                      case 'string.concat':
                          var values = Object.values(this.data[stateId].read).filter(value => typeof value === 'string');
                          var separator = " ";
                          if (this.config.states[stateId].logic.parameters && typeof this.config.states[stateId].logic.parameters.separator === 'string') {
                              separator = this.config.states[stateId].logic.parameters.separator;
                          }
                          aggregatedVal = values.join(separator);
                          break;
                      default:
                          //use value of state that triggered this function
                          aggregatedVal = this.data[stateId].read[readId];
                          break;
                  }
                  return aggregatedVal;
              }
              

              Als Beispiel für die neue Funktion bietet sich ein Gruppierungsgerät an, das die Werte mehrerer Geräte zusammenfasst.
              In diesem Fall habe ich meine Thermostate zusammengefasst. valveOpen zeigt z.B. die Anzahl der Ventile mit einem Wert > 0 an, maxTemp zeigt die höchste Temperatur an usw.

              new VirtualDevice({
                  "namespace": "Heizung.Gruppe",
                  "name": "alle",
                  "states": {
                      "valveOpen": {
                          "common": {
                              "type": "number",
                              "min": 0,
                              "def": 0,
                              "read": true,
                              "write": false,
                              "unit": "Valves",
                              "role": "value.counter"
                          },
                          "read": {
                              "0_userdata.0.virtualDevice.Heizung.Wohnzimmer.valve": {
                                  "convert": function(value) {
                                      return value ? 1 : 0
                                  }
                              },
                              "0_userdata.0.virtualDevice.Heizung.Badezimmer.valve": {
                                  "convert": function(value) {
                                      return value ? 1 : 0
                                  }
                              },
                              "0_userdata.0.virtualDevice.Heizung.Schlafzimmer.valve": {
                                  "convert": function(value) {
                                      return value ? 1 : 0
                                  }
                              }
                          },
                          "logic": {
                              "type": "number.sum"
                          }
                      },
                      "valveClosed": {
                          "common": {
                              "type": "number",
                              "min": 0,
                              "def": 0,
                              "read": true,
                              "write": false,
                              "unit": "Valves",
                              "role": "value.counter"
                          },
                          "read": {
                              "0_userdata.0.virtualDevice.Heizung.Wohnzimmer.valve": {
                                  "convert": function(value) {
                                      return value ? 0 : 1
                                  }
                              },
                              "0_userdata.0.virtualDevice.Heizung.Badezimmer.valve": {
                                  "convert": function(value) {
                                      return value ? 0 : 1
                                  }
                              },
                              "0_userdata.0.virtualDevice.Heizung.Schlafzimmer.valve": {
                                  "convert": function(value) {
                                      return value ? 0 : 1
                                  }
                              }
                          },
                          "logic": {
                              "type": "number.sum"
                          }
                      },
                      "minTemp": {
                          "common": {
                              "type": "number",
                              "unit": "°C",
                              "def": 0,
                              "min": -10,
                              "max": 50,
                              "read": true,
                              "write": false,
                              "role": "value.temperature"
                          },
                          "read": {
                              "0_userdata.0.virtualDevice.Heizung.Wohnzimmer.temperature": {},
                              "0_userdata.0.virtualDevice.Heizung.Badezimmer.temperature": {},
                              "0_userdata.0.virtualDevice.Heizung.Schlafzimmer.temperature": {}
                          },
                          "logic": {
                              "type": "number.min"
                          }
                      },
                      "maxTemp": {
                          "common": {
                              "type": "number",
                              "unit": "°C",
                              "def": 0,
                              "min": -10,
                              "max": 50,
                              "read": true,
                              "write": false,
                              "role": "value.temperature"
                          },
                          "read": {
                              "0_userdata.0.virtualDevice.Heizung.Wohnzimmer.temperature": {},
                              "0_userdata.0.virtualDevice.Heizung.Badezimmer.temperature": {},
                              "0_userdata.0.virtualDevice.Heizung.Schlafzimmer.temperature": {}
                          },
                          "logic": {
                              "type": "number.max"
                          }
                      },
                      "avgTemp": {
                          "common": {
                              "type": "number",
                              "unit": "°C",
                              "def": 0,
                              "min": -10,
                              "max": 50,
                              "read": true,
                              "write": false,
                              "role": "value.temperature"
                          },
                          "read": {
                              "0_userdata.0.virtualDevice.Heizung.Wohnzimmer.temperature": {},
                              "0_userdata.0.virtualDevice.Heizung.Badezimmer.temperature": {},
                              "0_userdata.0.virtualDevice.Heizung.Schlafzimmer.temperature": {}
                          },
                          "logic": {
                              "type": "number.average",
                              "parameters": {
                                  "decimals": 1
                              }
                          }
                      },
                      "test": {
                          "common": {
                              "type": "string",
                              "read": true,
                              "write": false,
                              "role": "value"
                          },
                          "read": {
                              "0_userdata.0.virtualDevice.Heizung.Wohnzimmer.temperature": {
                                  "convert": function(value) {
                                      return value + '°C'
                                  }
                              },
                              "0_userdata.0.virtualDevice.Heizung.Badezimmer.temperature": {
                                  "convert": function(value) {
                                      return value + '°C'
                                  }
                              },
                              "0_userdata.0.virtualDevice.Heizung.Schlafzimmer.temperature": {
                                  "convert": function(value) {
                                      return value + '°C'
                                  }
                              }
                          },
                          "logic": {
                              "type": "string.concat",
                              "parameters": {
                                  "separator": " | "
                              }
                          }
                      }
                  }
              });
              
              Johannes TäuberJ Offline
              Johannes TäuberJ Offline
              Johannes Täuber
              wrote on last edited by
              #46

              Hallo zusammen, dieser Thread ist ziehmlich alt. Ich würde gerne wissen ob das immer noch die beste Lösung fuer das virtualisieren von devices ist oder ob es inzwischen andere Sachen gibt! Danke!

              HomoranH Jey CeeJ 2 Replies Last reply
              0
              • Johannes TäuberJ Johannes Täuber

                Hallo zusammen, dieser Thread ist ziehmlich alt. Ich würde gerne wissen ob das immer noch die beste Lösung fuer das virtualisieren von devices ist oder ob es inzwischen andere Sachen gibt! Danke!

                HomoranH Do not disturb
                HomoranH Do not disturb
                Homoran
                Global Moderator Administrators
                wrote on last edited by
                #47

                @johannes-täuber sagte in Virtual Devices:

                ob das immer noch die beste Lösung fuer das virtualisieren von devices ist

                was verstehst du darunter?

                Vermutlich alias?

                kein Support per PN! - Fragen im Forum stellen - es gibt fast nichts, was nicht auch für andere interessant ist.

                Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.

                der Installationsfixer: curl -fsL https://iobroker.net/fix.sh | bash -

                1 Reply Last reply
                0
                • Johannes TäuberJ Johannes Täuber

                  Hallo zusammen, dieser Thread ist ziehmlich alt. Ich würde gerne wissen ob das immer noch die beste Lösung fuer das virtualisieren von devices ist oder ob es inzwischen andere Sachen gibt! Danke!

                  Jey CeeJ Online
                  Jey CeeJ Online
                  Jey Cee
                  Developer
                  wrote on last edited by
                  #48

                  @johannes-täuber Das hängt davon ab ob du neue, also nicht existente Datenpunkte für das Gerät brauchst oder ob du nur existente Bündeln möchtest.

                  Mit dem devices Adapter lassen sich bestehende Datenpunkte bündeln, diese werden dann als Alias in einer gemeinsamen Objekt Struktur angelegt.

                  Sobald man eigene Datenpunkte hinzufügen möchte muss man sich das selber Bauen, da es keinen Alias geben kann der mit keinem anderen Datenpunkte verknüpft ist.

                  Persönlicher Support
                  Spenden -> paypal.me/J3YC33

                  1 Reply Last reply
                  0
                  Reply
                  • Reply as topic
                  Log in to reply
                  • Oldest to Newest
                  • Newest to Oldest
                  • Most Votes


                  Support us

                  ioBroker
                  Community Adapters
                  Donate

                  787

                  Online

                  32.5k

                  Users

                  81.6k

                  Topics

                  1.3m

                  Posts
                  Community
                  Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                  ioBroker Community 2014-2025
                  logo
                  • Login

                  • Don't have an account? Register

                  • Login or register to search.
                  • First post
                    Last post
                  0
                  • Home
                  • Recent
                  • Tags
                  • Unread 0
                  • Categories
                  • Unreplied
                  • Popular
                  • GitHub
                  • Docu
                  • Hilfe