Solarnachführung mit Shelly UNI und Shelly 1(2)PM

VPN/Proxy erkannt

Es scheint, dass Sie einen VPN- oder Proxy-Dienst verwenden. Bitte beachten Sie, dass die Nutzung eines solchen Dienstes die Funktionalität dieser Webseite einschränken kann.

  • Guten zusammen und allen noch ein gesundes neues Jahr.

    Ich bin nun schon einige Zeit im Shelly Universum unterwegs und habe bereits mein Haus teil "Shelly'siert".
    Ihr kennt das ja alle. Wenn man einmal mit dem Thema angefangen hat finden sich immer mehr Möglichkeiten/Anwendungsbereiche in denen man zumindest theoretisch Shelly's einsetzen könnte.
    Aufgrund der Vielseitigkeit und der humanen Preise findet man sich plötzlich am "Labortisch" wieder.
    Aktuell wieder so geschehen. :-)

    Nun zum eigentlichen Thema.
    Umsetzung einer Solarnachführung mit diversen Shelly Modulen.
    Wie ein solches Gestell aussieht könnt ihr euch mit Sicherheit ja vorstellen, vermutlich kennt die Großteil diese Gestelle sogar aus dem Netz.
    Kurze Gedankenspiel um euch zum Aufbau und Ablauf abzuholen.

    Das Gestell ist eine Eigenkonstruktion.
    Fundament 100x100x60cm.
    Höhe des Stehers etwa 3,5m.
    Modulhalterung aus Maytec Profil.
    4 Module a 500W welche sich automatisch zur Sonne ausrichten sollen.
    Antrieb für Elevation (NORD-SÜD-Ausrichtung) Linearantrieb mit etwa 300mm Hub.
    Antrieb für Azimut (OST-WEST-Ausrichtung) Linearantrieb oder Schrittmotor. Die Entscheidung ist noch nicht zu 100% gefallen. Präferiert wird jedoch ein Schrittmotor.

    Nun zum Thema Steuerung.
    Die Steuerung soll über Home Assistant in Verbindung mit Shelly Plus UNI laufen.

    Aufbau:

    Fotowiderstände angebracht an Modulhalterung mit kleinen Edelstahlblech um einen Schattenwurf zu generieren.
    Jeweils einer davon an den enden Ost-Süd-West-Nord angebracht.

    ShellyUNI_A misst über den Analog-Eingang den abgegebenen Spannungswert für Sensor OST und stellt diesen in Prozent (%) zur Weiterverarbeitung zu Verfügung.
    ShellyUNI_B misst über den Analog-Eingang den abgegebenen Spannungswert für Sensor WEST und stellt diesen in Prozent (%) zur Weiterverarbeitung zu Verfügung.
    ShellyUNI_C misst über den Analog-Eingang den abgegebenen Spannungswert für Sensor NORD und stellt diesen in Prozent (%) zur Weiterverarbeitung zu Verfügung.
    ShellyUNI_D misst über den Analog-Eingang den abgegebenen Spannungswert für Sensor SÜD und stellt diesen in Prozent (%) zur Weiterverarbeitung zu Verfügung.

    Zusätzlich:
    ShellyUNI_A misst über den Digital-IN 1 einen Endschalter-Kontakt angebracht an Position max. OST der Azimut-Achse.
    ShellyUNI_A misst über den Digital-IN 2 einen Endschalter-Kontakt angebracht an Position SÜD der Azimut-Achse.
    Dadurch wird die Laufzeit überprüft welche vergeht wenn der Schrittmotor das Gestell von OST nach SÜD dreht. Dieser Wert (x2) ist gleich die Gesamtlaufzeit von OST nach WEST.
    Dient zur Verschleißkontrolle wenn die Konstruktion etwas schwergängig werden sollte (Verschleiß oder Schmutz in Laufschienen/Gelenken).

    ShellyUNI_A übernimmt die Steuerung von Shelly1PM_OST. Zusätzlich überwacht ShellyUNI_A die Stromaufnahme von Shelly1PM_OST.
    ShellyUNI_B übernimmt die Steuerung von Shelly1PM_WEST. Zusätzlich überwacht ShellyUNI_B die Stromaufnahme von Shelly1PM_WEST.
    Pause-Zeit zwischen den Schaltvorgängen ~3 Sekunden. Dient zum Schutz des Stellantriebs und der Shellys vor Kurzschluss.
    Die Überwachung der Stromaufnahme dient zur Überwachung einer Blockade während der Fahrt von OST->WEST oder WEST->OST.

    Elevation (NORD-SÜD) thematisiere ich vorerst mal nicht in diesem Thread.
    Ein Dreieck-Stern-Anemometer zur Überwachung der Windgeschwindigkeit wird aktuell noch nicht Berücksichtigt. Dieses Wird Später an einen Count-IN angeschlossen.
    Gedanke hierbei wäre, X m/s > ~15m/s = Elevation Fahrt in Position Waagerecht (0%)

    Ich habe mir Gedanken zu dem Skript gemacht.
    Was muss alles beachtet werden, Sicherheitseinrichtung, Verschleißüberwachung inkl. Warnung, Referenz-Stellungen über Endschalterabfrage und diverse andere Kleinigkeiten.

    In Zusammenarbeit mit der KI habe ich ein Skript erstellt welches für mich als Skript-Laie grundsätzlich nicht schlecht aussieht.
    Aber wir wissen ja alle, Kontrolle ist besser als Vertrauen. :-)

    Vielleicht gibt es hier ja einen Findigen Skript-Künstler der sich die Sache mal ansehen möchte und noch auf eventuelle Probleme Hinweisen kann oder im schlimmsten Fall die Funktion dieses Skript's in Frage stellt und seine Bedenken fachlich belegen kann.

    Ich wäre sehr dankbar für sämtliche Arten von Hilfestellung/Unterstützung.

    Skript folgt ->

    Mit freundlichen Grüßen

    crYteK2601

    Raspberry Pi4B -> Home Assistant
    Shelly Pro 3EM
    Shelly Pro 3 -> Steuerung Heizstab (4,5kW)
    Shelly PM Mini -> Balkonkraftwerk Erzeugung
    Shelly 1 Mini -> Flurlicht Taster
    Shelly 1PM -> Raumüberwachung
    Shelly Plus Addon -> Temperatur Warmwasser
    Shelly Plug S -> Server, Trockner, Waschmaschine, Gefrierschrank
    Shelly Plus UNI -> Versuchsaufbau

  • Hier das aktuelle Skript.
    script_east-west_rev1_030125.txt
    ____________________________________________


    // KONFIGURATION
    let CONFIG = {
    ip_Uni_West: "192.168.178.xx",
    ip_1PM_Ost: "192.168.178.xx",
    ip_1PM_West: "192.168.178.xx",
       
    max_power_block: 45, // Harter Stopp (Blockade)
    nominal_power: 25, // Erwartete Leistung (wird vom Skript angepasst)
    wear_threshold: 1.3, // Faktor 1.3 = 30% über Normalwert gilt als Verschleiß
    full_drive_time: 30000,
    updateInterval: 1500,
    homingTimeMorning: "05:00",
    homingTimeEvening: "18:00",
    darknessThreshold: 10
    };

    let STATE = {
    currentPos: 50,
    isMoving: false,
    isHoming: false,
    lastDirection: null,
    startTime: 0,
    nextMoveAllowed: 0,
    systemHalt: false,
    eveningCheckDone: false,
    ostReachedTime: 0,
    powerSamples: [], // Speichert Leistungswerte für Durchschnitt
    };

    // --- STEUERUNG ---

    function emergencyStop(reason) {
    if (!STATE.systemHalt) {
    print("!!! STOPP: " + reason + " !!!");
    STATE.systemHalt = true;
    }
    Shelly.call("HTTP.GET", { url: "http://" + CONFIG.ip_1PM_Ost + "/rpc/Switch.Set?id=0&on=false" });
    Shelly.call("HTTP.GET", { url: "http://" + CONFIG.ip_1PM_West + "/rpc/Switch.Set?id=0&on=false" });
    STATE.isMoving = false;
    STATE.isHoming = false;
    }

    // --- VERSCHLEISS-ANALYSE ---

    function checkWear(currentPower) {
    if (currentPower < 5) return; // Motor läuft noch nicht richtig an

    // 1. Harte Blockade prüfen
    if (currentPower > CONFIG.max_power_block) {
    emergencyStop("Mechanische Blockade (" + Math.round(currentPower) + "W)");
    return;
    }

    // 2. Verschleiß-Warnung (Schwergängigkeit)
    let limit = CONFIG.nominal_power * CONFIG.wear_threshold;
    if (currentPower > limit) {
    print("HINWEIS: Erhöhter Widerstand erkannt! Aktuell: " + Math.round(currentPower) + "W (Normal: " + Math.round(CONFIG.nominal_power) + "W)");
    }

    // 3. Den Normalwert schleichend anpassen (Lerneffekt für gesunde Mechanik)
    // Wir nehmen nur Werte auf, die keine Blockade sind
    CONFIG.nominal_power = (CONFIG.nominal_power * 0.95) + (currentPower * 0.05);
    }

    // --- MONITORING ---

    function monitor() {
    let now = new Date();
    let currentTime = ("0" + now.getHours()).slice(-2) + ":" + ("0" + now.getMinutes()).slice(-2);
       
    if (currentTime === "00:00") STATE.eveningCheckDone = false;

    // Homing Logik (Ost/Süd)
    if ((currentTime === CONFIG.homingTimeMorning || (currentTime >= CONFIG.homingTimeEvening && !STATE.eveningCheckDone))
    && !STATE.isHoming && !STATE.isMoving && !STATE.systemHalt) {
    STATE.isHoming = true;
    drive("OST", false);
    }

    // Endschalter-Abfragen
    Shelly.call("Input.GetStatus", { id: 2 }, function(res) {
    if (res && res.state && STATE.isHoming && STATE.lastDirection === "OST") {
    STATE.currentPos = 0;
    STATE.ostReachedTime = Date.now();
    drive("WEST", false);
    }
    });

    Shelly.call("Input.GetStatus", { id: 1 }, function(res) {
    if (res && res.state && STATE.isHoming && STATE.lastDirection === "WEST") {
    let travelTime = Date.now() - STATE.ostReachedTime;
    CONFIG.full_drive_time = (CONFIG.full_drive_time * 0.7) + (travelTime * 2 * 0.3);
    print("Laufzeit-Kalibrierung: 100% Weg = " + (CONFIG.full_drive_time/1000).toFixed(2) + "s");
    emergencyStop("Referenzpunkt erreicht");
    STATE.currentPos = 50.0;
    STATE.systemHalt = false;
    STATE.isHoming = false;
    STATE.eveningCheckDone = true;
    }
    });

    // Stromaufnahme während der Fahrt prüfen
    if (STATE.isMoving) {
    let activeIP = (STATE.lastDirection === "WEST") ? CONFIG.ip_1PM_West : CONFIG.ip_1PM_Ost;
    Shelly.call("HTTP.GET", { url: "http://" + activeIP + "/rpc/Switch.GetStatus?id=0" }, function(res) {
    if (res && res.code === 200) {
    let status = JSON.parse(res.body);
    checkWear(status.apower); // Hier wird auf Verschleiß geprüft
    }
    });
    }
    }

    // --- HAUPTLOGIK (Drive & Sync) ---

    function drive(direction, isPulse, duration) {
    if (STATE.systemHalt) return;
    let targetIP = (direction === "OST") ? CONFIG.ip_1PM_Ost : CONFIG.ip_1PM_West;
    let otherIP = (direction === "OST") ? CONFIG.ip_1PM_West : CONFIG.ip_1PM_Ost;
    Shelly.call("HTTP.GET", { url: "http://" + otherIP + "/rpc/Switch.Set?id=0&on=false" });
    Shelly.call("HTTP.GET", { url: "http://" + targetIP + "/rpc/Switch.Set?id=0&on=true" });
    STATE.isMoving = true;
    STATE.lastDirection = direction;
    if (isPulse) {
    Timer.set(duration, false, function() {
    Shelly.call("HTTP.GET", { url: "http://" + targetIP + "/rpc/Switch.Set?id=0&on=false" });
    STATE.currentPos += (STATE.lastDirection === "WEST" ? 1 : -1) * (duration / CONFIG.full_drive_time) * 100;
    STATE.isMoving = false;
    STATE.nextMoveAllowed = Date.now() + 3000;
    });
    }
    }

    function solveLogic() {
    let devices = [CONFIG.ip_Uni_West, CONFIG.ip_1PM_Ost, CONFIG.ip_1PM_West];
    let checks = 0; let errors = 0;
    for (let i = 0; i < devices.length; i++) {
    Shelly.call("HTTP.GET", { url: "http://" + devices[i] + "/rpc/Shelly.GetDeviceInfo", timeout: 2 }, function(r, e) {
    checks++; if (e !== 0) errors++;
    if (checks === devices.length) {
    if (errors > 0) { emergencyStop("Hardware offline"); return; }
    if (STATE.systemHalt) STATE.systemHalt = false;
    monitor();
    if (STATE.isMoving || STATE.isHoming || Date.now() < STATE.nextMoveAllowed) return;
                   
    // LDR Auswertung
    Shelly.call("Input.GetStatus", { id: 0 }, function(vOst) {
    let valOst = vOst.xpercent;
    Shelly.call("HTTP.GET", { url: "http://" + CONFIG.ip_Uni_West + "/rpc/Input.GetStatus?id=0" }, function(res) {
    if (!res || res.code !== 200) return;
    let valWest = JSON.parse(res.body).xpercent;
    if (valOst < CONFIG.darknessThreshold && valWest < CONFIG.darknessThreshold) return;
    let diff = Math.abs(valOst - valWest);
    let pulse = diff > 20 ? 900 : (diff >= 10 ? 500 : (diff > 3 ? 250 : 0));
    if (pulse > 0) {
    if (valOst > valWest && STATE.currentPos > 0) drive("OST", true, pulse);
    else if (valWest > valOst && STATE.currentPos < 100) drive("WEST", true, pulse);
    }
    });
    });
    }
    });
    }
    }

    Timer.set(CONFIG.updateInterval, true, solveLogic);

    Mit freundlichen Grüßen

    crYteK2601

    Raspberry Pi4B -> Home Assistant
    Shelly Pro 3EM
    Shelly Pro 3 -> Steuerung Heizstab (4,5kW)
    Shelly PM Mini -> Balkonkraftwerk Erzeugung
    Shelly 1 Mini -> Flurlicht Taster
    Shelly 1PM -> Raumüberwachung
    Shelly Plus Addon -> Temperatur Warmwasser
    Shelly Plug S -> Server, Trockner, Waschmaschine, Gefrierschrank
    Shelly Plus UNI -> Versuchsaufbau

  • Da du es ja mit Homeassistant steuern willst, wäre es nicht sinnvoll, die Nachführung anhand des Standorts und Datum und Zeit zu berechnen? Die Helligkeit könntest du ja als KO-Kriterium hinzunehmen, an absolut trüben Tagen evtl eine mittlere Position anfahren...
    Für diese Art der Nachführung brauchst du aber eine Rückmeldung über die aktuellen Positionen der Antriebe, evtl mit Hilfe von Potis und den Analogeingängen der Unis...

  • horkatz

    Das war tatsächlich mein erster Gedanke. Ich möchte jedoch nur ungerne den astronomische Sonnenverlauf zur Nachführung nutzen.

    Ich möchte damit, sobald das Skript den finalen Stand erreicht hat einen Prototypen im Miniaturformat bauen um die Funktionen alle nochmals zu prüfen.
    Erst wenn dieser Prototyp gebaut und dessen Funktion überprüft wurde möchte ich das 1:1 Modell starten.

    Zu deiner Aussage bzgl. Rückmeldung der aktuellen Positionen.
    Durch den Endschalter auf Position OST und den zweiten Endschalter auf Position SÜD kann die Logik die Laufzeit von OST nach SÜD feststellen.
    Diesen Wert nimmt er *2 und kann so die Laufzeit von OST nach WEST berechnen. Als Beispiel wurde im Skript (full_drive_time: 30000) gesetzt. Dieser Wert muss während der Erstinbetriebnahme noch korrigiert werden.

    Die Werte sind in Prozent zu verstehen.
    OST = 0%
    SÜD = 50%
    WEST = 100%

    Die Logik sollte also bei 100% halt machen. Dadurch erspare ich mir beispielsweise Potentiometer zur Positionserkennung.

    Mit freundlichen Grüßen

    crYteK2601

    Raspberry Pi4B -> Home Assistant
    Shelly Pro 3EM
    Shelly Pro 3 -> Steuerung Heizstab (4,5kW)
    Shelly PM Mini -> Balkonkraftwerk Erzeugung
    Shelly 1 Mini -> Flurlicht Taster
    Shelly 1PM -> Raumüberwachung
    Shelly Plus Addon -> Temperatur Warmwasser
    Shelly Plug S -> Server, Trockner, Waschmaschine, Gefrierschrank
    Shelly Plus UNI -> Versuchsaufbau

  • Ist halt nicht so genau, aber für die von dir angedachte Variante sicher ausreichend. Bei der Größe dürfte die Windlast eine große Rolle spielen, demzufolge wird das Ganze nicht billig werden, da würde ich vermutlich nicht mit Shellys steuern sondern eine SPS nehmen, auf den Mehrpreis kommt es dann auch nicht mehr an.

  • Dieses Thema enthält 13 weitere Beiträge, die nur für registrierte Benutzer sichtbar sind.