remove obsolete stuff + small adjustments
This commit is contained in:
@ -215,18 +215,10 @@ function handleUpdate(message, src) {
|
||||
function updateTarget(component) {
|
||||
let matches = document.getElementsByName(component.name);
|
||||
let elem = matches[0]; // Should be the only match
|
||||
elem.value = component.value;
|
||||
// elem.value = component.value;
|
||||
let row = elem.closest('div');
|
||||
row.classList.remove('row-waiting-for-answer');
|
||||
|
||||
let oldValue = ('oldValue' in elem) ? elem.oldValue : elem.value;
|
||||
if (component.value != elem.value && parseFloat(component.value) != parseFloat(elem.value) && component.value != oldValue) {
|
||||
if (elem == document.activeElement || oldValue != elem.value) {
|
||||
row.classList.add('row-waiting-for-answer');
|
||||
} else {
|
||||
elem.value = component.value;
|
||||
}
|
||||
}
|
||||
elem.actualValue = component.value;
|
||||
if(elem.__ctype__ == 'input') {
|
||||
resizeTextfield(elem);
|
||||
|
@ -342,7 +342,7 @@ function loadGraphicsMenu(panel){
|
||||
menuGraphicsPopup.addEntry(aboutTopRightHandCornerCrossHelpEntry);
|
||||
|
||||
// let graphicsMenuControl = new Control("res/menu_white_wide.png", "res/menu_white_wide.png", "Menu", () => {
|
||||
let graphicsMenuControl = new Control("res/icon_menu_graphics.png", "icon_menu_graphics.png.png", "Menu", () => {
|
||||
let graphicsMenuControl = new Control("res/icon_menu_graphics.png", "res/icon_menu_graphics.png", "Menu", () => {
|
||||
datesPopup.hide();
|
||||
exportPopup.hide();
|
||||
curvesSettingsPopup.hide();
|
||||
|
@ -1,4 +1,4 @@
|
||||
var writePermission = true;
|
||||
var writePermission = false;
|
||||
var showParams = false;
|
||||
var showConsole = false;
|
||||
var prompt = false // True while a prompt is opened.
|
||||
@ -505,369 +505,3 @@ function appendToGridElement(s, title, type, content) {
|
||||
gridelements[s].innerHTML = "";
|
||||
gridelements[s].appendChild(gridContainer);
|
||||
}
|
||||
|
||||
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||||
/* ---------------------------------------------------------------------------------- */
|
||||
// obsolete...
|
||||
|
||||
// function createPushbutton_row(component) {
|
||||
// // Creates row-element containing a push button
|
||||
// var name = component.name;
|
||||
// var command = component.command;
|
||||
// var left = createTitle(component);
|
||||
|
||||
// left.id = component.name;
|
||||
// left.name = component.title;
|
||||
|
||||
// var right = createInputElement(component);
|
||||
// right.classList.add("push-button");
|
||||
|
||||
// row = appendToContent(left, right);
|
||||
// right.onclick = function () {
|
||||
// if (writePermission) {
|
||||
// var row = left.parentNode;
|
||||
// right.style.backgroundColor = "orangered";
|
||||
// // Request for command
|
||||
// sendCommand(s, command);
|
||||
// } else {
|
||||
// prompt = true;
|
||||
// alertify.confirm("", "You are connected with <b>" + clientTitle
|
||||
// + "</b>. <br>"
|
||||
// + "Are you sure you want to modify things here?",
|
||||
// function () {
|
||||
// // User decided to proceed.
|
||||
// writePermission = true;
|
||||
// writePermissionTimeout = setTimeout(function () {
|
||||
// writePermission = false;
|
||||
// }, 3600000);
|
||||
// var row = left.parentNode;
|
||||
// row.style.backgroundColor = "orangered";
|
||||
// // Request for command
|
||||
// sendCommand(s, command);
|
||||
// prompt = false;
|
||||
// }, function () {
|
||||
// // User decided to cancel
|
||||
// prompt = false;
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// row.classList.add("row");
|
||||
// return row;
|
||||
// }
|
||||
|
||||
// function create_input_row(component) {
|
||||
// // Creates row-element containing input-item.
|
||||
// var name = component.name;
|
||||
// var command = component.command;
|
||||
// var left = createTitle(component);
|
||||
|
||||
// var input = createInputElement(component, 'input', 'input-text');
|
||||
// input.type = "text";
|
||||
// input.style.width = "100px";
|
||||
// input.addEventListener("focus", function(evt) {
|
||||
// let elm = evt.target;
|
||||
// setTimeout(function(){elm.setSelectionRange(0, elm.value.length);},0);
|
||||
// });
|
||||
|
||||
// input.onkeydown = function (e) {
|
||||
// if (e.which === 27 || e.key == "Escape") {
|
||||
// // User decided to cancel
|
||||
// input.value = intput.oldValue;
|
||||
// resizeTextfield(input);
|
||||
// var row = left.parentNode;
|
||||
// row.style.backgroundColor = "white";
|
||||
// }
|
||||
// }
|
||||
|
||||
// input.onfocus = function () {
|
||||
// input.oldValue = input.value;
|
||||
|
||||
// if (isTouchDevice)
|
||||
// setTimeout(function () {
|
||||
// posTextfield(s, left);
|
||||
// }, 1);
|
||||
// }
|
||||
|
||||
// input.onblur = function () {
|
||||
// if (prompt) {
|
||||
// return false;
|
||||
// }
|
||||
// var row = left.parentNode;
|
||||
// var value = input.value;
|
||||
// let oldValue = 'oldValue' in input ? input.oldValue : value;
|
||||
// if (!('actualValue' in input)) input.actualValue = oldValue;
|
||||
// actualValue = input.actualValue;
|
||||
// if (value == actualValue || value == oldValue ||
|
||||
// parseFloat(value) == parseFloat(actualValue) || parseFloat(value) == parseFloat(oldValue)) {
|
||||
// input.value = actualValue;
|
||||
// // nothing to do.
|
||||
// row.style.backgroundColor = "white";
|
||||
// return false;
|
||||
// }
|
||||
// // User changed value and moved focus to other object.
|
||||
// alertify.confirm("", "You changed a field without pressing the return key.<br>"
|
||||
// + "Hint: press ESC for leaving a field unchanged.<b>"
|
||||
// + "You are connected with <b>" + clientTitle + "</b>.<br>"
|
||||
// + "Are you sure you want to change the value of<br><b>"
|
||||
// + name + "</b> from <b>" + actualValue
|
||||
// + "</b> to <b>" + value + "</b>?", function () {
|
||||
// // User decided to proceed.
|
||||
// writePermission = true;
|
||||
// writePermissionTimeout = setTimeout(function () {
|
||||
// writePermission = false;
|
||||
// }, 3600000);
|
||||
// row.style.backgroundColor = "orangered";
|
||||
// // Request for command
|
||||
// sendCommand(s, command + " " + value);
|
||||
// resizeTextfield(input);
|
||||
// prompt = false;
|
||||
// }, function () {
|
||||
// // User decided to cancel
|
||||
// input.value = input.actualValue;
|
||||
// resizeTextfield(input);
|
||||
// row.style.backgroundColor = "white";
|
||||
// prompt = false;
|
||||
// });
|
||||
// }
|
||||
|
||||
// var form = document.createElement('form');
|
||||
// form.onsubmit = function (e) {
|
||||
// e.preventDefault();
|
||||
// if (writePermission) {
|
||||
// var row = left.parentNode;
|
||||
// row.style.backgroundColor = "orangered";
|
||||
// // Request for command
|
||||
// input.actualValue = input.value;
|
||||
// sendCommand(s, name + " " + input.value);
|
||||
// input.blur();
|
||||
// } else {
|
||||
// var value = input.value
|
||||
// prompt = true;
|
||||
// alertify.confirm("", "You are connected with <b>" + clientTitle
|
||||
// + "</b>. <br>"
|
||||
// + "Are you sure you want to modify things here?",
|
||||
// function () {
|
||||
// // User decided to proceed.
|
||||
// writePermission = true;
|
||||
// writePermissionTimeout = setTimeout(function () {
|
||||
// writePermission = false;
|
||||
// }, 3600000);
|
||||
// var row = left.parentNode;
|
||||
// row.style.backgroundColor = "orangered";
|
||||
// input.actualValue = value;
|
||||
// // Request for command
|
||||
// sendCommand(s, command + " " + value);
|
||||
// resizeTextfield(input);
|
||||
// prompt = false;
|
||||
// }, function () {
|
||||
// // User decided to cancel
|
||||
// input.value = input.oldValue;
|
||||
// resizeTextfield(input);
|
||||
// prompt = false;
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
// form.appendChild(input);
|
||||
// var right = createInputElement(component);
|
||||
// right.appendChild(form);
|
||||
// return appendToContent(left, right);
|
||||
|
||||
// function posTextfield(s, left) {
|
||||
// if (debug_group_daniel) {
|
||||
// console.log("%cfunction: posTextfield", "color:white;background:salmon");
|
||||
// }
|
||||
|
||||
// // var content = swiper[s].slides[swiper[s].activeIndex].childNodes[1];
|
||||
// // var row = left.parentNode;
|
||||
// // content.scrollTop = row.offsetTop - 30;
|
||||
|
||||
// // ---------------------> Not working anymore since swiper was removed!!!
|
||||
// }
|
||||
// }
|
||||
|
||||
// function createCheckbox_row(component) {
|
||||
// // Creates row-element containing checkbox-item
|
||||
// var command = component.command;
|
||||
// var left = createTitle(component);
|
||||
// var input = createInputElement(component, 'input', 'parameter-checkbox');
|
||||
// input.type = "checkbox";
|
||||
// input.onkeyup = function (e) {
|
||||
// if (e.keyCode === 32) {
|
||||
// handleCheckbox();
|
||||
// }
|
||||
// }
|
||||
|
||||
// var label = document.createElement('label');
|
||||
// label.for = input;
|
||||
// label.classList.add("parameter-label");
|
||||
|
||||
// label.onclick = function () {
|
||||
// handleCheckbox();
|
||||
// }
|
||||
|
||||
// function handleCheckbox() {
|
||||
// if (writePermission) {
|
||||
// var row = left.parentNode;
|
||||
// row.style.backgroundColor = "orangered";
|
||||
// if (input.checked) {
|
||||
// var value = "0";
|
||||
// input.checked = false;
|
||||
// } else {
|
||||
// var value = "1";
|
||||
// input.checked = true;
|
||||
// }
|
||||
// // Request for command
|
||||
// sendCommand(s, command + " " + value);
|
||||
// } else {
|
||||
// alertify.confirm("", "You are connected with <b>" + clientTitle
|
||||
// + "</b>. <br>"
|
||||
// + "Are you sure you want to modify things here?",
|
||||
// function () {
|
||||
// // User decided to proceed.
|
||||
// writePermission = true;
|
||||
// writePermissionTimeout = setTimeout(function () {
|
||||
// writePermission = false;
|
||||
// }, 3600000);
|
||||
// var row = left.parentNode;
|
||||
// row.style.backgroundColor = "orangered";
|
||||
// if (input.checked) {
|
||||
// var value = "0";
|
||||
// input.checked = false;
|
||||
// } else {
|
||||
// var value = "1";
|
||||
// input.checked = true;
|
||||
// }
|
||||
// // Request for command
|
||||
// sendCommand(s, command + " " + value);
|
||||
// }, function () {
|
||||
// // User decided to cancel
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
// var right = document.createElement('span');
|
||||
// right.classList.add("col-right");
|
||||
// right.appendChild(input);
|
||||
// right.appendChild(label);
|
||||
// return appendToContent(left, right);
|
||||
// }
|
||||
|
||||
// function createEnum_row(component) {
|
||||
// // Creates row-element containing dropdown-selection.
|
||||
// var name = component.name;
|
||||
// var command = component.command;
|
||||
// var buttons = component.enum_names;
|
||||
// var left = createTitle(component);
|
||||
// var select = createInputElement(component, 'select', 'select-params');
|
||||
// select.onfocus = function () {
|
||||
// select.oldIndex = select.selectedIndex;
|
||||
// }
|
||||
|
||||
// select.oninput = function () {
|
||||
// if (writePermission && component.title != "device config") {
|
||||
// var row = left.parentNode;
|
||||
// row.style.backgroundColor = "orangered";
|
||||
// // Request for command
|
||||
// sendCommand(s, command + " " + this.value);
|
||||
// } else {
|
||||
// alertify.confirm("", "You are connected with <b>" + clientTitle
|
||||
// + "</b>. <br>"
|
||||
// + "Are you sure you want to modify things here?",
|
||||
// function () {
|
||||
// // User decided to proceed.
|
||||
// writePermission = true;
|
||||
// writePermissionTimeout = setTimeout(function () {
|
||||
// writePermission = false;
|
||||
// }, 3600000);
|
||||
// var row = left.parentNode;
|
||||
// row.style.backgroundColor = "orangered";
|
||||
// // Request for command
|
||||
// sendCommand(s, command + " " + select.value);
|
||||
// }, function () {
|
||||
// // User decided to cancel
|
||||
// select.value = select.options[select.oldIndex].value
|
||||
// });
|
||||
// }
|
||||
// };
|
||||
|
||||
// for (var i = 0; i < buttons.length; i++) {
|
||||
// var option = document.createElement('option');
|
||||
// option.type = "enum";
|
||||
// option.classList.add("option-params");
|
||||
// option.value = buttons[i].value;
|
||||
// option.appendChild(document.createTextNode(buttons[i].title));
|
||||
// select.add(option);
|
||||
// }
|
||||
// select.style.display = "none";
|
||||
|
||||
// var right = document.createElement('span');
|
||||
// right.classList.add("col-right");
|
||||
// right.appendChild(select);
|
||||
// return appendToContent(left, right);
|
||||
// }
|
||||
|
||||
// ...obsolete
|
||||
/* ---------------------------------------------------------------------------------- */
|
||||
// The following two functions seem unreachable
|
||||
|
||||
/* ########## Is this function used? How? Where? ########## */
|
||||
// function create_group_row(component) {
|
||||
// // Creates row-element containing link.
|
||||
// var title = component.title;
|
||||
// var row = document.createElement('row');
|
||||
// row.id = component.name;
|
||||
// row.name = title;
|
||||
// row.classList.add("row");
|
||||
// row.tabIndex = "0";
|
||||
|
||||
// console.log('createGroupRow');
|
||||
|
||||
// row.onclick = function () {
|
||||
// var slideNames = getSlideNames();
|
||||
// slideNames[0] = component.name;
|
||||
// document.title = "SEA "+ clientTitle + " " + slideNames.join(" ");
|
||||
// history.pushState({func: "gotoGroups", funarg: slideNames.join("%20")}, document.title, "#" + slideNames.join("%20"));
|
||||
// getGroup(s, component.name);
|
||||
// }
|
||||
|
||||
// if (title === "console" || title === "device config") {
|
||||
// row.classList.add("row");
|
||||
// row.innerHTML = "console";
|
||||
// }
|
||||
// row.innerHTML = title;
|
||||
// return row;
|
||||
// }
|
||||
|
||||
/* ########## Is this function used? How? Where? ########## */
|
||||
// function create_rdonly_row(component) {
|
||||
// // Creates row-element containing link AND read-only-item.
|
||||
// var link = component.link;
|
||||
// if (!link) // simple rdonly
|
||||
// return appendToContent(createTitle(component),
|
||||
// createInputElement(component));
|
||||
|
||||
// // with link
|
||||
// var left = document.createElement('a');
|
||||
// left.innerHTML = component.title;
|
||||
|
||||
// console.log('ccc')
|
||||
|
||||
// left.id = component.name;
|
||||
|
||||
// row = appendToContent(left, createInputElement(component));
|
||||
// row.onclick = function () {
|
||||
// this.style.backgroundColor = "orangered";
|
||||
// left.click();
|
||||
// }
|
||||
// if (link.charAt(0) == ':') {
|
||||
// left.href = "http://" + location.hostname + link + "/";
|
||||
// } else {
|
||||
// left.href = link;
|
||||
// }
|
||||
|
||||
// row.classList.add("row");
|
||||
// return row;
|
||||
// }
|
||||
|
@ -1 +1,9 @@
|
||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
{"name":"",
|
||||
"short_name":"",
|
||||
"icons":[
|
||||
{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},
|
||||
{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],
|
||||
"theme_color":"#ffffff",
|
||||
"background_color":"#ffffff",
|
||||
"display":"fullscreen"}
|
||||
|
||||
|
624
influxdb.py
624
influxdb.py
@ -1,624 +0,0 @@
|
||||
from influxdb_client import InfluxDBClient
|
||||
from configparser import ConfigParser
|
||||
import ast
|
||||
from datetime import datetime
|
||||
from pandas import DataFrame as df, merge_ordered
|
||||
from numpy import NaN
|
||||
|
||||
MEASURMENT_PREFIX = "nicos/se_"
|
||||
BUCKET_PREFIX = "nicos-cache-"
|
||||
|
||||
class InfluxDB:
|
||||
"""
|
||||
Class used to handle the connection with the InfluxDB instance
|
||||
Attributes :
|
||||
_client (InfluxDBClient) : the InfluxDB client
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
config = ConfigParser()
|
||||
config.optionxform = str
|
||||
config.read("./config/influx.ini")
|
||||
self._client = InfluxDBClient(url=config["INFLUX"]["url"], token=config["INFLUX"]["token"],
|
||||
org=config["INFLUX"]["org"])
|
||||
|
||||
def disconnet(self):
|
||||
"""
|
||||
Disconnects from the InfluxDB instance
|
||||
"""
|
||||
self._client.close()
|
||||
|
||||
def query(self, query_str):
|
||||
"""
|
||||
Executes the query on the InfluxDB instance
|
||||
|
||||
Parameters :
|
||||
query_str (string) : the Flux query to execute
|
||||
|
||||
Returns :
|
||||
TableList : an InfluxDB list of the tables returned by the query
|
||||
"""
|
||||
return self._client.query_api().query(query_str)
|
||||
|
||||
def query_data_frame(self, query_str):
|
||||
"""
|
||||
Executes the query on the InfluxDB instance and gets the response as a pandas DataFrame
|
||||
|
||||
Parameters :
|
||||
query_str (string) : the Flux query to execute
|
||||
|
||||
Returns :
|
||||
DataFrame : the query response as a DataFrame
|
||||
"""
|
||||
return self._client.query_api().query_data_frame(query_str)
|
||||
|
||||
class PrettyFloat(float):
|
||||
"""saves bandwidth when converting to JSON
|
||||
|
||||
a lot of numbers originally have a fixed (low) number of decimal digits
|
||||
as the binary representation is not exact, it might happen, that a
|
||||
lot of superfluous digits are transmitted:
|
||||
|
||||
str(1/10*3) == '0.30000000000000004'
|
||||
str(PrettyFloat(1/10*3)) == '0.3'
|
||||
"""
|
||||
def __repr__(self):
|
||||
return '%.15g' % self
|
||||
|
||||
class InfluxDataGetter:
|
||||
"""
|
||||
Class used to get data from InfluxDB.
|
||||
|
||||
Attributes :
|
||||
_bucket (str) : the name of the InfluxDB bucket to query (used for all queries)
|
||||
_db (InfluxDB) : the InfluxDB instance of the database to query
|
||||
"""
|
||||
|
||||
def __init__(self, db, instrument_name):
|
||||
"""
|
||||
Parameters :
|
||||
db (InfluxDB) : the InfluxDB instance of the database to query
|
||||
instrument_name (str) : the name of the instrument from which the data will be got
|
||||
"""
|
||||
self._bucket = BUCKET_PREFIX + instrument_name
|
||||
self._db = db
|
||||
|
||||
# ----- PUBLIC METHODS
|
||||
|
||||
def get_curves_in_timerange(self, variables, time, interval = None):
|
||||
"""
|
||||
Gets the curves for the given variables within a timerange.
|
||||
|
||||
Parameters :
|
||||
variables ([(str)]) : an array of variable names (Influx) to get the curves for
|
||||
time ([int]) : the timerange we want the values in. It consists of two values which are Unix timestamps in seconds, first included, second excluded.
|
||||
interval (int|None) : the interval (resolution) of the values to get (in nanoseconds)
|
||||
|
||||
Returns :
|
||||
{(str):[[(int), (float)]]} : a dictionnary of curves. The key is the name of the influx variable, and the value is an array of pairs (also arrays), the first value being the Unix timestamp in second (x), the seconds being the value (y).
|
||||
"""
|
||||
res = {}
|
||||
for variable in variables:
|
||||
var_param = variable.split(".")
|
||||
variable_name_for_query = var_param[0]
|
||||
parameter = "value" if len(var_param) == 1 else var_param[1]
|
||||
curve = self._get_curve(variable_name_for_query, parameter, time, interval)
|
||||
res[variable] = curve
|
||||
|
||||
return res
|
||||
|
||||
def poll_last_values(self, variables, lastvalues, end_time):
|
||||
"""
|
||||
Polls the lastest values for the given variables since their last known point to end_time.
|
||||
|
||||
Parameters :
|
||||
variables ([(str)]) : an array of variable names (Influx) to get the last known values for
|
||||
lastvalues ({(str):((float), (float))}) : a dictionnary of tuples, first value being the floating Unix timestamp in seconds (precision = ms) of the last known value for the curve, and the second value being the associated value, indexed by the curve name
|
||||
end_time (int) : the Unix timestamp in seconds of the last point in time to include the values in
|
||||
|
||||
Returns :
|
||||
{(str):[[(int), (float)]]} : a dictionnary of points. The key is the name of the influx variable, and the value is an array of pairs (also array), the first value being the Unix timestamp in second (x), the seconds being the value (y).
|
||||
"""
|
||||
res = {}
|
||||
for variable in variables:
|
||||
|
||||
var_param = variable.split(".")
|
||||
variable_name_for_query = var_param[0]
|
||||
parameter = "value" if len(var_param) == 1 else var_param[1]
|
||||
start_time = int(lastvalues[variable][0]) if variable in lastvalues.keys() else None #if the call to poll_last_values is more recent than getgraph, we trigger only one value, which is the last one available from 0 to end_time
|
||||
points = []
|
||||
if start_time == None or start_time < end_time: # start_time might be more recent than end_time if getgraph is called between graphpoll and pol_last_values (influxgraph.py)
|
||||
points = self._get_last_values(variable_name_for_query,parameter,start_time, end_time)
|
||||
if len(points) > 0 :
|
||||
res[variable] = points
|
||||
return res
|
||||
|
||||
def get_device_name(self, time):
|
||||
"""
|
||||
Gets the device name available at time with stick and addons
|
||||
|
||||
Parameters :
|
||||
time (int) : the Unix timestamp in seconds of the time we want the device name
|
||||
|
||||
Returns :
|
||||
str : the device name
|
||||
"""
|
||||
components = self._get_device_name_components(time)
|
||||
return "/".join(components)
|
||||
|
||||
def get_curves_data_frame(self, variables, times, interval, variables_name_label_map=None):
|
||||
"""
|
||||
Gets the curves for the given variables within a timerange times, as a pandas dataframe.
|
||||
All curves are on a single common time axis.
|
||||
The first column is called "relative", and consists of floating seconds, relative to the beginning of the query.
|
||||
The "timestamp" column (absolute floating UNIX timestamps in seconds, precise to the nanosecond) is the last one.
|
||||
If a curve does not have a point at a given point, the last known value for this curve is used.
|
||||
If a curve gets expired, it is filled with NaN value for the corresponding expired time windows.
|
||||
The first value (for each variable) is the last known value before the time interval.
|
||||
|
||||
Parameters :
|
||||
variables ([(str)]) : an array of variable names (Influx) to get the curves for.
|
||||
times ([int]) : the timerange we want the values in. It consists of two values which are Unix timestamps in seconds, first included, second excluded.
|
||||
interval (int) : the interval (resolution) of the values to get (in seconds). Allows data binning.
|
||||
variables_name_label_map ({(str):(str)} | None) : a dictionnary containing curve labels, indexed by INFLUX names. The corresponding label will be used in the TSV header for each variable if found, else the influx name.
|
||||
Returns :
|
||||
pandas.DataFrame : the curves in a single pandas DataFrame
|
||||
"""
|
||||
variables_info = {}
|
||||
for variable in variables:
|
||||
var_param = variable.split(".")
|
||||
variable_name_for_query = var_param[0]
|
||||
parameter = "value" if len(var_param) == 1 else var_param[1]
|
||||
|
||||
# we need to rename the "_time" column to simply "time" in case we want binning because of the comparison done later in the "binned points with same timestamp" process.
|
||||
query = f"""
|
||||
from(bucket: "{self._bucket}")
|
||||
|> range(start: {times[0]}, stop: {times[1] + 1})
|
||||
|> filter(fn : (r) => r._measurement == "{variable_name_for_query}")
|
||||
|> filter(fn : (r) => r._field == "{parameter+"_float"}")
|
||||
{f'|> aggregateWindow(every: duration(v: {self._seconds_to_nanoseconds(interval)}), fn: last, createEmpty:false, timeDst:"binning_time")' if interval != 'None' else ''}
|
||||
|> map(fn: (r) => ({{r with relative: ( float(v: uint(v: {"r.binning_time" if interval != "None" else "r._time"}) - uint(v:{self._seconds_to_nanoseconds(times[0])})) / 1000000000.0 )}}))
|
||||
|> map(fn: (r) => ({{r with timestamp: float(v: uint(v: {"r.binning_time" if interval != "None" else "r._time"})) / 1000000000.0}}))
|
||||
{'|> rename(columns: {_time:"time"})' if interval != 'None' else ''}
|
||||
|> drop(columns:["_start", "_stop", "_field"])
|
||||
|> pivot(rowKey:["relative", "timestamp", "expired"{', "time"' if interval != "None" else ''}], columnKey: ["_measurement"], valueColumn: "_value")
|
||||
"""
|
||||
data_frame = self._db.query_data_frame(query)
|
||||
|
||||
# If there is a binning asked, there can be two points with the same timestamp/relative value, because points with expired=True and expired=False can be found in the same interval, leading to two rows, one with last True, and one with last False.
|
||||
# For each identified couple, we look at the real timestamp of the points used to feed this interval. We then keep the value which is the more recent in this interval.
|
||||
|
||||
if interval != "None" and not data_frame.empty:
|
||||
# we first identify the expired points
|
||||
expired_rows = data_frame.loc[data_frame["expired"] == "True"]
|
||||
|
||||
# we use itertuples to preserve the pandas dtypes, so comparisons can be done. "tuple" is a Python named tuple
|
||||
for expired_point_tuple in expired_rows.itertuples():
|
||||
# Then, we identify if there is a valid point with the same relative time as the current expired point
|
||||
corresponding_valid_point = data_frame.loc[(data_frame["expired"] == "False") & (data_frame["relative"] == expired_point_tuple.relative)]
|
||||
# we make sure that there is only one corresponding valid point, even if in theory, there will never be more than one corresponding valid point
|
||||
if not corresponding_valid_point.empty and len(corresponding_valid_point.index) == 1:
|
||||
# if we did not rename "_time" to "time" sooner in the query, "_time" would have been renamed to a positionnal name (because it starts with a _, see itertuples() doc), making confusion while reading the code
|
||||
if corresponding_valid_point.iloc[0]["time"] > expired_point_tuple.time:
|
||||
data_frame.drop(expired_point_tuple.Index, inplace=True)
|
||||
else:
|
||||
data_frame.drop(corresponding_valid_point.index, inplace=True)
|
||||
# we do not need the "time" column anymore
|
||||
data_frame.drop(["time"], axis=1, inplace=True)
|
||||
|
||||
# Needed for last known value
|
||||
query_last_known = f"""
|
||||
from(bucket: "{self._bucket}")
|
||||
|> range(start: 0, stop: {times[0] + 1})
|
||||
|> filter(fn : (r) => r._measurement == "{variable_name_for_query}")
|
||||
|> filter(fn : (r) => r._field == "{parameter+"_float"}")
|
||||
|> last()
|
||||
|> map(fn: (r) => ({{r with relative: 0.0}}))
|
||||
|> map(fn: (r) => ({{r with timestamp: float(v: uint(v: r._time)) / 1000000000.0}}))
|
||||
|> drop(columns:["_start", "_stop", "_field"])
|
||||
|> pivot(rowKey:["relative", "timestamp", "expired"], columnKey: ["_measurement"], valueColumn: "_value")
|
||||
"""
|
||||
|
||||
data_frame_last_known = self._db.query_data_frame(query_last_known)
|
||||
row_to_insert = None
|
||||
for index, row in data_frame_last_known.iterrows():
|
||||
try: #needed because row_to_insert == None is not possible
|
||||
if row_to_insert.empty or row["timestamp"] > row_to_insert["timestamp"]:
|
||||
row_to_insert = row
|
||||
except:
|
||||
row_to_insert = row
|
||||
try: #row_to_insert might be None
|
||||
if not row_to_insert.empty :
|
||||
row_to_insert["timestamp"] = float(times[0])
|
||||
if data_frame.empty:
|
||||
data_frame = row_to_insert.to_frame().T
|
||||
else:
|
||||
data_frame.loc[-1] = row_to_insert
|
||||
except:
|
||||
pass
|
||||
|
||||
if data_frame.empty:
|
||||
continue
|
||||
|
||||
variable_df_column_name = variables_name_label_map.get(variable, variable) if not variables_name_label_map == None else variable
|
||||
data_frame.rename(columns={variable_name_for_query : variable_df_column_name}, inplace=True)
|
||||
data_frame.drop(["result", "table"], axis=1, inplace=True)
|
||||
data_frame.sort_values(by=["timestamp"], inplace=True)
|
||||
data_frame.reset_index()
|
||||
|
||||
variables_info[variable_df_column_name] = {}
|
||||
variables_info[variable_df_column_name]["expired_ranges"] = []
|
||||
|
||||
# Identify time windows for which the curve is expired
|
||||
|
||||
for index, row in data_frame.iterrows():
|
||||
if row["expired"] == "True":
|
||||
data_frame.loc[index, variable_df_column_name] = NaN
|
||||
variables_info[variable_df_column_name]["expired_ranges"].append([row["timestamp"]])
|
||||
elif row["expired"] == "False":
|
||||
if len(variables_info[variable_df_column_name]["expired_ranges"]) > 0 and len(variables_info[variable_df_column_name]["expired_ranges"][-1]) == 1:
|
||||
variables_info[variable_df_column_name]["expired_ranges"][-1].append(row["timestamp"])
|
||||
data_frame.reset_index()
|
||||
data_frame.drop(["expired"], axis=1, inplace=True)
|
||||
variables_info[variable_df_column_name]["df"] = data_frame
|
||||
res = None
|
||||
non_empty_variables = list(variables_info.keys())
|
||||
|
||||
# Merge single curve dataframes to a global one
|
||||
|
||||
if len(non_empty_variables) == 0:
|
||||
return df()
|
||||
elif len(non_empty_variables) == 1:
|
||||
res = variables_info[non_empty_variables[0]]["df"]
|
||||
else :
|
||||
for i in range(0, len(non_empty_variables)):
|
||||
if i == 1:
|
||||
res = merge_ordered(variables_info[non_empty_variables[0]]["df"], variables_info[non_empty_variables[1]]["df"], on=["timestamp", "relative"], suffixes=(None, None))
|
||||
elif i > 1:
|
||||
res = merge_ordered(res, variables_info[non_empty_variables[i]]["df"], on=["timestamp", "relative"], suffixes=(None, None))
|
||||
|
||||
# Forward fill missing values, then set data points to NaN for those which are expired
|
||||
if len(non_empty_variables) > 1:
|
||||
res.ffill(inplace=True)
|
||||
for variable, info in variables_info.items():
|
||||
for expired_range in info["expired_ranges"]:
|
||||
res.loc[(res["timestamp"] >= expired_range[0]) & ((res["timestamp"] < expired_range[1]) if len(expired_range) == 2 else True), variable] = NaN
|
||||
|
||||
# Change order of columns
|
||||
cols = res.columns.tolist()
|
||||
cols = [cols[0]] + cols[2:] + [cols[1]]
|
||||
res = res[cols]
|
||||
|
||||
return res
|
||||
|
||||
# ----- PRIVATE METHODS
|
||||
|
||||
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.
|
||||
Lowers the secop_module_name and adds "nicos/se_" as prefix
|
||||
|
||||
Parameters :
|
||||
secop_module_name (str) : the secop module name of the variable in the setup_info dict.
|
||||
|
||||
Returns :
|
||||
str : the transformed variable name that matches the Influx names reqauirements
|
||||
"""
|
||||
return MEASURMENT_PREFIX + secop_module_name.lower()
|
||||
|
||||
def _filter_params_with_config(self, available_variables, chart_config):
|
||||
"""
|
||||
Updates (cat, color, unit) the parameters of each variable according to the user_config object.
|
||||
|
||||
Parameters:
|
||||
available_variables ([{"name":(str), "label":(str), "params":{(str):{"cat":(str), "color":(str), "unit":(str)}}}]) : 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, and a dictionnary of parameters (including value), which consist of dictionnaries with the category, the color and the unit, indexed by the name of the parameter.
|
||||
chart_config (ChartConfig) : the object holding a configuration file for the chart.
|
||||
|
||||
Returns :
|
||||
[{"name":(str), "label":(str), "params":{(str):{"cat":(str), "color":(str), "unit":(str)}}}] : the available_variables parameter, updated
|
||||
"""
|
||||
|
||||
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 :
|
||||
for key, value in param_config.items():
|
||||
variable["params"][param_key][key] = value
|
||||
|
||||
return available_variables
|
||||
|
||||
def _filter_params_with_user_config(self, available_variables, user_config):
|
||||
"""
|
||||
Updates (cat, color, unit) the parameters of each variable according to the user_config object.
|
||||
|
||||
Parameters:
|
||||
available_variables ([{"name":(str), "label":(str), "params":{(str):{"cat":(str), "color":(str), "unit":(str)}}}]) : 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, and a dictionnary of parameters (including value), which consist of dictionnaries with the category, the color and the unit, indexed by the name of the parameter.
|
||||
user_config ({(str):{"cat":(str), "color":(str), "unit":(str)}}) : the Python dict representing the user configuration. The key is <secop_module.parameter>.
|
||||
|
||||
Returns :
|
||||
[{"name":(str), "label":(str), "params":{(str):{"cat":(str), "color":(str), "unit":(str)}}}] : the available_variables parameter, updated
|
||||
"""
|
||||
|
||||
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 = user_config[key] if key in user_config.keys() else None
|
||||
if param_config != None :
|
||||
for key, value in param_config.items():
|
||||
variable["params"][param_key][key] = value
|
||||
|
||||
return available_variables
|
||||
|
||||
def _remove_variables_params_not_displayed(self, available_variables):
|
||||
"""
|
||||
Removes the parameters of each variable if their category is "-".
|
||||
|
||||
Parameters:
|
||||
available_variables ([{"name":(str), "label":(str), "params":{(str):{"cat":(str), "color":(str), "unit":(str)}}}]) : 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, and a dictionnary of parameters (including value), which consist of dictionnaries with the category, the color and the unit, indexed by the name of the parameter.
|
||||
|
||||
Returns :
|
||||
[{"name":(str), "label":(str), "params":{(str):{"cat":(str), "color":(str), "unit":(str)}}}] : the available_variables parameter, updated
|
||||
"""
|
||||
for variable in available_variables:
|
||||
params = list(variable["params"].keys())
|
||||
for param_key in params:
|
||||
if variable["params"][param_key]["cat"] == "-":
|
||||
del variable["params"][param_key]
|
||||
|
||||
return available_variables
|
||||
|
||||
def _remove_variables_params_wihout_param_float_and_split(self, available_variables, time):
|
||||
"""
|
||||
For each variable, removes the parameters if the Influx database does not contain <param>.float field, and split the parameters to the corresponding output format.
|
||||
|
||||
Parameters:
|
||||
available_variables ([{"name":(str), "label":(str), "params":{(str):{"cat":(str), "color":(str), "unit":(str)}}}]) : 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, and a dictionnary of parameters (including value), which consist of dictionnares with the category, the color and the unit, indexed by the name of the parameter.
|
||||
time (int) : the unix timestamp in seconds of the point in time to get the variables at. Used to have an upper limit in the query.
|
||||
|
||||
|
||||
Returns :
|
||||
[{"name":(str), "label":(str), "cat":(str), "color":(str), "unit":(str)}] : an array of dictionnaries, each containing the name of the variable[.<param>],
|
||||
the label to display in the Web GUI, its category, its color and its unit.
|
||||
"""
|
||||
res = []
|
||||
for variable in available_variables:
|
||||
query = f"""
|
||||
import "influxdata/influxdb/schema"
|
||||
schema.measurementFieldKeys(bucket: "{self._bucket}", measurement: "{variable["name"]}", start:0, stop: {time + 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:
|
||||
curve = {
|
||||
"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"],
|
||||
}
|
||||
if param == 'target':
|
||||
curve['period'] = 0
|
||||
res.append(curve)
|
||||
|
||||
return res
|
||||
|
||||
def _group_variables_by_cat_unit(self, available_variables):
|
||||
"""
|
||||
Performs a group by cat if specified (different than "*"), or by unit instead for the available variables
|
||||
|
||||
Parameters :
|
||||
available_variables ([{"name":(str), "label":(str), "cat":(str), "color":(str), "unit":(str)}]) : an array of dictionnaries, each containing the name of the variable[.<param>],
|
||||
the label to display in the Web GUI, its category, its color and its unit.
|
||||
|
||||
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 is the unit or the category if given and not "unit".
|
||||
"""
|
||||
groups = {}
|
||||
|
||||
for available_variable in available_variables:
|
||||
unit = available_variable.pop("unit")
|
||||
key = available_variable.pop("cat").replace("*", unit)
|
||||
if key not in groups.keys():
|
||||
groups[key] = {"tag":key, "unit": unit, "curves":[]}
|
||||
groups[key]["curves"].append(available_variable)
|
||||
|
||||
return list(groups.values())
|
||||
|
||||
def _get_curve(self, variable, parameter, time, interval=None):
|
||||
"""
|
||||
Gets the points (curve) within a timerange for the given variable and parameter.
|
||||
|
||||
Parameters :
|
||||
variable (str) : the name (Influx) of the variable we want the values of.
|
||||
parameter (str) : the parameter of the variable to get the values from
|
||||
time ([(int)]) : the timerange we want the values in. It consists of two values which are Unix timestamps in seconds, first included, second excluded.
|
||||
interval (int) : the interval (resolution) of the values to get (in milliseconds)
|
||||
|
||||
Returns :
|
||||
[[(int), (float)]] : an array of pairs (also arrays), the first value being the Unix timestamp in second (x), the seconds being the value (y)
|
||||
"""
|
||||
raw = []
|
||||
query = f"""
|
||||
from(bucket: "{self._bucket}")
|
||||
|> range(start: {time[0]}, stop: {time[1] + 1})
|
||||
|> filter(fn : (r) => r._measurement == "{variable}")
|
||||
|> filter(fn : (r) => r._field == "{parameter+"_float"}")
|
||||
{"|> aggregateWindow(every: duration(v:"+str(self._milliseconds_to_nanoseconds(interval))+"), fn: last, createEmpty:false, timeDst:"+chr(34)+"binning_time"+chr(34)+")" if interval else ""}
|
||||
|> keep(columns: ["_time","_value","expired"{", "+chr(34)+"binning_time"+chr(34) if interval else ""}])
|
||||
|> yield(name: "res")
|
||||
"""
|
||||
tables = self._db.query(query)
|
||||
for table in tables:
|
||||
for record in table.records:
|
||||
t = round(datetime.timestamp(record["binning_time"] if interval else record.get_time()), 3) # t is the real timestamp if no interval is given, or the binned timestamp if interval
|
||||
value = record.get_value()
|
||||
try:
|
||||
value = PrettyFloat(value)
|
||||
except:
|
||||
value = None
|
||||
point = [t, value, record["expired"]]
|
||||
if interval: # t is the binning time, we need to add the real time of the point that was used in this interval
|
||||
point.append(record.get_time())
|
||||
raw.append(point)
|
||||
|
||||
if interval:
|
||||
indexes_to_delete = []
|
||||
expired_points = {i:point for i,point in enumerate(raw) if point[2] == "True"} #we need to keep track of the indexes of the expired point in the list
|
||||
for expired_point_index, expired_point in expired_points.items():
|
||||
for i, point in enumerate(raw):
|
||||
if point[2] == "False" and expired_point[0] == point[0]: # if the current point is expired and has the same binning time as the current expired point
|
||||
if point[3] > expired_point[3]: # comparison on the real timestamp used.
|
||||
indexes_to_delete.insert(0, expired_point_index)
|
||||
else:
|
||||
indexes_to_delete.insert(0,i)
|
||||
sorted(indexes_to_delete, reverse=True) #we have to make sure that the list is sorted in reverse to then delete at the given indexes
|
||||
for index in indexes_to_delete:
|
||||
del raw[index]
|
||||
|
||||
sorted_raw = sorted(raw, key=lambda pair: pair[0]) #expired=True and expired=False are in two Influx tables, so they need to be synchronized
|
||||
res = []
|
||||
for pair in sorted_raw:
|
||||
if pair[2] == "True":
|
||||
res.append([pair[0], pair[1]]) # So the user can know precisely when a curve is expired
|
||||
res.append([pair[0], None]) # So chartJS will cut the curve from this point (which is expired)
|
||||
else:
|
||||
res.append([pair[0], pair[1]])
|
||||
|
||||
return self._insert_last_known_value(variable, parameter, res, time)
|
||||
|
||||
def _insert_last_known_value(self, variable, parameter, curve, time):
|
||||
"""
|
||||
Adds the last known value as the first point in the curve if the last known value is outside the viewing window, for the given variable and parameter.
|
||||
The point is added only if it is not expired.
|
||||
|
||||
Parameters :
|
||||
variable (str) : the name (Influx) of the variable we want the values of.
|
||||
parameter (str) : the parameter of the variable to get the values from
|
||||
curve ([[(int), (float)]]) : an array of pairs (arrays), the first value being the Unix timestamp in second (x), the seconds being the value (y)
|
||||
time ([(int)]) : the timerange we want the values in. It consists of two values which are Unix timestamps in seconds, first included, second excluded.
|
||||
|
||||
Returns :
|
||||
[[(int), (float)]] : the curve of the parameter, updated with a potential new first point
|
||||
"""
|
||||
|
||||
if len(curve) == 0 or curve[0][0] != time[0]:
|
||||
query = f"""
|
||||
from(bucket: "{self._bucket}")
|
||||
|> range(start: 0, stop: {time[0]+1})
|
||||
|> filter(fn : (r) => r._measurement == "{variable}")
|
||||
|> filter(fn : (r) => r._field == "{parameter+"_float"}")
|
||||
|> last()
|
||||
|> keep(columns: ["_time", "_value", "expired"])
|
||||
|> yield(name: "res")
|
||||
"""
|
||||
tables = self._db.query(query)
|
||||
pair_to_insert = []
|
||||
for table in tables:
|
||||
for record in table.records:
|
||||
t = round(datetime.timestamp(record.get_time()), 3)
|
||||
value = None
|
||||
if record["expired"] == "False":
|
||||
value = record.get_value()
|
||||
try:
|
||||
value = PrettyFloat(value)
|
||||
except:
|
||||
value = None
|
||||
if len(pair_to_insert) == 0 or t >= pair_to_insert[0]:
|
||||
pair_to_insert = [t, value]
|
||||
|
||||
if len(pair_to_insert)==2 and pair_to_insert[1] != None:
|
||||
curve.insert(0, [time[0], pair_to_insert[1]])
|
||||
return curve
|
||||
|
||||
def _get_last_values(self, variable, parameter, start_time, end_time):
|
||||
"""
|
||||
Gets the lastest values for the given variable and parameter that are in [start_time, end_time].
|
||||
The process is the same as _get_curve.
|
||||
|
||||
Parameters :
|
||||
variable (str) : the name (Influx) of the variable we want the last value of.
|
||||
parameter (str) : the parameter of the variable to get the values from
|
||||
start_time (int|None) : the start of time range (Unix timestamp in seconds) to include the values in
|
||||
end_time (int) : the end of time range (Unix timestamp in seconds) to include the values in
|
||||
|
||||
Returns :
|
||||
[[(int), (float)]] : an array of points (also arrays). The first value is the Unix timestamp in second (x), the seconds is the value (y)
|
||||
"""
|
||||
|
||||
raw = []
|
||||
query = f"""
|
||||
from(bucket: "{self._bucket}")
|
||||
|> range(start: {start_time if start_time != None else 0}, stop: {end_time+1})
|
||||
|> filter(fn : (r) => r._measurement == "{variable}")
|
||||
|> filter(fn : (r) => r._field == "{parameter+ "_float"}")
|
||||
{"|> last()" if start_time == None else ""}
|
||||
|> keep(columns: ["_time","_value", "expired"])
|
||||
|> yield(name: "res")
|
||||
"""
|
||||
|
||||
# this loop might be simplified, but it has to be kept to catch the case when there is unavailable data
|
||||
tables = self._db.query(query)
|
||||
for table in tables:
|
||||
for record in table.records:
|
||||
t = round(datetime.timestamp(record.get_time()), 3)
|
||||
value = record.get_value()
|
||||
try:
|
||||
value = PrettyFloat(value)
|
||||
except:
|
||||
value = None
|
||||
raw.append([t, value, record["expired"]])
|
||||
|
||||
sorted_raw = sorted(raw, key=lambda pair: pair[0])
|
||||
res = []
|
||||
for pair in sorted_raw:
|
||||
if pair[2] == "True":
|
||||
res.append([pair[0], pair[1]])
|
||||
res.append([pair[0], None])
|
||||
else:
|
||||
res.append([pair[0], pair[1]])
|
||||
|
||||
return res
|
||||
|
||||
def _get_device_name_components(self, time):
|
||||
"""
|
||||
Gets the components of the device name, first in the main name, then stick, then addons.
|
||||
|
||||
Parameters :
|
||||
time (int) : the Unix timestamp in seconds of the time we want the device name
|
||||
|
||||
Returns :
|
||||
[str] : an array of string, each one being a component of the device name
|
||||
"""
|
||||
measurements = ["nicos/se_main", "nicos/se_stick", "nicos/se_addons"]
|
||||
res = []
|
||||
for measurement in measurements:
|
||||
query = f"""
|
||||
from(bucket: "{self._bucket}")
|
||||
|> range(start: 0, stop: {time + 1})
|
||||
|> filter(fn: (r) => r._measurement == "{measurement}")
|
||||
|> filter(fn: (r) => r._field == "value")
|
||||
|> last()
|
||||
"""
|
||||
tables = self._db.query(query)
|
||||
for table in tables:
|
||||
for record in table.records:
|
||||
name = ast.literal_eval(record.get_value())
|
||||
if name != None and name != '':
|
||||
res.append(ast.literal_eval(record.get_value()))
|
||||
return res
|
||||
|
||||
def _seconds_to_nanoseconds(self, seconds):
|
||||
return seconds * 1000000000
|
||||
|
||||
def _milliseconds_to_nanoseconds(self, milliseconds):
|
||||
return milliseconds * 1000000
|
Reference in New Issue
Block a user