Navigation

    Logo
    • Register
    • Login
    • Search
    • Recent
    • Tags
    • Unread
    • Categories
    • Unreplied
    • Popular
    • GitHub
    • Docu
    • Hilfe
    1. Home
    2. Zerberus

    NEWS

    • Neuer Blog: Fotos und Eindrücke aus Solingen

    • ioBroker@Smart Living Forum Solingen, 14.06. - Agenda added

    • ioBroker goes Matter ... Matter Adapter in Stable

    Z
    • Profile
    • Following 0
    • Followers 0
    • Topics 0
    • Posts 1
    • Best 0
    • Groups 0

    Zerberus

    @Zerberus

    0
    Reputation
    3
    Profile views
    1
    Posts
    0
    Followers
    0
    Following
    Joined Last Online

    Zerberus Follow

    Latest posts made by Zerberus

    • RE: Virtual Devices

      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);
      
      posted in Skripten / Logik
      Z
      Zerberus
    Community
    Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
    The ioBroker Community 2014-2023
    logo