Automatische Provisionierung von Shelly-Geräten

Einleitung

Shelly-Geräte gehören zu den beliebtesten Smart-Home-Komponenten, doch das manuelle Einbinden in das Heimnetzwerk kann zeitraubend sein, besonders wenn man mehrere Geräte nacheinander installiert. Mit einem speziellen Skript lässt sich dieser Prozess automatisieren, sodass neue Shelly-Geräte automatisch konfiguriert und mit dem WLAN verbunden werden. In diesem Beitrag zeige ich, wie das Provisioning-Skript funktioniert, wie man es installiert und optimal einrichtet, um Zeit und Aufwand zu sparen.

Basis des Skripts Dieses Skript basiert auf einem Open-Source-Projekt von GitHub, das ich als Grundlage verwendet habe. Die Skriptvorlage wurde an die Bedürfnisse meiner Installation und die Anforderungen meines Hauses angepasst, um eine nahtlose Integration aller Shelly-Geräte zu ermöglichen.

Was das Provisioning-Skript macht

Das Skript scannt regelmäßig nach neuen Shelly-Geräten, die als Access Points (APs) mit voreingestellter SSID erscheinen. Sobald es einen solchen AP findet, verbindet sich das Skript mit dem Gerät und überträgt automatisch die WLAN-Zugangsdaten, die in der Skript-Konfiguration hinterlegt sind. Das Shelly-Gerät wird daraufhin in das Heimnetzwerk integriert, und der Access Point kann deaktiviert werden, um die Netzwerkstabilität zu verbessern.

Für wen ist das Skript sinnvoll?

Dieses Skript ist ideal für Nutzer, die mehrere Shelly-Geräte nach und nach installieren möchten, etwa bei einer schrittweisen Installation in verschiedenen Räumen. Das Skript spart viel Zeit, da es den manuellen Prozess des Einbindens ins Netzwerk und der Konfiguration der WLAN-Daten automatisiert.

Schritt-für-Schritt-Anleitung zur Installation des Skripts

Zugriff auf das Shelly-Gerät:

  • Öffne die Weboberfläche Deines Shelly-Geräts, indem Du die IP-Adresse des Geräts im Browser eingibst.
  • Navigiere zu den Scripting-Optionen (normalerweise unter „Settings“ oder „Developer Options“ verfügbar).

Neues Skript hinzufügen:

WLAN-Zugangsdaten konfigurieren

  • In der CONFIG.provisionAP-Sektion des Skripts musst Du Deine WLAN-SSID und das Passwort hinterlegen:
  • provisionAP: { ssid: „YourWiFiSSID“, pass: „YourPassword“, enable: false, }
  • Ersetze "YourWiFiSSID" und "YourPassword" durch die tatsächlichen WLAN-Zugangsdaten.
  • Setzte „enable“ auf True, wenn der AP Modus weiterhin aktiv sein soll, somit kannst du dich später mit dem Shelly via WLAN verbinden, ich würde es deaktivieren.

Scan-Intervall einstellen:

  • Das Skript enthält die Zeile, runPeriod: 5 * 1000,
  • Dies legt das Scan-Intervall auf alle 5 Sekunden fest. Für eine ressourcenschonendere Nutzung des Shelly-Geräts empfiehlt es sich jedoch, das Intervall auf 5 Minuten zu verlängern:
  • runPeriod: 5 * 60 * 1000, // Scan-Intervall auf 5 Minuten verlängert
  • Dadurch scannt das Gerät nur alle 5 Minuten nach neuen Shelly-APs und reduziert so die Ressourcenbelastung.

Automatisches oder manuelles Auslösen des Skripts:

  • Wenn Du das Skript nicht kontinuierlich laufen lassen möchtest, kannst Du es deaktiviert lassen und nur dann manuell ausführen, wenn ein neues Shelly-Gerät verbunden werden soll.

Weitere Konfigurationsoptionen im Skript

Das Skript bietet mehrere Einstellungsmöglichkeiten, um das Provisioning an Deine Bedürfnisse anzupassen:

  • Shelly Access Point Adresse (shellyAPAddress):
    • Die IP-Adresse des Shelly-Access Points ist standardmäßig 192.168.33.1. Diese Einstellung muss nur geändert werden, falls die Shelly-Standardadresse nicht verwendet wird.
  • Access Point deaktivieren (disableShellyAP):
    • In der Einstellung disableShellyAP kannst Du festlegen, ob der Access Point des Shelly-Geräts nach der Konfiguration deaktiviert werden soll.
    • Wenn disableShellyAP auf true gesetzt ist, wird der AP deaktiviert, nachdem das Gerät ins WLAN integriert wurde, was die Netzwerksicherheit verbessert.

Fazit

Mit diesem Skript kannst Du Shelly-Geräte schneller und einfacher in Dein Netzwerk integrieren. Das Skript spart Zeit, indem es neue Shelly-Geräte automatisch mit den WLAN-Zugangsdaten versorgt und diese ins Netzwerk integriert. Besonders sinnvoll ist es, wenn Du mehrere Shelly-Geräte nach und nach installierst. Stelle sicher, dass das Intervall auf etwa 5 Minuten verlängert wird, oder aktiviere das Skript nur manuell, um die Ressourcen des Shelly-Geräts zu schonen. Viel Erfolg beim Einrichten Deines Smart Homes!

				
					// Copyright 2021 Allterco Robotics EOOD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Shelly is a Trademark of Allterco Robotics

// Shelly Script example: Provisioning of new Shelly Plus gen 2 devices
//
// This scripts periodically scans for access points with SSID matching the
// template for Shelly Plus device APs and if found, will connect to that AP
// and provision wifi credentials

let CONFIG = {
  scanSSID: [
    {
      gen: 1,
      apn: ["shelly1", "shelly1pm"],
    },
    {
      gen: 2,
      apn: ["ShellyPlus", "Shelly"],
    },
  ],
  provisionAP: {
    ssid: "YourWiFiSSID", //Mein Name des WLANs
    pass: "YourPassword", //Mein Passwort
    enable: false,
  },
  shellyAPAddress: "192.168.33.1",
  //disable the AP once set up
  disableShellyAP: true, // Ja, es soll nach der Einbindung der AP Modus deaktiviert werden
  runPeriod: 5 * 60 * 1000, // Scan-Intervall auf 5 Minuten verlängert
};

function shallowCopy(dst, src) {
  for (let k in src) {
    if (typeof dst[k] === "undefined" && typeof dst[k] !== "object") {
      dst[k] = src[k];
    }
  }
}

let Scanner = {
  scanning: false,
  provisionining: false,
  scanCache: [],
  targetCache: [],
  eventHandlerId: null,
  scanTimer: null,

  addToTargetCache: function (ssid) {
    this.targetCache.push(ssid);
  },

  popScanCacheItem: function () {
    this.scanCache.splice(0, 1);
  },

  startScanning: function () {
    print("Starting scanning...");
    if (this.scanTimer !== null) return;
    if (typeof CONFIG.runPeriod !== undefined) {
      this.scanTimer = Timer.set(
        CONFIG.runPeriod,
        true,
        function (self) {
          self.scan();
        },
        this
      );
    }
  },

  stopScanning: function () {
    if (this.scanTimer !== null) {
      Timer.clear(this.scanTimer);
      this.scanTimer = null;
    }
  },

  initEventListener: function () {
    this.eventHandlerId = Shelly.addEventHandler(function (event, self) {
      if (typeof event.info.ssid === "undefined") return;
      if (typeof event.info.status === "undefined") return;
      if (event.info.status.indexOf("got ip") === -1) return;

      let ssid = self.scanCache[0];
      if (event.info.ssid.indexOf(ssid) !== -1) {
        self.provision();
      }
    }, this);
  },

  clearEventListener: function () {
    Shelly.removeEventHandler(this.eventHandlerId);
  },

  clearTargetCache: function () {
    this.targetCache.splice(0, this.targetCache.length);
  },

  /**
   * Looks for access point name match with prefixes from CONFIG.scanSSID
   * @param {string} apName
   * @returns {number|false} generation number or false if not found
   */
  matchAP: function (ssid) {
    for (let gi in CONFIG.scanSSID) {
      for (let api in CONFIG.scanSSID[gi].apn) {
        if (ssid.indexOf(CONFIG.scanSSID[gi].apn[api]) === -1) continue;
        return CONFIG.scanSSID[gi].gen;
      }
    }
    return false;
  },

  isInCache: function (ssid) {
    for (let papk in this.targetCache) {
      let tSSID = this.targetCache[papk];
      if (tSSID.indexOf(ssid) !== -1) {
        return true;
      }
    }
    return false;
  },

  scan: function () {
    //prevent reentry
    if (this.scanning || this.provisionining) {
      print("Already scanning. Abort.");
      return;
    }
    print("Scanning for wifi networks");
    this.scanning = true;
    this.stopScanning();
    this.scanCache.splice(0, this.scanCache.length);
    Shelly.call(
      "WiFi.Scan",
      {},
      function (result, error_code, error_message, self) {
        if (error_code !== 0) {
          print("WiFi scan failed", error_message);
          self.startScanning();
          return;
        }
        let wifiAPs = result.results;
        //walk through all the found ssids and look for starting
        //with shelly with no auth enabled
        for (let apk in wifiAPs) {
          let cSSID = wifiAPs[apk].ssid;
          if (wifiAPs[apk].auth !== 0) continue;
          if (self.matchAP(cSSID) === false) continue;
          if (self.isInCache(cSSID) === false) {
            print("This AP is a target: ", cSSID);
            self.scanCache.push(cSSID);
          }
        }
        self.scanning = false;
        if (self.scanCache.length) {
          print("Found ", self.scanCache.length, " APs. Start provisioning");
          self.startProvision();
        } else {
          print("APs not found or already provisioned");
          self.startScanning();
        }
      },
      this
    );
  },

  startProvision: function () {
    this.initEventListener();
    Shelly.call(
      "wifi.setconfig",
      { config: { sta: { enable: false } } },
      function (res, error_code, error_msg) {
        print("Disabled station on device");
      }
    );
    this.doNextProvision();
  },

  doNextProvision: function () {
    if (this.scanCache.length === 0) {
      this.endProvision();
      return;
    }
    let ssid = this.scanCache[0];
    Shelly.call(
      "wifi.setconfig",
      { config: { sta1: { ssid: ssid, pass: "", enable: true } } },
      function (res, error_code, error_msg, ssid) {
        print("Enable sta1 configured to connect to: ", ssid);
      },
      ssid
    );
  },

  endProvision: function () {
    print("End provisioning");
    this.clearEventListener();
    this.startScanning();
    Shelly.call(
      "wifi.setconfig",
      { config: { sta1: { enable: false } } },
      function (res, error_code, error_msg) {
        print("Disable sta1");
      }
    );
    Shelly.call(
      "wifi.setconfig",
      { config: { sta: { enable: true } } },
      function (res, error_code, error_msg) {
        print("Enable sta ");
      }
    );
  },

  composeStaEndpoint: function (gen) {
    let url = "http://" + CONFIG.shellyAPAddress;
    if (gen === 2) {
      url += "/rpc/wifi.setconfig";
    } else {
      url += "/settings/sta?";
    }
    return url;
  },

  provision: function () {
    print("Will provision ", this.scanCache[0]);
    this.provisionining = true;
    let gen = this.matchAP(this.scanCache[0]);
    let url = this.composeStaEndpoint(gen);
    if (gen === 2) {
      let wifiData = {};
      shallowCopy(wifiData, CONFIG.provisionAP);
      let postData = {
        url: url,
        body: {
          config: {
            sta1: wifiData,
            ap: {
              enable: !CONFIG.disableShellyAP,
            },
          },
        },
      };
      print(JSON.stringify(postData));
      Shelly.call(
        "HTTP.POST",
        postData,
        function (response, error_code, error_message, self) {
          let rpcCode = response.code;
          // if (rpcCode === 200) {
          print("Success provisioning: ", self.scanCache[0]);
          self.addToTargetCache(self.scanCache[0]);
          self.popScanCacheItem();
          // };
          self.provisionining = false;
          self.doNextProvision();
        },
        this
      );
    } else if (gen === 1) {
      url =
        url +
        "ssid=" +
        CONFIG.provisionAP.ssid +
        "&key=" +
        CONFIG.provisionAP.pass +
        "&enabled=1";
      Shelly.call(
        "HTTP.GET",
        { url: url },
        function (response, error_code, error_message, self) {
          let rpcCode = response.code;
          // if (rpcCode === 200) {
          print("Success provisioning: ", self.scanCache[0]);
          self.addToTargetCache(self.scanCache[0]);
          self.popScanCacheItem();
          // };
          self.provisionining = false;
          self.doNextProvision();
        },
        this
      );
    }
  },
};

Scanner.startScanning();
				
			

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert