Ich habe ein Skript geschrieben, um eine Heizung (in meinem Fall: Öl) per BSB-LAN-Interface an IPS anzubinden.
Es gab hier im Forum schon andere Codesnippets, aber ich wollte gern ein etwas universelleres Skript mit automatischem Setup.
Das Skript erlaubt
[ul]
[li]das regelmäßige Auslesen aller per BSB-LAN verfügbaren Werte,[/li][li]das Setzen von Parametern sowie das Absenden von INF-Nachrichten per RunScriptEx,[/li][li]das Ändern von Parametern via WebFront.[/li][/ul]
Es werden automatisch diverse Variablenprofile angelegt (erkennbar am BSB_LAN_-Präfix), um Enums und andere Datentypen halbwegs ansehnlich darzustellen. Diese können natürlich nach dem Setup beliebig geändert oder auch gelöscht werden.
Gleiches gilt für die Benennung von Variablen und Kategorien. Auch ist es erlaubt, diese zu löschen wenn sie nicht benötigt werden oder keine sinnvollen Daten enthalten.
Wie bei den meisten meiner Skripte muss der Code zum Setup in ein leeres Skript kopiert, einige Variablen oben verändert und das Skript dann manuell ausgeführt werden. Da in diesem Fall das Setup länger dauern kann als die maximale Skriptlaufzeit bitte ggf. mehrmals hintereinander ausführen. Das Ergebnis seht ihr jeweils in der Konsolenausgabe! Ein Setup, das noch nicht abgeschlossen wurde wird beim nächsten manuellen Ausführen automatisch fortgesetzt.
Ich kann versuchen, bei Problemen zu helfen, habe allerdings gerade nicht all zu viel Zeit. Bitte um Verständnis wenn’s länger dauert. Und bitte keine Fragen per PN, lieber im Thread, dann haben andere auch was davon.
Weitere Informationen finden sich oben im Skript-Quellcode.
<?
// BSB LAN Script for IP Symcon, written 2019 by sokkederheld
// Please adjust the following settings before executing this script for setup.
// IP address (or domain name) of the BSB-LAN interface
$ip = "192.168.188.30";
// optional "pass key" to make 100% hack-proof - leave empty if no pass key is
// being used. But seriously, this provides no security at all, so don't forget
// to isolate the device from any open networks.
$pass_key = "";
// Root id of the category under which the instances should be created and
// maintained. Please do not move instances outside of that category. For
// visualization purposes, please use links. Renaming of elements is okay.
$root_id = 54660 /*[Software-Instanzen\Stall\Heizung]*/;
// Update interval in seconds - can not be less than 20, 30 should be good.
$update_interval = 30;
// For setup mode, the "Setup Status" variable underneath this script must be
// deleted if it exists!
// Afterwards, execute this script manually to begin setup. Attention: You may
// need to execute the script several times for setup to complete, because it
// may not complete during one maximum run time. Check result status at the end
// of the log!
// To uninstall (delete all profiles beginning with the defined prefix, delete
// all instances underneath the specified root id and all variables below that)
// set the "Setup Status" variable underneath this script to -1 and execute it
// manually.
// This variable allows for forced updating (from the bsb-lan interface) of a
// single category, by specifying its ID. This may be handy if you deleted some
// or all parameters (or the entire instance) and wish to recreate it. The value
// should normally be false, unless you wish to force an update of the specified
// category id! If set to a valid category id, all parameter variables and enum
// profiles of the category will be created if they don't exist.
$force_update_cat = false;
// List of prameters you want writeable. Feel free to modify BEFORE setup.
// Afterwards, you can set or unset this script as the corresponding variable's
// action script in order to change a parameters' writeability.
$writeable_parameters = array(
700, // heating operation mode
701, // presence button
720, 721, // kennlinie steilheit, verschiebung
730, // heizgrenze
750, // room influence
641, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, // holiday schedule
648, // holiday setting
500, 501, 502, 503, 504, 505, 506, // heating schedules
1601, // tww push
2200, // kessel betriebsart
10000, // room temperature INF!
);
$write_only_parameters = array(
1601, // tww push
);
$day_month_parameters = array(
5, 6, // dst start end
641, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, 646, 647, // holiday schedule
);
$rel_temp_parameters = array(
721, // kennlinie verschiebung
732, // Tagesheizgrenze
4721,
4722,
5020, 5024, 5025, 5026, 5027, 5028, 5029,
);
$sync_time = true;
// Outputting status messages during timer events is normally disabled in order
// to avoid spamming the log file. Can be enabled for debug purposes.
$timer_log_messages = false;
// Outputting status messages during variable actions is normally disabled in
// order to avoid spamming the log file.
$action_log_messages = false;
// To set parameters from a script:
// $par = array("PARAMETER" => 1601, "VALUE" = 1, "ACTION" = "SET", "VARIABLE"
// => 12345);
// IPS_RunScriptEx($id, $par);
////////////////////////////////////////////////////////////////////////////////
// no changes required below this line
// Some constants you should not fumble with.
define("SETUP_INIT", "0");
define("SETUP_COMPLETED", "100");
define("SETUP_UNINSTALL", "-1");
define("SETUP_REQUEST_POSTPONE_UPDATE", "101");
define("MAX_EXECUTION_TIME", "60"); // setting higher causes problems over Connect
define("ENUM_PROFILE_PREFIX", "BSB_LAN_ENUM_");
define("UNIT_PROFILE_PREFIX", "BSB_LAN_UNIT_");
define("NO_VALUE", "-100"); // used in profiles to denounce a lack of a value
define("SEMAPHORE_NAME", "BSB_LAN_SCRIPT");
define("PARAM_ACTION_NONE", "0");
define("PARAM_ACTION_SET", "1");
define("PARAM_ACTION_INF", "2");
ini_set('max_execution_time', MAX_EXECUTION_TIME);
// Buffer for enum data received by single category update requests - may spare
// us from fiddling with the HTML based "get enum values" request later when
// creating the parameter variables and, potentially, profiles.
$enum_buffer = array();
// measure time at beginning of this script
$begin_ts = time();
// If we're doing something immediate, postpone updates in the background
if(get_setup_status(true) == SETUP_COMPLETED && $_IPS['SENDER'] != 'TimerEvent') {
set_setup_status(SETUP_REQUEST_POSTPONE_UPDATE);
}
if(!IPS_SemaphoreEnter(SEMAPHORE_NAME, (MAX_EXECUTION_TIME - 15) * 1000)) {
msg("Other script instance is running too long. Exiting.");
return;
}
if(get_setup_status(true) == SETUP_REQUEST_POSTPONE_UPDATE) {
set_setup_status(SETUP_COMPLETED);
}
if(get_elapsed_time() > 1) {
msg("Needed to wait " . get_elapsed_time() . " s for other script instance to finish.");
}
// Manual execution of this script is for maintenance tasks like setup, forced
// category updates or uninstallation.
if($_IPS['SENDER'] == 'Execute') {
if($force_update_cat !== false) { // forced category update
msg("Forced updating mode for category #" . $force_update_cat . ".");
update_categories($force_update_cat);
} else if(get_setup_status() <= SETUP_UNINSTALL) { // uninstall
msg("Uninstall mode.");
IPS_SetScriptTimer($_IPS['SELF'], 0);
set_setup_status(SETUP_INIT);
$inst_ids = IPS_GetChildrenIDs($root_id);
foreach($inst_ids as $inst_id) {
msg("Clearing out instance \"" . IPS_GetName($inst_id) . "\".");
$var_ids = IPS_GetChildrenIDs($inst_id);
foreach($var_ids as $var_id) {
msg("Removing variable \"" . IPS_GetName($var_id) . "\".");
IPS_DeleteVariable($var_id);
}
msg("Removing instance \"" . IPS_GetName($inst_id) . "\".");
IPS_DeleteInstance($inst_id);
}
$prof_names = IPS_GetVariableProfileListByType(1);
foreach($prof_names as $prof_name) {
if(substr($prof_name, 0, strlen(ENUM_PROFILE_PREFIX)) == ENUM_PROFILE_PREFIX) {
msg("Deleting ENUM profile \"" . $prof_name . "\".");
IPS_DeleteVariableProfile($prof_name);
} else if(substr($prof_name, 0, strlen(UNIT_PROFILE_PREFIX)) == UNIT_PROFILE_PREFIX) {
msg("Deleting unit profile \"" . $prof_name . "\".");
IPS_DeleteVariableProfile($prof_name);
}
}
msg("Uninstall completed.");
} else if(get_setup_status() < SETUP_COMPLETED) { // setup
if(get_setup_status() == 0) {
msg("Setup mode (beginning)! Attention, check end of log for setup status!");
} else {
msg("Setup mode (continued)! Attention, check end of log for setup status!");
}
if(update_categories()) {
if($update_interval < 20) $update_interval = 20;
IPS_SetScriptTimer($_IPS['SELF'], $update_interval);
msg("Setup completed.");
set_setup_status(SETUP_COMPLETED);
}
} else { // nothing to do
msg("Setup already completed. To uninstall, set \"Setup Status\" variable" .
" to " . SETUP_UNINSTALL . " or lower and execute manually.");
msg("To repeat setup (recreating potentially deleted elements), delete " .
"\"Setup Status\" variable and execute manually.");
}
} else if($_IPS['SENDER'] == 'TimerEvent') { // regular update of param values
$force_update_cat = false;
if(get_setup_status() >= SETUP_COMPLETED) {
do {
msg("Attempting to find the most outdated category.");
$inst_ids = IPS_GetChildrenIDs($root_id);
$oldest_inst_id = false;
$oldest_ts = time();
$cat_id = false;
foreach($inst_ids as $inst_id) {
$inst_ident = IPS_GetObject($inst_id)["ObjectIdent"];
if(substr($inst_ident, 0, 4) == "Cat_") {
$var_ids = IPS_GetChildrenIDs($inst_id);
$newest_ts_in_cat = false;
foreach($var_ids as $var_id) {
$this_update_ts = IPS_GetVariable($var_id)["VariableUpdated"];
if($newest_ts_in_cat === false) $newest_ts_in_cat = $this_update_ts;
if($this_update_ts > $newest_ts_in_cat) {
$newest_ts_in_cat = $this_update_ts;
}
}
if($newest_ts_in_cat !== false && $newest_ts_in_cat < $oldest_ts) {
$cat_id = substr($inst_ident, 4);
$oldest_ts = $newest_ts_in_cat;
$oldest_inst_id = $inst_id;
}
}
} // foreach
$age = time() - $oldest_ts;
if($cat_id !== false) {
msg("Updating category #" . $cat_id . " (\"" .
IPS_GetName($oldest_inst_id) .
"\", " . $age . " second(s) old).");//*/
update_categories($cat_id);
}
msg("Execution time elapsed: " . get_elapsed_time() . "s.");
if(get_setup_status() == SETUP_REQUEST_POSTPONE_UPDATE) {
msg("Further updating postponed by other script instance. Exiting " .
"from main loop.");
break;
}
} while (get_elapsed_time() < $update_interval - 15);
} else {
msg("Setup not completed. Disabling update timer.");
IPS_SetScriptTimer($_IPS['SELF'], 0);
}
} else {
// webfront action script or execution from other script
$force_update_cat = false;
$param_action = PARAM_ACTION_SET;
if($_IPS['SENDER'] == 'WebFront') {
$ident = IPS_GetObject($_IPS['VARIABLE'])['ObjectIdent'];
if(substr($ident, 0, 4) == "Par_") { // if the variable is a parameter variable
$p = IPS_GetVariable($_IPS['VARIABLE'])["VariableCustomProfile"];
$parent_id = IPS_GetParent($_IPS['VARIABLE']);
// extract the parameter index and, if present, the field index of the variable
$index = substr($ident, 4);
$field_index = false;
$pos = strpos($index, "_");
if($pos) {
$field_index = substr($index, $pos + 1);
$index = substr($index, 0, $pos);
}
// if a field index (for parameters that are presented as multiple fields,
// like for example, heating schedules) is present, read all fields and
// combine the values in order to form a proper set request
if($field_index !== false) {
// determine the number of fields by looking for the highest field index
// which exists for this parameter index
$field_count = $field_index + 1;
while(@IPS_GetObjectIDByIdent("Par_" . $index . "_" . $field_count, $parent_id)) {
$field_count++;
}
$v = "";
// If we're setting a heating schedule, we must provide a valid start and
// end time at the same time, so if we're changing from an xx:xx value to
// something valid, we have to specify something valid for the end time as
// otherwise the receiver would just discard the change. So by default, we
// send an end time one hour past the start time. Otherwise it would be
// impossible to reenable a heating schedule after disabling it.
$force_value_index = false;
if($p == "~UnixTimestampTime") {
if($_IPS['VALUE'] > 0 && GetValue($_IPS['VARIABLE']) == 0) {
if($field_index % 2 == 0) {
$force_value_index = $field_index + 1;
$force_value = $_IPS['VALUE'] + 60 * 60 *
($field_index == 0 ? 16 : 1);
}
}
}
// Combine the set value out of all fields which belong to this param id.
for($fi = 0; $fi < $field_count; $fi++) {
if($fi == $field_index) {
$v_raw = $_IPS['VALUE'];
} else {
$var_id = IPS_GetObjectIDByIdent("Par_" . $index . "_" . $fi, $parent_id);
$v_raw = GetValue($var_id);
if($v_raw == 0 && $force_value_index !== false && $force_value_index == $fi) {
$v_raw = $force_value;
}
}
// convert timestamp values to strings
if($p == "~UnixTimestampTime") {
if($v_raw == 0) {
$v_field = "xx:xx";
} else {
$v_field = date("H:i", $v_raw);
}
} else {
$v_field = $v_raw;
}
// append to resulting set value string
$v .= $v_field;
// include separators for schedule strings
if($p == "~UnixTimestampTime") {
if($fi % 2 == 0 && $fi < $field_count - 1) {
$v .= "-";
} else if($fi % 2 == 1 && $fi < $field_count - 1) {
$v .= "_";
}
} // if
} // for
} else { // single field value (the vast majority)
$v_raw = $_IPS['VALUE'];
if($p == "~UnixTimestampTime") { // convert timestamp to string
$v = date("H:i", $v_raw);
} else if($p == "~UnixTimestampDate") { // convert timestamp to string
$v = date("d.m", $v_raw);
IPS_SetVariableCustomProfile($_IPS['VARIABLE'], setup_day_month_profile($index, $v_raw));
} else if(array_search($index, $day_month_parameters) !== false) {
if($v_raw == NO_VALUE) {
$v = '';
} else if($v_raw == GetValue($_IPS['VARIABLE'])) {
IPS_SetVariableCustomProfile($_IPS['VARIABLE'], "~UnixTimestampDate");
$param_action = PARAM_ACTION_NONE;
} else {
$v = date("d.m", $v_raw);
}
} else {
if($v_raw == NO_VALUE) {
$v = "";
} else {
$v = $v_raw;
}
}
}
}
} else if($_IPS['SENDER'] == 'RunScript') {
$field_index = false;
if(!array_key_exists("PARAMETER", $_IPS)) {
msg("Missing PARAMETER argument.");
return;
}
if(!array_key_exists("VALUE", $_IPS)) {
msg("Missing VALUE argument.");
return;
}
$index = $_IPS['PARAMETER'];
$v = $_IPS['VALUE'];
if(array_key_exists("ACTION", $_IPS)) {
if($_IPS['ACTION'] == 'INF') {
$param_action = PARAM_ACTION_INF;
} else if($_IPS['ACTION'] != 'SET') {
msg("Unsupported parameter action: \"" . $_IPS['ACTION'] . "\".");
return;
}
}
}
if($param_action == PARAM_ACTION_SET) {
msg("Setting parameter #" . $index . " to \"" . $v . "\".");
$result = set_parameter($index, $v);
if(array_key_exists('VARIABLE', $_IPS)) {
$parent_id = IPS_GetParent($_IPS['VARIABLE']);
if($field_index === false && array_search($index, $day_month_parameters) === false) {
SetValue($_IPS['VARIABLE'], $result);
} else {
msg("Updating parameter.");
update_parameters($parent_id, $index, $index);
}
}
} else if($param_action == PARAM_ACTION_INF) {
msg("Sending for parameter #" . $index . " INF message \"" . $v . "\".");
set_parameter($index, $v, true);
}
} // else
IPS_SemaphoreLeave(SEMAPHORE_NAME);
////////////////////////////////////////////////////////////////////////////////
// functions
function sync_time() {
msg("Syncing time...");
set_parameter(0, date("d.m.Y_H:i:s"), false);
} // sync_time
// Returns the setup status level
function get_setup_status($ignore_force_update_cat = false) {
global $force_update_cat;
$id = @IPS_GetObjectIDByIdent("SetupStatus", $_IPS['SELF']);
if($id === false) {
$result = SETUP_INIT;
} else {
$result = GetValue($id);
}
if($force_update_cat !== false && !$ignore_force_update_cat) {
$result = SETUP_INIT;
}
return $result;
} // get_setup_status
// Sets the setup status level
function set_setup_status($status) {
global $force_update_cat;
$id = @IPS_GetObjectIDByIdent("SetupStatus", $_IPS['SELF']);
if($status == SETUP_INIT && $id !== false) {
IPS_DeleteVariable($id);
return;
} else if($id === false) {
$id = IPS_CreateVariable(1);
IPS_SetParent($id, $_IPS['SELF']);
IPS_SetIdent($id, "SetupStatus");
IPS_SetName($id, "Setup Status");
}
if($status != SETUP_REQUEST_POSTPONE_UPDATE && $status != SETUP_COMPLETED &&
$force_update_cat !== false) {
return;
}
SetValue($id, $status);
} // set_setup_status
// Update all values from the category with the specified id (all if omitted)
// This function creates a dummy instance for the category if in setup mode or
// doing a forced category update.
function update_categories($cat_id = "ALL") {
global $root_id, $force_update_cat, $enum_buffer;
$dummy_guid = "{485D0419-BE97-4548-AA9C-C083EB82E61E}";
if($cat_id == "ALL" || $force_update_cat !== false) { // update from bsb-lan
$cats = req("JK=" . $cat_id);
if(!$cats) {
msg("Requesting categories failed.");
return false;
}
if(strval($cat_id) != "ALL") {
$min = 9999;
$max = 0;
foreach($cats as $par_index => $par_info) {
if($par_index < $min) $min = $par_index;
if($par_index > $max) $max = $par_index;
if(array_key_exists("possibleValues", $par_info) &&
count($par_info["possibleValues"]) > 0) {
$enum_buffer[$par_index] = $par_info["possibleValues"];
}
}
if($min > $max) {
if($force_update_cat !== false) {
msg("Category is empty.");
}
return true;
}
$inst_id = @IPS_GetObjectIDByIdent("Cat_" . $cat_id, $root_id);
$cats = array(
$cat_id => array(
"min" => $min,
"max" => $max,
"name" => $inst_id ? IPS_GetName($inst_id) : ("Unbekannt (#" . $cat_id . ")")
)
);
}
} else { // regular refresh of existing category, no update from bsb-lan
$inst_id = @IPS_GetObjectIDByIdent("Cat_" . $cat_id, $root_id);
if(!$inst_id) return true;
$min = 9999;
$max = 0;
$par_ids = IPS_GetChildrenIDs($inst_id);
foreach($par_ids as $par_id) {
$ident = IPS_GetObject($par_id)["ObjectIdent"];
if(substr($ident, 0, 4) == "Par_") {
$par_index = intval(substr($ident, 4));
if($par_index < $min) $min = $par_index;
if($par_index > $max) $max = $par_index;
}
}
if($min > $max) return true;
$cats = array(
$cat_id => array(
"min" => $min,
"max" => $max,
"name" => $inst_id ? IPS_GetName($inst_id) : ("Unbekannt (#" . $cat_id . ")")
)
);
}
foreach($cats as $i => $cat) {
$ident = 'Cat_' . $i;
$inst_id = @IPS_GetObjectIDByIdent($ident, $root_id);
if(get_setup_status() < SETUP_COMPLETED) {
if(!$inst_id) {
msg("Creating dummy instance for category #" . $i . " (" .
$cat["name"] . "), spanning parameters #" . $cat["min"] . " to #" .
$cat["max"] . ".");
$inst_id = IPS_CreateInstance($dummy_guid);
IPS_SetParent($inst_id, $root_id);
IPS_SetName($inst_id, $cat["name"]);
IPS_SetIdent($inst_id, $ident);
}
}
if($inst_id) {
if($i == $cat_id || $i >= get_setup_status()) {
if(get_setup_status() < SETUP_COMPLETED) {
msg("Processing parameters in category #" . $i . " (" .
$cat["name"] . "): #" . $cat["min"] . " to #" .
$cat["max"] . ".");
}
if(update_parameters($inst_id, $cat["min"], $cat["max"])) {
if(get_setup_status() <= $i) {
set_setup_status($i + 1);
if(get_setup_status() < SETUP_COMPLETED) {
msg("Execution time elapsed: " . get_elapsed_time() . "s.");
}
}
}
}
}
if(get_elapsed_time() >= MAX_EXECUTION_TIME - 15) {
if(get_setup_status() < SETUP_COMPLETED) {
msg("Setup incomplete: Execution time limit near. Please execute again to continue!");
}
return false;
}
if(get_setup_status() == SETUP_REQUEST_POSTPONE_UPDATE) {
msg("Other script instance requested to postpone this update. Exiting " .
"from category update.");
return false;
}
} // foreach
return true;
} // update_categories
function get_elapsed_time() {
global $begin_ts;
return time() - $begin_ts;
}
function msg($msg) {
global $timer_log_messages, $action_log_messages;
if($_IPS['SENDER'] == 'WebFront' && !$action_log_messages) return;
if($_IPS['SENDER'] == 'RunScript' && !$action_log_messages) return;
if($_IPS['SENDER'] == 'TimerEvent' && !$timer_log_messages) return;
IPS_LogMessage(IPS_GetName($_IPS['SELF']), $msg);
if($_IPS['SENDER'] == 'Execute') echo $msg . "\r";
} // msg
// Query single parameter over html to get its value when json returns faulty
// value for float number
function get_num_html_value($par_index, $html = false) {
if($html === false) $html = req($par_index);
$str_pre = "id='value" . $par_index . "' VALUE='";
$str_pre_offs = 0;
$str_post = "'>";
$str_post_offs = 0;
$html = substr($html, strpos($html, $str_pre) + strlen($str_pre) + $str_pre_offs);
$value = substr($html, 0, strpos($html, $str_post) + $str_post_offs);
return $value;
} // get_num_html_value
// Query single parameter over html to get its value when json returns faulty
// value for string or other
function get_text_html_value($par_index, $html = false) {
if($html === false) $html = req($par_index);
$str_pre1 = "<table align=center width=80%>";
$str_pre2 = ": ";
$str_post = "\r";
$str_post_offs = 0;
$html = substr($html, strpos($html, $str_pre1) + strlen($str_pre1));
$html = substr($html, strpos($html, $str_pre2) + strlen($str_pre2));
$value = substr($html, 0, strpos($html, $str_post) + $str_post_offs);
return $value;
} // get_text_html_value
// return result of bitmask flags as string. This is kinda lazy and obviously
// not suited to be writeable.
function get_bitmask_html_value($par_index) {
$html = req($par_index);
$str_pre = "<select multiple id='value" . $par_index . "'>";
$str_pre_offs = 2;
$str_post = "</select>";
$str_post_offs = -2;
$html = substr($html, strpos($html, $str_pre) + strlen($str_pre) + $str_pre_offs);
$html = substr($html, 0, strpos($html, $str_post) + $str_post_offs);
$arr_raw = explode("</option>
", $html);
$result = "";
foreach($arr_raw as $entry) {
if(strpos($entry, " SELECTED>") !== false) {
if($result != "") $result .= ", ";
$result .= substr($entry, strpos($entry, ">") + 1);
}
}
return $result;
} // get_bitmask_html_value
// Update parameters with the specified indices (range) and place their variables
// underneath the specified category's dummy instance.
function update_parameters($cat_id, $min_index, $max_index) {
global $writeable_parameters, $day_month_parameters, $rel_temp_parameters, $sync_time;
$ips_dt_name = ["boolean", "integer", "float", "string"];
$bsb_lan_dt_name = ["number","enum","???","bitmask","hour.minute","day.month","string","timestamp"];
if(!IPS_ObjectExists($cat_id)) return false;
// construct request string
$req_str = "JQ=";
$count = 0;
for($i = $min_index; $i <= $max_index; $i++) {
$ident = 'Par_' . $i;
$var_id = @IPS_GetObjectIDByIdent($ident, $cat_id);
if($var_id === false) {
$ident = 'Par_' . $i . '_0';
$var_id = @IPS_GetObjectIDByIdent($ident, $cat_id);
}
if(get_setup_status() < SETUP_COMPLETED || $var_id) {
$req_str .= $i . ",";
$count++;
}
}
if($count > 0) {
$req_str = substr($req_str, 0, strlen($req_str) - 1);
$pars = req($req_str);
} else {
return true; // nothing to update
}
if(!$pars) {
msg("Requesting parameters #". $min_index . " to #" . $max_index .
" has failed.");
return false;
}
// We use this variable to count the unused day.month parameters in this
// category, so we can begin hiding some after a while to remove clutter.
$count_empty_day_month_params = 0;
foreach($pars as $i => $par) {
// workaround to filter out unrequested (6705..6846 returned 675 for example)
if($i < $min_index || $i > $max_index) {
msg("Returned parameter index #" . $i .
" is outside the requested range. Skipping.");
continue;
}
if(get_setup_status() == SETUP_REQUEST_POSTPONE_UPDATE) {
msg("Other script instance requested to postpone this update. Exiting " .
"from parameter update.");
return false;
}
$ident_prefix = 'Par_' . $i;
msg("Raw value for parameter #" . $i . " (" . $par["name"] . "): \"" .
$par["value"] . "\" (data type " . $par["dataType"] . ", unit \"" .
$par["unit"] . "\").");
$t = 3;
$p = "";
$v = [$par["value"]];
$field_suffix = [""];
$dt = $par["dataType"];
$u = $par["unit"];
$field_count = 1;
$writeable = false;
// writable fields
if(array_search($i, $writeable_parameters) !== false) $writeable = true;
// relative temperatures should be visualized as Kelvin rather than °C
if(array_search($i, $rel_temp_parameters) !== false) $unit = "K";
// Some parameters are sent as strings, but they're supposed to hold
// dd.mm (day.month). According to documentation, data type 5 would be the
// appropriate choice for them, so we override the data type with that
// internally. Workaround..?
if(array_search($i, $day_month_parameters) !== false) {
$dt = 5;
$v[0] = strval($v[0]);
} else if($dt == 5 && strlen($v[0]) != 5) {
// Special handling for data type 5, which is documented to return dd.mm,
// but never actually returns that. Sometimes it returns the current
// date/time, sometimes it is used for heating schedules. Workaround!
$full_value = get_text_html_value($i);
if(strlen($full_value) == 50) { // heating schedule
$dt = 4;
$field_count = 6;
$v = array(
substr($full_value, 3, 5),
substr($full_value, 11, 5),
substr($full_value, 20, 5),
substr($full_value, 28, 5),
substr($full_value, 37, 5),
substr($full_value, 45, 5)
);
$field_suffix = array(
" Anfang 1",
" Ende 1",
" Anfang 2",
" Ende 2",
" Anfang 3",
" Ende 3",
);
} else {
$dt = 7;
$v = [$full_value];
}
}
$idents = array();
$var_ids = array();
if($field_count > 1) {
for($fi = 0; $fi < $field_count; $fi++) {
$idents[$fi] = $ident_prefix . "_" . $fi;
$var_ids[$fi] = @IPS_GetObjectIDByIdent($idents[$fi], $cat_id);
}
} else {
$idents[0] = $ident_prefix;
$var_ids[0] = @IPS_GetObjectIDByIdent($ident_prefix, $cat_id);
}
$hide_fields_after = false;
for($fi = 0; $fi < $field_count; $fi++) {
switch($dt) {
case 0: // number
if($v[$fi] == "" || $v[$fi] == "---") {
$v[$fi] = NO_VALUE;
}
if($field_count == 1 && substr($v[$fi], -1) == '.') {
// workaround! Faulty float result (decimal places missing after dot)
$v[$fi] = get_num_html_value($i);
}
$t = 2;
$p = setup_unit_profile($u, $writeable);
break;
case 1: // enum
if($v[$fi] == "" || $v[$fi] == "---") $v[$fi] = NO_VALUE;
$p = setup_enum_profile($i, $writeable);
// if no enum profile could be created, do not create or update this
// variable.
if($p === false) continue 2;
$t = 1;
break;
case 2: // bit mask? as OBSERVED
$v[$fi] = get_bitmask_html_value($i);
break;
case 4: // time h:mm as OBSERVED!
$t = 1;
$p = "~UnixTimestampTime";
$v[$fi] = strtotime($v[$fi]);
if($v[$fi] == 0 && $hide_fields_after === false) {
$hide_fields_after = $fi + 1;
}
break;
/*case 4: // full date time
if($v[$fi] == "" || $v[$fi] == "---") $v[$fi] = NO_VALUE;
$t = 1;
$p = "~UnixTimestamp";
$v[$fi] = strtotime(str_replace('.', '-', $v[$fi]));
break;*/
case 5: // date day.month
$t = 1;
//$p = "~UnixTimestampDate";
if($v[$fi] == "" || $v[$fi] == "---") {
$v[$fi] = NO_VALUE;
} else {
$v[$fi] = substr($v[$fi], 0, 5);
$v[$fi] = strtotime(str_replace('.', '-', $v[$fi]) . "-" . date("Y"));
}
$p = setup_day_month_profile($i, $v[$fi]);
if($v[$fi] == NO_VALUE) {
$count_empty_day_month_params++;
} else {
$count_empty_day_month_params = 0;
}
if($count_empty_day_month_params > 2) $hide_fields_after = 0;
break;
case 6: // string
break;
case 7: // full date time (internally used only)
if($v[$fi] == "" || $v[$fi] == "---") $v[$fi] = NO_VALUE;
$t = 1;
$p = "~UnixTimestamp";
$v[$fi] = strtotime(str_replace('.', '-', $v[$fi]));
if($i == 0) {
$diff = $v[$fi] - time();
msg("Difference between heating / local time is " . $diff . " s.");
if(abs($diff) >= 15 && $sync_time) sync_time();
}
break;
} // switch
if(!$var_ids[$fi] && get_setup_status() < SETUP_COMPLETED) {
msg("Creating variable for parameter #" . $i . "." . $fi . " (" . $par["name"] .
") of data type " . $bsb_lan_dt_name[$dt] . "(" . $dt . ") => " .
$ips_dt_name[$t] .
", profile name \"" . $p . "\" with value \"" . $v[$fi] . "\".");
$var_ids[$fi] = IPS_CreateVariable($t);
IPS_SetParent($var_ids[$fi], $cat_id);
IPS_SetName($var_ids[$fi], $par["name"] . $field_suffix[$fi]);
IPS_SetIdent($var_ids[$fi], $idents[$fi]);
IPS_SetPosition($var_ids[$fi], $i * 10 + $fi);
IPS_SetVariableCustomProfile($var_ids[$fi], $p);
if($writeable) {
IPS_SetVariableCustomAction($var_ids[$fi], $_IPS['SELF']);
}
}
if($var_ids[$fi]) {
IPS_SetHidden($var_ids[$fi],
$hide_fields_after !== false && $fi >= $hide_fields_after);
//if($v[$fi] == "---") msg($par["name"].": " . $dt);
SetValue($var_ids[$fi], $v[$fi]);
}
}
} // foreach
return true;
} // update_parameters
// Setup a variable specific profile for "day.month" type parameters. This is
// a bit hacky, but it works better than the ~UnixTimestampDate profile, as
// there is no year 1970 shown and it is possible to disable the parameter as
// well.
function setup_day_month_profile($par_index, $par_value) {
$profile_name = ENUM_PROFILE_PREFIX . $par_index;
$c_selected = 0xFFFFFF;
$c_unselected = 0x888888;
if(!IPS_VariableProfileExists($profile_name)) {
IPS_CreateVariableProfile($profile_name, 1);
msg("Day.Month profile created for parameter #" . $par_index . ".");
}
$par_formatted = date("d.m", $par_value);
$associations = IPS_GetVariableProfile($profile_name)["Associations"];
foreach($associations as $assoc) {
$value = $assoc["Value"];
if($value != $par_value && $value != NO_VALUE) {
IPS_SetVariableProfileAssociation($profile_name, $value, "", "", -1);
}
}
IPS_SetVariableProfileAssociation(
$profile_name,
($par_value == NO_VALUE) ? 1546297200 : $par_value,
($par_value == NO_VALUE) ? "Ein" : $par_formatted,
"Calendar", // icon
$par_value == $par_value ? $c_selected : $c_unselected // color
);
IPS_SetVariableProfileAssociation(
$profile_name,
NO_VALUE,
"Aus",
"Cross", // icon
$par_value == NO_VALUE ? $c_selected : $c_unselected // color
);
return $profile_name;
} // setup_day_month_profile
// Retrieve possible values of an ENUM parameter and create a profile for it.
// The request is done as an HTML call, since there is no json-based request
// for it yet. May break if the HTML format changes.
function setup_enum_profile($par_index, $writeable) {
global $enum_buffer;
$profile_name = ENUM_PROFILE_PREFIX . $par_index;
if(!IPS_VariableProfileExists($profile_name)) {
$arr_enum = array();
if(array_key_exists($par_index, $enum_buffer)) {
$source_name = " previous single category update request";
foreach($enum_buffer[$par_index] as $entry) {
$arr_enum[$entry["enumValue"]] = $entry["desc"];
}
} else if(get_setup_status() < SETUP_COMPLETED) {
$source_name = "HTML request of enum values";
$html = req("E" . $par_index);
$str_pre = "<table align=center width=80%>";
$str_pre_offs = 10;
$str_post = "</td></tr></table>";
$str_post_offs = -8;
$str_sep = "
<br>
";
$html = substr($html, strpos($html, $str_pre) + strlen($str_pre) + $str_pre_offs);
if(substr($html, 0, 7) == "FEHLER:") {
$source_name = "HTML request of current value (guessing boolean)";
$html_value = get_text_html_value($par_index);
switch($html_value) {
case "0 - Nein":
case "1 - Ja":
$arr_enum[0] = "Nein";
$arr_enum[1] = "Ja";
break;
case "0 - Aus":
case "1 - Ein":
$arr_enum[0] = "Aus";
$arr_enum[1] = "Ein";
break;
case "0 - Aus":
case "255 - Ein":
$arr_enum[0] = "Aus";
$arr_enum[255] = "Ein";
break;
default:
msg("Unable to create enum profile for parameter #" . $par_index . ": " .
"Error returned when requesting enum values and unable to " .
"deduce boolean values from current value \"" . $html_value .
"\".");
return false;
}
} else {
$html = substr($html, 0, strpos($html, $str_post) + $str_post_offs);
$arr_raw = explode($str_sep, $html);
foreach($arr_raw as $entry) {
$index = intval(substr($entry, 0, strpos($entry, " - ")));
$label = substr($entry, strpos($entry, " - ") + 3);
if(substr($label, 0, 1) == '?') $label = substr($label, 1);
$arr_enum[$index] = $label;
}
}
} else {
return false;
}
IPS_CreateVariableProfile($profile_name, 1);
msg("Enum profile created for parameter #" . $par_index . " with " .
count($arr_enum) . " entries from " . $source_name . ".");
foreach($arr_enum as $index => $label) {
IPS_SetVariableProfileAssociation(
$profile_name,
$index,
$label,
"", // icon
-1 // color
);
// Workaround for "booleans" which are being described as enums with only
// one possible value ("False")
if(count($arr_enum) == 1 && $label == "False") {
IPS_SetVariableProfileAssociation(
$profile_name,
1,
"True",
"", // icon
-1 // color
);
}
}
if(!$writeable) {
IPS_SetVariableProfileAssociation(
$profile_name,
NO_VALUE,
"Kein Wert",
"Cross", // icon
0x888888 // color
);
}
}
return $profile_name;
} // setup_enum_profile
function setup_unit_profile($unit, $writeable) {
// for writeable values we should use the builtin profiles
if($writeable) {
switch($unit) {
case "°C": return "~Temperature";
case "K": return "~Temperature.Difference";
case "%": return "~Humidity.F";
}
}
switch($unit) {
case " ": return ""; // workaround, sometimes a space gets sent
case "°C": $unit = "degrees"; $digits = 1; $icon = "Temperature"; break;
case "%": $unit = "percent"; $digits = 0; $icon = "Intensity"; break;
case "K": $digits = 0; $icon = "Temperature"; break;
case "h":
case "min":
case "s":
$icon = "Clock";
$digits = 0;
break;
default: msg("Unknown unit: \"" . $unit . "\"."); return "";
}
$profile_name = UNIT_PROFILE_PREFIX . $unit;
if(!IPS_VariableProfileExists($profile_name)) {
IPS_CreateVariableProfile($profile_name, 2);
msg("Unit profile created for unit \"" . $unit . "\".");
if($unit == "percent") {
$unit = "%%";
} else if($unit == "degrees") {
$unit = "°C";
} else {
$unit = ' ' . $unit;
}
IPS_SetVariableProfileDigits($profile_name, $digits);
IPS_SetVariableProfileAssociation(
$profile_name,
NO_VALUE + 1,
(($digits == 0) ? "%d" : ("%." . $digits . "f")) . $unit,
$icon, // icon
-1 // color
);
IPS_SetVariableProfileAssociation(
$profile_name,
NO_VALUE,
"Kein Wert",
"Cross", // icon
0x888888 // color
);
}
return $profile_name;
} // setup_unit_profile
function set_parameter($index, $value, $inf = false) {
$html = req(($inf ? "I" : "S") . $index . "=" . $value);
$result_raw = get_text_html_value($index, $html);
if(is_int($value)) {
$result = intval($result_raw);
} else if(is_float($value)) {
$result = floatval($result_raw);
} else {
$result = $result_raw;
}
msg("Parameter #" . $index . ($inf ? " sent INF message \"" : " set to \"") .
$value . "\", returned raw value" .
" \"" . $result_raw . "\", interpreted as \"". $result . "\".");
return $result;
} // set_parameter
function req($req) {
global $ip, $pass_key;
$expect_json = (substr($req, 0, 1) == "J");
if($pass_key) {
$req = $pass_key . "/" . $req;
}
$result = @file_get_contents("http://" . $ip . "/" . $req);
if($expect_json) {
// workaround to filter out illegale characters from json response
$result = preg_replace('/[\x00-\x1F\x7F]/', '', $result);
$data = @json_decode($result, true);
if($data && count($data) > 0) {
return $data;
}
} else {
return $result;
}
return false;
} // req
?>