Navigation

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

    NEWS

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

    • ioBroker goes Matter ... Matter Adapter in Stable

    • Monatsrückblick - April 2025

    P
    • Profile
    • Following 0
    • Followers 0
    • Topics 32
    • Posts 778
    • Best 8
    • Groups 2

    Pman

    @Pman

    21
    Reputation
    146
    Profile views
    778
    Posts
    0
    Followers
    0
    Following
    Joined Last Online

    Pman Follow
    Pro Starter

    Best posts made by Pman

    • Hue Push API für Hue Adapter

      Bisher hat Philips die Push API nicht offiziell released und sie wird noch nicht vom Hue-Adapter unterstützt. Dieses Skript baut eine Verbindung zur Bridge auf und aktualisiert entsprechende States "live", ich empfehle das polling im Adapter zu deaktivieren. Welche Datenpunkte aktualisiert werden kann man in der UPDATEMAP nachlesen.
      Weitere Hinweise siehe Skript.
      Update
      1.1.0 hue-extended support
      1.1.1 ZGPSwitch support
      1.1.2 bekannte push calls ignorieren um weniger log zu erzeugen
      1.1.3 Zone support (only on)

      /**
      * Version: 1.1.3 (HUE + HUE-EXTENDED)
      * Anleitung: 
      *  - npm-Modul im Javascript-Adapter hinzufügen (Adaptereinstellung): hue-push-client 
      *  - IP: IP der Bridge eintragen
      *  - TOKEN: gültigen User für die Bridge eintragen (z.B. aus der Hue Adapter Konfiguration)
      *  - INSTANCE: korrekte Instanz eingeben, meist hue.0 oder hue-extended.0
      **/
      const IP = '<bridge ip>';
      const TOKEN = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
      const INSTANCE = 'hue.0';
      
      /**
      * DO NOT EDIT BELOW
      **/
      const UPDATEMAP_HUE = {
          //lights
          'lights.status.status': {stateName: 'reachable', convert: (val) => {return val === 'connected' ? true : false;}, validTypes: ['Extended color light', 'Color temperature light', 'Dimmable light', 'On/Off plug-in unit'], after: (stateId, value) => {updateHueState(stateId.substring(0, stateId.lastIndexOf('.'))  + '.on', value)}},
          'lights.on.on': {stateName: 'on', validTypes: ['Extended color light', 'Color temperature light', 'Dimmable light', 'On/Off plug-in unit']},
          'lights.dimming.brightness': {stateName: 'level', convert: (val) => {return Math.ceil(val)}, validTypes: ['Extended color light', 'Color temperature light', 'Dimmable light'], after: (stateId, value) => {updateRGBHue(stateId.substring(0, stateId.lastIndexOf('.')))}},
          'lights.color_temperature.mirek': {stateName: 'ct', convert: (val) => {return Math.round(1000000/val)}, validTypes: ['Extended color light', 'Color temperature light']},
          'lights.color.xy': {stateName: 'xy', convert: (val) => {return val.x + ',' + val.y}, validTypes: ['Extended color light'], after: (stateId, value) => {updateRGBHue(stateId.substring(0, stateId.lastIndexOf('.')))}},
      
          //groups
          'groups.on.on': {stateName: 'on', validTypes: ['Room', 'LightGroup', 'Zone']},
      
          //sensors
          'sensors.motion.motion': {stateName: 'presence', validTypes: ['ZLLPresence']},
          'sensors.light.light_level':  {stateName: 'lightlevel', convert: (val) => {return Math.round(val)}, validTypes: ['ZLLLightLevel']},
          'sensors.temperature.temperature': {stateName: 'temperature', convert: (val) => {return Number.parseFloat(val.toPrecision(4))}, validTypes: ['ZLLTemperature']},
          'sensors.power_state.battery_level': {stateName: 'battery', convert: (val) => {return Math.round(val)}, validTypes: ['ZLLPresence', 'ZLLLightLevel', 'ZLLTemperature', 'ZLLSwitch']},
          //buttons
          'sensors.button.last_event': {stateName: 'buttonevent', convert: (val) => {return ((UUIDs[this.idv2] && UUIDs[this.idv2].metadata) ? UUIDs[this.idv2].metadata.control_id : 0) * 1000 + (val === 'repeat' ? 1 : 0) + (val === 'short_release' ? 2 : 0) + (val === 'long_release' ? 3 : 0)}, validTypes: ['ZLLSwitch', 'ZGPSwitch']},
      
          //ignore
          'lights.owner.rid':{},
          'lights.owner.rtype':{},
          'sensors.owner.rid':{},
          'sensors.owner.rtype':{},
          'sensors.power_state.battery_state': {},
      };
      
      const UPDATEMAP_HUE_EXTENDED = {
          //lights
          'lights.status.status': {stateName: 'state.reachable', convert: (val) => {return val === 'connected' ? true : false;}, validTypes: ['Extended color light', 'Color temperature light', 'Dimmable light', 'On/Off plug-in unit']},
          'lights.on.on': {stateName: 'action.on', validTypes: ['Extended color light', 'Color temperature light', 'Dimmable light', 'On/Off plug-in unit']},  
          'lights.dimming.brightness': {stateName: 'action.level', convert: (val) => {return Math.ceil(val)}, validTypes: ['Extended color light', 'Color temperature light', 'Dimmable light'], after: (stateId, value) => {updateRGBHueExtended(stateId.substring(0, stateId.lastIndexOf('.')))}},
          'lights.color_temperature.mirek': {stateName: 'action.colorTemperature', convert: (val) => {return Math.round(1000000/val)}, validTypes: ['Extended color light', 'Color temperature light']},
          'lights.color.xy': {stateName: 'action.xy', convert: (val) => {return val.x + ',' + val.y}, validTypes: ['Extended color light'], after: (stateId, value) => {updateRGBHueExtended(stateId.substring(0, stateId.lastIndexOf('.')))}},
        
          //groups
          'groups.on.on': {stateName: 'state.any_on', validTypes: ['Room', 'LightGroup', 'Zone']},
      
          //sensors
          'sensors.motion.motion': {stateName: 'state.presence', convert: (val) => {return val.toString()}, validTypes: ['ZLLPresence']},
          'sensors.light.light_level':  {stateName: 'state.lightlevel', convert: (val) => {return Math.round(val).toString()}, validTypes: ['ZLLLightLevel']},
          'sensors.temperature.temperature': {stateName: 'state.temperature', convert: (val) => {return Number.parseFloat(val.toPrecision(4))}, validTypes: ['ZLLTemperature']},
          'sensors.power_state.battery_level': {stateName: 'config.battery', convert: (val) => {return Math.round(val)}, validTypes: ['ZLLPresence', 'ZLLLightLevel', 'ZLLTemperature', 'ZLLSwitch']},
          //buttons
          'sensors.button.last_event': {stateName: 'state.buttonevent', convert: (val) => {return ((UUIDs[this.idv2] && UUIDs[this.idv2].metadata) ? UUIDs[this.idv2].metadata.control_id : 0) * 1000 + (val === 'short_release' ? 2 : 0) + (val === 'long_release' ? 3 : 0)}, validTypes: ['ZLLSwitch', 'ZGPSwitch']},
      
          //ignore
          'lights.owner.rid':{},
          'lights.owner.rtype':{},
          'sensors.owner.rid':{},
          'sensors.owner.rtype':{},
          'sensors.power_state.battery_state': {},
      };
      
      //select UPDATEMAP for hue or hue-extended
      const UPDATEMAP = INSTANCE.toLowerCase().includes('extended') ? UPDATEMAP_HUE_EXTENDED : UPDATEMAP_HUE;
      const findState = INSTANCE.toLowerCase().includes('extended') ? findStateHueExtended : findStateHue;
      
      //connect to event stream
      const HuePushClient = require('hue-push-client');
      const client = new HuePushClient({ip: IP, user: TOKEN});
      client.addEventListener('open', function () {
          log('connected');
      });
      client.addEventListener('close', function () {
          log('disconnected', 'warn');
      });
      client.addEventListener('error', function (e) {
          log('connection error: ' + e.message, 'warn');
      });
      client.addEventListener('message', function (packet) {
          parsePacket(packet);
      });
      
      //get resouce ids (required to identify buttons)
      let UUIDs = {};
      async function getUUIDs() {
          try {
              UUIDs = await client.uuids();
          } catch (e) {
              log(e, 'warn');
          }
      };
      getUUIDs();
      
      
      // close connection if script is stopped
      onStop(function (callback) {
          client.close();
          callback();
      }, 2000);
      
      function parsePacket (packet) {
          log('RECEIVED PACKET: ' + "\n" + JSON.stringify(packet), 'debug');
          try {
              if (!packet.data) {
                  log('packet has no data: ' + JSON.stringify(packet), 'warn');
                  return;
              }
              for (let message of JSON.parse(packet.data)) {
                  if (message.type !== 'update') {
                      log ('unknown message type: ' + JSON.stringify(message), 'warn');
                      continue;
                  }
                  if (typeof message.data !== 'object') {
                      log ('message contains no data: '  + JSON.stringify(message), 'warn');
                      continue;
                  }
                  for (let update of message.data) {
                      parseUpdate(update);
                  }
              }
          } catch (e) {
              log('could not read packet: ' + JSON.stringify(packet), 'warn');
              log(e.message, 'warn');
              return;
          }
      }
      
      function parseUpdate(update) {
          log('PARSING UPDATE: ' + "\n" + JSON.stringify(update), 'debug');
      
          if (!update.id_v1) return;
          const [resource, idv1] = update.id_v1.split('/').filter(Boolean);
          const idv2 = update.id;
      
          //remove id, id_v1, type
          delete  update.id;
          delete  update.id_v1;
          delete  update.type;
      
          //status to object as other updates are objects
          if (update.status) {
              update.status = {status: update.status};
          }
      
          processUpdate(resource, idv1, idv2, update);
      }
      
      let IdCache = [];
      function processUpdate(resource, resourceId, idv2, data) {
          this.resource = resource;
          this.resourceId = resourceId;
          this.idv2 = idv2;
          this.data = data;
      
          log('PROCESSING UPDATE: ' + 'resource ' + resource + "\n" + JSON.stringify(data), 'debug');
          //check for values to data
          for (let action in data) {
              if (typeof data[action] !== 'object') {continue;}
      
              //check if update invalid and delete _valid entries
              for (let endpoint in data[action]) {
                  if (endpoint.substr(-6) === '_valid') {
                      if (!data[action][endpoint]) {
                          log('skipping invalid value' + resource + '.' + action + '.' + endpoint, 'debug');
                          return;
                      } else {
                          delete data[action][endpoint];
                      }
                  } 
              }
              //find hue adapter states to update and set new values
              for (let endpoint in data[action]) {
                  if (UPDATEMAP[resource + '.' + action + '.' + endpoint]) {    
                      log('found UPDATEMAP for ' + resource + '.' + action + '.' + endpoint, 'debug');
                      let updateValue = data[action][endpoint];
                      //convert value?
                      if (typeof UPDATEMAP[resource + '.' + action + '.' + endpoint].convert === 'function') {
                          let updateValueOld = updateValue;
                          updateValue = UPDATEMAP[resource + '.' + action + '.' + endpoint].convert.call(this, updateValue);
                          log('converted ' + resource + '.' + action + '.' + endpoint + ' from ' + updateValueOld + ' to ' + updateValue, 'debug');
                      }
                      let stateName = UPDATEMAP[resource + '.' + action + '.' + endpoint].stateName;
                      
                      //check if state id is cached
                      if (!IdCache[resource + '.' + resourceId + '.' + stateName]) {
                          findState(resource, resourceId, stateName, action, endpoint, () => {
                              updateHueState(IdCache[resource + '.' + resourceId + '.' + stateName], updateValue, UPDATEMAP[resource + '.' + action + '.' + endpoint].after);
                          });
                      } else {
                          log('found cache entry for ' + resource + '.' + resourceId + '.' + stateName + ': ' + IdCache[resource + '.' + resourceId + '.' + stateName], 'debug');
                          updateHueState(IdCache[resource + '.' + resourceId + '.' + stateName], updateValue, UPDATEMAP[resource + '.' + action + '.' + endpoint].after);
                      }
      
                  } else {
                      log('missing update instructions for ' + resource + '.' + action + '.' + endpoint);
                      log(JSON.stringify(data));
                  }
              }
          }
      }
      
      async function findStateHue(resource, resourceId, stateName, action, endpoint, callback) {
          log('searching for object with id ' + resourceId + ' and stateName ' + stateName, 'debug');
          $(INSTANCE + '.*' + '.' + stateName).each(function (stateId, i) {  //better way to find matching objects?
              getObject(stateId, (err, obj) => {
                  if (!err) {
                      if (obj.native && obj.native.id && obj.native.id == resourceId) {
                          log('found ' + stateId + ', checking parent for matching type...', 'debug');
                          //get parent object and check type
                          let parentId = stateId.substring(0, stateId.lastIndexOf('.'));
                          getObject(parentId, (err2, obj2) => {
                              if (!err2) {
                                  if (obj2.native && obj2.native.type && UPDATEMAP[resource + '.' + action + '.' + endpoint].validTypes.indexOf(obj2.native.type) !== -1 && !IdCache[resource + '.' + resourceId + '.' + stateName]) {
                                      log('found matching type for ' + parentId + ': ' + obj2.native.type, 'debug');
                                      log('save cache entry "' + resource + '.' + resourceId + '.' + stateName + '":' + stateId, 'debug');
                                      IdCache[resource + '.' + resourceId + '.' + stateName] = stateId;
                                      callback();
                                  }
                              } else {
                                  log('could not find obj: ' + stateId, 'warn');
                              }
                          });    
                      }
                  } else {
                      log('could not find obj: ' + stateId, 'warn');
                  }
              });
          });
      }
      
      async function findStateHueExtended(resource, resourceId, stateName, action, endpoint, callback) {
          log('searching for object with id ' + resourceId + ' and stateName ' + stateName, 'debug');
          $(INSTANCE + '.' + resource + '.*' + '.uid').each(async (stateId, i) => {  //better way to find matching objects?
              const deviceId = await getStateAsync(stateId);
              //check if device id matches
              if (deviceId.val !== resourceId) {
                  return;
              }
              const device = stateId.substr(0,stateId.length-4);
              //make sure final object exists
              const stateExists = await existsObjectAsync(device + '.' + stateName);
              if (!stateExists) {
                  return;
              }
              //check if type is valid and save cache entry
              const deviceType = await getStateAsync(device + '.type');
              if (UPDATEMAP[resource + '.' + action + '.' + endpoint].validTypes.indexOf(deviceType.val) !== -1 && !IdCache[resource + '.' + resourceId + '.' + stateName]) {
                  log('found matching type for ' + resourceId + ': ' + deviceType.val, 'debug');
                  log('save cache entry "' + resource + '.' + resourceId + '.' + stateName + '":' + device + '.' + stateName, 'debug');
                  IdCache[resource + '.' + resourceId + '.' + stateName] = device + '.' + stateName;
                  callback();                    
              }
          });
      }
      
      function updateHueState(stateId, value, after) {
          log('set ' + stateId + ' to ' + value + ' (' + typeof value + ')', 'debug');
          setStateDelayed(stateId, value, true, 50, true, () => {
              if (typeof after === 'function') {
                  log ('running  function after change of ' + stateId, 'debug');
                  after(stateId, value);
              }
          });
      } 
      
      
      //helpers
      async function updateRGBHue(stateId) {
          let xyState = await getStateAsync(stateId + '.xy');
          if (!xyState) return;
          let xy = xyState.val.replace('[', '').replace(']', '').split(',');
          let levelState = await getStateAsync(stateId + '.level');
          let level = levelState.val / 100;
          //let onState = await getStateAsync(stateId + '.on');
          //if (onState.val === false) level = 0;
          let rgb = HelperXYBtoRGB(xy[0], xy[1], level);
      
          updateHueState(stateId + '.r', Math.round(rgb.Red * 255));
          updateHueState(stateId + '.g', Math.round(rgb.Green * 255));
          updateHueState(stateId + '.b', Math.round(rgb.Blue * 255));
      }
      
      async function updateRGBHueExtended(stateId) {
          let xyState = await getStateAsync(stateId + '.xy');
          if (!xyState) return;
          let xy = xyState.val.replace('[', '').replace(']', '').split(',');
          let levelState = await getStateAsync(stateId + '.level');
          let level = levelState.val / 100;
          let rgb = HelperXYBtoRGB(xy[0], xy[1], level);
          updateHueState(stateId + '.rgb', Math.round(rgb.Red * 255) + ',' + Math.round(rgb.Green * 255) + ',' + Math.round(rgb.Blue * 255));
      }
      
      function HelperXYBtoRGB (x, y, Brightness) { // Source: https://github.com/PhilipsHue/PhilipsHueSDK-iOS-OSX/blob/master/ApplicationDesignNotes/RGB%20to%20xy%20Color%20conversion.md
          if (Brightness <= 0) {
              return {Red: 0, Green: 0, Blue: 0};
          }
          Brightness = Brightness || 1.0; // Default full brightness
          const z = 1.0 - x - y;
          const Y = Brightness;
          const X = (Y / y) * x;
          const Z = (Y / y) * z;
          // XYZ to RGB [M]-1 for Wide RGB D65, http://www.developers.meethue.com/documentation/color-conversions-rgb-xy
          let Red = X * 1.656492 - Y * 0.354851 - Z * 0.255038;
          let Green = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
          let Blue = X * 0.051713 - Y * 0.121364 + Z * 1.011530;
          // Limit RGB on [0..1]
          if (Red > Blue && Red > Green && Red > 1.0) { // Red is too big
              Green = Green / Red;
              Blue = Blue / Red;
              Red = 1.0;
          }
          if (Red < 0) {
              Red = 0;
          }
          if (Green > Blue && Green > Red && Green > 1.0) { // Green is too big
              Red = Red / Green;
              Blue = Blue / Green;
              Green = 1.0;
          }
          if (Green < 0) {
              Green = 0;
          }
          if (Blue > Red && Blue > Green && Blue > 1.0) { // Blue is too big
              Red = Red / Blue;
              Green = Green / Blue;
              Blue = 1.0;
          }
          if (Blue < 0) {
              Blue = 0;
          }
          // Apply reverse gamma correction
          if (Red <= 0.0031308) {
              Red = Red * 12.92;
          } else {
              Red = 1.055 * Math.pow(Red, (1.0 / 2.4)) - 0.055;
          }
          if (Green <= 0.0031308) {
              Green = Green * 12.92;
          } else {
              Green = 1.055 * Math.pow(Green, (1.0 / 2.4)) - 0.055;
          }
          if (Blue <= 0.0031308) {
              Blue = Blue * 12.92;
          } else {
              Blue = 1.055 * Math.pow(Blue, (1.0 / 2.4)) - 0.055;
          }
          // Limit RGB on [0..1]
          if (Red > Blue && Red > Green && Red > 1.0) { // Red is too big
              Green = Green / Red;
              Blue = Blue / Red;
              Red = 1.0;
          }
          if (Red < 0) {
              Red = 0;
          }
          if (Green > Blue && Green > Red && Green > 1.0) { // Green is too big
              Red = Red / Green;
              Blue = Blue / Green;
              Green = 1.0;
          }
          if (Green < 0) {
              Green = 0;
          }
          if (Blue > Red && Blue > Green && Blue > 1.0) { // Blue is too big
              Red = Red / Blue;
              Green = Green / Blue;
              Blue = 1.0;
          }
          if (Blue < 0) {
              Blue = 0;
          }
          return {Red: Red, Green: Green, Blue: Blue};
      };
      
      
      posted in Skripten / Logik
      P
      Pman
    • RE: Hue Push API für Hue Adapter

      Neue Version (siehe start Post), ich habe gleich noch Versucht das Skript kompatibel mit dem hue-extended Adapter zu machen, ist aber weitestgehend ungetestet.
      Wichtig: benötigt nun das modul "hue-push-client" statt "eventsource".

      posted in Skripten / Logik
      P
      Pman
    • Virtual Devices

      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]
      
      posted in Skripten / Logik
      P
      Pman
    • RE: Bedingungen für die kommerzielle Nutzung von ioBroker?

      Vorweg: zur Frage mit den Lizenzen kann ich nicht viel sagen.

      ABER ein ganz anderer wichtiger Punkt:

      Ich habe die Befürchtung, und evtl. ist das auch schon vorgekommen, dass zunehmend Leute hier im Forum auftauchen werden, welche Geld für eine ioBroker Installation bzw. Hardware gezahlt haben und sich dann über mangelenden Support und ähnliches Ärgern. Immerhin haben sie ja dafür gezahlt.

      Das bitte ich bei solchen Vorhaben dringend zu bedenken.

      posted in ioBroker Allgemein
      P
      Pman
    • RE: Hue Push API für Hue Adapter

      @mcu Den habe ich nicht, denke mit kleinen Anpassungen sollte das aber auch gehen. Vielleicht gucke ich mir das die Tage mal an.

      posted in Skripten / Logik
      P
      Pman
    • RE: Entscheidungshilfe ioBroker oder OpenHAB 2.0

      Also zum Thema Visualisierung:

      Das ist eigentlich genau die Stärke von ioBroker, du kannst extrem angepasste Views basteln, welche genau deinen Ansprüchen gerecht werden. Die fertigen Widgets sind ja nur eine kleine Hilfestellung.

      Bei vielen anderen Systemen (OpenHAB 2.0 habe ich noch nicht getestet) ist man stark an vorgefertigte Widgets gebunden, welche man bestenfalls noch irgendwie anordnen kann. Wenn einem vorgekautes allerdings reicht, könnte es schnell passieren, dass man mit ioBroker überfordert ist oder es für unnötig kompliziert hält.

      posted in ioBroker Allgemein
      P
      Pman
    • RE: Hue Push API für Hue Adapter

      @mcu
      Das ist absicht und kommt für alle unbekannten Updates. Wenn es dich stört kannst du die Zeile im Code auskommentieren wo geloggt wird.

      posted in Skripten / Logik
      P
      Pman
    • RE: Hue Push API für Hue Adapter

      @stony2k
      Freut mich, dass dir das Skript nützlich ist. Bei Gruppen vom Typ Room und LightGroup sollte on funktionieren, zumindest hat die Bridge diesen Status in der Push API zuletzt noch übermittelt. Nach dem letzten Bridge Update habe ich es noch nicht getestet. Andere Datenpunkte der Gruppen werden von der Push API leider nicht übermittelt.

      posted in Skripten / Logik
      P
      Pman

    Latest posts made by Pman

    • RE: IoBroker mit Warema WMS Web Control

      Habe das Update nun auch hinter mir und hinterlasse hier nochmal meine aktuelle Version des Skripts.

      //updated for serialport 10.x
      
      const { SerialPort } = require('serialport');
      const { ReadlineParser } = require('@serialport/parser-readline')
      const namespace = '0_userdata.0.Custom.WMS';
      log('namespace: ' + namespace);
      
      //config
      const PATH = "/dev/ttyWMS";
      const CHANNEL = 17;
      const PANID = "FFFF"; //inclusion mode: FFFF
      const KEY = "00112233445566778899AABBCCDDEEFF"; //inclusion mode: "00112233445566778899AABBCCDDEEFF"
      var positionInterval = 60; //how often current position is requested (seconds)
      var scanInterval = 10;  //how often to scan for devices (seconds)
      var scanDevicesMax = 3; //stop scanning after discovering all devices
      //listPorts();  //uncomment to list all available serial ports
      
      
      /* do not edit below! */
      //globals
      var knownDevices = {}; //stores known devices
      var lastData = ''; //contains last packet
      var writeQueue = []; //stores data to be sent to serial port
      var timers = {};
      
      /* open serial port and setup parser */
      function init() {
          //scan initially
          timers.scanInit = setTimeout(function () {
              wmsScan();
          }, 5000);
          //scan again every scanInterval seconds
          timers.scan = setInterval(function () {
              wmsScan();
          }, scanInterval * 1000);
      }
      
      //connect to serial port
      
      const port = new SerialPort({
          path: PATH,
          baudRate: 125000,
          parity: 'none',
          dataBits: 8,
          stopBits: 1,
          autoOpen: false,
      });
      
      //create parser with '}' as delemiter
      const parser = port.pipe(new ReadlineParser({ delimiter: '}' }))
      
      // handle serial port errors
      port.on('error', function () {
          log('serial port error!', 'warn');
          closePort().then((msg) => {
              log(msg, 'info');
          }).catch((err) => {
              log(err, 'warn');
          });
      });
      
      //parse incomming packets
      parser.on('data', parseData);
      
      //open serial port
      portOpen().then((msg) => {
          log(msg);
          writeAndWaitFor('{G}', 'gWMS USB-Stick', true).then((line) => {
              return writeAndWaitFor('{V}', 'v', true);
          }).then((line) => {
              log('Stick Version: ' + line);
              return writeAndWaitFor(encodeWMS('setKey', {key: KEY}), 'a', true);
          }).then((line) => {
              return writeAndWaitFor(encodeWMS('switchChannel', {
                  channel: CHANNEL,
                  panId: PANID
              }), 'a', true);
          }).then((line) => {
              return writeAndWaitFor(encodeWMS('switchChannelRequest', {
                  panId: PANID
              }), 'a', true);
          }).then((line) => {
              init();
          }).catch((err) => {
              log(err, 'warn');
              closePort().then((msg) => {
                  log(msg, 'info');
              }).catch((err) => {
                  log(err, 'warn');
              });
          });
      }).catch((err) => {
          log(err, 'warn');
      });
      
      
      /* serialport helper functions */
      
      //opens port with promise
      function portOpen() {
          return new Promise((resolve, reject) => {
              port.open((err) => {
                  err ? reject(err) : resolve('port opened');
              })
          });
      }
      
      //close port if open
      function closePort() {
          return new Promise((resolve, reject) => {
              log('closing open serial ports', 'info');
              clearInterval(timers.scan);
              if (port && port.isOpen) {
                  // close connection
                  port.close(() => {
                      resolve('port closed')
                  });
              } else {
                  reject('no port was opened');
              }
          });
      }
      
      //on script stop close port
      onStop(() => {
          clearInterval(timers.scan);
          //@todo clear all timers;
          closePort().then((msg) => {
              log(msg, 'info');
          }).catch((err) => {
              log(err, 'warn');
          });
      
      }, 2000);
      
      //handle incomming data
      function parseData(data) {
          //trim data
          data = wmsTrim(data);
          //do nothing, if packet is received twice
          if (lastData === data) return
          lastData = data;
          log('received message: ' + data, 'debug');
          //decode data into object
          var obj = decodeWMS(data);
          log('received: ' + JSON.stringify(obj), 'debug');
          //process object
          setTimeout( () => processWMS(obj), 10);
      }
      
      //list available serial ports
      function listPorts() {
          SerialPort.list().then((ports) => {
              log('Serial Ports: ' + JSON.stringify(ports));
          }).catch((err) => {
              log('error listing ports: ' + JSON.stringify(err));
          });
      }
      
      //write to serial port and wait for answer
      function writeAndWaitFor(data, expect, rejectOnTimeout, timeout) {
          return new Promise((resolve, reject) => {
              if (isNaN(timeout)) timeout = 5000;
              let listener = (line) => {
                  log('listener received message: "' + wmsTrim(line) + '" / expected: "' + expect + '"', 'debug');
                  if (wmsTrim(line).substr(0, expect.length) === expect) {
                      log('received expected answer: ' + expect, 'debug');
                      parser.removeListener('data', listener);
                      resolve(wmsTrim(line));
                  } else {
                      log('received unexpected answer (still waiting): ' + wmsTrim(line).substr(0, expect.length) + '!=' + expect, 'debug');
                  }
              };
              parser.on('data', listener);
              enqueue(data);
              //remove listener after 5 seconds
              setTimeout(() => {
                  parser.removeListener('data', listener);
                  rejectOnTimeout ? reject(expect) : resolve(false);
              }, timeout);
          });
      }
      
      var portReady = true;
      var sendInterval;
      function enqueue(data) {
          if (typeof data === 'string'){
              writeQueue.push(data);
          } 
          clearInterval(sendInterval);
          sendInterval = setInterval(sendData, 50);
      }
      function sendData() {
          if (writeQueue.length === 0 && portReady) {
              clearInterval(sendInterval);
              return;
          }
          if (portReady) {
              portReady = false;
              var sendData = writeQueue.shift();
              log('sending ' + sendData, 'debug');
              port.write(sendData);
              port.drain((err) => {
                  portReady = true;
              });
          }
      }
      
      /* WMS helper functions */
      
      //trim wms string
      function wmsTrim(data) {
          return data.trim().substr(1);
      }
      
      //decode wms strings into an object
      function decodeWMS(packet) {
          var obj = {};
          switch (packet.substr(0, 1)) {
              case 'g':
                  obj.type = 'stickType';
                  obj.payload = {name: packet.substr(1)};
                  break;
              case 'v':
                  obj.type = 'stickVersion';
                  obj.payload = {version: packet.substr(1)};
                  break;
              case 'f':
                  obj.type = 'error';
                  break;
              case 'a':
                  obj.type = 'ack';
                  break;
              case 'r':
                  obj.type = 'message';
                  obj.payload = decodeWMSMessage(packet.substr(1));
                  break;
              default:
                  obj.type = 'unknown';
                  obj.payload = packet.substr(1);
          }
          return obj;
      }
      
      //decode wms messages into an object
      function decodeWMSMessage(message) {
          var obj = {};
          obj.src = message.substr(0, 6);
          var type = message.substr(6, 4);
          var payload = message.substr(10);
          switch (type) {
              case '5018':
                  obj.type = 'joinNetworkRequest';
                  obj.messagePayload = {
                      panId: payload.substr(0, 4),
                      networkKey: payload.substr(4, 32).match(/../g).reverse().join(""),
                      unknown: payload.substr(36, 2),
                      channel: parseInt(payload.substr(38, 2), 16)
                  };
                  break;
              case '5060':
                  obj.type = 'switchChannelRequest';
                  obj.messagePayload = {
                      panId: payload.substr(0, 4),
                      deviceType: payload.substr(4, 2),
                      channel: parseInt(payload.substr(6, 2), 16)
                  };
                  break;
              case '50AC':
                  obj.type = 'ack';
                  obj.messagePayload = {
                      unknown: payload.substr(0, 4)
                  };
                  break;
              case '7020':
                  obj.type = 'scanRequest';
                  obj.messagePayload = {
                      panId: payload.substr(0, 4),
                      deviceType: payload.substr(4, 2)
                  };
                  break;
              case '7021':
                  obj.type = 'scanResponse';
                  obj.messagePayload = {
                      panId: payload.substr(0, 4),
                      deviceType: payload.substr(4, 2), //63: wetterstation, 06: webcontrol, 02: stick/software, 20: zwischenstecker, 00: Handsender
                      unknown: payload.substr(6) //optional
                  };
                  break;
              case '7080':
                  obj.type = 'weatherBroadcast';
                  obj.messagePayload = {
                      unknown_1: payload.substr(0, 2),
                      wind: parseInt(payload.substr(2, 2), 16),
                      lumen: payload.substr(4, 2) === '00' ? parseInt(payload.substr(12, 2), 16) * 2 : parseInt(payload.substr(4, 2), 16) * parseInt(payload.substr(12, 2), 16) * 2,
                      unknown_2: payload.substr(6, 6),
                      unknown_3: payload.substr(14, 2),
                      rain: payload.substr(16, 2) === 'C8',
                      temp: parseInt(payload.substr(18, 2), 16) / 2 - 35,
                      unknown_4: payload.substr(20)
                  };
                  break;
              case '7050':
                  obj.type = 'beckonRequest';
                  break;
              case '7070':
                  obj.type = 'controlRequest';
                  obj.messagePayload = {
                      unknown: payload.substr(0, 2),
                      position: parseInt(payload.substr(2, 2), 16) / 2,
                      angle: parseInt(payload.substr(4, 2), 16) - 127,
                      valance_1: payload.substr(6, 2),
                      valance_2: payload.substr(8, 2)
                  };
                  break;
              case '7071':
                  obj.type = 'controlResponse';
                  obj.messagePayload = payload;
                  break;
              case '8010':
                  obj.type = 'parameterGetRequest';
                  obj.messagePayload = {
                      parameter: payload.substr(0) //01000005: position, 26000046: clock timer settings, 0C000006: auto modes & limits
                  };
                  break;
              case '8011':
                  obj.type = 'parameterGetResponse';
                  obj.messagePayload = {
                      parameter: payload.substr(0, 8)
                  };
                  switch (obj.messagePayload.parameter) {
                      case '01000003': //position
                      case '01000005': //position
                          obj.messagePayload.type = 'position';
                          obj.messagePayload.position = parseInt(payload.substr(8, 2), 16) / 2;
                          obj.messagePayload.angle = parseInt(payload.substr(10, 2), 16) - 127;
                          obj.messagePayload.valance_1 = payload.substr(12, 2);
                          obj.messagePayload.valance_2 = payload.substr(14, 2);
                          break;
                      case '0C000006': //auto modes & limits
                          obj.messagePayload.type = 'autoSettings';
                          obj.messagePayload.wind = parseInt(payload.substr(8, 2), 16);
                          obj.messagePayload.rain = parseInt(payload.substr(10, 2), 16);
                          obj.messagePayload.sun = parseInt(payload.substr(12, 2), 16);
                          obj.messagePayload.dusk = parseInt(payload.substr(14, 2), 16);
                          obj.messagePayload.op = parseInt(payload.substr(16, 2), 16);
                          break;
                      case '26000046':
                          obj.messagePayload.type = 'clock';
                          obj.messagePayload.unknown = payload.substr(8);
                          break;
                      default:
                          obj.messagePayload.type = 'unknown';
                          obj.messagePayload.unknown = payload.substr(8);
                  }
                  break;
              case '8020':
                  obj.type = 'parameterSetRequest';
                  obj.messagePayload = {
                      parameter: payload.substr(0, 8)
                  };
                  switch (obj.messagePayload.parameter) {
                      case '0B080009':
                          obj.messagePayload.type = 'clock';
                          obj.messagePayload.year = parseInt(payload.substr(8, 2), 16);
                          obj.messagePayload.month = parseInt(payload.substr(10, 2), 16);
                          obj.messagePayload.day = parseInt(payload.substr(12, 2), 16);
                          obj.messagePayload.hour = parseInt(payload.substr(14, 2), 16);
                          obj.messagePayload.minute = parseInt(payload.substr(16, 2), 16);
                          obj.messagePayload.second = parseInt(payload.substr(18, 2), 16);
                          obj.messagePayload.day_of_week = parseInt(payload.substr(20, 2), 16);
                          obj.messagePayload.unknown = payload.substr(22);
                          break;
                      default:
                          obj.messagePayload.type = 'unknown';
                          obj.messagePayload.unknown = payload.substr(8);
                  }
                  break;
              default:
                  obj.type = 'unknown';
                  obj.messagePayload = payload;
          }
          return obj;
      }
      
      //create wms strings
      function encodeWMS(type, parameter) {
          log('encoding: ' + type + ' with parameters ' + JSON.stringify(parameter), 'debug');
          if (!parameter) parameter = {};
          switch (type) {
              case 'setKey':
                  if (!parameter.key) return false;
                  return '{K401' + parameter.key + '}';
                  break;
              case 'setScanMode':
                  if (isNaN(parameter.channel) || !parameter.panId) return false;
                  return '{M#' + parameter.channel + parameter.panId.match(/../g).reverse().join("") + '}';
                  break;
              case 'switchChannel':
                  if (isNaN(parameter.channel) || !parameter.panId) return false;
                  return '{M%' + parameter.channel + parameter.panId + '}';
                  break;
              case 'ack':
                  if (!parameter.dst) return false;
                  return '{R21' + parameter.dst + '50AC}';
                  break;
              case 'switchChannelRequest': //channel 17 fixed
                  if (!parameter.panId) return false;
                  return '{R04FFFFFF5060' + parameter.panId + '021100}'; // dst or FFFFFF???
                  break;
              case 'scanRequest':
                  return '{R04FFFFFF7020' + parameter.panId + '02}';
                  break;
              case 'scanResponse':
                  if (!parameter.panId || !parameter.dst || !parameter.deviceType) return false;
                  return '{R01' + parameter.dst + '7021' + parameter.panId + parameter.deviceType + (parameter.payload ? parameter.payload : '') + '}'; //fixed to deviceType 02 for now
                  break;
              case 'beckonRequest':
                  if (!parameter.dst) return false;
                  return '{R06' + parameter.dst + '7050}';
                  break;
              case 'controlRequest':
                  if (!parameter.dst || isNaN(parameter.position) || isNaN(parameter.angle)) return false;
                  return '{R06' + parameter.dst + '7070' + '03'
                      + ('0' + (Math.min(Math.max(parameter.position, 0), 100) * 2).toString(16)).substr(-2).toUpperCase()
                      + ('0' + (Math.min(Math.max(parameter.angle, 0), 90) + 127).toString(16)).substr(-2).toUpperCase()
                      + 'FFFF}'; //no idea how valance works
                  break;
              case 'parameterGetRequest':
                  if (!parameter.dst || !parameter.parameter) return false;
                  return '{R06' + parameter.dst + '8010' + parameter.parameter + '}';
                  break;
              case 'parameterGetRequestPosition':
                  if (!parameter.dst) return false;
                  return '{R06' + parameter.dst + '8010' + '01000005}';
                  break;
              case 'parameterGetRequestClock':
                  if (!parameter.dst) return false;
                  return '{R06' + parameter.dst + '8010' + '26000046}';
                  break;
              case 'parameterGetRequestAutoSettings':
                  if (!parameter.dst) return false;
                  return '{R06' + parameter.dst + '8010' + '0C000006}';
                  break;
              case 'parameterSetRequestAutoSettings':
                  if (!parameter.dst || !parameter.parameter
                      || isNaN(parameter.wind) || isNaN(parameter.rain)
                      || isNaN(parameter.sun) || isNaN(parameter.dusk))
                      return false;
                  return '{R06' + parameter.dst + '8020' + '0D000004'
                      + ('0' + Math.min(Math.max(parameter.wind, 0), 9).toString(16)).substr(-2).toUpperCase()
                      + ('0' + Math.min(Math.max(parameter.rain, 0), 9).toString(16)).substr(-2).toUpperCase()
                      + ('0' + Math.min(Math.max(parameter.sun, 0), 9).toString(16)).substr(-2).toUpperCase()
                      + ('0' + Math.min(Math.max(parameter.dusk, 0), 9).toString(16)).substr(-2).toUpperCase()
                      + (parameter.op ? '01' : '00')
                      + '}';
                  break;
              case 'parameterSetRequestAutoAll':
                  if (!parameter.dst) return false;
                  return '{R06' + parameter.dst + '8020' + '0D040001' + (parameter.op ? '01' : '00') + '}';
                  break;
              default: //unkown message type
                  return false;
                  break;
          }
      }
      
      //process packets
      function processWMS(obj) {
          //log(JSON.stringify(obj));
          if (obj.type !== 'message') return;
          switch (obj.payload.type) {
              case 'switchChannelRequest':
                  log('received switchChannelRequest, switching channel to ' + obj.payload.messagePayload.channel, 'debug');
                  writeAndWaitFor(encodeWMS('switchChannel', {
                      channel: obj.payload.messagePayload.channel,
                      panId: PANID
                  }), 'a');
                  break;
              case 'scanRequest':
                  // send scanResponse
                  log('received scanRequest, sending scanResponse', 'debug');
                  writeAndWaitFor(encodeWMS('scanResponse', {dst: obj.payload.src, panId: PANID, deviceType: '20', payload: '8FFF03000000000000000000000201010000000000000000'}), 'a');
                  break;
              case 'joinNetworkRequest':
                  log('received joinNetworkRequest:', 'debug');
                  log('KEY: ' + obj.payload.messagePayload.networkKey);
                  log('CHANNEL: ' + obj.payload.messagePayload.channel);
                  log('PANID: ' + obj.payload.messagePayload.panId);
                  writeAndWaitFor(encodeWMS('ack', {dst: obj.payload.src}), 'a');
                  break;
              case 'scanResponse':
                  log('received scanResponse', 'debug');
                  log('TYPE: ' + obj.payload.messagePayload.deviceType, 'debug');
                  log('SNR:' + obj.payload.src, 'debug');
                  if (obj.payload.messagePayload.deviceType === '20') {
                      let src = '' + obj.payload.src.trim();
                      if (knownDevices[src] === true) {
                          //log('skipping device: ' + src);
                          return;   
                      };
                      knownDevices[src] = true;
                      log('device type 20 found: ' + src);
      
                      scanDevicesMax--;
                      if (scanDevicesMax === 0) {  
                          clearInterval(timers.scan);
                          log ('stop scanning for devices');
                      }
      
                      
                      //log('creating state: ' + namespace + '.Raffstore.' + src + '.position');
                      createState(namespace + '.Raffstore.' + src + '.position', 0, {
                          type: 'number',
                          min: 0,
                          max: 100,
                          unit: '%'
                      });
                      //log('creating state: ' + namespace + '.Raffstore.' + src + '.angle');
                      createState(namespace + '.Raffstore.' + src + '.angle', 0, {
                          type: 'number',
                          min: 0,
                          max: 90,
                          unit: '°'
                      }, function () {
                          var deviceId = namespace + '.Raffstore.' + src;
                          on({id: deviceId + '.position', change: 'ne', ack: false}, function (obj) {
                              //send parameter
                              writeAndWaitFor(
                                  encodeWMS('controlRequest', {
                                      dst: src,
                                      position: obj.state.val,
                                      angle: getState(deviceId + '.angle').val
                                  }),
                                  'r' + src + '7071'
                              ).then(() => {
                                  clearInterval(timers[deviceId + 'parameterGetRequestPosition']);
                                  var lastValueAngle = -1;
                                  var lastValuePosition = -1;
                                  var noChange = 0;
                                  writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a');
                                  timers[deviceId + 'parameterGetRequestPosition'] = setInterval(function () {
                                      //get parameter periodically until no change is detected
                                      log(getState(deviceId + '.position').val + ':' + lastValuePosition + ' | ' + getState(deviceId + '.angle').val + ':' + lastValueAngle, 'info')
                                      if (getState(deviceId + '.position').val === lastValuePosition && getState(deviceId + '.angle').val === lastValueAngle) {
                                          noChange++;
                                          if (noChange === 2) {
                                              clearInterval(timers[deviceId + 'parameterGetRequestPosition']);
                                          }
                                      } else {
                                          noChange = 0;
                                      }
                                      lastValuePosition = getState(deviceId + '.position').val;
                                      lastValueAngle = getState(deviceId + '.angle').val;
                                      writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a');
                                  },  5000 + Math.random() * 1000);
                                  //setState(deviceId + '.position', getState(deviceId + '.position').val, true);
                              });
                          });
                          on({id: deviceId + '.angle', change: 'ne', ack: false}, function (obj) {
                              //send parameter
                              writeAndWaitFor(encodeWMS('controlRequest', {
                                      dst: src,
                                      position: getState(deviceId + '.position').val,
                                      angle: obj.state.val
                                  }),
                                  'r' + src + '7071'
                              ).then(() => {
                                  clearInterval(timers[deviceId + 'parameterGetRequestPosition']);
                                  var lastValueAngle = -1;
                                  var lastValuePosition = -1;
                                  var noChange = 0;
                                  writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a');
                                  timers[deviceId + 'parameterGetRequestPosition'] = setInterval(function () {
                                      //get parameter periodically until no change is detected
                                      log(getState(deviceId + '.position').val + ':' + lastValuePosition + ' | ' + getState(deviceId + '.angle').val + ':' + lastValueAngle, 'info');
                                      if (getState(deviceId + '.position').val === lastValuePosition && getState(deviceId + '.angle').val === lastValueAngle) {
                                          noChange++;
                                          if (noChange === 2) {
                                              clearInterval(timers[deviceId + 'parameterGetRequestPosition']);
                                          }
                                      } else {
                                          noChange = 0;
                                      }
                                      lastValuePosition = getState(deviceId + '.position').val;
                                      lastValueAngle = getState(deviceId + '.angle').val;
                                      writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a');
                                  },  3500 + Math.random() * 1000);
                                  //setState(deviceId + '.angle', getState(deviceId + '.angle').val, true);
                              });
                          });
                          setTimeout(function () {
                              //get parameter once
                              writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a');
                          }, 5000 + Math.random() * 5000);
                          setInterval(function () {
                              //get parameter periodicaly
                              writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a');
                          }, positionInterval * 1000 + Math.random() * 5000);
                      });
                  }
                  break;
              case 'parameterGetResponse':
                  log('received parameterGetResponse', 'debug');
                  switch (obj.payload.messagePayload.type) {
                      case 'position':
                          setStateDelayed(namespace + '.Raffstore.' + obj.payload.src + '.position', obj.payload.messagePayload.position, true, 100, true);
                          setStateDelayed(namespace + '.Raffstore.' + obj.payload.src + '.angle', obj.payload.messagePayload.angle, true, 100, true);
                      default:
                          break;
                  }
                  break;
              case 'weatherBroadcast':
                  log('received weatherBroadcast', 'debug');
                  let src = '' + obj.payload.src.trim();
                  createState(namespace + '.Wetter.' + src + '.temp', 0, {
                      type: 'number',
                      unit: '°C',
                      write: false
                  }, function () {
                      setStateDelayed(namespace + '.Wetter.' + src + '.temp', obj.payload.messagePayload.temp, true, 100, true);
                  });
                  createState(namespace + '.Wetter.' + src + '.wind', 0, {
                      type: 'number',
                      min: 0,
                      unit: 'm/s',
                      write: false
                  }, function () {
                      setStateDelayed(namespace + '.Wetter.' + src + '.wind', obj.payload.messagePayload.wind, true, 100, true);
                  });
                  createState(namespace + '.Wetter.' + src + '.lux', 0, {
                      type: 'number',
                      min: 0,
                      unit: 'lux',
                      write: false
                  }, function () {
                      setStateDelayed(namespace + '.Wetter.' + src + '.lux', obj.payload.messagePayload.lumen, true, 100, true);
                  });
                  createState(namespace + '.Wetter.' + src + '.rain', false, {
                      type: 'boolean',
                      write: false
                  }, function () {
                      setStateDelayed(namespace + '.Wetter.' + src + '.rain', obj.payload.messagePayload.rain, true, 100, true);
                  });
                  break;
              case 'beckonRequest':
                  writeAndWaitFor(encodeWMS('ack', {dst: obj.payload.src}), 'a');
                  break;
              case 'controlRequest':
                  writeAndWaitFor(encodeWMS('ack', {dst: obj.payload.src}), 'a');
                  break;      
              default:
                  break;
          }
      }
      
      //scan for devices
      function wmsScan() {  
          log('scanning for devices');
          writeAndWaitFor(encodeWMS('scanRequest', {panId: PANID}), 'a');   
      }
      
      
      
      posted in ioBroker Allgemein
      P
      Pman
    • RE: Hue Push API für Hue Adapter

      @zarsten said in Hue Push API für Hue Adapter:

      Meine Hue Bloom werden (mindestens seit Version 1.1.3) nicht unterstützt. Der Type ist 'Color light'. Ich habe den Type manuell in die Updatemap hinzugefügt. Falls es keinen Grund für den Ausschluss gab, könnte der Type ja im nächsten Update noch mit berücksichtigt werden...

      Der Grund ist einfach nur, dass ich keine solche Lampe besitze. Welche Datenpunkte unterstützt dieser Lampentyp, brightness, ct, xy?

      posted in Skripten / Logik
      P
      Pman
    • RE: Hue Push API für Hue Adapter

      @xsev
      Ich gehe demnach davon aus, dass du hue-extended nutzt? Bei mir hat hue-extended den .presence als String angelegt, daher auch die Konvertierung, ansonsten beschwert sich auch ioBroker.

      posted in Skripten / Logik
      P
      Pman
    • RE: Hue Push API für Hue Adapter

      @cash
      Danke, Zonen hatte ich nicht auf dem Schirm, da ich diese nicht nutze.

      posted in Skripten / Logik
      P
      Pman
    • RE: Hue Push API für Hue Adapter

      Habe jetzt nochmal getestet und bei meinen Räumen wird on live aktualisiert: Alle lampen aus -> on wird false ; Mindestens eine Lampe an -> on wird true.

      posted in Skripten / Logik
      P
      Pman
    • RE: Hue Push API für Hue Adapter

      Dann sendet die Bridge wohl keine Updates für diese Art von Gruppen. Sollte die Bridge etwas senden und das Skript weiß nichts damit anzufangen gibt es eine Meldung im LOG:
      missing update instructions for ...

      Polling parallel laufen lassen sollte eigentlich kein Problem sein, getestet habe ich es nicht.

      posted in Skripten / Logik
      P
      Pman
    • RE: Hue Push API für Hue Adapter

      @stony2k
      Freut mich, dass dir das Skript nützlich ist. Bei Gruppen vom Typ Room und LightGroup sollte on funktionieren, zumindest hat die Bridge diesen Status in der Push API zuletzt noch übermittelt. Nach dem letzten Bridge Update habe ich es noch nicht getestet. Andere Datenpunkte der Gruppen werden von der Push API leider nicht übermittelt.

      posted in Skripten / Logik
      P
      Pman
    • RE: Hue Push API für Hue Adapter

      @aufruf
      Eigentlich nur bei INSTANCE deine hue-extended Instanz eintragen.
      Beispiel:

      const INSTANCE = 'hue-extended.0';
      
      posted in Skripten / Logik
      P
      Pman
    • RE: Hue Push API für Hue Adapter

      @minta79
      Da weiß ich leider auch nicht mehr weiter, das Skript verbindet sich, empfängt die Änderungen und will sie auch mit setForeignState in das richtige Objekt schreiben.

      posted in Skripten / Logik
      P
      Pman
    • RE: Hue Push API für Hue Adapter

      @minta79
      Ich denke du hast den Debug modus im Skript selber aktiviert (oben rechts), dann werden die Objekte wohl nicht aktualisiert.

      posted in Skripten / Logik
      P
      Pman
    Community
    Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
    The ioBroker Community 2014-2023
    logo