Discovery: alle Geraete finden und stabil ablegen
Ein Lauf mit allen Modulen brachte 54 Geraete und 441 Messwerte - und fuenf Fehler ans Licht, die vorher niemand sehen konnte, weil clear_tables die Tabellen bei jedem Lauf geleert hat. Vier davon sind Schluessel- und Upsert-Fehler derselben Familie: * command_parameters war ueber (command_id, url) eindeutig. WLED-Parameter haben keine URL - ihr Name ist der Platzhalter in der Kommando-Vorlage -, und eine NULL kollidiert in MySQL nie. ON DUPLICATE KEY UPDATE griff also nicht, und jeder Lauf legte dieselben Parameter erneut an: aus 18 wurden nach drei Laeufen 54. Schluessel jetzt (command_id, parameter_name). * actor_states war ueber (actor_id, url) eindeutig. Umgekehrtes Problem: Messwerte, die sich ein Topic teilen, ueberschrieben einander. Der go-eCharger schickt sechzehn Werte als JSON-Feld auf einem Topic - von denen kam genau einer in der Datenbank an. Schluessel jetzt (actor_id, state_name), das bringt 50 verlorene Messwerte zurueck. * Kommandos, Messwerte und Parameter aktualisierten ihre URL beim Wiederholungslauf nicht - sie stand nicht im UPDATE-Teil. Eine Korrektur in einem Modul kam damit nie in einer bestehenden Datenbank an. * Der Typ eines Kommando-Parameters wurde als Text in eine int-Spalte geschrieben. MariaDB macht daraus stillschweigend 0, und 0 ist "bool" - deshalb bot der Editor fuer die WLED-Helligkeit (0..255) ein Ja/Nein an. Der fuenfte: die Shelly-Gen1-Relais hatten weder eine Kommando-URL noch eine URL am Zustand. Ohne die weiss niemand, was zu schicken und wo nachzusehen ist. Gen1 schaltet ueber ?turn=on|off|toggle und meldet sich in "ison". Damit entfaellt auch die Uebersetzungstabelle im HTTPTransport: die Zuordnung gehoert ins Geraetemodell, nicht in den Runner. Zwei Luecken auf der Runner-Seite, die derselbe Lauf gezeigt hat: * WLED liess sich gar nicht ansteuern - es gab keinen Transport dafuer. Der neue nutzt die JSON-Vorlage aus command_url, fuellt die Platzhalter und schickt sie am Stueck an /json/state. * Tahoma wurde am Schema "io://" erkannt. Das Schema beschreibt aber die Funkart: dieselbe Box liefert rts:// fuer die Dachfenster und internal:// fuer die Alarmanlage. Drei von 22 Geraeten fielen durch. Erkannt wird jetzt an der Box-Kennung in der URL. Nachgemessen: zwei Laeufe hintereinander aendern keine einzige Zeile mehr, die Geraete-IDs bleiben ueber Laeufe stabil (Voraussetzung fuer die Fremdschluessel der Automatiken), alle 54 Geraete finden genau einen Transport, und 440 der 441 Messwerte sind tatsaechlich lesbar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+10
-2
@@ -261,7 +261,11 @@ ALTER TABLE `actor_commands`
|
|||||||
--
|
--
|
||||||
ALTER TABLE `actor_states`
|
ALTER TABLE `actor_states`
|
||||||
ADD PRIMARY KEY (`id`),
|
ADD PRIMARY KEY (`id`),
|
||||||
ADD UNIQUE KEY `actor_id_2` (`actor_id`,`url`),
|
-- Auf den Namen und nicht auf die URL, aus demselben Grund wie bei
|
||||||
|
-- command_parameters: korrigiert ein Modul die URL eines Messwerts, soll
|
||||||
|
-- die vorhandene Zeile geaendert werden. Am URL-Schluessel entstand
|
||||||
|
-- stattdessen eine zweite Zeile, und die alte blieb ohne URL zurueck.
|
||||||
|
ADD UNIQUE KEY `actor_id_2` (`actor_id`,`state_name`),
|
||||||
ADD KEY `actor_id` (`actor_id`),
|
ADD KEY `actor_id` (`actor_id`),
|
||||||
ADD KEY `idx_actor_states_name` (`state_name`),
|
ADD KEY `idx_actor_states_name` (`state_name`),
|
||||||
ADD KEY `state_type` (`state_type`);
|
ADD KEY `state_type` (`state_type`);
|
||||||
@@ -271,7 +275,11 @@ ALTER TABLE `actor_states`
|
|||||||
--
|
--
|
||||||
ALTER TABLE `command_parameters`
|
ALTER TABLE `command_parameters`
|
||||||
ADD PRIMARY KEY (`id`),
|
ADD PRIMARY KEY (`id`),
|
||||||
ADD UNIQUE KEY `command_id_2` (`command_id`,`url`),
|
-- Auf den Namen und nicht auf die URL: Parameter ohne URL (WLED traegt
|
||||||
|
-- alles in der Kommando-Vorlage) haetten dort NULL, und eine NULL
|
||||||
|
-- kollidiert in MySQL nie - ON DUPLICATE KEY UPDATE griff nicht und jeder
|
||||||
|
-- Discovery-Lauf legte die Parameter erneut an.
|
||||||
|
ADD UNIQUE KEY `command_id_2` (`command_id`,`parameter_name`),
|
||||||
ADD KEY `command_id` (`command_id`);
|
ADD KEY `command_id` (`command_id`);
|
||||||
|
|
||||||
--
|
--
|
||||||
|
|||||||
@@ -114,19 +114,27 @@ Fenster gar nichts gelaufen ist — dann ist auch keine Sperre aktiv.
|
|||||||
|
|
||||||
Welcher Weg zum Gerät führt, entscheidet die URL des Aktors in `actors`:
|
Welcher Weg zum Gerät führt, entscheidet die URL des Aktors in `actors`:
|
||||||
|
|
||||||
| URL | Messwert (`actor_states.url`) | Kommando |
|
| URL | Messwert (`actor_states.url`) | Kommando (`actor_commands.command_url`) |
|
||||||
|-------------|-------------------------------|-------------------------------------|
|
|---|---|---|
|
||||||
| `mqtt://…` | vollständiges Topic, abonniert | publiziert auf das Parameter-Topic |
|
| `mqtt://…` | vollständiges Topic, abonniert | Nutzlast auf das Parameter-Topic |
|
||||||
| `http://…` | Feldname in der JSON-Antwort, gepollt | Parameter als Abfrageargumente |
|
| `http://…` | Feldname in der JSON-Antwort, gepollt | Abfrageargumente an die Geräte-URL (`turn=on`) |
|
||||||
| `io://…` | Tahoma-Statusname, gepollt | `exec/apply` an die Tahoma-Box |
|
| `wled://…` | Pfad in `/json/state` (`seg[0].col[0]`), gepollt | JSON-Vorlage mit Platzhaltern, als Ganzes gesendet |
|
||||||
|
| Tahoma | Statusname (`core:ClosureState`), gepollt | `exec/apply` an die Box |
|
||||||
| `Logic` | gerechnet: Uhrzeit, Datum, Sonne | – |
|
| `Logic` | gerechnet: Uhrzeit, Datum, Sonne | – |
|
||||||
|
|
||||||
Alle vier stehen in `transports.py`. Eine fünfte Geräteart kommt als weitere
|
Alle fünf stehen in `transports.py`. Eine sechste Geräteart kommt als weitere
|
||||||
Klasse dazu; sie braucht `passt()`, `zustaende_lesen()` und `senden()`.
|
Klasse dazu; sie braucht `passt()`, `zustaende_lesen()` und `senden()`.
|
||||||
|
|
||||||
Bei HTTP heißen die Schaltbefehle nicht so wie in der Datenbank — dafür gibt es
|
Tahoma ist der einzige, der nicht am URL-Schema erkannt wird, sondern an der
|
||||||
in `HTTPTransport` eine kleine Übersetzungstabelle (`turn_on` → `on=true`).
|
**Box-Kennung** in der URL. Das Schema beschreibt dort die Funkart, und
|
||||||
Das ist die Stelle, die beim Anschluss neuer HTTP-Geräte wächst.
|
dieselbe Box liefert `io://` für die Jalousien, `rts://` für die Dachfenster
|
||||||
|
und `internal://` für die Alarmanlage. Ohne `pin` in der `config.ini` ist
|
||||||
|
niemand zuständig — dann meldet der Runner beim Auslösen „kein Transport",
|
||||||
|
statt still nichts zu tun.
|
||||||
|
|
||||||
|
Bei WLED trägt die Kommando-Vorlage alles: `{"seg":[{"col":[[%red%,%green%,%blue%]]}]}`
|
||||||
|
wird mit den Parameterwerten gefüllt und am Stück geschickt. Deshalb haben die
|
||||||
|
Parameter dort keine eigene URL — ihr Name *ist* der Platzhalter.
|
||||||
|
|
||||||
## Voraussetzungen
|
## Voraussetzungen
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ import requests
|
|||||||
import paho.mqtt.client as mqtt
|
import paho.mqtt.client as mqtt
|
||||||
|
|
||||||
from transports import (HTTPTransport, LogicTransport, MQTTTransport,
|
from transports import (HTTPTransport, LogicTransport, MQTTTransport,
|
||||||
TahomaTransport)
|
TahomaTransport, WLEDTransport)
|
||||||
|
|
||||||
logger = logging.getLogger("autoaction")
|
logger = logging.getLogger("autoaction")
|
||||||
|
|
||||||
@@ -377,6 +377,7 @@ class Runner:
|
|||||||
self.mqtt = self._mqtt_verbinden()
|
self.mqtt = self._mqtt_verbinden()
|
||||||
self.transporte = [
|
self.transporte = [
|
||||||
MQTTTransport(self.mqtt, self.dry_run),
|
MQTTTransport(self.mqtt, self.dry_run),
|
||||||
|
WLEDTransport(requests, dry_run=self.dry_run),
|
||||||
HTTPTransport(requests, dry_run=self.dry_run),
|
HTTPTransport(requests, dry_run=self.dry_run),
|
||||||
TahomaTransport(requests, config.text("tahoma", "pin"),
|
TahomaTransport(requests, config.text("tahoma", "pin"),
|
||||||
config.text("tahoma", "token"),
|
config.text("tahoma", "token"),
|
||||||
|
|||||||
@@ -8,11 +8,14 @@ URL des Aktors in der Tabelle `actors`:
|
|||||||
|
|
||||||
mqtt://... MQTT-Geraete (Home-Assistant-Discovery)
|
mqtt://... MQTT-Geraete (Home-Assistant-Discovery)
|
||||||
http://... Shelly und andere HTTP-Geraete
|
http://... Shelly und andere HTTP-Geraete
|
||||||
io://... Tahoma
|
wled://... WLED-Lampen
|
||||||
|
io://, rts://, internal://, ogp:// Tahoma - das Schema haengt an der
|
||||||
|
Funkart des Geraets, deshalb wird dort nicht danach
|
||||||
|
entschieden, sondern an der Box-Kennung in der URL
|
||||||
Logic das gerechnete Geraet "Zeitpunkt" (Uhrzeit, Datum, Sonne)
|
Logic das gerechnete Geraet "Zeitpunkt" (Uhrzeit, Datum, Sonne)
|
||||||
|
|
||||||
Alle liegen in einer Datei statt in einem Paket wie bei deviceDiscovery: es
|
Alle liegen in einer Datei statt in einem Paket wie bei deviceDiscovery: es
|
||||||
sind vier kurze Klassen, und wer eine fuenfte Geraeteart anschliesst, sieht
|
sind fuenf kurze Klassen, und wer eine sechste Geraeteart anschliesst, sieht
|
||||||
hier auf einen Blick, was dafuer zu tun ist.
|
hier auf einen Blick, was dafuer zu tun ist.
|
||||||
|
|
||||||
Jeder Transport hat zwei Haelften:
|
Jeder Transport hat zwei Haelften:
|
||||||
@@ -28,11 +31,19 @@ Jeder Transport hat zwei Haelften:
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
import urllib3
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
logger = logging.getLogger("autoaction.transport")
|
logger = logging.getLogger("autoaction.transport")
|
||||||
|
|
||||||
|
# Die Tahoma-Box hat ein selbst ausgestelltes Zertifikat auf einen Namen, den
|
||||||
|
# nur das Heimnetz kennt. Die Pruefung ist dort bewusst aus (wie in
|
||||||
|
# ajax/tahoma.php); ohne diese Zeile warnt urllib3 bei jeder einzelnen
|
||||||
|
# Abfrage und uebertoent das eigentliche Protokoll.
|
||||||
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
|
|
||||||
class Transport:
|
class Transport:
|
||||||
"""Gemeinsame Form. Wer nichts zu lesen hat, erbt die leeren Methoden."""
|
"""Gemeinsame Form. Wer nichts zu lesen hat, erbt die leeren Methoden."""
|
||||||
@@ -137,20 +148,15 @@ class HTTPTransport(Transport):
|
|||||||
JSON-Antwort ("tC", "a_voltage"). Solche Geraete melden sich nicht von
|
JSON-Antwort ("tC", "a_voltage"). Solche Geraete melden sich nicht von
|
||||||
selbst, sie werden im Poll-Takt gefragt.
|
selbst, sie werden im Poll-Takt gefragt.
|
||||||
|
|
||||||
Beim Senden haengen die Parameter als Abfrageargumente an die actor_url.
|
Beim Senden wird die command_url als Abfrageargument an die actor_url
|
||||||
Die Schaltbefehle von Shelly heissen aber nicht so, wie sie in der
|
gehaengt ("turn=on") und die Parameter mit ihrem eigenen Namen dazu.
|
||||||
Datenbank stehen - dafuer steht unten eine kleine Uebersetzungstabelle.
|
Frueher stand hier eine Uebersetzungstabelle, weil die Shelly-Kommandos
|
||||||
Wer ein anderes HTTP-Geraet anschliesst, erweitert genau die.
|
ohne URL in der Datenbank landeten - das ist im Discovery behoben, die
|
||||||
|
Zuordnung gehoert dorthin und nicht in den Runner.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
schema = "http"
|
schema = "http"
|
||||||
|
|
||||||
SHELLY_BEFEHLE = {
|
|
||||||
"turn_on": {"on": "true"},
|
|
||||||
"turn_off": {"on": "false"},
|
|
||||||
"toggle": {"toggle": "true"},
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, requests_modul, timeout=5, dry_run=False):
|
def __init__(self, requests_modul, timeout=5, dry_run=False):
|
||||||
self.requests = requests_modul
|
self.requests = requests_modul
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
@@ -181,14 +187,17 @@ class HTTPTransport(Transport):
|
|||||||
return werte
|
return werte
|
||||||
|
|
||||||
def senden(self, aktion):
|
def senden(self, aktion):
|
||||||
argumente = dict(self.SHELLY_BEFEHLE.get(aktion["command_url"], {}))
|
argumente = {}
|
||||||
|
for teil in (aktion["command_url"] or "").split("&"):
|
||||||
|
if "=" in teil:
|
||||||
|
schluessel, wert = teil.split("=", 1)
|
||||||
|
argumente[schluessel] = wert
|
||||||
for p in aktion["params"]:
|
for p in aktion["params"]:
|
||||||
if p["url"]:
|
if p["url"]:
|
||||||
argumente[p["url"]] = p["wert"]
|
argumente[p["url"]] = p["wert"]
|
||||||
if not argumente and aktion["command_url"]:
|
if not argumente:
|
||||||
# Kein bekannter Schaltbefehl und keine Parameter: das Kommando
|
raise RuntimeError("Kommando ohne URL und ohne Parameter - im "
|
||||||
# als Argument anhaengen, mehr laesst sich hier nicht ableiten.
|
"Geraetemodell fehlt die Angabe, was zu schicken ist")
|
||||||
argumente[aktion["command_url"]] = "true"
|
|
||||||
if self.dry_run:
|
if self.dry_run:
|
||||||
logger.info("[dry-run] HTTP %s %s", aktion["actor_url"], argumente)
|
logger.info("[dry-run] HTTP %s %s", aktion["actor_url"], argumente)
|
||||||
return
|
return
|
||||||
@@ -197,16 +206,119 @@ class HTTPTransport(Transport):
|
|||||||
raise RuntimeError("HTTP %d von %s" % (antwort.status_code, aktion["actor_url"]))
|
raise RuntimeError("HTTP %d von %s" % (antwort.status_code, aktion["actor_url"]))
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# WLED
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
class WLEDTransport(Transport):
|
||||||
|
"""
|
||||||
|
WLED-Lampen sprechen ueber eine einzige JSON-Schnittstelle:
|
||||||
|
GET http://IP/json/state liefert den Zustand, POST dorthin setzt ihn.
|
||||||
|
|
||||||
|
Das Geraetemodell nutzt das elegant aus - die command_url ist eine
|
||||||
|
JSON-Vorlage mit Platzhaltern:
|
||||||
|
|
||||||
|
{"bri":%brightness%}
|
||||||
|
{"seg":[{"col":[[%red%,%green%,%blue%]]}]}
|
||||||
|
|
||||||
|
Gesendet wird also nicht Argument fuer Argument, sondern die ausgefuellte
|
||||||
|
Vorlage am Stueck. Deshalb haben die Parameter hier auch keine eigene URL:
|
||||||
|
ihr Name ist der Platzhalter.
|
||||||
|
|
||||||
|
Die state_url ist ein Pfad in die Antwort ("on", "bri",
|
||||||
|
"seg[0].col[0]") - dieselbe Schreibweise, die auch in current_value steht.
|
||||||
|
"""
|
||||||
|
|
||||||
|
schema = "wled://"
|
||||||
|
|
||||||
|
def __init__(self, requests_modul, timeout=5, dry_run=False):
|
||||||
|
self.requests = requests_modul
|
||||||
|
self.timeout = timeout
|
||||||
|
self.dry_run = dry_run
|
||||||
|
self.states = []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _adresse(actor_url):
|
||||||
|
return "http://" + actor_url[len("wled://"):].rstrip("/")
|
||||||
|
|
||||||
|
def zustaende_anmelden(self, states):
|
||||||
|
self.states = [s for s in states if s["state_url"]]
|
||||||
|
logger.info("WLED: %d Messwerte an %d Lampen",
|
||||||
|
len(self.states), len({s["actor_url"] for s in self.states}))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _pfad(daten, pfad):
|
||||||
|
""""seg[0].col[0]" in der Antwort nachschlagen."""
|
||||||
|
for teil in pfad.split("."):
|
||||||
|
treffer = re.match(r"^([^\[]*)((?:\[\d+\])*)$", teil)
|
||||||
|
if not treffer:
|
||||||
|
return None
|
||||||
|
if treffer.group(1):
|
||||||
|
daten = daten[treffer.group(1)]
|
||||||
|
for index in re.findall(r"\[(\d+)\]", treffer.group(2)):
|
||||||
|
daten = daten[int(index)]
|
||||||
|
return daten
|
||||||
|
|
||||||
|
def zustaende_lesen(self):
|
||||||
|
werte = {}
|
||||||
|
nach_lampe = {}
|
||||||
|
for s in self.states:
|
||||||
|
nach_lampe.setdefault(s["actor_url"], []).append(s)
|
||||||
|
for actor_url, states in nach_lampe.items():
|
||||||
|
try:
|
||||||
|
antwort = self.requests.get(self._adresse(actor_url) + "/json/state",
|
||||||
|
timeout=self.timeout)
|
||||||
|
daten = antwort.json()
|
||||||
|
except Exception as fehler:
|
||||||
|
logger.debug("WLED %s nicht erreichbar: %s", actor_url, fehler)
|
||||||
|
continue
|
||||||
|
for s in states:
|
||||||
|
try:
|
||||||
|
werte[s["id"]] = str(self._pfad(daten, s["state_url"]))
|
||||||
|
except (KeyError, IndexError, TypeError):
|
||||||
|
logger.debug("WLED %s: Pfad %s nicht gefunden", actor_url, s["state_url"])
|
||||||
|
return werte
|
||||||
|
|
||||||
|
def senden(self, aktion):
|
||||||
|
vorlage = aktion["command_url"]
|
||||||
|
if not vorlage:
|
||||||
|
raise RuntimeError("WLED-Kommando ohne Vorlage")
|
||||||
|
for p in aktion["params"]:
|
||||||
|
vorlage = vorlage.replace("%" + p["name"] + "%", str(p["wert"]))
|
||||||
|
try:
|
||||||
|
rumpf = json.loads(vorlage)
|
||||||
|
except ValueError:
|
||||||
|
# Ein nicht ersetzter Platzhalter oder ein Textwert an einer
|
||||||
|
# Stelle, wo eine Zahl stehen muss. Lieber hier abbrechen als der
|
||||||
|
# Lampe etwas Unverstaendliches schicken.
|
||||||
|
raise RuntimeError("WLED-Vorlage ergibt kein gueltiges JSON: " + vorlage[:120])
|
||||||
|
if self.dry_run:
|
||||||
|
logger.info("[dry-run] WLED %s <- %s", aktion["actor_url"],
|
||||||
|
json.dumps(rumpf, ensure_ascii=False))
|
||||||
|
return
|
||||||
|
antwort = self.requests.post(self._adresse(aktion["actor_url"]) + "/json/state",
|
||||||
|
json=rumpf, timeout=self.timeout)
|
||||||
|
if antwort.status_code >= 400:
|
||||||
|
raise RuntimeError("WLED antwortete mit %d" % antwort.status_code)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Tahoma
|
# Tahoma
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
class TahomaTransport(Transport):
|
class TahomaTransport(Transport):
|
||||||
"""
|
"""
|
||||||
Die actor_url ist die deviceURL (io://...), die state_url ein Statusname
|
Die actor_url ist die deviceURL, die state_url ein Statusname
|
||||||
("core:ClosureState"), die command_url ein Kommandoname ("setClosure").
|
("core:ClosureState"), die command_url ein Kommandoname ("setClosure").
|
||||||
Geschickt wird ueber exec/apply - genauso wie in ajax/tahoma.php, nur ohne
|
Geschickt wird ueber exec/apply - genauso wie in ajax/tahoma.php, nur ohne
|
||||||
die dortige Sonderbehandlung fuer "faehrt gerade".
|
die dortige Sonderbehandlung fuer "faehrt gerade".
|
||||||
|
|
||||||
|
Zustaendig ist dieser Transport fuer alles, was die Kennung der eigenen
|
||||||
|
Box in der URL traegt. Am Schema laesst sich das nicht festmachen: es
|
||||||
|
beschreibt die Funkart, und dieselbe Box liefert io:// fuer die
|
||||||
|
Jalousien, rts:// fuer die Dachfenster und internal:// fuer die Alarm-
|
||||||
|
anlage. Ohne PIN in der config.ini ist niemand zustaendig - dann meldet
|
||||||
|
der Runner beim Ausloesen "kein Transport", statt still nichts zu tun.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
schema = "io://"
|
schema = "io://"
|
||||||
@@ -219,6 +331,9 @@ class TahomaTransport(Transport):
|
|||||||
self.dry_run = dry_run
|
self.dry_run = dry_run
|
||||||
self.states = []
|
self.states = []
|
||||||
|
|
||||||
|
def passt(self, actor_url):
|
||||||
|
return bool(self.pin) and ("://" + self.pin + "/") in actor_url
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def basis(self):
|
def basis(self):
|
||||||
return "https://gateway-%s:8443/enduser-mobile-web/1/enduserAPI" % self.pin
|
return "https://gateway-%s:8443/enduser-mobile-web/1/enduserAPI" % self.pin
|
||||||
|
|||||||
@@ -365,13 +365,13 @@ class DatabaseManager:
|
|||||||
INSERT INTO actor_commands (actor_id, command_name, command_url)
|
INSERT INTO actor_commands (actor_id, command_name, command_url)
|
||||||
VALUES (%s, %s, %s)
|
VALUES (%s, %s, %s)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
command_name = VALUES(command_name)
|
command_url = VALUES(command_url)
|
||||||
"""
|
"""
|
||||||
cursor.execute(cmd_query, (actor_id, command_name, command_url))
|
cursor.execute(cmd_query, (actor_id, command_name, command_url))
|
||||||
query = """
|
query = """
|
||||||
SELECT ID FROM actor_commands where actor_id = %s and command_url = %s
|
SELECT ID FROM actor_commands where actor_id = %s and command_name = %s
|
||||||
"""
|
"""
|
||||||
cursor.execute(query, (actor_id, command_url))
|
cursor.execute(query, (actor_id, command_name))
|
||||||
command_id = cursor.fetchone()[0]
|
command_id = cursor.fetchone()[0]
|
||||||
|
|
||||||
# Parameter einfügen
|
# Parameter einfügen
|
||||||
@@ -381,14 +381,14 @@ class DatabaseManager:
|
|||||||
(command_id, parameter_name, parameter_type, min_value, max_value, possible_values, url)
|
(command_id, parameter_name, parameter_type, min_value, max_value, possible_values, url)
|
||||||
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
parameter_name = VALUES(parameter_name),
|
|
||||||
parameter_type = VALUES(parameter_type),
|
parameter_type = VALUES(parameter_type),
|
||||||
min_value = VALUES(min_value),
|
min_value = VALUES(min_value),
|
||||||
max_value = VALUES(max_value),
|
max_value = VALUES(max_value),
|
||||||
possible_values = VALUES(possible_values)
|
possible_values = VALUES(possible_values),
|
||||||
|
url = VALUES(url)
|
||||||
"""
|
"""
|
||||||
param_name = param.get('name', '')
|
param_name = param.get('name', '')
|
||||||
param_type = param.get('type', '')
|
param_type = self.PARAMETER_TYPES_MAPPING.get(param.get('type', ''), 4)
|
||||||
min_val = param.get('min')
|
min_val = param.get('min')
|
||||||
max_val = param.get('max')
|
max_val = param.get('max')
|
||||||
possible_vals = json.dumps(param.get('values')) if 'values' in param else ""
|
possible_vals = json.dumps(param.get('values')) if 'values' in param else ""
|
||||||
@@ -404,11 +404,11 @@ class DatabaseManager:
|
|||||||
(actor_id, state_name, state_type, current_value, unit, url, possible_values)
|
(actor_id, state_name, state_type, current_value, unit, url, possible_values)
|
||||||
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
state_name = VALUES(state_name),
|
|
||||||
state_type = VALUES(state_type),
|
state_type = VALUES(state_type),
|
||||||
current_value = VALUES(current_value),
|
current_value = VALUES(current_value),
|
||||||
possible_values = VALUES(possible_values),
|
possible_values = VALUES(possible_values),
|
||||||
unit = VALUES(unit)
|
unit = VALUES(unit),
|
||||||
|
url = VALUES(url)
|
||||||
"""
|
"""
|
||||||
state_name = state.get('name', '')
|
state_name = state.get('name', '')
|
||||||
state_type = self.PARAMETER_TYPES_MAPPING.get(state.get('type', 0), 4)
|
state_type = self.PARAMETER_TYPES_MAPPING.get(state.get('type', 0), 4)
|
||||||
@@ -474,10 +474,10 @@ class DatabaseManager:
|
|||||||
(actor_id, state_name, state_type, current_value, unit, url)
|
(actor_id, state_name, state_type, current_value, unit, url)
|
||||||
VALUES (%s, %s, %s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s, %s, %s)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
state_name = VALUES(state_name),
|
|
||||||
state_type = VALUES(state_type),
|
state_type = VALUES(state_type),
|
||||||
current_value = VALUES(current_value),
|
current_value = VALUES(current_value),
|
||||||
unit = VALUES(unit)
|
unit = VALUES(unit),
|
||||||
|
url = VALUES(url)
|
||||||
"""
|
"""
|
||||||
state_name = state.get('name', '')
|
state_name = state.get('name', '')
|
||||||
state_type = self.PARAMETER_TYPES_MAPPING.get(state.get('type', 0), 4)
|
state_type = self.PARAMETER_TYPES_MAPPING.get(state.get('type', 0), 4)
|
||||||
|
|||||||
@@ -267,14 +267,15 @@ class ShellyModule(BaseModule):
|
|||||||
'name': f"{device_name}_Switch_{i}",
|
'name': f"{device_name}_Switch_{i}",
|
||||||
'url': f"http://{ip}/rpc/Switch.Set?id={i}",
|
'url': f"http://{ip}/rpc/Switch.Set?id={i}",
|
||||||
'commands': [
|
'commands': [
|
||||||
{'command': 'turn_on', 'parameters': []},
|
{'command': 'turn_on', 'url': 'on=true', 'parameters': []},
|
||||||
{'command': 'turn_off', 'parameters': []},
|
{'command': 'turn_off', 'url': 'on=false', 'parameters': []},
|
||||||
{'command': 'toggle', 'parameters': []}
|
{'command': 'toggle', 'parameters': []}
|
||||||
],
|
],
|
||||||
'states': [
|
'states': [
|
||||||
{
|
{
|
||||||
'name': 'output',
|
'name': 'output',
|
||||||
'type': 'boolean',
|
'type': 'boolean',
|
||||||
|
'url': 'output',
|
||||||
'current_value': switch_data.get('output', False)
|
'current_value': switch_data.get('output', False)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -444,14 +445,15 @@ class ShellyModule(BaseModule):
|
|||||||
'name': f"{device_name}_Relay_{i}",
|
'name': f"{device_name}_Relay_{i}",
|
||||||
'url': f"http://{ip}/relay/{i}",
|
'url': f"http://{ip}/relay/{i}",
|
||||||
'commands': [
|
'commands': [
|
||||||
{'command': 'turn_on', 'parameters': []},
|
{'command': 'turn_on', 'url': 'turn=on', 'parameters': []},
|
||||||
{'command': 'turn_off', 'parameters': []},
|
{'command': 'turn_off', 'url': 'turn=off', 'parameters': []},
|
||||||
{'command': 'toggle', 'parameters': []}
|
{'command': 'toggle', 'url': 'turn=toggle', 'parameters': []}
|
||||||
],
|
],
|
||||||
'states': [
|
'states': [
|
||||||
{
|
{
|
||||||
'name': 'ison',
|
'name': 'ison',
|
||||||
'type': 'boolean',
|
'type': 'boolean',
|
||||||
|
'url': 'ison',
|
||||||
'current_value': relay.get('ison', False)
|
'current_value': relay.get('ison', False)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user