Ich denke nicht, dass ein direkter Login bei PayPal (dauerhaft) funktionieren wird. Dazu basteln die zu viel dran rum.
Dafür gibt's ja die API
PayPal kann auch Webhooks. Wäre auch noch eine Möglichkeit.
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.
Ich denke nicht, dass ein direkter Login bei PayPal (dauerhaft) funktionieren wird. Dazu basteln die zu viel dran rum.
Dafür gibt's ja die API
PayPal kann auch Webhooks. Wäre auch noch eine Möglichkeit.
Hier ein bisschen Theorie, da ich kein Shelly-Gerät besitze und erst recht keine "Münzwaschmaschine".
Vorausgesetzt, du verfügst über einen Server (das kann z.B. ein kleiner Raspberry sein, oder ein 4€ Cloud-Server bei Hetzner) mit PHP und MySQL (zum Speichern von Transaktionen, wer weiß, wofür das mal nützlich sein kann). Außerdem benötigst du ein PayPal-Business-Konto, sowie API-Zugang via https://developer.paypal.com.
Dann ein paar (rudimentäre) PHP-Scripts:
<?php
// PayPal
define('PAYPAL_CLIENT_ID', 'DEINE_CLIENT_ID');
define('PAYPAL_SECRET', 'DEIN_SECRET');
// Gib hier die API-Umgebung an:
// - "api-m.sandbox.paypal.com" für Sandbox
// - "api-m.paypal.com" für Live
define('PAYPAL_API_BASE', 'https://api-m.sandbox.paypal.com');
// Shelly
define('SHELLY_IP', '192.168.1.50'); // IP deines Shelly-Geräts
define('SHELLY_RELAY', 0); // In der Regel "0" für den ersten Relais-Kanal
// Waschmaschine
define('POWER_THRESHOLD', 10); // Beispielschwelle (in Watt), ab der die Maschine als "läuft" gilt
define('RUNTIME_IN_HOURS', 3); // Laufzeit, die nach Freischaltung gewährt wird
// MySQL
define('DB_HOST', 'localhost');
define('DB_NAME', 'shelly_wama');
define('DB_USER', 'root');
define('DB_PASS', '');
Alles anzeigen
<?php
require_once 'config.php';
/**
* Gibt ein Access Token für PayPal zurück.
*/
function getPayPalAccessToken(): ?string
{
$url = PAYPAL_API_BASE . "/v1/oauth2/token";
$auth = base64_encode(PAYPAL_CLIENT_ID . ":" . PAYPAL_SECRET);
$headers = [
"Authorization: Basic $auth",
"Content-Type: application/x-www-form-urlencoded",
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, "grant_type=client_credentials");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
if ($response === false) {
return null;
}
curl_close($ch);
$data = json_decode($response, true);
return $data['access_token'] ?? null;
}
/**
* Holt Informationen zu einem PayPal-Order (Bezahlvorgang).
*/
function getPayPalOrderDetails(string $orderId, string $accessToken): ?array
{
$url = PAYPAL_API_BASE . "/v2/checkout/orders/$orderId";
$headers = [
"Authorization: Bearer $accessToken",
"Content-Type: application/json",
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
if ($response === false) {
return null;
}
curl_close($ch);
$data = json_decode($response, true);
return $data;
}
/**
* Schaltet das Shelly-Relais an oder aus.
* @param string $state 'on' oder 'off'
*/
function switchShellyRelay(string $state): bool
{
$url = "http://" . SHELLY_IP . "/relay/" . SHELLY_RELAY;
$data = http_build_query(['turn' => $state]);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
return $response !== false;
}
/**
* Fragt den aktuellen Stromverbrauch (Watt) des Shelly 1PM ab.
*/
function getShellyPower(): float
{
$url = "http://" . SHELLY_IP . "/status";
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
if ($response === false) {
return 0.0;
}
$data = json_decode($response, true);
// Je nach Shelly-Typ kann die Struktur leicht variieren:
return $data['meters'][0]['power'] ?? 0.0;
}
/**
* Stellt fest, ob die Waschmaschine läuft.
*/
function isWashingMachineRunning(): bool
{
$power = getShellyPower();
return $power > POWER_THRESHOLD;
}
/**
* Datenbank-Funktionen
*/
function dbConnect(): ?PDO
{
try {
return new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME, DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
} catch (PDOException $e) {
// Im Fehlerfall null zurückgeben oder Exception werfen
return null;
}
}
/**
* Speicherung einer Transaktion
*/
function saveTransaction(string $orderId, string $status): void
{
$pdo = dbConnect();
if (!$pdo) {
return;
}
$stmt = $pdo->prepare("INSERT INTO transactions (order_id, status, created_at) VALUES (:oid, :status, NOW())");
$stmt->execute([
':oid' => $orderId,
':status' => $status
]);
}
Alles anzeigen
<?php
require_once 'functions.php';
// Prüfe, ob die "orderID" vorhanden ist
if (!isset($_GET['orderID'])) {
exit("Keine Order-ID vorhanden.");
}
$orderId = $_GET['orderID'];
// 1. Access Token holen
$accessToken = getPayPalAccessToken();
if (!$accessToken) {
exit("Fehler beim Abrufen des PayPal Access Tokens.");
}
// 2. Order-Details abfragen
$orderDetails = getPayPalOrderDetails($orderId, $accessToken);
if (!$orderDetails || !isset($orderDetails['status'])) {
exit("Fehler beim Abrufen der Zahlungsdetails.");
}
// 3. Bestätigen, dass Zahlung erfolgreich (COMPLETED) ist
if ($orderDetails['status'] !== 'COMPLETED') {
exit("Zahlung nicht abgeschlossen. Status: " . $orderDetails['status']);
}
// Transaktion speichern
saveTransaction($orderId, $orderDetails['status']);
// 4. Status der Waschmaschine prüfen
if (isWashingMachineRunning()) {
// Wenn die Maschine läuft, abbrechen oder Meldung ausgeben
echo "Die Waschmaschine ist bereits in Betrieb. Bitte warte, bis sie frei wird.";
exit;
}
// 5. Shelly-Relais einschalten (Waschmaschine einschalten)
if (switchShellyRelay('on')) {
// Zeitstempel merken (z. B. in einer DB, hier nur als Session-Beispiel)
session_start();
$_SESSION['washer_enabled_at'] = time();
echo "Waschmaschine wurde für " . RUNTIME_IN_HOURS . " Stunden freigeschaltet.";
} else {
echo "Fehler beim Einschalten der Waschmaschine.";
}
Alles anzeigen
<?php
require_once 'functions.php';
try {
$pdo = dbConnect();
if (!$pdo) {
// Falls keine Verbindung aufgebaut werden kann, einfach beenden
exit("Datenbankverbindung fehlgeschlagen.\n");
}
// 1) Alle aktiven Transaktionen suchen, bei denen das Ablaufdatum <= jetzt ist
$stmt = $pdo->query("
SELECT id, order_id
FROM transactions
WHERE is_active = 1
AND ends_at <= NOW()
");
$expiredTransactions = $stmt->fetchAll(PDO::FETCH_ASSOC);
// 2) Für jede abgelaufene Freischaltung: Waschmaschine ausschalten
foreach ($expiredTransactions as $tx) {
echo "Freischaltung abgelaufen für Order-ID: {$tx['order_id']}. Schalte Waschmaschine ab.\n";
// Shelly-Relais ausschalten
$success = switchShellyRelay('off');
if ($success) {
// 3) Transaktion in DB auf 'inaktiv' setzen
$updateStmt = $pdo->prepare("
UPDATE transactions
SET is_active = 0
WHERE id = :id
");
$updateStmt->execute([':id' => $tx['id']]);
echo "Waschmaschine erfolgreich deaktiviert.\n";
} else {
echo "Fehler: Konnte Shelly-Relais nicht ausschalten.\n";
}
}
// Optional: Wenn du mehrere Waschmaschinen oder mehrere Transaktionen parallel verwaltest,
// passt du diesen Vorgang entsprechend an.
// Hier haben wir nur ein Shelly-Relais (eine Waschmaschine) pro Transaktion angenommen.
} catch (Exception $e) {
// Fehlerbehandlung
echo "Fehler in cron.php: " . $e->getMessage() . "\n";
}
Alles anzeigen
Das ist der erste Streich, weitestgehend kommentiert.
Damit das funktional ist, muss dann noch eine Datenbank mitsamt Schema angelegt werden:
-- ########################################
-- # 1) Datenbank anlegen (falls gewünscht)
-- ########################################
CREATE DATABASE IF NOT EXISTS `shelly_wama`
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
-- In die neu angelegte DB wechseln
USE `shelly_wama`;
-- #########################################################
-- # 2) Tabelle 'transactions' für Zahlungs-/Nutzungsdaten
-- #########################################################
CREATE TABLE IF NOT EXISTS `transactions` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`order_id` VARCHAR(255) NOT NULL,
`status` VARCHAR(50) NOT NULL,
`created_at` DATETIME NOT NULL,
`started_at` DATETIME DEFAULT NULL,
`ends_at` DATETIME DEFAULT NULL,
`is_active` TINYINT(1) NOT NULL DEFAULT '0',
INDEX `idx_active` (`is_active`),
INDEX `idx_order_id` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Alles anzeigen
Damit sind die Grundvoraussetzungen geschaffen.
Was du nun benötigst, ist eine Möglichkeit für deine Kunden, dass diese die Zahlung initiieren können. Dazu könnte man einfach eine Seite erstellen, die für Kunden aus dem Internet heraus erreichbar ist:
<?php
require_once 'config.php';
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>Waschmaschine - Smart Payment</title>
</head>
<body>
<h1>Waschmaschine freischalten</h1>
<p>Preis pro Waschgang: 2,50 EUR</p>
<!-- PayPal Buttons integrieren (Achtung: Sandbox-Link vs. Live-Link) -->
<script src="https://www.paypal.com/sdk/js?client-id=<?php echo PAYPAL_CLIENT_ID; ?>¤cy=EUR"></script>
<div id="paypal-button-container"></div>
<script>
paypal.Buttons({
createOrder: function(data, actions) {
return actions.order.create({
purchase_units: [{
amount: {
value: '2.50' // Preis anpassen
}
}]
});
},
onApprove: function(data, actions) {
return actions.order.capture().then(function(details) {
// Nach erfolgreicher Zahlung an callback.php senden
window.location.href = 'callback.php?orderID=' + data.orderID;
});
}
}).render('#paypal-button-container');
</script>
</body>
</html>
Alles anzeigen
Was jetzt nur noch fehlt ist, dass die cron.php regelmäßig (z.B. alle 5 Minuten) ausgeführt wird:
Das war's. Wie gesagt, ist das aus einer Vielzahl von Gründen ein Minimalbeispiel und soll vor allem veranschaulichen, wie es gehen könnte. Bevor man das produktiv nutzt, sollte es zum einen getestet- und zum anderen weiter abgesichert werden.
TL;DR: Rein über ein Shelly-Gerät wird das nicht funktionieren. Es braucht zwischen PayPal und dem Shelly noch etwas, was die Zahlungsverarbeitung durchführt, protokolliert, etc.
Tools wie Zapier oder Integromat (Make) könnten vielleicht auch dabei helfen, die PayPal-Transaktion mit einem Shelly-Switch oder einer anderen Steuerung zu verbinden, ohne dass zusätzlich etwas programmiert werden muss. Aber das weiß ich noch weniger.
Die Übernahme erfolgte am 06. November, die Änderung der Domain, sowie das vollständige Rebranding Ende November. So wird es dann jetzt auch auf absehbare Zeit bleiben.
Das Benutzerkonto kann hier gelöscht werden: https://shelly-forum.com/account-management/
Ich hab den Code mal etwas geändert, da ich glaube, dass die Dinger mit der ES6-Syntax nicht ganz klar kommen.
Es ist noch früh, ich habe Hausbesetzer gelesen ![]()
Willkommen ![]()
Das wird es nicht geben.
SoftCreatR geht das technisch?
Ich bin eigentlich im Urlaub ![]()
Danke für den Code -- leider stoppt der mit 'ner Fehlermeldung:
Ah, die (ES6-)Syntax scheint bei Shelly-Scripts nicht zu funktionieren. Gut zu wissen
Ersetze in dem Script:
durch:
Ich habe mir noch einmal die Definition des Begriffs "Kollaborateur" zu Gemüte geführt und mich dazu entschieden, diesen in "Schreiberling" abzuändern. Welche Trophäen es wofür gibt, siehst du hier: https://shelly-forum.com/trophy-list/
Ich habe mir das jetzt nochmal angesehen, vor allem die auskommentierten Teile. Ich habe mir einmal die Dokumentationen dazu angesehen:
Demnach sollte sollte transition_duration grundsätzlich funktionieren. Aber kann es sein, dass der Shelly diese Option nicht so akzeptiert, wie Du es erwartest. Nicht jede Shelly-Version oder jedes Firmware-Update unterstützt alle Parameter wie transition_duration. Bei einigen Geräten oder Firmwares musst Du möglicherweise den Parameter anders nennen oder er wird gar nicht unterstützt. Das müsste man genauer "beleuchten".
Und zum eigentlichen Code: Ich habe mir mal die Mühe gemacht, diesen umzuschreiben, wie ich denke, dass es funktionieren könnte. Disclaimer: Ich hab kein Shelly-Gerät und kenne mich nicht mit der Programmierung selbiger aus. Meine Aussagen berufen sich auf meine Javascript-Kenntnisse, sowie die API-Dokumentation ![]()
Kurz und schmerzlos:
// Ampelsteuerung für Shelly Plus RGBW PM
// ===================================================
//
// Ampel-Zustände:
// 0 = alles aus
// Gelb blinken: 101 (gelb ein), 102 (gelb aus, dann zurück zu 101)
// Normale Ampel: 201...208 (Grün -> Gelb -> Rot -> Rot+Gelb)
// Lauflicht: 301...308 (Rot -> Gelb -> Grün -> Rot ... zyklisch)
//
// Eingänge (Schalter) bestimmen, in welchem Modus die Ampel läuft:
// Schalter-Kombinationen (summe der aktivierten Eingänge):
// 0 = alles aus
// 1 = Gelb blinkend
// 2 = normale Ampel
// 3 = Lauflicht
// 4 = alles aus
//
// Die Eingänge werden einzeln abgefragt (S1...S4) und über einfache
// Additionen zu einer "Schalter"-Variable kombiniert, um den Modus zu wählen.
// ============================
// Konfiguration & Konstanten
// ============================
const BRIGHTNESS = 15; // Standard-Helligkeit
const LOOP_INTERVAL = 500; // Intervall für den Hauptloop (ms)
// Zustände definieren (Konstanten für bessere Lesbarkeit)
const STATES = {
OFF: 0,
YELLOW_BLINK_ON: 101,
YELLOW_BLINK_OFF: 102,
NORMAL_GREEN_START: 201,
NORMAL_GREEN: 202,
NORMAL_YELLOW_START: 203,
NORMAL_YELLOW: 204,
NORMAL_RED_START: 205,
NORMAL_RED: 206,
NORMAL_RED_YELLOW_START: 207,
NORMAL_RED_YELLOW: 208,
RUN_START: 301,
RUN_WAIT_1: 302,
RUN_SWITCH_YELLOW: 303,
RUN_WAIT_2: 304,
RUN_SWITCH_GREEN: 305,
RUN_WAIT_3: 306,
RUN_SWITCH_RED: 307,
RUN_WAIT_4: 308,
};
// Dauer der einzelnen Phasen (wie oft Loop durchlaufen wird, bis nächster Schritt)
const TIMINGS = {
NORMAL_GREEN: 6,
NORMAL_YELLOW: 4,
NORMAL_RED: 6,
NORMAL_RED_YELLOW: 2,
RUN_PHASE: 2, // Wartezeit für jede Lauflicht-Zwischenphase
};
// ============================
// Globale Variablen
// ============================
let Ampel = STATES.OFF;
let LoopCount = 0;
let oldSchalter = 10; // Startwert für Schalter, um initialen Wechsel zu erkennen
// ============================
// Hilfsfunktionen
// ============================
// Alle Kanäle ausschalten
function AlleAus() {
for (let i = 0; i < 4; i++) {
Shelly.call("Light.set", { id: i, on: false, brightness: BRIGHTNESS });
}
}
// Eingänge (Schalter) auslesen und kombinierten Wert zurückgeben
function readInputs() {
const S1 = Shelly.getComponentStatus("input:0").state ? 1 : 0;
const S2 = Shelly.getComponentStatus("input:1").state ? 2 : 0;
const S3 = Shelly.getComponentStatus("input:2").state ? 3 : 0;
const S4 = Shelly.getComponentStatus("input:3").state ? 4 : 0;
// Summe bildet den "Schalter"-Wert
return S1 + S2 + S3 + S4;
}
// Ampel-Wert anpassen, wenn sich der Schalterzustand ändert
function updateAmpelFromInputs() {
const Schalter = readInputs();
if (oldSchalter !== Schalter) {
switch (Schalter) {
case 0:
Ampel = STATES.OFF;
break;
case 1:
Ampel = STATES.YELLOW_BLINK_ON;
break;
case 2:
Ampel = STATES.NORMAL_GREEN_START;
break;
case 3:
Ampel = STATES.RUN_START;
break;
case 4:
Ampel = STATES.OFF;
break;
default:
Ampel = STATES.OFF; // Fallback, falls unerwarteter Wert
break;
}
print(`Old: ${oldSchalter} Neu: ${Schalter} Ampel: ${Ampel} (${typeof Ampel})`);
oldSchalter = Schalter;
}
}
// Zustandslogik anwenden, abhängig von Ampel
function applyState() {
switch (Ampel) {
case STATES.OFF:
AlleAus();
break;
// Gelb blinken
case STATES.YELLOW_BLINK_ON:
print("Blinklicht AN");
Shelly.call("Light.set", { id: 1, on: true });
Ampel = STATES.YELLOW_BLINK_OFF;
break;
case STATES.YELLOW_BLINK_OFF:
Shelly.call("Light.setAll", { on: false });
Ampel = STATES.YELLOW_BLINK_ON;
break;
// Normale Ampel
case STATES.NORMAL_GREEN_START:
print("Normale Ampel: Grünphase starten");
Shelly.call("Light.set", { id: 2, on: true }); // Grün an
Shelly.call("Light.set", { id: 1, on: false }); // Gelb aus
Shelly.call("Light.set", { id: 0, on: false }); // Rot aus
LoopCount = 0;
Ampel = STATES.NORMAL_GREEN;
break;
case STATES.NORMAL_GREEN:
LoopCount++;
if (LoopCount > TIMINGS.NORMAL_GREEN) {
Ampel = STATES.NORMAL_YELLOW_START;
}
break;
case STATES.NORMAL_YELLOW_START:
Shelly.call("Light.set", { id: 1, on: true }); // Gelb an
Shelly.call("Light.set", { id: 2, on: false }); // Grün aus
LoopCount = 0;
Ampel = STATES.NORMAL_YELLOW;
break;
case STATES.NORMAL_YELLOW:
LoopCount++;
if (LoopCount > TIMINGS.NORMAL_YELLOW) {
Ampel = STATES.NORMAL_RED_START;
}
break;
case STATES.NORMAL_RED_START:
Shelly.call("Light.set", { id: 0, on: true }); // Rot an
Shelly.call("Light.set", { id: 1, on: false }); // Gelb aus
LoopCount = 0;
Ampel = STATES.NORMAL_RED;
break;
case STATES.NORMAL_RED:
LoopCount++;
if (LoopCount > TIMINGS.NORMAL_RED) {
Ampel = STATES.NORMAL_RED_YELLOW_START;
}
break;
case STATES.NORMAL_RED_YELLOW_START:
LoopCount = 0;
Shelly.call("Light.set", { id: 1, on: true }); // Gelb an
Ampel = STATES.NORMAL_RED_YELLOW;
break;
case STATES.NORMAL_RED_YELLOW:
LoopCount++;
if (LoopCount > TIMINGS.NORMAL_RED_YELLOW) {
Ampel = STATES.NORMAL_GREEN_START; // Zyklus von vorn (Grünphase)
}
break;
// Lauflicht
case STATES.RUN_START:
print("Lauflicht starten");
AlleAus();
LoopCount = 0;
Shelly.call("Light.set", { id: 0, on: true }); // Rot an
Ampel = STATES.RUN_WAIT_1;
break;
case STATES.RUN_WAIT_1:
LoopCount++;
if (LoopCount > TIMINGS.RUN_PHASE) {
Ampel = STATES.RUN_SWITCH_YELLOW;
}
break;
case STATES.RUN_SWITCH_YELLOW:
Shelly.call("Light.set", { id: 0, on: false });
Shelly.call("Light.set", { id: 1, on: true });
LoopCount = 0;
Ampel = STATES.RUN_WAIT_2;
break;
case STATES.RUN_WAIT_2:
LoopCount++;
if (LoopCount > TIMINGS.RUN_PHASE) {
Ampel = STATES.RUN_SWITCH_GREEN;
}
break;
case STATES.RUN_SWITCH_GREEN:
Shelly.call("Light.set", { id: 1, on: false });
Shelly.call("Light.set", { id: 2, on: true });
LoopCount = 0;
Ampel = STATES.RUN_WAIT_3;
break;
case STATES.RUN_WAIT_3:
LoopCount++;
if (LoopCount > TIMINGS.RUN_PHASE) {
Ampel = STATES.RUN_SWITCH_RED;
}
break;
case STATES.RUN_SWITCH_RED:
Shelly.call("Light.set", { id: 2, on: false });
Shelly.call("Light.set", { id: 0, on: true });
LoopCount = 0;
Ampel = STATES.RUN_WAIT_4;
break;
case STATES.RUN_WAIT_4:
LoopCount++;
if (LoopCount > TIMINGS.RUN_PHASE) {
Ampel = STATES.RUN_WAIT_1; // Zurück zum Start des Laufs (Endlos-Schleife)
}
break;
default:
// Unerwarteter Zustand: Sicherheitshalber alles aus und zurück auf OFF
AlleAus();
Ampel = STATES.OFF;
break;
}
}
// Haupt-Schleifenfunktion, wird alle LOOP_INTERVAL ms aufgerufen
function loop() {
updateAmpelFromInputs();
applyState();
}
// Initialisierung: alles aus
AlleAus();
// Timer setzen für die Hauptschleife
Timer.set(LOOP_INTERVAL, true, loop);
Alles anzeigen
Generell sollte Ampel nur ein einziges Mal definiert- und im nachfolgenden nur der Wert gesetzt werden. Denn wenn z.B. Schalter einen Zustand erreicht, der von dir nicht gehändelt wird, ist Ampel undefiniert, erst recht, weil es in deinem Switch/Case keinen Default gibt. Ist Schalter also z.B. NaN, oder größer als 5 bzw. kleiner als 0, ist Ampel zwangsläufig undefiniert.
Also:
let Schalter = 1 * S1 + 2 * S2 + 3 * S3 + 4 * S4;
let Ampel = 0; // Standardwert
// ....
Ampel = 123;
// ...
Ampel = 456;
Andererseits hätte ich vermutlich noch eine Vielzahl von Optimierungen vorzuschlagen ![]()
Für Schalter bitte auch Punkt vor Strich beachten ![]()
Versuchs so:
// 🌡️ Skript zur einmaligen Anzeige der aktuellen Temperatur vom AM2301A
Shelly.call(
"HTTP.GET",
{ url: "http://127.0.0.1/rpc/Shelly.GetStatus" },
function(response, error_code, error_message) {
if (error_code === 0) {
let data = JSON.parse(response.body);
// Abhängig von der Shelly-Firmware kann der Key unterschiedlich sein.
// Bei Shelly Plus/Pro Geräten werden Sensoren meist als "sensor:0" benannt.
let sensorData = data.status && data.status["sensor:0"];
if (sensorData && 'temperature' in sensorData) {
let temperature = sensorData.temperature;
print("🌡️ Aktuelle Temperatur: " + temperature + "°C");
} else {
print("❌ Temperaturdaten nicht verfügbar.");
}
} else {
print("❌ Fehler beim Abrufen der Temperatur: " + error_message);
}
}
);
print("📢 Das Skript wurde erfolgreich ausgeführt.");
Alles anzeigen
Hat das tatsächlich schon mal jemand mit den oben angegebenen Quellen funktionieren umgesetzt?
Entweder ich bin blind, oder ein "HowTo" um ein Script von extern nachladen wird nicht aufgeführt.
Selbst ein js ex-/import funktioniert bei mir nicht...
Nach meinem Verständnis sind Script.Create, Script.PutCode und Script.Eval nicht dafür gedacht, von innerhalb eines laufenden Shelly-Skriptes zusätzlichen Code dynamisch zu laden. Sie dienen als externe Management- und Deployment-Tools über die RPC-Schnittstelle. Innerhalb eines Skriptes bleibt man meist auf den initial geladenen Code beschränkt.
Wie an anderer Stelle bereits erwähnt, hab ich von Shelly-Scripting keine Ahnung. Aber ich hab mir die Doku gerade kurz angesehen und hätte zumindest eine Idee, die man testen könnte:
const externalJsUrl = "https://domain.de/script/test.js";
Shelly.call("HTTP.GET", { url: externalJsUrl }, function(result, error_code, error_message) {
if (error_code === 0 && result && result.body) {
try {
eval(result.body);
// Nach erfolgreichem eval können nun Funktionen/Variablen aus dem geladenen Code genutzt werden.
Shelly.call("Sys.Log", { message: "Externer Code erfolgreich geladen und ausgeführt." });
} catch (e) {
Shelly.call("Sys.Log", { message: `Fehler beim Ausführen des externen Codes: ${e}` });
}
} else {
Shelly.call("Sys.Log", { message: `Fehler beim Abrufen des externen Codes: ${error_msg}` });
}
});
Alles anzeigen
Bsp url: url
Das ist in der Form vollkommen legitim. Links steht der Parameter-Name, der übergeben wird und rechts der Wert dessen. Dass die Variable (für den Wert) denselben Namen hat, ist vielleicht etwas undienlich für die Lesbarkeit, aber nicht per se schlechter Programmierstil ![]()
Ich kenne mich mit Shelly-Scripting nicht aus, aber das ist in erster Linie ja Vanilla-Javascript, weshalb ich behaupte, dass das Problem die angegebene Webhook-URL ist, denn eine URL darf nur ein Fragezeichen für Parameter enthalten, beim Zusammensetzen wird aber ?humidity an die angegebene Webhook-URL gehangen, was im Ergebnis eine ungültige URL erzeugt.
Außerdem gibt es eine kleine Diskrepanz an dieser Stelle:
// Überprüfen, ob die Feuchtigkeit über 40% gestiegen ist und höher als der vorherige Wert ist
if (humidity > 35 && humidity > lastHumidity) {
print(`🚀 Feuchtigkeit ist über 40% gestiegen! Sende Webhook...`);
sendWebhook(humidity);
}
Denn sowohl im Kommentar, als auch in der ausgegebenen Nachricht steht 40%, geprüft wird aber auf > 35%. Das ist kein Fehler, aber halt auch nicht ganz korrekt ![]()
Eine korrekte Version des Scripts könnte z.B. so aussehen:
// -------------------------------
// 🌡️ Skript für Shelly Plus 1 mit AM2301A Addon
// 🔥 Sendet Webhook, wenn die Feuchtigkeit > 40% steigt
// -------------------------------
// Konfiguriere die URL deines Webhooks
var webhookUrl = "http://192.168.178.89/api/v2/webhook_trigger?webhooks_key=OKVVRKIQLMHAVLDPIXSNENBECCXIBYWKNARVLOEFRUITPWIMVMORVFPJVFTVPWNJ&event=Keller";
// Aktueller Feuchtigkeitswert speichern, um nur bei Änderung zu reagieren
var lastHumidity = null;
// Event-Listener, der auf Sensor-Änderungen lauscht
Shelly.addStatusHandler(function(event) {
// Überprüfe, ob der Sensor-Status geändert wurde
if (event.component === "sensor_0" && event.delta && ("humidity" in event.delta)) {
var humidity = event.delta.humidity; // Neuer Feuchtigkeitswert
print("💧 Neue Feuchtigkeit: " + humidity + "%");
// Überprüfen, ob die Feuchtigkeit über 40% gestiegen ist und höher als der vorherige Wert ist
if (humidity > 40 && (lastHumidity === null || humidity > lastHumidity)) {
print("🚀 Feuchtigkeit ist über 40% gestiegen! Sende Webhook...");
sendWebhook(humidity);
}
// Speichere den letzten Feuchtigkeitswert
lastHumidity = humidity;
}
});
// Funktion zum Senden des Webhooks
function sendWebhook(humidity) {
var url = webhookUrl + (webhookUrl.indexOf("?") !== -1 ? "&" : "?") + "humidity=" + encodeURIComponent(humidity);
Shelly.call(
"HTTP.GET",
{ url: url },
function(response, error_code, error_message) {
if (error_code === 0) {
print("✅ Webhook erfolgreich gesendet: " + url);
} else {
print("❌ Fehler beim Senden des Webhooks: " + error_message);
}
}
);
}
Alles anzeigen
Um zu verhindern, dass bei kleinen, schnellen Änderungen mehrfach Webhooks gesendet werden, kannst du ein einfaches Debouncing implementieren. Hier ein Beispiel:
// -------------------------------
// 🌡️ Skript für Shelly Plus 1 mit AM2301A Addon
// 🔥 Sendet Webhook, wenn die Feuchtigkeit > 40% steigt
// -------------------------------
// Konfiguriere die URL deines Webhooks
var webhookUrl = "http://192.168.178.89/api/v2/webhook_trigger?webhooks_key=OKVVRKIQLMHAVLDPIXSNENBECCXIBYWKNARVLOEFRUITPWIMVMORVFPJVFTVPWNJ&event=Keller";
// Aktueller Feuchtigkeitswert speichern, um nur bei Änderung zu reagieren
var lastHumidity = null;
// Event-Listener, der auf Sensor-Änderungen lauscht
Shelly.addStatusHandler(function(event) {
// Überprüfe, ob der Sensor-Status geändert wurde
if (event.component === "sensor_0" && event.delta && ("humidity" in event.delta)) {
var humidity = event.delta.humidity; // Neuer Feuchtigkeitswert
print("💧 Neue Feuchtigkeit: " + humidity + "%");
// Überprüfen, ob die Feuchtigkeit über 40% gestiegen ist und höher als der vorherige Wert ist
if (humidity > 40 && (lastHumidity === null || humidity > lastHumidity)) {
print("🚀 Feuchtigkeit ist über 40% gestiegen! Sende Webhook...");
sendWebhook(humidity);
}
// Speichere den letzten Feuchtigkeitswert
lastHumidity = humidity;
}
});
var webhookTimeout = null;
var debounceDelay = 30000; // 30 Sekunden
function sendWebhook(humidity) {
if (webhookTimeout !== null) {
print("🚫 Webhook bereits gesendet. Warte auf das nächste Intervall.");
return;
}
var url = webhookUrl + (webhookUrl.indexOf("?") !== -1 ? "&" : "?") + "humidity=" + encodeURIComponent(humidity);
Shelly.call(
"HTTP.GET",
{ url: url },
function(response, error_code, error_message) {
if (error_code === 0) {
print("✅ Webhook erfolgreich gesendet: " + url);
// Setze das Timeout, um weitere Webhooks für die nächste Zeitspanne zu blockieren
webhookTimeout = Timer.set(debounceDelay, false, function() {
webhookTimeout = null;
});
} else {
print("❌ Fehler beim Senden des Webhooks: " + error_message);
}
}
);
}
Alles anzeigen
Dieses Debouncing stellt sicher, dass nach dem Senden eines Webhooks eine bestimmte Zeit (z.B. 30 Sekunden) gewartet wird, bevor ein weiterer Webhook gesendet werden kann. Dies verhindert übermäßige Webhook-Aufrufe bei häufigen Feuchtigkeitsänderungen.
weil welcher Hacker will schon was von uns 0815 User?
Mal in deinen Spam-Ordner geguckt? Gelegenheit macht Diebe, egal ob 0815, oder 0900-User. Und wenn es bei dir nichts zu holen gibt, kann man dich auch noch anderweitig als Ressource nutzen (z.B. für DDos-Angriffe), wofür du am Ende haftbar gemacht werden kannst.
Naja, dass es die jetzt gibt heißt nicht, dass das Repertoire nicht noch erweitert wird.
Mir fehlt es als einiges. Die Frage ist, ob ich es will.
Etwas ernüchternd, wenn man innerhalb einer Stunde alles abgeräumt hat, was geht.
Hast du ja nicht 😊 Da die Trophäen gestern eingeführt wurden und gemäß ihrer Vergabebedingungen vergeben wurden, haben einige von euch halt binnen kürzester Zeit mehrere Trophäen erhalten.
Das sollte auf der Übersichtsseite ersichtlich sein 😅 Zwei-Faktor-Authentifizierung fehlt dir zum Beispiel noch.