An alle die sich einen übertriebenen Wohnungsputz-Weihnachtsstress machen.
Es kommt das Christkind - nicht das Gesundheitsamt.
Trotzdem eine frohe Weihnachtszeit und einen guten Rutsch in das neue Jahr.
An alle die sich einen übertriebenen Wohnungsputz-Weihnachtsstress machen.
Es kommt das Christkind - nicht das Gesundheitsamt.
Trotzdem eine frohe Weihnachtszeit und einen guten Rutsch in das neue Jahr.
@mcm57
Displays:
www.tindie.com/products/electronics-by-nic/5-pcs-29-epaper-tags-for-openepaperlink
AP:
www.tindie.com/products/electronics-by-nic/openepaperlink-mini-ap-v3-zigbee-wifi-gateway/
@doggie sagte in Wasserzähler - Version 2 - all-in-device:
Mir scheint das ganze AI on the edge Projekt ist nicht annähernd ausgereift. Sehr, sehr schade. Und enttäuschend.
Was sagen andere dazu?
Gibt es eigentlich Systeme die fehlerfrei laufen?
Als Entwickler des Systems würde mich so ein Fehler nicht schlafen lassen.
Ein klein wenig Demut wäre nicht schlecht. Hier macht jemand ein Projekt in seiner Freizeit. Alles kostenlos. Letztendlich ist es "Bastelei". Was nicht bedeutet das es sehr, sehr gute Projekte sind.
Wenn ein 100% zuverlässiges System gesucht wird, sollte man sich event. im Fachhandel umschauen. Ob dort die entsprechenden System und Programme auch kostenlos angeboten werden, kann ich so nicht sagen. Ich glaube aber eher nicht.
Grüße
Manfred
Vielen Dank.
Ich liebe dieses Forum und die Leute darin.
@bananajoe sagte in EPS E-INK Display Ansteuerung -> Statusdisplay für 2€:
@beowolf hast du die selbst geflasht? In der Bucht sind die gerade zu kaufen und ich starre gerade schon eine ganze weile auf das Pinout wo ich denn ran müsste ... (ich bin Tasmota-verwöhnt, da wird immer ein Bild hinterlegt welche Pins man nehmen muss)
Eines vorab. Das ist nicht auf meinem Mist gewachsen. Ich habe das auch nur mit der Hilfe von Aaron Christophel hin bekommen.
Vielen, vielen Dank dafür.
Hier z.B. ein Video von ihm.
https://www.youtube.com/watch?v=8oQdo9bJ7Rk
Es geht um diesen TAG
https://github.com/jjwbruijn/OpenEPaperLink/wiki/2.9″-EL029H3WRA#pinout
Für den Anschluss und Positionierung der POGO-Pins geht dieses z.B.
https://github.com/jjwbruijn/OpenEPaperLink/blob/master/Hardware/M3 Newton Jigs by Jelmer/M3-2.9.stl
Ich habe dieses
https://github.com/jjwbruijn/OpenEPaperLink/tree/master/Tag_Flasher
herunter geladen.
Nun in das Verzeichnis ESP32_Flasher wechseln und dann mit der Hilfe von Visual Studio Code den Flasher auf einen ESP32 S2 aufgespielen.
Den ESP dann so wie hier mit dem TAG verbinden.
Nun ein Verzeichnis höher gehen. Also in den Tag_Flasher Ordner gehen.
Dann diese Datei herunterladen und in das Verzeichnis ablegen.
https://github.com/jjwbruijn/OpenEPaperLink/blob/master/binaries/Tag/SOL_M3_Uni_full_26.bin
Ich habe es mit diesem Befehl gemacht.
python3 OEPL-Flasher.py -p COM11 -e -n write SOL_M3_Uni_full_26.bin --flash --pt
COM11 natürlich anpassen.
Für das Flashen muß auf dem Rechner Python installiert sein.
Hier die passende Version laden.
Bei dem Versuch kamen bei mir ein paar Meldungen das diese oder jenes nicht da ist.
Ich musste z.b. dieses nachinstallieren: "pip install pyserial". Sollten da noch Meldungen kommen. einfach kurz bei google suchen.
Danach sollte der Tag mit OPenEPaper geflasht sein.
Ich hoffe ich habe nichts vergessen.
Halloö zusammen,
event. ist das ja etwas für den ein oder anderen.
https://www.tindie.com/products/muino/smart-water-meter-reader/
Grüße
@bananajoe sagte in EPS E-INK Display Ansteuerung -> Statusdisplay für 2€:
@beowolf sagte in EPS E-INK Display Ansteuerung -> Statusdisplay für 2€:
Ich bin ein wenig Ratlos und weiß nicht wo ich anfangen soll.
Hier mal grob in Schritten zusammengefasst:
Schritt 1: Eigene VIS für die ePaper Tags
.........
Für die schwarzen TAGs muß die Auflösung geändert werden. Sonst gibt es nur Schrott in der Anzeige.
Also auf 384 x 168 Pixel
Grüße
So, ich habe jetzt das Skript mal ein wenig umgebaut.
sendTo('puppeteer.0', 'screenshot', {
url: urlOfVISView,
path: imageSaveToFilenameWithPath,
width: viewWidth,
height: viewHeight,
quality: jpgQuality,
waitOption: {
waitForSelector: waitForSelector,
waitForTimeout: 20000
},
fullPage: false,
clip: {
x: cutoutX,
y: cutoutY,
width: cutoutWidth,
height: cutoutHeight
}
}, obj => {
if (obj.error) {
console.warn("Fehler beim Aufruf der View: " + urlOfVISView + " => " + obj.error.message);
} else {
const http = require('http');
const https = require('https');
const { URL } = require('url');
const boundary = '--------------------------' + Date.now().toString(16);
const CRLF = '\r\n';
// Payload-Erstellung
const payload = Buffer.concat([
Buffer.from(`--${boundary}${CRLF}Content-Disposition: form-data; name="dither"${CRLF}${CRLF}0${CRLF}`),
Buffer.from(`--${boundary}${CRLF}Content-Disposition: form-data; name="mac"${CRLF}${CRLF}${ePaperMAC}${CRLF}`),
Buffer.from(`--${boundary}${CRLF}Content-Disposition: form-data; name="image"; filename="screenshot.jpg"${CRLF}Content-Type: image/jpeg${CRLF}${CRLF}`),
Buffer.from(obj.result, 'binary'),
Buffer.from(`${CRLF}--${boundary}--${CRLF}`)
]);
// URL Parsing
const url = new URL(imageUploadURL);
const options = {
hostname: url.hostname,
port: url.port || (url.protocol === 'https:' ? 443 : 80),
path: url.pathname + (url.search || ''),
method: 'POST',
headers: {
'Content-Type': 'multipart/form-data; boundary=' + boundary,
'Content-Length': payload.length
}
};
// Protokollwahl
const protocol = url.protocol === 'https:' ? https : http;
// HTTP Request
const req = protocol.request(options, function(res) {
console.log('ImageUploadStatusCode:', res.statusCode);
res.on('data', function(chunk) {
console.log('Response:', chunk.toString());
});
});
req.on('error', function(e) {
console.error('Fehler beim Hochladen:', e.message);
});
req.write(payload);
req.end();
}
});
Na ja, ich bin jetzt mal ehrlich. Ich habe ChatGPT solange damit gernervt bis ich eine Version hatte die jetzt funktioniert.
Ich habe keine Ahnung was da wie gemacht wird, aber es läuft. Die TAG werden sehr schnell aktualisiert und alles ohne Fehlermeldungen.
Grüße
Manfred
Genau für den Fall, weil "request" nicht mehr geht, hatte ich doch das Skript geändert.
Hier läuft es ohne Probleme mit der Java-Skriptversion 8.3.1
Habe da mal ne Frage.
Im Netz findet man mehrere Version der Installationsroutine
curl -sSLf https://iobroker.net/install.sh | bash -
curl -sL https://iobroker.net/install.sh | bash -
curl -sLf https://iobroker.net/install.sh | bash -
Welche ist denn nun die Richtige, bzw. wo ist der Unterschied?
Grüße
Manfred
@arteck
Mag sein. Mein kleines PRogramm läuft "auserhalb" von ioBroker.
Und das tolle an meinem Programm ist, es ist vollkommen kostenlos. Es kostet keine 5,95 €.
Das ist ein kleiner Webserver-Scanner für das eigene Netzwerk. Das Programm listet dann die gefundenen Adressen auf und durch einen Mausklick kann die entsprechende Adresse direkt aufgerufen werden.
Der IP-Bereich kann eingestellt werden.
Ich habe mal versucht eine Anleitung für das Programm zu schreiben.
Entpacke die ZIP-Datei.
Installiere PyInstaller (falls noch nicht geschehen):
pip install pyinstaller
Starte das Build-Skript build_webscanner.bat per Doppelklick oder über die Eingabeaufforderung:
build_webscanner.bat
Die fertige EXE-Datei findest du danach im Ordner dist.
Hier der Inhalt der einzelnen Dateien. Nichts böses drin.
import tkinter as tk
from tkinter import ttk
import webbrowser
import socket
import threading
import queue
from concurrent.futures import ThreadPoolExecutor
START_IP = "192.168.49.1"
END_IP = "192.168.49.254"
PORTS = [80]
def ip_to_int(ip):
parts = list(map(int, ip.split(".")))
return (parts[0]<<24) + (parts[1]<<16) + (parts[2]<<8) + parts[3]
def int_to_ip(i):
return f"{(i>>24)&0xFF}.{(i>>16)&0xFF}.{(i>>8)&0xFF}.{i&0xFF}"
def generate_ips(start_ip, end_ip):
start = ip_to_int(start_ip)
end = ip_to_int(end_ip)
for i in range(start, end+1):
yield int_to_ip(i)
class WebserverScanner:
def __init__(self, progress_callback=None):
self.progress_callback = progress_callback
def scan_ip_port(self, ip, port):
try:
with socket.create_connection((ip, port), timeout=0.5):
return True
except:
return False
def get_hostname(self, ip):
try:
return socket.gethostbyaddr(ip)[0]
except:
return "Kein Hostname"
def scan_threaded(self, ips, ports):
results = []
total = len(ips)*len(ports)
count_lock = threading.Lock()
count = [0]
def scan_one(ip_port):
ip, port = ip_port
if self.scan_ip_port(ip, port):
hostname = self.get_hostname(ip)
res = {"ip": ip, "hostname": hostname, "port": port}
else:
res = None
with count_lock:
count[0] += 1
if self.progress_callback:
self.progress_callback(count[0], total)
return res
ip_ports = [(ip, port) for ip in ips for port in ports]
with ThreadPoolExecutor(max_workers=100) as executor:
for r in executor.map(scan_one, ip_ports):
if r:
results.append(r)
return results
class WebserverViewerApp:
def __init__(self, root):
self.root = root
self.root.title("Webserver Scanner 192.168.49.x")
self.root.geometry("900x700")
self.entries = []
self.queue = queue.Queue()
self.resize_after_id = None
self.current_columns = None # Merke letzte Spaltenanzahl
self.entries_displayed = None
self.header = tk.Label(root, text="Webserver Scanner 192.168.49.1 - 254", font=("Arial", 18))
self.header.pack(pady=10)
self.progress = ttk.Progressbar(root, orient="horizontal", length=600, mode="determinate")
self.progress.pack(pady=10)
self.status_label = tk.Label(root, text="Bereit", font=("Arial", 12))
self.status_label.pack()
self.update_btn = tk.Button(root, text="Jetzt scannen", command=self.start_scan)
self.update_btn.pack(pady=10)
self.canvas = tk.Canvas(root)
self.scroll_frame = tk.Frame(self.canvas)
self.scrollbar = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.scrollbar.pack(side="right", fill="y")
self.canvas.pack(fill="both", expand=True)
# Erstelle Fenster-Item für scroll_frame im Canvas
self.canvas_frame = self.canvas.create_window((0,0), window=self.scroll_frame, anchor="nw")
# Scrollregion aktualisieren bei Größe des scroll_frame
self.scroll_frame.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
# Breite des scroll_frame immer an Canvas-Breite anpassen
self.canvas.bind('<Configure>', self.resize_scroll_frame)
# Debounced Resize-Handler für Fenstergröße
self.root.bind("<Configure>", self.on_resize)
self.root.after(100, self.check_queue)
def resize_scroll_frame(self, event):
self.canvas.itemconfig(self.canvas_frame, width=event.width)
def on_resize(self, event):
# Debounce mit 300ms Delay
if self.resize_after_id:
self.root.after_cancel(self.resize_after_id)
self.resize_after_id = self.root.after(300, lambda: self.update_display(self.entries))
def start_scan(self):
self.update_btn.config(state="disabled")
self.progress['value'] = 0
self.status_label.config(text="Scan läuft...")
threading.Thread(target=self.run_scan, daemon=True).start()
def progress_callback(self, count, total):
percent = int((count / total) * 100)
self.queue.put(("progress", percent))
def run_scan(self):
scanner = WebserverScanner(progress_callback=self.progress_callback)
ips = list(generate_ips(START_IP, END_IP))
results = scanner.scan_threaded(ips, PORTS)
self.queue.put(("done", results))
def check_queue(self):
try:
while True:
msg, data = self.queue.get_nowait()
if msg == "progress":
self.progress['value'] = data
self.status_label.config(text=f"Scan läuft... {data}%")
elif msg == "done":
self.entries = data
self.update_display(data)
self.status_label.config(text=f"Scan abgeschlossen. {len(data)} Server gefunden.")
self.update_btn.config(state="normal")
except queue.Empty:
pass
self.root.after(100, self.check_queue)
def update_display(self, entries):
# Berechne aktuelle Spaltenanzahl
frame_width = self.canvas.winfo_width() or 900
box_min_width = 170
columns = max(1, frame_width // box_min_width)
# Prüfe, ob Spaltenanzahl oder Einträge sich geändert haben
if self.current_columns == columns and self.entries_displayed == entries:
return
self.current_columns = columns
self.entries_displayed = entries
# Alte Widgets löschen
for widget in self.scroll_frame.winfo_children():
widget.destroy()
if not entries:
tk.Label(self.scroll_frame, text="Keine Webserver gefunden", font=("Arial", 14), fg="red").pack(pady=20)
return
box_height = 110
for index, entry in enumerate(entries):
row = index // columns
col = index % columns
container = tk.Frame(self.scroll_frame, bd=1, relief="solid", padx=5, pady=5)
container.grid(row=row, column=col, padx=5, pady=5, sticky="nsew")
ip_label = tk.Label(container, text=entry['ip'], font=("Arial", 12, "bold"))
ip_label.pack()
if entry['hostname']:
host_label = tk.Label(container, text=entry['hostname'], font=("Arial", 10), fg="gray")
host_label.pack()
btn_color = "#4CAF50" if entry['port'] == 80 else "#2196F3"
open_button = tk.Button(container, text=f"Öffnen (Port {entry['port']})",
bg=btn_color, fg="white",
command=lambda ip=entry['ip'], port=entry['port']: self.open_url(ip, port))
open_button.pack(pady=5, fill="x")
# Spalten gleichmäßig verteilen
for c in range(columns):
self.scroll_frame.grid_columnconfigure(c, weight=1)
def open_url(self, ip, port):
url = f"https://{ip}" if port == 443 else f"http://{ip}"
webbrowser.open(url)
if __name__ == "__main__":
root = tk.Tk()
app = WebserverViewerApp(root)
root.mainloop()
build_webscanner.bat
@echo off
echo.
echo ===============================
echo Building webscanner.exe ...
echo ===============================
echo.
pyinstaller webscanner.spec
echo.
echo Fertig! EXE liegt in /dist/webscanner.exe
pause
webscanner.spec
# webscanner.spec
block_cipher = None
a = Analysis(
['webscanner.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='webscanner',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False, # GUI-Anwendung (kein Terminalfenster)
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
name='webscanner',
)
Das Programm kann natürlich auch direkt im cmd-Fenster (Windows) durch python webscanner.py im entsprechenden Verzeichnis gestartet werden.
Vor dem Start oder der Erstellung der EXE-Datei muß natürlich der IP-Bereich in webscanner.py angepasst werden.
START_IP = "192.168.49.1"
END_IP = "192.168.49.254"
PORTS = [80]
Wenn es läuft, sollte es etwa so aussehen:
Viel Spaß damit
Hier ein geändertes Skript für die, die mehrere APs haben.
//
// #############################################
//
// Dieses Script ermöglicht die Buttonabfrage.
//
// In dem Script wird die Socket-Verbindung von Beowolf verwendet (https://forum.iobroker.net/topic/66380/e-ink-display-openepaperlink-displayanzeige-mit-batterie/809?_=1747419968864)
//
// Ein erweitertes Skript von Eisbaeeer ist hier (https://forum.iobroker.net/topic/81101/openepaperlink-script-für-tastenabfrage )
//
// Die Button-States werden unter dem konfigurierten basePathPrefix erzeugt.
//
// #############################################
// #############################################
//
// Konfigurierbare Variablen - Hier die Anpassungen vornehmen!
//
const rootPath = '0_userdata.0'; // Bei Bedarf anpassen
const controlRoot = 'EPaperControl'; // Oberordner für Steuerung
const buttonRoot = 'Buttons'; // Unterordner für Buttons
const accesspoints = [
{ location: 'Erdgeschoss', ip: '192.168.49.185' }, // Accesspoint IP Adresse
{ location: 'Obergeschoss', ip: '192.168.49.186' }, // Accesspoint IP Adresse
{ location: 'Dachgeschoss', ip: '192.168.49.187' }, // Accesspoint IP Adresse
{ location: 'Hühnerhaus', ip: '192.168.49.139' } // Accesspoint IP Adresse
];
//
// ENDE Anpassungen! Ab hier nichts mehr ändern!
// #############################################
//
// Automatisch generierte Pfade für jeden Accesspoint
accesspoints.forEach(ap => {
ap.name = `${ap.location}-AP`;
ap.controlState = `${rootPath}.${controlRoot}.${ap.location}.Start`;
ap.buttonPathPrefix = `${rootPath}.${controlRoot}.${ap.location}.${buttonRoot}`;
});
const WebSocket = require('ws');
let wsConnections = {};
let pingIntervals = {};
let scriptStatus = {};
// Initialisierung der Steuerobjekte
accesspoints.forEach(ap => {
scriptStatus[ap.name] = false;
setObject(ap.controlState, {
type: 'state',
common: {
name: `Start/Stop ${ap.name}`,
type: 'boolean',
role: 'switch',
read: true,
write: true,
def: false
},
native: {}
});
on({ id: ap.controlState, change: 'ne' }, (obj) => {
if (obj.state.val === true) {
scriptStatus[ap.name] = true;
connectWebSocket(ap.name, ap.ip);
} else {
scriptStatus[ap.name] = false;
disconnectWebSocket(ap.name);
}
});
getState(ap.controlState, (err, state) => {
if (!err && state && state.val === true) {
scriptStatus[ap.name] = true;
connectWebSocket(ap.name, ap.ip);
}
});
});
function connectWebSocket(name, ip) {
if (wsConnections[name]) return;
const url = `ws://${ip}/ws`;
const ws = new WebSocket(url);
wsConnections[name] = ws;
ws.on('open', () => {
console.log(`WebSocket ${name} verbunden (${ip})`);
pingIntervals[name] = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.ping();
}
}, 10000);
});
ws.on('message', (data) => {
try {
const parsed = JSON.parse(data);
handleData(name, parsed);
} catch {
// Parsing-Fehler ignorieren
}
});
ws.on('close', () => {
clearInterval(pingIntervals[name]);
delete wsConnections[name];
if (scriptStatus[name]) {
setTimeout(() => connectWebSocket(name, ip), 5000);
}
});
ws.on('error', () => {
// Fehler ignorieren
});
}
function disconnectWebSocket(name) {
if (wsConnections[name]) {
wsConnections[name].close();
clearInterval(pingIntervals[name]);
delete wsConnections[name];
delete pingIntervals[name];
}
}
function handleData(apName, parsedData) {
const ap = accesspoints.find(a => a.name === apName);
if (!ap || !scriptStatus[ap.name]) return;
if (!parsedData.tags || !Array.isArray(parsedData.tags) || parsedData.tags.length === 0) return;
parsedData.tags.forEach(tag => {
if (!tag.mac) return;
const macClean = tag.mac.replace(/:/g, '');
const basePath = `${ap.buttonPathPrefix}.${macClean}`;
ensureChannelExists(basePath, tag.alias || tag.name || 'Unbenannt', () => {
if ('wakeupReason' in tag) {
const statePath = `${basePath}.wakeupReason`;
updateStateIfChanged(statePath, tag.wakeupReason);
}
});
});
}
function ensureChannelExists(id, name, callback) {
getObject(id, (err, obj) => {
if (!obj) {
setObject(id, {
type: 'channel',
common: { name: name },
native: {}
}, callback);
} else {
callback();
}
});
}
function updateStateIfChanged(id, value) {
if (value === null || value === undefined) return;
const isPrimitive = val => ['string', 'number', 'boolean'].includes(typeof val);
let storedValue = value;
let valueType = typeof value;
if (!isPrimitive(value)) {
try {
storedValue = JSON.stringify(value);
valueType = 'string';
} catch {
return;
}
}
getState(id, (err, state) => {
if (err || !state) {
setObject(id, {
type: 'state',
common: {
name: id.split('.').pop(),
type: valueType,
role: 'value',
read: true,
write: false
},
native: {}
}, () => setState(id, storedValue, true));
} else if (state.val !== storedValue) {
setState(id, storedValue, true);
}
});
}
Habe das Skript mal NUR für die Button geändert. Es wird kein Inhalt an die TAGs gesendet.
//
// #############################################
//
// Dieses Script ermöglicht die Buttonabfrage.
//
// In dem Script wird die Socket-Verbindung von Beowolf verwendet (https://forum.iobroker.net/topic/66380/e-ink-display-openepaperlink-displayanzeige-mit-batterie/809?_=1747419968864)
//
// Ein erweitertes Skript von Eisbaeeer ist hier (https://forum.iobroker.net/topic/81101/openepaperlink-script-für-tastenabfrage )
//
// Die Button-States werden unter dem konfigurierten basePathPrefix erzeugt.
//
// #############################################
// #############################################
//
// Konfigurierbare Variablen - Hier die Anpassungen vornehmen!
//
const controlStatePath = '0_userdata.0.Buttons-DG-TAGs'; // Basis für controlState und basePath
const controlState = `${controlStatePath}.Start`; // Skript starten oder beenden
const basePathPrefix = `${controlStatePath}.Tag_Buttons`; // Basis-Pfad für Button-States
const accesspointIP = '192.168.49.187'; // Accesspoint IP Adresse
//
// ENDE Anpassungen! Ab hier nichts mehr ändern!
// #############################################
const wsUrl = `ws://${accesspointIP}/ws`;
const WebSocket = require('ws');
let ws;
let pingInterval;
let scriptStopping = false;
function connectWebSocket() {
if (scriptStopping) return;
ws = new WebSocket(wsUrl);
ws.on('open', () => {
console.log('WebSocket verbunden');
startHeartbeat();
});
ws.on('message', (data) => {
try {
const parsed = JSON.parse(data);
handleData(parsed);
} catch (err) {
// Parsing-Fehler ignorieren
}
});
ws.on('close', () => {
clearInterval(pingInterval);
if (!scriptStopping) setTimeout(connectWebSocket, 5000);
});
ws.on('error', () => {
// WebSocket-Fehler ignorieren
});
}
function startHeartbeat() {
pingInterval = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.ping();
}
}, 10000);
}
function handleData(parsedData) {
if (!parsedData.tags || !Array.isArray(parsedData.tags) || parsedData.tags.length === 0) {
return;
}
parsedData.tags.forEach(tag => {
if (!tag.mac) return;
const macClean = tag.mac.replace(/:/g, '');
const basePath = `${basePathPrefix}.${macClean}`;
ensureChannelExists(basePath, tag.alias || tag.name || 'Unbenannt', () => {
if ('wakeupReason' in tag) {
const statePath = `${basePath}.wakeupReason`;
updateStateIfChanged(statePath, tag.wakeupReason);
}
});
});
}
function ensureChannelExists(id, name, callback) {
getObject(id, (err, obj) => {
if (!obj) {
setObject(id, {
type: 'channel',
common: {
name: name
},
native: {}
}, callback);
} else {
callback();
}
});
}
function updateStateIfChanged(id, value) {
if (value === null || value === undefined) return;
const isPrimitive = val => ['string', 'number', 'boolean'].includes(typeof val);
let storedValue = value;
let valueType = typeof value;
if (!isPrimitive(value)) {
try {
storedValue = JSON.stringify(value);
valueType = 'string';
} catch {
return;
}
}
getState(id, (err, state) => {
if (err || !state) {
setObject(id, {
type: 'state',
common: {
name: id.split('.').pop(),
type: valueType,
role: 'value',
read: true,
write: false
},
native: {}
}, () => setState(id, storedValue, true));
} else if (state.val !== storedValue) {
setState(id, storedValue, true);
}
});
}
function disconnectWebSocket() {
if (ws) {
ws.close();
ws = null;
}
clearInterval(pingInterval);
}
function setupControl() {
setObject(controlState, {
type: 'state',
common: {
name: 'EPaper Script Control',
type: 'boolean',
role: 'switch',
read: true,
write: true,
def: true
},
native: {}
});
on({ id: controlState, change: 'ne' }, (obj) => {
if (obj.state.val === true) {
scriptStopping = false;
connectWebSocket();
} else {
scriptStopping = true;
disconnectWebSocket();
}
});
getState(controlState, (err, state) => {
if (!err && state && state.val === true) {
connectWebSocket();
}
});
}
setupControl();
Hier wird jetzt nur der Datenpunkt "wakeupReason" angelegt.
Fein, fein. Danke dafür.
Ich habe das Skript jetzt noch nicht probiert.
Besteht mit dem Skript auch die Möglichkeit, das ich mit den Tasten auch eine z.B. Lampe über ioBroker schalten kann?
@mcm1957 sagte in Shelly Pro3EM ohne Shelly Adapter?:
Allerdings musst du dich dann selbst um alles kümmern
Ok, das ist natürlich ein guter Grund für den Adapter. Danke für die Erklärungen.
Grüße
Hallo zusammen,
ich hatte schon gesucht, aber nicht wirklich was gefunden bei dem ich sicher bin das es passt.
Deshalb hier die Frage: Kann ich einen Shelly Pro3EM ohne Shelly Adapter mit ioBroker verbinden?
Ich hatte, weiß leider nicht mehr wo gelesen, das der Shelly nur sein "eigenen mqtt-Broker" anspricht und nicht den mqtt-Adapter den ich unter ioBroker schon habe.
Stimmt das?
Grüße
Manfred