Mein Skript für BSB-LAN

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
?>

Hi,
Danke für dein Skript, Ich wollte es gerade ausführen, bekomme aber folgende Fehlermeldung :frowning:


Parse error:  syntax error, unexpected '"."' (T_CONSTANT_ENCAPSED_STRING), expecting ')' in /var/lib/symcon/scripts/55698.ips.php on line 147
Abort Processing during Fatal-Error: syntax error, unexpected '"."' (T_CONSTANT_ENCAPSED_STRING), expecting ')'
   Error in Script /var/lib/symcon/scripts/55698.ips.php on Line 147



Kann mir keiner helfen :roll_eyes:

Die Fehler zeigen immer auf Zeile 147.
Schau doch mal nach, was da falsch sein könnte oder poste die Zeile hier.

Das hier steht in der Zeile 147


  msg("Clearing out instance "" . IPS_GetName($inst_id) . "".");


Im Script oben steht aber :

msg("Clearing out instance \"" . IPS_GetName($inst_id) . "\".");

WTF! Das gibt es doch nicht, das Script habe ich einfach rauskopiert. Jetzt noch mal und es funktioniert :banghead::banghead::banghead::banghead::banghead:

Danke für die Hilfe und den Hinweis :smiley:

Auf der suche nach dem BSB-LAN-Interface.

Hat zufällig jemand noch einen fertig bestückten Adapter ,
oder eine bezugsadresse ?

Gruß Jens