Navigation

    Logo
    • Register
    • Login
    • Search
    • Recent
    • Tags
    • Unread
    • Categories
    • Unreplied
    • Popular
    • GitHub
    • Docu
    • Hilfe
    1. Home
    2. Deutsch
    3. Skripten / Logik
    4. [GELÖST] Homepilot Umweltsensor mit Javascript auslesen

    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

    [GELÖST] Homepilot Umweltsensor mit Javascript auslesen

    This topic has been deleted. Only users with topic management privileges can see it.
    • M
      meicker last edited by

      Vielen Dank auch dafür 🙂

      Habe ich jetzt am laufen - kann es aber nirgenswo sehen … Wo steht das Ergebnis ? Und wenn ich den Code richtig deute kommen ja auch 2 Variablen dabei raus - Sonnenstand -und höhe

      Für Blocky habe ich mir eine Abfrage gemacht und den Sonnenstand mit Start und Ende als Variable gesetzt. Jetzt muss ich das Ergebnis aus dem Script damit abgleichen, also das Wergebnis als Wert aufrufen ...

      Ich hätte vielleicht auch noch erwähnen sollen das ich ein lernwilliger blutiger Anfänger bin :lol:

      12:45:42.864 [info] javascript.0 Start javascript script.js.sonnenhoehe

      12:45:42.864 [error] javascript.0 script.js.sonnenhoehe compile failed: at script.js.sonnenhoehe:1

      1 Reply Last reply Reply Quote 0
      • paul53
        paul53 last edited by

        @meicker:

        Wo steht das Ergebnis ? Und wenn ich den Code richtig deute kommen ja auch 2 Variablen dabei raus - Sonnenstand -und höhe `
        Um die Werte weiter zu verwenden / anzuzeigen müssen sie in Datenpunkte vom Typ "Zahl" (number) geschrieben werden.

        const request = require('request');
        const link = 'http://...';
        
        // Datenpunkte erzeugen
        createState('Sonnenstand.Richtung', 0, {type: 'number', unit: '°'});
        createState('Sonnenstand.Höhe', 0, {type: 'number', unit: '°'});
        
        function wetter() {
            request(link, function(error,response, body) {
                if(error) log('Fehler request: ' + error, 'error');
                else {
                    var data = JSON.parse(body).data;
                    var dir = data[5].Sonnenrichtung;
                    dir = parseFloat(dir.substr(dir.indexOf('(') + 1));
                    setState('Sonnenstand.Richtung', dir, true); 
                    var ele = parseFloat(data[4]['Sonnenhöhe']);
                    setState('Sonnenstand.Höhe', ele, true); 
                }    
            });
        }
        
        schedule('* * * * *', wetter); // jede Minute
        
        1 Reply Last reply Reply Quote 0
        • M
          meicker last edited by

          OK 🙂

          Und was ist an der ersten Zeile falsch ?

          13:09:44.167 [error] javascript.0 script.js.sonnenhoehe compile failed: at script.js.sonnenhoehe:1

          Erste zeile: const request = require('request');

          Ich kann keinen Fehler sehen …

          1 Reply Last reply Reply Quote 0
          • paul53
            paul53 last edited by

            @meicker:

            Ich kann keinen Fehler sehen … `
            Ich auch nicht 😮

            Du hast hoffentlich das Skript nicht unter der Gruppe global erstellt ?

            EDIT: Habe .Sonnenhöhe wegen des Umlautes gegen ['Sonnenhöhe'] getauscht, da Javascript es zwar mit der Punktnotation richtig ausführt, aber der Editor meckert.

            1 Reply Last reply Reply Quote 0
            • M
              meicker last edited by

              OK, der Fehler war mein Fehler ! Ursprünglich in global erstellt, dann aber geändert. Schien aber trotzdem irgendwie noch gehangen zu haben. ich habe es gelöscht und frisch einfefügt, kein fehler mehr …

              Aber wo kann ich denn jetzt die states sehen ? Ich finde die einfach nicht ... :roll:

              1 Reply Last reply Reply Quote 0
              • paul53
                paul53 last edited by

                @meicker:

                Aber wo kann ich denn jetzt die states sehen ? Ich finde die einfach nicht … `
                Im Reiter "Objekte" unter javascript.0.Sonnenstand (wenn das Skript in der Instanz 0 läuft).

                1 Reply Last reply Reply Quote 0
                • M
                  meicker last edited by

                  Also - der fehler in zeile 1 ist doch noch da - kam eben nicht weil script nicht gestartet war …

                  In Objekte - schlag mich - steht nix von dem Script, es wird nichts erstellt - nur das was ich schon drin habe, mehr nicht ... Ich vermute wegen dem Fehler läuft das script überhaupt nicht erst an und erstell auch nix ... Aber wenn Du keinen Fehler findest in der ersten Zeile dann sieht es wohl schlecht aus ...

                  Regex ist keine Option ? Absolut unmöglich ?

                  1 Reply Last reply Reply Quote 0
                  • paul53
                    paul53 last edited by

                    @meicker:

                    Ich vermute wegen dem Fehler läuft das script überhaupt nicht erst an und erstell auch nix … Aber wenn Du keinen Fehler findest in der ersten Zeile dann sieht es wohl schlecht aus ... `
                    Ja, wenn ein Fehler-Log kommt, läuft das Skript nicht und erstellt folglich auch keine Datenpunkte. Schau mal in den Reiter "Log". Dort werden meist noch weitere Informationen zu dem Fehler ausgegeben (in rot).

                    1 Reply Last reply Reply Quote 0
                    • M
                      meicker last edited by

                      jupp 🙂

                      javascript.0 2018-08-15 14:07:11.001 error at Object.createScript (vm.js:80:10)

                      javascript.0 2018-08-15 14:07:11.001 error SyntaxError: Identifier 'request' has already been declared

                      javascript.0 2018-08-15 14:07:11.001 error ^

                      javascript.0 2018-08-15 14:07:11.000 error const request = require('request');

                      javascript.0 2018-08-15 14:07:10.999 error at script.js.Homepilot.grad:1

                      javascript.0 2018-08-15 14:07:10.999 error script.js.Homepilot.grad compile failed:

                      verstehe aber nicht warum - ich hatte das erste script doch gelöscht 😞

                      1 Reply Last reply Reply Quote 0
                      • M
                        meicker last edited by

                        soooooooo !!!!! 🙂 Klappt 🙂 Javascript Adapter neu gestartet … VIELEN DANK SOWEIT !!!

                        Ich müsste doch dann theoretisch auch die anderen Teile aus dem Link eingepflegt bekommen, oder ?

                        { "response" : "get_meter_data", "status" : "ok", "data":[{"Lichtwert":"30000 lx"},{"Windgeschw.":"0 m/s"},{"Temperatur":"22.1 °C"},{"Regen":"Nicht erkannt"},{"Sonnenhöhe":"53°"},{"Sonnenrichtung":"Süd-Osten (165°)"},{"Aktualisiert":"15.08.18 - 13:17"}] }

                        Das ° Zeichen bekomme ich weg indem ich unit mit '' schreibe ?

                        1 Reply Last reply Reply Quote 0
                        • paul53
                          paul53 last edited by

                          @meicker:

                          ich hatte das erste script doch gelöscht `
                          Ist nicht noch etwas unter der Gruppe global ?

                          Starte mal die Javascript-Instanz neu. Manchmal genügt das Löschen eines Skriptes nicht.

                          1 Reply Last reply Reply Quote 0
                          • M
                            meicker last edited by

                            lies mal einen über Deinem letzten - haben uns überschnitten

                            1 Reply Last reply Reply Quote 0
                            • paul53
                              paul53 last edited by

                              @meicker:

                              Klappt 🙂 Javascript Adapter neu gestartet `
                              Schön, dass Du es selbst gelöst hast 😄
                              @meicker:

                              Ich müsste doch dann theoretisch auch die anderen Teile aus dem Link eingepflegt bekommen, oder ? `
                              Ja, wie es geht, weißt Du jetzt.
                              @meicker:

                              {"Windgeschw.":"0 m/s"} `
                              Da der Bezeichner ""Windgeschw." ein unzulässiges Zeichen (Punkt) enthält, muss der Zugriff so erfolgen:

                                          var wind = parseFloat(data[1]['Windgeschw.']);
                              
                              

                              @meicker:

                              Das ° Zeichen bekomme ich weg indem ich unit mit '' schreibe ? `
                              Nein, mit parseFloat() wird aus dem String "165°)" eine Zahl gemacht und dabei abgebrochen, wenn keine Ziffer mehr folgt.

                              1 Reply Last reply Reply Quote 0
                              • M
                                meicker last edited by

                                Wissen kann man noch nicht behaupten, aber ich reime mir das gerade mal zusammen 🙂 Will ja was lernen … 😄

                                Ist data 1 nicht eigentlich LICHTWERT ? Und Wingeschwindigkeit 2 ??

                                Anders gefragt zum ° - wie bekomme ich das weg ? Oder stört das nicht in einer Blocky Abfrage ?

                                1 Reply Last reply Reply Quote 0
                                • paul53
                                  paul53 last edited by

                                  @meicker:

                                  Ist data 1 nicht eigentlich LICHTWERT ? Und Wingeschwindigkeit 2 ?? `
                                  Nein, die Zählung in Arrays beginnt mit 0.
                                  @meicker:

                                  Anders gefragt zum ° - wie bekomme ich das weg ? Oder stört das nicht in einer Blocky Abfrage ? `
                                  Das "°" ist im Datenpunktwert nicht enthalten, denn der Wert ist eine reine Zahl (durch parseFloat()).

                                  Mittels createState() wurde eine zusätzliche Datenpunkteigenschaft common.unit: "°" erzeugt, damit die Darstellung im Reiter "Objekte" ordentlich erfolgen kann.

                                  1 Reply Last reply Reply Quote 0
                                  • M
                                    meicker last edited by

                                    Wieder ein Stück weiter 🙂

                                    Jetzt hab ich noch zwei Sachen:

                                    const request = require('request');
                                    const link = 'http://192.168.1.180/deviceajax.do?meter=1010005';
                                    
                                    // Datenpunkte erzeugen
                                    createState('Homepilot.Sonnenrichtung', 0, {type: 'number', unit: '°'});
                                    createState('Homepilot.Sonnenhöhe', 0, {type: 'number', unit: '°'});
                                    createState('Homepilot.Lichtwert', 0, {type: 'number', unit: 'lux'});
                                    createState('Homepilot.Windgeschwindigkeit', 0, {type: 'number', unit: 'm/s'});
                                    createState('Homepilot.Temperatur', 0, {type: 'number', unit: '°C'});
                                    createState('Homepilot.Regen', 0, {type: 'boolean', unit: ''});
                                    createState('Homepilot.Aktualisiert', 0, {type: 'string', unit: ''});
                                    
                                    function sensordaten() {
                                        request(link, function(error,response, body) {
                                            if(error) log('Fehler request: ' + error, 'error');
                                            else {
                                                var data = JSON.parse(body).data;
                                                var dir = data[5].Sonnenrichtung;
                                                dir = parseFloat(dir.substr(dir.indexOf('(') + 1));
                                                setState('Homepilot.Sonnenrichtung', dir, true); 
                                                var ele = parseFloat(data[4].Sonnenhöhe);
                                                setState('Homepilot.Sonnenhöhe', ele, true); 
                                                var lux = parseFloat(data[0].Lichtwert);
                                                setState('Homepilot.Lichtwert', lux, true);
                                                var wind = parseFloat(data[1]['Windgeschw.']);
                                                setState('Homepilot.Windgeschwindigkeit', wind, true);
                                                var temp = parseFloat(data[2].Temperatur);
                                                setState('Homepilot.Temperatur', temp, true);
                                                var regen = parseFloat(data[3].Regen);
                                                setState('Homepilot.Regen', regen, true);
                                                var aktualisiert = parseFloat(data[6].Aktualisiert);
                                                setState('Homepilot.Aktualisiert', aktualisiert, true);
                                            }    
                                        });
                                    }
                                    schedule('*/30 * * * * *', sensordaten); // alle 30 Sekunden
                                    

                                    Regen soll true/false liefern -> boolean -> habe ich im datenpunkt eingetragen - wirkt nicht …

                                    Aktualisiert zeigt nur das datum ohne Zeit. war bei number schon so, habe ich auf String gesetzt - wirkt auch nicht ...

                                    Was mache ich da für einen Denkfehler ? 😄

                                    1 Reply Last reply Reply Quote 0
                                    • paul53
                                      paul53 last edited by

                                      unit macht bei Datenpunkten vom Typ "string" und "boolean" keinen Sinn.

                                      createState('Homepilot.Regen', false, {type: 'boolean'});
                                      createState('Homepilot.Aktualisiert', 'Init', {type: 'string'});
                                      
                                      

                                      @meicker:

                                      Regen soll true/false liefern -> boolean -> habe ich im datenpunkt eingetragen - wirkt nicht … `

                                      var regen = data[3].Regen; // Das ist ein String !
                                      if(regen == 'Nicht erkannt') setState('Homepilot.Regen', false, true);
                                      else setState('Homepilot.Regen', true, true);
                                      

                                      @meicker:

                                      Aktualisiert zeigt nur das datum ohne Zeit. war bei number schon so, habe ich auf String gesetzt - wirkt auch nicht … `

                                                  var aktualisiert = data[6].Aktualisiert; // Das ist ein String !
                                      
                                      

                                      parseFloat() wandelt einen String in eine Zahl, wenn möglich !

                                      1 Reply Last reply Reply Quote 0
                                      • M
                                        meicker last edited by

                                        DANKESCHÖN !!! Klappt alles wie gewünscht und ich habe eine Vorlage für weitere Schandtaten :lol:

                                        1 Reply Last reply Reply Quote 0
                                        • M
                                          meicker last edited by

                                          @paul53

                                          Ich habe das Script jetzt erweitert und einen weitern Sensor hinzugefügt der über zwei verschiedene Links aufgerufen wird.

                                          das was ich da gebastelt habe bringt allerdings den Javascript Adapter zum Absturz 😞 Hättest Du, oder jemand anders Lust da mal kurz drüber zu schauen ? Kannst Du die Fehler entdecken ? Habe ich den Aufbau soooo falsch gemacht ? 🙂

                                          const request = require('request');
                                          const link = 'http://192.168.1.180/deviceajax.do?meter=1010005';
                                          const linkfsupunten = 'http://192.168.1.180/deviceajax.do?meter=1010009';
                                          const linkfsupoben = 'http://192.168.1.180/deviceajax.do?meter=1010008';
                                          
                                          // Datenpunkte erzeugen
                                          createState('Homepilot.Sonnenrichtung', 0, {type: 'number', unit: '°'});
                                          createState('Homepilot.Sonnenhöhe', 0, {type: 'number', unit: '°'});
                                          createState('Homepilot.Lichtwert', 0, {type: 'number', unit: 'lux'});
                                          createState('Homepilot.Windgeschwindigkeit', 0, {type: 'number', unit: 'm/s'});
                                          createState('Homepilot.Temperatur', 0, {type: 'number', unit: '°C'});
                                          createState('Homepilot.Regen', 0, {type: 'boolean', unit: ''});
                                          createState('Homepilot.Aktualisiert', 0, {type: 'string', unit: ''});
                                          createState('Homepilot.balkontüreoben', 0, {type: 'boolean', unit: ''});
                                          createState('Homepilot.balkontüreobenaktualisiert', 0, {type: 'string', unit: ''});
                                          createState('Homepilot.balkontüreunten', 0, {type: 'boolean', unit: ''});
                                          createState('Homepilot.balkontüreuntenaktualisiert', 0, {type: 'string', unit: ''});
                                          
                                          function sensordaten() {
                                              request(link, function(error,response, body) {
                                                  if(error) log('Fehler request: ' + error, 'error');
                                                  else {
                                                      var data = JSON.parse(body).data;
                                                      var dir = data[5].Sonnenrichtung;
                                                      dir = parseFloat(dir.substr(dir.indexOf('(') + 1));
                                                      setState('Homepilot.Sonnenrichtung', dir, true); 
                                                      var ele = parseFloat(data[4].Sonnenhöhe);
                                                      setState('Homepilot.Sonnenhöhe', ele, true); 
                                                      var lux = parseFloat(data[0].Lichtwert);
                                                      setState('Homepilot.Lichtwert', lux, true);
                                                      var wind = parseFloat(data[1]['Windgeschw.']);
                                                      setState('Homepilot.Windgeschwindigkeit', wind, true);
                                                      var temp = parseFloat(data[2].Temperatur);
                                                      setState('Homepilot.Temperatur', temp, true);
                                                     // var regen = parseFloat(data[3].Regen);
                                                      var regen = data[3].Regen; // Das ist ein String !
                                                          if(regen == 'Nicht erkannt') setState('Homepilot.Regen', false, true);
                                                          else setState('Homepilot.Regen', true, true);
                                                     // setState('Homepilot.Regen', regen, true);
                                                      //var aktualisiert = parseFloat(data[6].Aktualisiert);
                                                      var aktualisiert = data[6].Aktualisiert; // Das ist ein String !
                                                      setState('Homepilot.Aktualisiert', aktualisiert, true);
                                                  }    
                                              });
                                          }
                                          
                                          function balkontuere() {
                                              request(linkfsupunten, function(error,response, body) {
                                                  if(error) log('Fehler request: ' + error, 'error');
                                                  else {
                                                      var schliesserunten = data[0].Schliesser; // Das ist ein String !
                                                          [u]if(schliesserunten == 'Geöffnet') setState('Homepilot.balkontüreunten', false, true);[/u]
                                                          else setState('Homepilot.balkontüreunten', true, true);
                                                      var aktualisiertunten = data[1].Aktualisiert; // Das ist ein String !
                                                      setState('Homepilot.balkontüreuntenaktualisiert', aktualisiert, true);
                                                  }    
                                              });
                                              request(linkfsupoben, function(error,response, body) {
                                                  if(error) log('Fehler request: ' + error, 'error');
                                                  else {
                                                      var schliesseroben = data[0].Schliesser; // Das ist ein String !
                                                          [u]if(schliesseroben == 'Geöffnet') setState('Homepilot.balkontüreoben', false, true);[/u]
                                                          else setState('Homepilot.balkontüreoben', true, true);
                                                      var aktualisiertoben = data[1].Aktualisiert; // Das ist ein String !
                                                      setState('Homepilot.balkontüreobenaktualisiert', aktualisiert, true);
                                                  }    
                                              });
                                          }
                                          schedule('*/30 * * * * *', sensordaten); // alle 30 Sekunden
                                          schedule('*/30 * * * * *', balkontuere); // alle 30 Sekunden
                                          

                                          Diese fehlermeldung habe ich aus dem Log sobald ich das Script laufen lasse:

                                          host.ioBroker-RasPi	2018-08-16 13:37:30.320	error	instance system.adapter.javascript.0 terminated with code 0 (OK)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.320	error	Caught by controller[1]: at emitNone (events.js:111:20)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.319	error	Caught by controller[1]: at Object.onceWrapper (events.js:313:30)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.319	error	Caught by controller[1]: at IncomingMessage. <anonymous>(/opt/iobroker/node_modules/request/request.js:1085:12)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.319	error	Caught by controller[1]: at Request.emit (events.js:211:7)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.319	error	Caught by controller[1]: at emitOne (events.js:116:13)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.318	error	Caught by controller[1]: at Request. <anonymous>(/opt/iobroker/node_modules/request/request.js:1163:10)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.318	error	Caught by controller[1]: at Request.emit (events.js:214:7)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.318	error	Caught by controller[1]: at emitTwo (events.js:126:13)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.318	error	Caught by controller[1]: at Request.self.callback (/opt/iobroker/node_modules/request/request.js:186:22)
                                          [u]host.ioBroker-RasPi	2018-08-16 13:37:30.317	error	Caught by controller[1]: at Request._callback (script.js.Homepilot.Sensordaten:52:35)[/u]
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.317	error	Caught by controller[1]: ReferenceError: data is not defined
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.316	error	Caught by controller[0]: at emitNone (events.js:111:20)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.316	error	Caught by controller[0]: at Object.onceWrapper (events.js:313:30)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.308	error	Caught by controller[0]: at IncomingMessage. <anonymous>(/opt/iobroker/node_modules/request/request.js:1085:12)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.308	error	Caught by controller[0]: at Request.emit (events.js:211:7)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.307	error	Caught by controller[0]: at emitOne (events.js:116:13)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.307	error	Caught by controller[0]: at Request. <anonymous>(/opt/iobroker/node_modules/request/request.js:1163:10)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.306	error	Caught by controller[0]: at Request.emit (events.js:214:7)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.306	error	Caught by controller[0]: at emitTwo (events.js:126:13)
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.305	error	Caught by controller[0]: at Request.self.callback (/opt/iobroker/node_modules/request/request.js:186:22)
                                          [u]host.ioBroker-RasPi	2018-08-16 13:37:30.305	error	Caught by controller[0]: at Request._callback (script.js.Homepilot.Sensordaten:62:34)[/u]
                                          host.ioBroker-RasPi	2018-08-16 13:37:30.304	error	Caught by controller[0]: ReferenceError: data is not defined</anonymous></anonymous></anonymous></anonymous>
                                          

                                          Die beiden unterstrichenen Zeilen sind vom Script - habe ich im Scriptcode ebenfalls unterstrichen.

                                          Kann mir jemand einen Tip geben ?

                                          Viele grüße,

                                          Marc

                                          1 Reply Last reply Reply Quote 0
                                          • paul53
                                            paul53 last edited by

                                            @meicker:

                                            Die beiden unterstrichenen Zeilen sind vom Script - habe ich im Scriptcode ebenfalls unterstrichen. `
                                            Unterstreichen funktioniert in Skripten und Code tags nicht. Lösche die wieder raus.
                                            @meicker:

                                            var aktualisiertunten = data[1].Aktualisiert; // Das ist ein String ! 
                                            setState('Homepilot.balkontüreuntenaktualisiert', aktualisiert, true);
                                            ```` `  
                                            

                                            aktualisiert ist nicht deklariert. Da die Variablen lokal sind, kann der gleiche Name mehrfach verwendet werden.

                                            var aktualisiert = data[1].Aktualisiert; // Das ist ein String ! 
                                            setState('Homepilot.balkontüreuntenaktualisiert', aktualisiert, true);
                                            

                                            Das gleiche für oben

                                                        var aktualisiert = data[1].Aktualisiert; // Das ist ein String !
                                                        setState('Homepilot.balkontüreobenaktualisiert', aktualisiert, true);
                                            
                                            

                                            2 gleichzeitig auslösende Schedule würde ich vermeiden

                                            function alledaten() {
                                               sensordaten();
                                               balkontuere();
                                            }
                                            schedule('*/30 * * * * *', alledaten); // alle 30 Sekunden
                                            
                                            1 Reply Last reply Reply Quote 0
                                            • First post
                                              Last post

                                            Support us

                                            ioBroker
                                            Community Adapters
                                            Donate

                                            730
                                            Online

                                            31.8k
                                            Users

                                            79.9k
                                            Topics

                                            1.3m
                                            Posts

                                            4
                                            51
                                            2778
                                            Loading More Posts
                                            • Oldest to Newest
                                            • Newest to Oldest
                                            • Most Votes
                                            Reply
                                            • Reply as topic
                                            Log in to reply
                                            Community
                                            Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen
                                            The ioBroker Community 2014-2023
                                            logo