Soo, es ist vollbracht. Ich werde versuchen, die Lösung hier zu präsentieren.
Dazu werden aber ein paar Anmerkungen notwendig sein, weil unsere IPS-Umgebung vermutlich ein wenig speziell ist.
Grundsätzlich ist die Lösung in 2 Skripts versteckt: eines stellt ein PHP-Objekt bereit, welches quasi das Umfeld einer ShutterControl-Instanz darstellt und bequem alle möglichen Eigenschaften und Methoden bereitstellt. Vorteil ist die gute Lesbarkeit und Strukturierbarkeit von objektorientierten Ansätzen und auch die gute Zugänglichkeit von Daten bei der Verwendung in eigenen Skripts.
Ziel ist, dass ein Shutter bei Druck auf die obere Taste z.B. nach ganz oben fahren will, aber bei einem Druck auf die andere Taste während der Fahrt der Shutter an der nächsten der Standardposition anhält.
Zuerst gibts mal die Objektdefinition, die bei mir in einem Skript steht, welches vom 2.5er autoload automatisch eingebunden wird und daher immer zur Verfügung steht:
<?
// Class definition for a shutter actuator
class actuator_shutter
// Erweitern um den zugehörigen Antrieb und dessen daten
// Am Ende erweitern um transiente Info (gerade in Betrieb, aktuelle Fahrrichtung, Fahrzeit)(ermöglicht die Überprüfung beim wiederholten Drücken des Knopfes)
{
private $instance_id = 0; // ID der IPS-Instanz
public $actuator_id = 0;
public $interface_id = 0;
public $position = false; // Shutter Control StatusVariable Position
public $name = ""; // Shutter Control Name
public $state = "unknown"; // Shutter Actuator active?
public $addEndTime = 5; // SC adds 5s when going to end position
public $driveTiming;
// These properties are only relevant when shutter is moving:
public $direction = false; // Shutter moving direction (true: up; false: down)
// These properties are only relevant when override is set:
public $override = false; // true, wenn override aktiv
// Constructor erstellen, der Daten füllt
function __construct() {
if (func_num_args() === 1) {
$this->instance_id = func_get_arg(0);
$this->actuator_id = SC_GetTransmitDevice($this->instance_id);
$this->interface_id = SC_GetHandlerScript($this->instance_id);
$this->getProps();
}
else {
throw new Exception('Only one argument allowed!');
}
}
// Private functions
private function getProps() {
$this->name = IPS_GetName($this->instance_id);
$this->position = GetValue(IPS_GetStatusVariableID($this->instance_id, "StatusVariable"));
$event_id = @IPS_GetEventIDByName("StopTimer", $this->actuator_id);
if ($event_id !== false) {
$this->state = "moving";
}
else {
$this->state = "idle";
}
$this->direction = GetValue(IPS_GetStatusVariableID($this->actuator_id, "StatusVariable"));
$this->getDriveTiming($this->direction); // Get drive timings of current direction
$override_id = @IPS_GetVariableIDByName("Override", $this->actuator_id);
if ($override_id !== false) { // Override state hat highest priority
$this->state = "override";
}
}
private function getDriveTiming($direction) {
$info = IPS_GetVariableProfile("~ShutterAssociation"); // Select the position definition used in this environment (may be changed by user)
if ($direction) { // driving up
$timing = SC_GetDriveUpTimings($this->instance_id);
foreach ($info['Associations'] as $value) {
$pos = $value['Value'];
switch ($pos) {
case 100: $timings[$pos] = 0; break;
case 99: $timings[$pos] = $timing['Down']; break;
case 0: $timings[$pos] = $timing['Open'] + $this->addEndTime; break;
default:
if ($pos <= 50) {
$timings[$pos] = $pos / 50 * ($timing['Half'] - $timing['Open']) + $timing['Open'];
}
else {
$timings[$pos] = ($pos / 50 - 1) * ($timing['Down'] - $timing['Half']) + $timing['Half'];
}
break;
}
}
}
else { // driving down
$timing = SC_GetDriveDownTimings($this->instance_id);
foreach ($info['Associations'] as $value) {
$pos = $value['Value'];
switch ($pos) {
case 99: $timings[$pos] = $timing['Down']; break;
case 100: $timings[$pos] = $timing['Close'] + $this->addEndTime; break;
default:
if ($pos <= 50) {
$timings[$pos] = $pos / 50 * $timing['Half'];
}
else {
$timings[$pos] = ($pos / 50 - 1) * ($timing['Down'] - $timing['Half']) + $timing['Half'];
}
break;
}
}
}
$this->driveTiming = $timings; // Save as property
}
private function setStopTimer($target_time) {
// Funktion erstellt oder aktualisiert den Anhaltetimer
$id_timer = @IPS_GetEventIDByName("StopTimer", $this->actuator_id); // Search for an event named StopTimer on the actuator
if ($id_timer === false) { // Neuen Timer anlegen
$id_timer = IPS_CreateEvent(1); // Create new cyclic event
IPS_SetName($id_timer, "StopTimer"); // Set new name - always the same
IPS_SetParent($id_timer, $this->actuator_id); // Attach the event to the corresponding actuator
IPS_SetEventCyclic($id_timer, 0, 0, 0, 0, 0, 0); // Run single event
IPS_SetEventScript($id_timer, "IPS_RunScriptEx($this->interface_id, array(\"INSTANCE\" => $this->instance_id));");
IPS_SetEventActive($id_timer, true); // Activate the timer.
}
IPS_SetEventCyclicTimeBounds($id_timer, $target_time, 0); // Set time some seconds away from now
}
public function clearStopTimer() { // Called by interface script
$id_timer = @IPS_GetEventIDByName("StopTimer", $this->actuator_id);
IPS_DeleteEvent($id_timer);
$override_id = @IPS_GetVariableIDByName("Override", $this->actuator_id);//
if ($override_id !== false) { IPS_DeleteVariable($override_id); }
}
private function startShutter($position) {
// Check the validity of target position and direction
if ($position == $this->position) { return true; } // nothing to do.
SC_Move($this->instance_id, $position); // Erlaubnis, die SC zu triggern.
if ($position < $this->position) { $newDirection = true; } // up
else { $newDirection = false; } // down
$this->getDriveTiming($newDirection); // Read the corresponding timing from SC instance
$runtime = time() + $this->driveTiming[$position] - $this->driveTiming[$this->position]; // Fahrzeit zu "jetzt" addieren
$this->setStopTimer($runtime);
return true;
}
private function interruptShutter($position) {
// Aktuelle Position bestimmen, Timer updaten, override aktivieren und SC aufrufen.
$event_id = @IPS_GetEventIDByName("StopTimer", $this->actuator_id);
if ($event_id !== false && $position != $this->position) { // Wenns fährt und nicht dahin, wo wir schon hinfahren
$targetPos = $this->position; // Set current target as initial destination
$info = IPS_getEvent($event_id);
$stopduration = $info['CyclicTimeFrom'] - time(); // Duration until stop event
$targetTime = $this->driveTiming[$this->position]; // Initial duration to target
asort($this->driveTiming);
reset($this->driveTiming); // Set pointer to the beginning...jedenfalls fürs runterfahren..
while (1) {
$test = current($this->driveTiming);
$compare = ($targetTime - $test +1) - $stopduration; // +1 ist Toleranz zum durchfahren...
if ($compare < 0) {
$targetPos = key($this->driveTiming);
break;
}
if (!next($this->driveTiming)) { break; } // Stop if all positions checked.
}
if ($targetPos != $this->position) {
$override_id = IPS_CreateVariable(0); // Create Boolean override signal
IPS_SetName($override_id, "Override"); // Set the name...
IPS_SetParent($override_id, $this->actuator_id); // Set the hierarchy...
SetValueBoolean($override_id, true); // And set true.
$this->setStopTimer(time() + round(abs($compare)) + 1); // Update the timer
SC_move($this->instance_id, $targetPos); // Call the SC instance
}
}
return true;
}
// Public functions
function move($position) { // No comment
switch ($this->state) {
case "idle": $this->startShutter($position); break; // Langeweile -> abfahren.
case "moving": $this->interruptShutter($position); break; // Oha, schnell anhalten.
case "override": break; // Da machen wir schön garnix.
}
return true;
}
}
function moveShutter($id, $position) {
$myShutter = new actuator_shutter($id);
$myShutter->move($position);
return true;
}
?>
In eurem Variablenevent oder einer Standardaktion muss nur noch die Funktion moveShutter(id, zielposition) aufgerufen werden und los gehts.
Der entsprechende Interface-Skript vereinfacht sich ein wenig. Ich habe es abgespeckt auf den Einsatz von EnOcean. Die anderen Systeme lassen sich aber vermutlich recht easy wieder hinzufügen. Wie bei jeder Timer-basierten Lösung kann das Skript von zwei Dingen gestartet werden: RunScript und ShutterControl.
<?
//Variables provided by ShutterControl Module
//IPS_LogMessage("InstanceID", $_IPS['INSTANCE']); /* InstanceID of the actuator! */
//IPS_LogMessage("Direction", $_IPS['DIRECTION']); /* {0..2} Stop, Up, Down */
//IPS_LogMessage("Duration", $_IPS['DURATION']); /* ms */
//Modified By Markus Loeben 2010
//Modified by Tobias Schluer, 2011 for local EnOcean Equipment
//Modified by Tobias Schluer, 2012 for new features (event driven and interrupt)
switch ($_IPS['SENDER']) {
case "ShutterControl":
define("SC_DIRECTION_STOP", 0);
define("SC_DIRECTION_UP", 1);
define("SC_DIRECTION_DOWN", 2);
$info = IPS_GetInstance($_IPS['INSTANCE']);
$direction = GetValue(IPS_GetStatusVariableID($_IPS['INSTANCE'], "StatusVariable"));
$override = @IPS_GetVariableIDByName("Override", $_IPS['INSTANCE']);
if ($override !== false) { break; } // Abbrechen, wenn es einen Override hat
switch($info['ModuleInfo']['ModuleID']) {
case ("{8492CEAF-ED62-4634-8A2F-B09A7CEDDE5B}" || "{FD46DA33-724B-489E-A931-C00BFD0166C9}"): //EnOcean RCM100 || Eltako Switch
switch($_IPS['DIRECTION']) { // Wunschrichtung der SC
case SC_DIRECTION_UP:
ENO_SwitchMode($_IPS['INSTANCE'], true); // Taster "oben" simulieren für Öffnen
break;
case SC_DIRECTION_DOWN:
ENO_SwitchMode($_IPS['INSTANCE'], false); // Taster "unten" simulieren für Schliessen
break;
case SC_DIRECTION_STOP:
ENO_SwitchMode($_IPS['INSTANCE'], $direction); // Anhalten Taster in gleiche Richtung
break;
}
break;
default: die("No supported module used.");
}
break;
case "RunScript":
$mySC = new actuator_shutter($_IPS['INSTANCE']);
$info = IPS_GetInstance($mySC->actuator_id);
switch($info['ModuleInfo']['ModuleID']) {
case ("{8492CEAF-ED62-4634-8A2F-B09A7CEDDE5B}" || "{FD46DA33-724B-489E-A931-C00BFD0166C9}"): //EnOcean RCM100 || Eltako Switch
ENO_SwitchMode($mySC->actuator_id, $mySC->direction); //FSA12 stoppen bei gleicher Richtung
$mySC->clearStopTimer();
break;
}
break;
default: die("Script may only be called by ShutterControl or RunScript!");
}
?>
Probierts mal ein wenig aus. Bei Bedarf helfe ich gerne weiter. Bin grad schreibfaul.
Grüsse,
Tobias
PS: grundsätzlich wäre es natürlich geil, wenn die ShutterControl-Instanz diese Funktionalität direkt implementieren würde…die Möglichkeiten wären vermutlich alle vorhanden.