Navigation

    Logo
    • Register
    • Login
    • Search
    • Recent
    • Tags
    • Unread
    • Categories
    • Unreplied
    • Popular
    • GitHub
    • Docu
    • Hilfe
    1. Home
    2. Deutsch
    3. Praktische Anwendungen (Showcase)
    4. Bluetooth LE Türsensor mit Puck.js

    NEWS

    • ioBroker goes Matter ... Matter Adapter in Stable

    • 15. 05. Wartungsarbeiten am ioBroker Forum

    • Monatsrückblick - April 2025

    Bluetooth LE Türsensor mit Puck.js

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

      Mein aktuelles Miniprojekt ist jetzt vollendet, daher wollte ich es mal vorstellen.

      Um die Beleuchtung in meiner Abstellkammer (die ich gerne mal vergesse auszuschalten) zu automatisieren, habe ich mir mit https://www.espruino.com/PuckJS und https://www.amazon.de/gp/product/B00F0BTTBU einen (fast) kabellosen Türsensor gebaut. Wie das ganze aussieht, sieht man hier:
      1097_img_0944.jpg

      Die Reed-Kontakte haben den Vorteil, dass sie sowohl NC (normal geschlossen) als auch NO (normal geöffnet) betrieben werden können. Bei Verwendung von NO ist der interne Kontakt geöffnet bei angelegtem Magneten, d.h. es fließt im Normalfall kein Strom.

      Das Kabel für NO (braun) ist am Puck an Pin D2 angeschlossen, das gemeinsame Kabel (weiß) an Pin D1.

      Der Code auf dem Puck wird nur ausgeführt, wenn ein Schaltvorgang erkannt wird, sodass er möglichst wenig Strom verbraucht (ca. 25µA im geschlossenen Zustand laut Hersteller).

      Über den Stromverbrauch im offenen Zustand habe ich noch keine Info. Evtl. sollte man hier die DAC-Funktion des Pins verwenden, um den Kontakt mit geringerer Spannung zu betreiben, oder den NC-Kontakt mitverwenden und im Code intelligent zwischen den Inputs wechseln.

      ! ````
      NRF.setScanResponse([
      //0x02,
      //0x01,0x05, // flags
      5, // length (including type byte)
      0x09, // name
      'T', 'u', 'e', 'r'
      ]);
      ! function send(value) {
      NRF.setAdvertising({
      0xEFA1: [value]
      }, {showName: false});
      }
      ! // set D2 to output, D1 to input with pulldown
      pinMode(D2, "output");
      digitalWrite(D2, 1);
      pinMode(D1, "input_pulldown");
      ! // watch the input for changes
      let status = null;
      setWatch(function(e) {
      if (e.state && status != "open") {
      // magnet open
      status = "open";
      console.log(status);
      digitalPulse(LED2, 1, 500);
      send(1);
      } else if (!e.state && status != "closed") {
      // magnet closed
      status = "closed";
      console.log(status);
      digitalPulse(LED1, 1, 500);
      send(0);
      }
      }, D1, {
      repeat: true,
      debounce: 150
      });

      
      Details zum Code im Spoiler:
      
      >! Der erste Block gibt dem Puck einen kurzen Namen und kann angepasst werden, sollte aber kurz bleiben, damit das Advertising noch funktioniert (die Datenmenge ist stark begrenzt).
      >! In der Funktion send wird der aktuelle Zustand des Kontakts (0 oder 1) auf dem Service 0xEFA1 bekanntgegeben. Die ID kann angepasst werden (sollte laut Spec eigentlich eine UUID sein, das ist mir zuhause aber egal), und muss im Script auf dem ioBroker ebenfalls eingetragen werden.
      >! Pin D2 wird als output festgelegt und auf den Status 1 gesetzt (d.h. hier liegen 3,3V an).
      >! Pin D1 ist ein input mit Pulldown-Widerstand, sodass bei geöffnetem Kontakt (Tür zu) der Sensorwert zu 0 definiert ist und nicht springt.
      >! Im Folgenden wird der Input-Pin überwacht und bei Änderungen kurz geblinkt (grün bei offener Tür, rot bei geschlossener) sowie die Bluetooth-Daten aktualisiert.
      
      Auf dem ioBroker läuft ein Skript, das per Bluetooth auf Advertising-Daten lauscht und diese als States anlegt:
      
      >! ````
      // Hier die BLE-Services eintragen, für die States angelegt werden sollen. 
      // Als HEX-strings, in Kleinbuchstaben und ohne führendes 0x
      const services = [
          "efa1"              // puck.js magnetometer proximity detection
      ];
      >! // =========================
      >! function updateState(stateId, value, ack) {
          const val = getState(stateId).val;
          if (val == null) {
              createState(stateId, value);
          } else if (val != value) {
              setState(stateId, value, ack);
          }
      }
      >! const noble = require("noble");
      >! const onDiscover = (p) => {
          if (!(p && p.advertisement && p.advertisement.serviceData)) return;
          const stateId_name = `BLE.${p.address}.name`;
          createState(`BLE.${p.address}.name`, p.advertisement.localName);
          for (let entry of p.advertisement.serviceData) {
              const uuid = entry.uuid;
              let data = entry.data;
              if (data.type === "Buffer") {
                  data = Buffer.from(data.data);
              }
              if (data.length === 1) {
                  // single byte
                  data = data[0];
              } else { // not supported yet
                  continue;
              }
      
              updateState(`BLE.${p.address}.${uuid}`, data, true);
          }
      };
      >! let isScanning = false;
      function startScanning() {
          if (isScanning) return;
          noble.on("discover", onDiscover);
          noble.startScanning(services, true);
          isScanning = true;
      }
      function stopScanning() {
          if (!isScanning) return;
          noble.removeAllListeners("discover");
          noble.stopScanning();
          isScanning = false;
      }
      >! noble.on("stateChange", (state) => {
          switch (state) {
              case "poweredOn":
                  startScanning();
                  break;
              case "poweredOff":
                  stopScanning();
                  break;
          }
      });
      if (noble.state === "poweredOn") startScanning();
      >! onStop((callback) => {
          stopScanning();
          noble.removeAllListeners("stateChange");
          if (callback) callback();
      }, 2000);
      

      Sollte auf dem Puck der Code des BLE-Service geändert worden sein, muss das hier bei services eingetragen werden.

      1 Reply Last reply Reply Quote 0
      • AlCalzone
        AlCalzone Developer last edited by

        Kleines Update:

        Ich habe mich nochmal mit der Firmware und der Verkabelung beschäftigt.

        Neue Verkabelung:

        NC (grau) => D29 (output)

        NO (braun) => D2 (output)

        COM (weiß) => D1 (input)

        Um dauerhaften Stromfluss zu verhindern, liegt Spannung jetzt entweder an NC (D29) oder NO (D2) an. Der Pin wird so ausgewählt, dass kein Strom fließt.

        Wenn sich der Zustand des Magnetkontakts ändert, fließt kurzzeitig Strom. Der Puck reagiert das und vertauscht die Polung.

        Neue Firmware für den Puck:

        ! ````
        NRF.setScanResponse([
        //0x02,
        //0x01,0x05, // flags
        5, // length (including type byte)
        0x09, // name
        'T', 'u', 'e', 'r'
        ]);
        ! const advertisingData = {
        0xEFA1: [0], // magnet sensor
        0x180F: [Puck.getBatteryPercentage()], // battery
        };
        ! function send(value) {
        advertisingData[0xEFA1] = [value];
        NRF.setAdvertising(advertisingData, {showName: false});
        }
        ! // ^^^ BLUETOOTH ^^^
        // =================================
        // vvv SENSOR CODE vvv
        ! const pinNC = D29;
        const pinNO = D2;
        const pinCOM = D1;
        ! const HIGH = 1;
        const LOW = 0;
        ! let circuit = "NO";
        let rawState = null;
        let bluetoothState = null; // the state reported over bluetooth
        ! function writeNO() {
        digitalWrite(pinNO, HIGH);
        digitalWrite(pinNC, LOW);
        circuit = "NO";
        console.log("using NO circuit");
        }
        function writeNC() {
        digitalWrite(pinNC, HIGH);
        digitalWrite(pinNO, LOW);
        circuit = "NC";
        console.log("using NC circuit");
        }
        ! function parseState(valCOM) {
        valCOM = valCOM || digitalRead(pinCOM) === 1;
        if (circuit === "NO") {
        // when the NO circuit is used, valCOM is true when the door is open
        rawState = valCOM ? "open" : "closed";
        } else {
        // when the NC circuit is used, valCOM is true when the door is closed
        rawState = valCOM ? "closed" : "open";
        }
        ! return rawState;
        }
        ! function isConducting() {
        console.log(checking for conductance: circuit=${circuit}, rawState=${rawState});
        if (circuit === "NO" /* NO / && rawState === "open" / open /) return true;
        if (circuit === "NC" /
        NC / && rawState === "closed" / closed /) return true;
        return false;
        }
        ! function switchCircuit() {
        console.log("switching circuit");
        if (circuit === "NO") { // NO
        writeNC();
        } else { // NC
        writeNO();
        }
        }
        ! // set NC and NO pin to output, COM pin to input with pulldown
        pinMode(pinNC, "output");
        pinMode(pinNO, "output");
        pinMode(pinCOM, "input_pulldown");
        ! function onChange(e) {
        // parse the state depending on the current circuit;
        parseState(e.state);
        console.log("pin state changed: " + rawState);
        // switch circuit if neccessary
        if (isConducting()) switchCircuit();
        ! // do the switch stuff
        if (rawState == "open" && bluetoothState != "open") {
        // magnet open
        bluetoothState = "open";
        console.log(bluetoothState);
        digitalPulse(LED2, 1, 500);
        send(1);
        } else if (rawState == "closed" && bluetoothState != "closed") {
        // magnet closed
        bluetoothState = "closed";
        console.log(bluetoothState);
        digitalPulse(LED1, 1, 500);
        send(0);
        }
        }
        ! // watch the input for changes
        setWatch(onChange, pinCOM, {
        repeat: true,
        debounce: 150
        });
        // also update the battery level every hour
        setInterval(() => {
        advertisingData[0x180F] = [Puck.getBatteryPercentage()];
        send();
        }, 3600
        1000);
        ! // initially try to close the NO circuit
        writeNO();
        onChange({
        state: digitalRead(pinCOM) === 1
        });

        1 Reply Last reply Reply Quote 0
        • First post
          Last post

        Support us

        ioBroker
        Community Adapters
        Donate

        689
        Online

        31.6k
        Users

        79.5k
        Topics

        1.3m
        Posts

        1
        2
        971
        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