diff --git a/chart_config.py b/chart_config.py new file mode 100644 index 0000000..5da65ee --- /dev/null +++ b/chart_config.py @@ -0,0 +1,28 @@ +from configparser import ConfigParser +class ChartConfig: + def __init__(self): + cfgp = ConfigParser() + cfgp.read("./variables_config.ini") + self.chart_config = cfgp["chart"] + + def get_variable_parameter_config(self, key): + config = {} + positionnal = ["cat", "color", "unit"] + if key in self.chart_config.keys(): + raw_value = self.chart_config[key] + + arguments = raw_value.split(",") + keyword_mode = False + for i, argument in enumerate(arguments): + pieces = argument.split(":") + if len(pieces) == 2: + keyword_mode = True + config[pieces[0]] = pieces[1] + else: + if not keyword_mode: #everything is going well + config[positionnal[i]] = pieces[0] + else: #we cannot have a positionnal argument after a keyword argument + return None + return config + else: + return None diff --git a/influxdb.py b/influxdb.py index 0a8106c..0a2f084 100644 --- a/influxdb.py +++ b/influxdb.py @@ -1,5 +1,6 @@ from influxdb_client import InfluxDBClient from configparser import ConfigParser +from chart_config import ChartConfig import ast from datetime import datetime @@ -73,6 +74,29 @@ class InfluxDataGetter: res = self._group_variables_by_unit(available_variables) return res + + def get_available_variables_at_time2(self, times, all=False): + """ + Gets the available variables (those that we can have a value for since the device has been installed on the instrument) at the given point in time. + We can get the last available variables at the given point in time or all the known variables for the day corresponding to the timestamp. + + Parameters : + times ([int]) : the unix timestamps in seconds of the range. The first value can be unused. The last can represent the point in time. + all (bool) : indicates if we want all the variables for the given times[1] timestamp (all the day) + + Returns : + [{"tag":(str), "unit":(str), "curves":[{"name":(str), "label":(str), "color":(str)}]}] : a list of dictionnaries, each one representing + a block of curves with their name, their label and their color to display, grouped by their tag (which can be the unit augmented with an index) and their unit. + """ + + all_setup_info = self._get_all_setup_info_as_dict(times, all) + + available_variables = self._extract_variables2(all_setup_info) + available_variables = self._filter_params_with_config(available_variables) + available_variables = self._remove_variables_params_wihout_param_float_and_split(available_variables, times) + res = self._group_variables_by_unit2(available_variables) + + return res def get_curves_in_timerange(self, variables, time, interval = None): """ @@ -207,6 +231,43 @@ class InfluxDataGetter: added_names.append(name) return available_varirables + def _extract_variables2(self, all_setup_info_dict): + """ + Extracts relevant information out of the setup_info dict for each available variable in measurements nicos/se_main, nicos/se_stick, nicos/se_addons + + Parameters : + all_setup_info_dict ([{(str):((str), {...})}]) : an array of the parsed "setup_info dict" of each measurements. The key is the secop_module prefixed with "se_", and the value is a tuple with its first value + being the type of Secop device for this module, and the value is too big to give its signature. Some tuple examples can be found under graphs/setup_info_examples. + + Returns : + [{"name":(str), "label":(str), "unit":(str), "has_potential_target":(bool)}] : an array of dictionnaries, each containing the Influx name of the corresponding variable out of the setup_info dict, + the label to display in the Web GUI, its unit and a boolean value indicating if the variable has a potential target available. + + """ + available_varirables = [] + added_names = [] + for setup_info_dict in all_setup_info_dict: + for (_, content) in setup_info_dict.items(): + if content[0] != "nicos.devices.secop.devices.SecopDevice": + name = self._transform_secop_module_name_to_influx(content[1]["secop_module"]) + if name not in added_names: + + variable = { + "name":name, + "label":content[1]["secop_module"], + "params":{"value":{"cat":"", "color":"", "unit":content[1]["unit"]}} # main value + } + + for param_name, param_content in content[1]["params_cfg"].items(): + variable["params"][param_name] = { + "cat":"", + "color":"", + "unit":param_content["unit"] if "unit" in param_content.keys() else "" #unit might not be there + } + available_varirables.append(variable) + added_names.append(name) + return available_varirables + def _transform_secop_module_name_to_influx(self, secop_module_name): """ Transforms the name of the variable available in the setup_info dict into the Influx name. @@ -220,6 +281,48 @@ class InfluxDataGetter: """ return self._influx_instrument_config["measurement_prefix"] + secop_module_name.lower() + def _filter_params_with_config(self, available_variables): + + chart_config = ChartConfig() + + for variable in available_variables: + params = list(variable["params"].keys()) + for param_key in params: + key = variable["label"] if param_key == "value" else variable["label"]+"."+param_key + param_config = chart_config.get_variable_parameter_config(key) + if param_config == None : # no entries were found + if param_key != "target" and param_key != "value": # and the current param is not value or target which are displayed by default + del variable["params"][param_key] + else: + if "cat" in param_config.keys() and param_config["cat"] == "None": # cat might not have been indicated + del variable["params"][param_key] + else: + for key, value in param_config.items(): + variable["params"][param_key][key] = value + return available_variables + + def _remove_variables_params_wihout_param_float_and_split(self, available_variables, times): + res = [] + for variable in available_variables: + query = f""" + import "influxdata/influxdb/schema" + schema.measurementFieldKeys(bucket: "{self._bucket}", measurement: "{variable["name"]}", start:0, stop: {times[1] + 1}) + |> yield(name: "res") + """ + records = self._db.query(query)[0].records + fields = [record.get_value() for record in records] + for param in variable["params"].keys(): + if param+"_float" in fields: + res.append({ + "name": variable["name"] if param == "value" else variable["name"]+"."+param, + "label": variable["label"] if param == "value" else variable["label"]+"."+param, + "cat": variable["params"][param]["cat"], + "color": variable["params"][param]["color"], + "unit": variable["params"][param]["unit"] + }) + + return res + def _remove_variables_without_value_float(self, available_variables, times): """ Removes some of the previously identified available_variables if they effectively do not have a value_float field in InfluxDB. @@ -273,6 +376,43 @@ class InfluxDataGetter: return available_variables + def _group_variables_by_unit2(self, available_variables): + """Performs a group by unit, while removing useless information and adding target curves. + + Parameters : + available_variables ([{"name":(str), "label":(str), "unit":(str), "has_potential_target":(bool)}]) : an array of dictionnaries, each containing the Influx name of the corresponding variable out of the setup_info dict, + the label to display in the Web GUI, its unit and a boolean value indicating if the variable has a target available. + + Returns : + + [{"tag":(str), "unit":(str), "curves":[{"name":(str), "label":(str), "color":(str)}]] : a list of dictionnaries, each one representing + a block of curves with their name, their label and their color to display, grouped by their tag (which can be the unit augmented with an index) and their unit. + """ + groups = {} + + for available_variable in available_variables: + self._append_variable2(groups, available_variable) + + return list(groups.values()) + + def _append_variable2(self, groups, variable): + """ + Appends the variable in the unit group with a tag and a color, and creates the unit key if not available. + + Parameters : + groups ({}) : a dictionnary that contains the curves grouped by unit, which will be updated + variable ({"name":(str), "label":(str), "unit":(str)[,"has_potential_target":(bool)]}) : a dictionnary containing the Influx name of the corresponding variable out of the setup_info dict, + the label to display in the Web GUI, its unit and possibly a boolean value indicating if the variable has a target available. + """ + key = variable["cat"]+variable["unit"] + if key not in groups.keys(): + groups[key] = {"tag":key, "unit":variable["unit"], "curves":[]} #tag is set to cat+unit while client is not modified + groups[key]["curves"].append({ + "name":variable["name"], + "label":variable["label"], + "color":variable["color"], + }) + def _group_variables_by_unit(self, available_variables): """Performs a group by unit, while removing useless information and adding target curves. diff --git a/influxgraph.py b/influxgraph.py index 1111f82..d59c648 100644 --- a/influxgraph.py +++ b/influxgraph.py @@ -138,7 +138,7 @@ class InfluxGraph: start_time = int(self.get_abs_time(time)[0]) end_time = int(self.get_abs_time(time)[-1]) - blocks = self.influx_data_getter.get_available_variables_at_time([start_time, end_time], all) + blocks = self.influx_data_getter.get_available_variables_at_time2([start_time, end_time], all) # updates the self.variables attribute to keep track of the available variables self.variables = [variable["name"] for block in blocks for variable in block["curves"]] diff --git a/variables_config.ini b/variables_config.ini index 4828e45..b145e72 100644 --- a/variables_config.ini +++ b/variables_config.ini @@ -1,3 +1 @@ -[chart] -T_stat.raw=cat:rawOhm -T_stat=color:#FF0000 \ No newline at end of file +[chart] \ No newline at end of file