-
Autor
Hallo und guten Tag.
Ich nutze mehrere Shelly i4 zusammen mit Shelly 4fach Tastenfeldern. Diese steuern derzeit überwiegend Rollläden, sind aber auch für andere Zwecke nutzbar, bspw. zum schalten von Lampen.
Hierfür habe ich ein kleines Script erstellt, welches per Konfiguration nutzbar ist. Wer es einsetzen will, braucht nur in der vorgesehenen Datenstruktur für ihn/sie passende Einträge zu erstellen.
Mein Script wertet per Eventhandler die gedrückte Taste (id) und die Dauer des Tastendrucks aus, um daraufhin die gewünschte Nachricht zu senden. Eine solche Nachricht kann derzeit ein HTTP Request oder eine MQTT Nachricht sein.
In der Allterco Terminologie wird ein HTTP Request auch als "IO URL action" oder "Webhook" bezeichnet.
Mein Englisch ist eingeschränkt. Ich bitte ggf. vorhandene englischsprachige Verfehlungen zu entschuldigen oder/und zu korrigieren.
Hinweis - insbesondere für Anfänger: Das folgende Script soll dazu dienen, es per Konfiguration zu verwenden. Die Konfiguration ist in deren Möglichkeiten etwas komplex, kann aber auch relativ einfach zusammengesetzt werden.
Hier mein Script für einen i4:
// MQTT topics
let Pre03 = 'Wohnen-links/'; // 03 means button 0 or button 3 at right hand side
let Cmd03 = Pre03+'cmd';
let Status03 = Pre03+'status';
// HTTP requests
let Url12 = 'http://172.16.3.67/roller/0/command?go=';
let AlterUrl = 'http://172.16.3.65/relay/0?turn=toggle';
// timestamps array for storing at the begin of a button press, one element for each button
let Timestamp = [0,0,0,0]; // id 0 .. 3
// in State the last state of the target device will be stored, if neccesary
let State = ['stopped','stop']; // here: the states of two roller shutters respective there shelly controllers - first a Plus 2, second a 2.5
let Idx = {left:1, right:0}; // assigning left/right to State index
// dependState imports a command value and returns a command value depending of the assigned device state
let dependState = function(id, p){
if(id<0 || id>3) return p;
let i = id===0 || id===3 ? Idx.right : Idx.left;
return p===State[i] ? 'stop' : p;
};
// list with one entry for each input id as index
// Each entry consists of a list of durations bounds (d),
// each followed by an MQTT message (msg), consists of topic (t) and payload (p)
// or an http-request URL (url) followed by a parameter string (p), which may be empty ('').
// Finally there may exist an optional function (pointer f) and an optional index (i) as call parameter to f.
// f may processes a payload respective parameter before sending.
// For useful codings this list entries should be ascending ordered by d values.
let cfg = [ // d=duration bound, msg=mqtt message, url=http request url, t=mqtt topic, p=payload/parameter
[{d:1, msg:{t:Cmd03,p:'open'}, f:dependState, i:Idx.right}], // id 0
[{d:1, url:Url12, p:'open', f:dependState, i:Idx.left}], // id 1
[{d:1, url:Url12, p:'close', f:dependState, i:Idx.left}], // id 2
[{d:1, msg:{t:Cmd03,p:'close'},f:dependState, i:Idx.right}] // id 3
];
// only assigned to button 0 or button 3 (right hand side)
MQTT.subscribe(Status03,
function(topic, payload){
State[Idx.right] = payload;
}
);
// act() publishs an mqtt message (topic, payload) or sends an http request registered in cfg
// with the first d value greater than the imported duration.
function act(id, duration) {
let n = cfg[id].length;
for(let i=0; i<n; ++i){
let entry = cfg[id][i];
if(duration < entry.d){
//print(JSON.stringify(entry));
if(entry.msg!==undefined){
let msg = entry.msg;
if(msg!==null){
let p = entry.f===undefined ? msg.p : entry.f(entry.i, msg.p);
//print(msg.t,p);
MQTT.publish(msg.t, p, 0, false);
return true;
}
}
if(entry.url!==undefined){
Shelly.call('http.get',
{url: entry.url + ((entry.p===undefined) ? ''
: (entry.f===undefined ? entry.p : entry.f(id, entry.p)))},
function(response, errc, errm, i){
//print(response.body);
State[i] = JSON.parse(response.body).state;
}, entry.i
);
return true;
}
}
}
return false;
}
Shelly.addEventHandler(
function (event) {
if(event.name==="input"){
let id = event.info.id;
if(event.info.state) Timestamp[id] = event.info.ts
else{
let ts = Timestamp[id];
if(ts>0){
let dt = event.info.ts - ts;
// true: act could performed, false: nothing done
let done = act(id,dt);
if(!done) {
// do something else
Shelly.call('http.get',{url:AlterUrl});
}
}
}
}
}
)
Alles anzeigen
Dieses Script für ein 4fach Tastenfeld steuert zwei Rollläden, einer per Shelly Plus 2, der andere per Shelly 2.5. Zusätzlich wird eine Leuchte damit geschaltet, wenn der Tastendruck einer der 4 Tasten mindestens 1 Sekunde dauert.
Die print() Aufrufe dienen nur der Betrachtung von Laufzeitwerten zwecks Debugging oder Erkenntnisgewinnung. Sie sind bei Bedarf zu "entkommentieren", d.h. die linksseitig terminierten Kommentarsymbole (//) sind dann zu entfernen.
Erforderliche bzw. zweckdienliche Anpassungen im Script:
- Selbstverständlich sind die definierten IP-Adressen und die MQTT Topics anzupassen.
- In erster Linie sind die Einträge in der Datenstruktur (in JavaScript ist dies bereits ein Objekt) "cfg" anzupassen und/oder zu erweitern. Dazu unten mehr.
- "dependState()" ist eine Funktion, die bei Bedarf einen zu sendenden Wert (MQTT Payload oder Teil von HTTP Get - ?key=value) vor dem Senden bearbeitet. Bei Bedarf können weitere Geräte abhängige Funktionen solcher Art erstellt und in die Datenstruktur cfg eingetragen werden.
- State ist hier ein Datenfeld aus zwei Elementen. Dies kann entfallen oder gar um weitere Elemente erweitert werden. Dessen Struktur hängt letztlich von der Anwendung ab.
- Im Eventhandler kann der Abschnitt unterhalb von "if(!done)" (ab Zeile 90) nach Bedarf abgeändert werden. Die dortigen Anweisungen werden abgearbeitet, wenn kein zum Tastendruck passender Eintrag in cfg gefunden wurde.
Zu Punkt 2: Datenstruktur cfg
cfg ist ein Datenfeld (array) aus 4 Einträgen, die komplex sein können - zu jeder Taste ein Eintrag. Die Tasten Id wird als Index zum Zugriff auf das Datenfeldelement verwendet.
In jedem dieser 4 Datenfeldelemente sind im JSON Format in knapper Form die gewünschten Nachrichten und deren Rahmenbedingungen einzutragen. Ein Element darf auch leer sein, wenn es nicht gebraucht wird. Dann ist hier ein Paar eckiger Klammern [ ] einzutragen.
In diesen eckigen Klammern (Datenfeld) können beliebig viele JavaScript Objekte bzw. Strukturen eingetragen werden, jeweils per Komma getrennt.
Die einzelen Eigenschaften (d, i, msg, url, f) der Objekte können auch in anderer Reihenfolge eingetragen werden. Entscheidend sind die Namen der Eigenschaften bzw. keys (d ... f). Die Bedeutung der einzelnen Eigenschaften eines solchen Objektes sind:
- d (duration upper bound in seconds): Obere Grenze der Tastendruckdauer. Die Nachricht wird dann gesendet, wenn die Dauer des Tastendrucks kürzer ist als der d-Wert. Hier wirkt "first fit", d.h. der erste passende Eintrag wirkt, die folgenden Einträge werden dann nicht weiter geprüft. Darum sind die Dauerwerte in aufsteigender Folge einzusetzen.
- msg (MQTT message): Wenn diese Eigenschaft vorliegt, wird sie als MQTT Nachricht gesendet. Diese besteht aus t für Topic und p für Payload. Alternativ hierzu kann url eingesetzt werden.
- url (http request url): Wenn diese Eigenschaft vorliegt, wird sie als HTTP Get Nachricht gesendet. Dies erfolgt nur, wenn es im selben Objekt keine msg Eigenschaft gibt. msg und url sind alternativ und nicht etwa beide im selben Objekt einzutragen.
- f (pre send function), optional: Falls der zu sendende Wert vorher zu bearbeiten ist, ist hierfür die aufzurufende Funktion einzutragen. Dies sollte der Übersicht wegen der Name einer an anderer Stelle definierten Funktion sein. Für JavaScript affine: Es kann selbstverständlich auch eine an dieser Stelle eingetragene anonyme Funktion sein. Diese Funktion importiert zwei Parameter - einen Index-Wert und den zu verarbeitenden Wert. Wie der Index-Wert verwendet wird, evtl. gar nicht, obliegt dem Ersteller der Funktion. In diesem Sript wird ausschließlich "dependState()" als eine solche Funktion verwendet.
- i (index) bedingt optional: Hier ist immer dann und nur dann ein Wert einzutragen, wenn ein optionaler f-Eintrag vorliegt. Im obigen Script wird der i-Wert als Index im State Datenfeld verwendet.
Da dieses Script recht vielseitig verwendbar ist ( a generic script ), erscheint dessen Konfiguration etwas gewöhnungsbedürftig.
Wenn bspw. eine Taste für mehrere unterschiedliche Nachrichten in Abhängigkeit deren Druckdauer verwendet werden soll, ist ein Eintrag in cfg folgender Struktur einzusetzen:
[ // Eintrag zu einer id, also zu einer der 4 Tasten
{d:1 /* kürzer als 1s */, url:'http://...', p:...}, // Hier sind f und i weggelassen, was durchaus zulässig ist. http get request
{d:3 /* >1s und <3s */, msg:{t:..., p:...}}, // mqtt message
{d:6 /* >3s und <6s */, url:'http://...', p:...}
]
Ich stehe bei Fragen gerne zur Verfügung.