Ich benutze an vielen Stellen numerische Variablen, die einen Status repräsentieren. Beispielsweise:
Auch Geräte wie z.B. HomeMatic-Fenstergriffe nutzen Variablenprofile mit Zuordnungen zu bestimmten Werten.
Die Statusänderungen solcher Variablen möchte ich gerne loggen - was in IPS ja grundsätzlich sehr einfach ist. Leider sieht das im WebFront aber in der Graphenansicht ziemlich schräg aus: Die aggregierten Werte bilden eine hübsche, abgerundete Kurve, was aber für eine Statusvariable wie diese wenig Sinn ergibt.
Was ich eigentlich möchte, ist ein Text-Log, also eine bestimmte Anzahl an jüngsten Ereignissen, jeweils mit Datum/Uhrzeit der Statusänderung. Und da der numerische Wert visuell irrelevant ist, möchte ich am liebsten nur die Texte in der entsprechenden Farbe angezeigt haben. Ungefähr so:
Und genau dafür habe ich ein Skript geschrieben, das vielleicht auch anderen nützt!
Es erzeugt ein Logfeld automatisch aus den letzten n aufgezeichneten Änderungen aller Variablen, die oben in die Liste eingetragen werden.
<?
// This is the list of variables you wish to display log fields for. Keys are
// variable ids to log, values are either:
//
// - the maximum number of entries to display in a log field for only this variable
// - the identifier of a shared log field defined in the $shared_logs_list
// - or an associative array containing the following keys:
// "single_log_size" - number of entries for single variable log (same effect
// as only supplying a number as the value of the list entry (see above)
// "shared_log" - identifier of a shared log field. The same identifier must
// exist as an entry in the $shared_logs_list, where further parameters of
// the shared log field are supplied.
// "name" - pretty name to display in shared log field (not used in single log
// fields, as there is no such column in them!)
// "display_old_value" - whether to display the old value in shared log field
// entries for this variable
//
$var_list = array(
54064 /*[Hardware-Instanzen\Geräte\Schneeballmaschine\Gerätestatus]*/ => 25,
14404 /*[Hardware-Instanzen\HomeMatic\Heizkörper\Heizkörper Bad\CLIMATECONTROL_VENT_DRIVE\VALVE_STATE]*/ => array(
"shared_log" => "test",
"name" => "Heizkörper Bad"
),
46290 /*[Hardware-Instanzen\HomeMatic\Heizkörper\Heizkörper Büro\CLIMATECONTROL_VENT_DRIVE\VALVE_STATE]*/ => array(
"shared_log" => "test",
"name" => "Heizkörper Büro"
),
38132 /*[Hardware-Instanzen\HomeMatic\Heizkörper\Heizkörper Eingangszimmer\CLIMATECONTROL_VENT_DRIVE\VALVE_STATE]*/ => array(
"shared_log" => "test",
"name" => "Heizkörper Eingang"
),
47435 /*[Hardware-Instanzen\HomeMatic\Heizkörper\Heizkörper Kino\CLIMATECONTROL_VENT_DRIVE\VALVE_STATE]*/ => array(
"shared_log" => "test",
"name" => "Heizkörper Kino"
),
18727 /*[Hardware-Instanzen\HomeMatic\Heizkörper\Heizkörper Schlafzimmer\CLIMATECONTROL_VENT_DRIVE\VALVE_STATE]*/ => array(
"shared_log" => "test",
"name" => "Heizkörper Schlafzimmer"
),
38565 /*[Konfiguration und Software-Instanzen\Heizung\Therme\eBus Manager\Heizung\Status\Fehlerstatus]*/ => array(
"shared_log" => "test",
"name" => "Heizungstherme"
),
54982 /*[Skripte\IO-Boxen\HWR\Register Variable für IO-Box HWR\Auswerteskript\Heizung Stufe]*/ => array(
"shared_log" => "test",
"name" => "Modulation",
"display_old_value" => false
),
);
// This list contains parameters for shared log fields. Every such field must have
// an entry here. Keys are the identifiers of the fields defined, values are:
//
// - a number representing the maximum number of entries, or
// - an associative array containing the following keys:
// "log_size" - number of entries, same effect as supplying a number only
// (may be enhanced in future versions of this script!)
//
$shared_logs_list = array(
"test" => 25
);
// After editing the lists, please execute the script in order to apply changes!
////////////////////////////////////////////////////////////////////////////////
// nothing needs to be edited below this line!
// determine whether the script is run manually in the console, in which case
// some debug output is generated and variable ids removed from the list will
// also have the corresponding events and fields removed
$debug = $_IPS['SENDER'] == 'Execute';
// retrieve id of archive control
$ac_id = IPS_GetInstanceListByModuleID('{43192F0B-135B-4CE7-A0A7-1475603F3060}')[0];
// remove log field and event for variables that have been removed from the list
if($debug) { // only when executing the script in the console!
$children = IPS_GetChildrenIDs($_IPS['SELF']);
foreach($children as $id) {
$event = @IPS_GetEvent($id);
if($event !== false && $event['EventType'] == 0) { // remove single variable log and variable event...
$var_id = $event['TriggerVariableID'];
if(!array_key_exists($var_id, $var_list)) {
echo "Removing logging for variable \"" . @IPS_GetName($var_id) . "\" (ID " . $var_id . ")!\r";
remove_var_event($var_id);
remove_single_log_field($var_id);
}
} else { // remove shared log fields...
$link = @IPS_GetLink($id);
if($link !== false) {
$field_id = $link['TargetID'];
$obj = IPS_GetObject($id);
$ident = $obj['ObjectIdent'];
if(substr($ident, 0, 24) == 'StateVarLog_SharedField_') {
$log_name = substr($ident, 24);
if(!array_key_exists($log_name, $shared_logs_list)) {
echo "Removing shared log field \"" . $log_name . "\"!\r";
remove_shared_log_field($log_name);
}
}
}
}
}
}
// array to hold history lists for shared log fields
$shared_history = array();
// cache for variable profiles
$profiles = array();
// process the variable list
foreach($var_list as $id => $parameters) {
if($_IPS['SENDER'] == 'Variable') {
if($id != $_IPS['VARIABLE'] && !have_shared_log($id, $_IPS['VARIABLE'])) continue;
}
$var = @IPS_GetVariable($id);
if($var === false) { // check if variable found
if($debug) echo "Variable with ID " . $id . " not found!\r";
remove_var_event($id);
remove_single_log_field($id);
continue;
}
// allow a numeric value instead of an associative array to easily specify
// the number of entries for a single variable log. For advanced parameters,
// such as shared multiple variable logs an associative array has to be used.
if(!is_array($parameters)) {
if(is_int($parameters)) { // most simple case - single variable log with n entries
$parameters = array("single_log_size" => $parameters);
} else if(is_string($parameters)) {
$parameters = array("shared_log" => $parameters);
} else {
if($debug) echo "Invalid parameter for variable \"" . @IPS_GetName($var_id) . "\"!\r";
continue;
}
}
ensure_var_event($id); // ensure there is an event for this variable
$var_name = IPS_GetName($id);
// if the type of the variable supports profiles with associations, try to
// retrieve the association list so that log entries can be created with
// text and color, rather than just bare values.
$profile = false;
$prof_name = $var['VariableCustomProfile'];
if($prof_name == '') $prof_name = $var['VariableProfile'];
if($prof_name != '') {
if(!array_key_exists($prof_name, $profiles)) {
$profile = @IPS_GetVariableProfile($prof_name);
if($profile === false) {
if($debug) echo "Profile \"" . $prof_name . "\" not found!\r";
} else {
$profiles[$prof_name] = $profile;
}
} else {
$profile = $profiles[$prof_name];
}
}
// logging must be activated for this variable in the archive control, make sure it is
if(!AC_GetLoggingStatus($ac_id, $id)) {
AC_SetLoggingStatus($ac_id, $id, true);
IPS_ApplyChanges($ac_id);
}
// displaying a graph doesn't make much sense for variables that represent a
// status, so disable graph display in the webfront for this variable
if(AC_GetGraphStatus($ac_id, $id) && $profile !== false && count($profile["Associations"]) > 0) {
AC_SetGraphStatus($ac_id, $id, false);
IPS_ApplyChanges($ac_id);
}
// create and/or update single variable log, if so desired for this variable id
if(array_key_exists('single_log_size', $parameters) && $parameters['single_log_size'] > 0) {
// retrieve variable change history entries from archive control
$var_history = AC_GetLoggedValues($ac_id, $id, 0, 0, $parameters['single_log_size']);
$log_id = ensure_single_log_field($id); // ensure a log field exists for this variable and get its id
// construct the html content for this log
$log_content = generate_log($var_history, $prof_name);
if($debug) echo $var_name . " Single Log:\r" . $log_content . "\r";
SetValue($log_id, $log_content); // write into log field
} else { // if no single log is desired, remove the log field
remove_single_log_field($id);
}
// if this variable should be in a shared log
if(array_key_exists('shared_log', $parameters)) {
$log_name = $parameters['shared_log'];
if(array_key_exists($log_name, $shared_logs_list)) {
// allow for simplified $shared_logs_list structure - if an entries' value
// is numeric, assume that it's the log's size.
if(is_int($shared_logs_list[$log_name])) {
$shared_logs_list[$log_name] = array("log_size" => $shared_logs_list[$log_name]);
}
// ensure a shared history array for this shared log exists
if(!array_key_exists($log_name, $shared_history)) $shared_history[$log_name] = array();
if(is_array($shared_logs_list[$log_name])) { // if log exists
$log_size = $shared_logs_list[$log_name]["log_size"];
// name of the entry will be variable name, or custom name if specified
// within the variable list entries' parameters
$name = $var_name;
if(array_key_exists('name', $parameters)) $name = $parameters['name'];
// determine whether to display the old value in the shared log. This
// is enabled by default for numeric variables.
$display_old_value = $var['VariableType'] != 3;
if(array_key_exists("display_old_value", $parameters)) {
$display_old_value = $parameters["display_old_value"];
}
$ok_value_used = false;
if(array_key_exists("ok_value", $parameters)) {
$ok_value = $parameters["ok_value"];
$ok_value_used = true;
}
$var_history = AC_GetLoggedValues($ac_id, $id, 0, 0, $log_size + 1);
foreach($var_history as $vhindex => $var_hist_entry_original) {
if($vhindex == $log_size) break;
$var_hist_entry = array(
"VariableId" => $id,
"ProfileName" => $prof_name,
"Name" => $name,
"TimeStamp" => $var_hist_entry_original["TimeStamp"],
"Value" => $var_hist_entry_original["Value"],
);
if($vhindex + 1 < count($var_history) && $display_old_value) {
$var_hist_entry["OldValue"] = $var_history[$vhindex + 1]["Value"];
}
if($ok_value_used) {
$var_hist_entry["FaultState"] = $var_hist_entry["Value"] != $ok_value;
}
$inserted = false;
foreach($shared_history[$log_name] as $shindex => $shared_hist_entry) {
if($var_hist_entry['TimeStamp'] > $shared_hist_entry['TimeStamp']) {
array_splice($shared_history[$log_name], $shindex, 0, array($var_hist_entry));
$inserted = true;
break;
}
}
if(!$inserted) array_push($shared_history[$log_name], $var_hist_entry);
if(count($shared_history[$log_name]) > $log_size) array_pop($shared_history[$log_name]);
}
}
} else { // if shared log name not found in $shared_logs_list
if($debug) echo "Shared log \"" . $log_name . "\" not defined in shared logs list!\r";
}
}
}
foreach($shared_logs_list as $log_name => $parameters) {
if(!is_array($parameters)) continue;
if(!array_key_exists("log_size", $parameters)) continue;
if(!array_key_exists($log_name, $shared_history)) continue;
if($parameters['log_size'] <= 0) continue;
$log_id = ensure_shared_log_field($log_name);
// construct the html content for this log
$log_content = generate_log($shared_history[$log_name]);
if($debug) echo "Shared Log \"" . $log_name . "\":\r" . $log_content . "\r";
SetValue($log_id, $log_content); // write into log field
}
// generate log as an html table string
function generate_log($history, $prof_name = "") {
$result = '<table cellspacing=5 cellpadding=1>';
foreach($history as $hist_entry) {
$ts = $hist_entry['TimeStamp'];
$val = $hist_entry['Value'];
$time_str = date('j.n.y H:i:s', $ts);
if(array_key_exists("ProfileName", $hist_entry)) {
$prof_name = $hist_entry["ProfileName"];
}
$log_entry_str = '<tr><td>' . $time_str . "\t</td>";
$name_colour_string = "aaaaaa";
if(array_key_exists("FaultState", $hist_entry)) {
$name_colour_string = $hist_entry["FaultState"] ? "ff0000" : "00ff00";
}
if(array_key_exists("Name", $hist_entry)) {
$log_entry_str .= "<td><font color=\"#" . $name_colour_string . "\">" . $hist_entry["Name"] . "\t</font></td>";
}
if(array_key_exists("OldValue", $hist_entry)) {
$old_val = $hist_entry["OldValue"];
//print_r($old_val);
$log_entry_str .= format_value($old_val, $prof_name) . "<td>→</td>";
$colspan = 1;
} else {
$colspan = 3;
}
$log_entry_str .= format_value($val, $prof_name, $colspan);
//if($colspan == 1) $log_entry_str .= "<td align=\"left\"> </td>";
$log_entry_str .= "</tr>\r";
$result .= $log_entry_str;
}
$result .= '</table>';
return $result;
}
function format_value($val, $prof_name, $colspan = 1) {
global $profiles;
$color_str = "ffffff";
$align = is_string($val) ? "left" : "right";
$val_str = htmlentities($val);
if(array_key_exists($prof_name, $profiles)) {
$profile = $profiles[$prof_name];
$val_str = $profile['Prefix'] . $val_str . trim($profile['Suffix']);
$assocs = $profile['Associations'];
$lastAssocVal = false;
foreach($assocs as $assoc) { // if associations array supplied, use it to display colorful text rather than bare values!
if($val >= $assoc['Value'] && ($lastAssocVal === false || $assoc['Value'] > $lastAssocVal)) {
$lastAssocVal = $assoc['Value'];
$val_str = sprintf($assoc['Name'], $val);
$color_str = str_pad(dechex($assoc['Color']), 6, '0', STR_PAD_LEFT);
$align = "left";
}
}
}
return "<td colspan=\"" . $colspan . "\"><div align=\"". $align . "\"><font color=\"#" . $color_str . "\">" . $val_str . "</font></div></td>";
}
function ensure_shared_log_field($log_name) {
$ident = 'StateVarLog_SharedField_' . $log_name;
// try to retrieve link to the log field for this variable
$log_link_id = @IPS_GetObjectIDByIdent($ident, $_IPS['SELF']);
if($log_link_id === false) {
$log_id = @IPS_GetObjectIDByIdent($ident, IPS_GetParent($_IPS['SELF']));
} else {
// normally we get the log field's id by following the link
$log_id = IPS_GetLink($log_link_id)['TargetID'];
}
if($log_id === false) {
$log_id = IPS_CreateVariable(3);
IPS_SetName($log_id, 'Shared Log ' . $log_name);
IPS_SetParent($log_id, IPS_GetParent($_IPS['SELF']));
IPS_SetIdent($log_id, $ident);
IPS_SetVariableCustomProfile($log_id, '~HTMLBox');
}
// if no link to the log field exists, create one
if($log_link_id === false) {
$log_link_id = IPS_CreateLink();
IPS_SetParent($log_link_id, $_IPS['SELF']);
IPS_SetIdent($log_link_id, $ident);
}
@IPS_SetLinkTargetID($log_link_id, $log_id);
return $log_id;
}
// remove shared log with the given log name
function remove_shared_log_field($log_name) {
$ident = 'StateVarLog_SharedField_' . $log_name;
$log_link_id = @IPS_GetObjectIDByIdent($ident, $_IPS['SELF']);
if($log_link_id !== false) {
$log_id = IPS_GetLink($log_link_id)['TargetID'];
if($log_id !== false) IPS_DeleteVariable($log_id);
IPS_DeleteLink($log_link_id);
}
}
// Ensure a log field, as well as a link to it exist for the specified variable
// id. The link serves as a reference in case the log field gets moved around.
function ensure_single_log_field($var_id) {
$ident = 'StateVarLog_Field_' . $var_id;
// try to retrieve link to the log field for this variable
$log_link_id = @IPS_GetObjectIDByIdent($ident, $_IPS['SELF']);
if($log_link_id === false) {
// if no link exists, we still try to retrieve the potential log field from
// below the same parent as the variable - just to make this script a bit
// more robust in case the link got manually deleted.
$log_id = @IPS_GetObjectIDByIdent($ident, IPS_GetParent($var_id));
} else {
// normally we get the log field's id by following the link
$log_id = IPS_GetLink($log_link_id)['TargetID'];
}
if($log_id === false) {
// get the sorting position of the variable, so that the log field can be
// put in the same position by default
$pos = IPS_GetObject($var_id)['ObjectPosition'];
$log_id = IPS_CreateVariable(3);
IPS_SetName($log_id, IPS_GetName($var_id) . ' Log');
IPS_SetParent($log_id, IPS_GetParent($var_id));
IPS_SetIdent($log_id, $ident);
IPS_SetPosition($log_id, $pos);
IPS_SetVariableCustomProfile($log_id, '~HTMLBox');
}
// if no link to the log field exists, create one
if($log_link_id === false) {
$log_link_id = IPS_CreateLink();
IPS_SetParent($log_link_id, $_IPS['SELF']);
IPS_SetIdent($log_link_id, $ident);
}
@IPS_SetLinkTargetID($log_link_id, $log_id);
return $log_id;
}
// Remove log field (and the link to it) for the specified variable id.
function remove_single_log_field($var_id) {
$ident = 'StateVarLog_Field_' . $var_id;
$log_link_id = @IPS_GetObjectIDByIdent($ident, $_IPS['SELF']);
if($log_link_id !== false) {
$log_id = IPS_GetLink($log_link_id)['TargetID'];
if($log_id !== false) IPS_DeleteVariable($log_id);
IPS_DeleteLink($log_link_id);
}
}
// Ensure that a change event exists for the specified variable id, and return
// the event id.
function ensure_var_event($var_id) {
$ident = 'StateVarLog_Event_' . $var_id;
$event_id = @IPS_GetObjectIDByIdent($ident, $_IPS['SELF']);
if($event_id === false) {
$event_id = IPS_CreateEvent(0);
IPS_SetParent($event_id, $_IPS['SELF']);
IPS_SetEventTrigger($event_id, 1, $var_id);
IPS_SetEventActive($event_id, true);
IPS_SetIdent($event_id, $ident);
}
return $event_id;
}
// Remove change event for the specified variable id.
function remove_var_event($var_id) {
$ident = 'StateVarLog_Event_' . $var_id;
$event_id = @IPS_GetObjectIDByIdent($ident, $_IPS['SELF']);
if($event_id !== false) {
IPS_DeleteEvent($event_id);
}
}
// determine whether there is a shared log containing both specified variable ids
function have_shared_log($id1, $id2) {
global $var_list;
if(!array_key_exists($id1, $var_list)) return false;
if(!array_key_exists($id2, $var_list)) return false;
$parameters1 = $var_list[$id1];
$parameters2 = $var_list[$id2];
$shared_log_name_1 = false;
if(is_string($parameters1)) {
$shared_log_name_1 = $parameters1;
} else if(is_array($parameters1) && array_key_exists("shared_log", $parameters1)) {
$shared_log_name_1 = $parameters1['shared_log'];
}
$shared_log_name_2 = false;
if(is_string($parameters2)) {
$shared_log_name_2 = $parameters2;
} else if(is_array($parameters2) && array_key_exists("shared_log", $parameters2)) {
$shared_log_name_2 = $parameters2['shared_log'];
}
if($shared_log_name_1 === false || $shared_log_name_2 === false) return false;
return $shared_log_name_1 == $shared_log_name_2;
}
?>
Es sollte extrem einfach zu benutzen sein - ihr packt das Skript irgendwo hin, tragt die zu loggenden Variablen in die Liste oben im Skript ein, mit der Anzahl der anzuzeigenden Logeinträge. Das Log-Feld wird an dem gleichen Ort erzeugt, wo die Variable liegt, kann aber beliebig verschoben werden. Falls die Variable bereits archiviert wird, sollten sofort Einträge im Log erscheinen.
Update:
Das Skript unterstützt jetzt auch gemeinsame Logs, in denen die letzten Wertänderungen aus mehreren Variablen auftauchen.
Viel Spaß damit!