ospaz-site/static/js/index-main.js

253 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// скрипт страницы index, на которой отображаются графики
// интервал обновления статуса в миллисекундах
const RELOAD_STATS_INTERVAL = 10000
const DATETIME_FORMAT = "DD MMM YYYY HH:mm:ss"
function unpackBits(num, desc) {
let out = ""
for (let i = 0; i < desc.length; i++) {
if (desc[i] !== null) {
if ((num & (1 << i)) !== 0) {
// бит установлен
if (out.length === 0) {
out = desc[i]
} else {
out += " + "
out += desc[i]
}
}
}
}
return out
}
/**
* Функция для линейной аппроксимации набора точек. Для заданных точек находит коэфицент a из уравнения y=ax+b.
* Уравнение подбирается при помощи метода наименьших квадратов.
* @param dataset набор точек в виде [[timestamp, val], [timestamp, val], ...], где timestamp - значение по X, val - значение по Y
* @returns {number}, коэфицент a
*/
function approximateWithTimestamps(dataset) {
const timestamp_offset = dataset[0][0]
const x = dataset.map((element) => element[0] - timestamp_offset);
const y = dataset.map((element) => element[1]);
const sumX = x.reduce((prev, curr) => prev + curr, 0);
const avgX = sumX / x.length;
const xDifferencesToAverage = x.map((value) => avgX - value);
const xDifferencesToAverageSquared = xDifferencesToAverage.map(
(value) => value ** 2
);
const SSxx = xDifferencesToAverageSquared.reduce(
(prev, curr) => prev + curr,
0
);
const sumY = y.reduce((prev, curr) => prev + curr, 0);
const avgY = sumY / y.length;
const yDifferencesToAverage = y.map((value) => avgY - value);
const xAndYDifferencesMultiplied = xDifferencesToAverage.map(
(curr, index) => curr * yDifferencesToAverage[index]
);
const SSxy = xAndYDifferencesMultiplied.reduce(
(prev, curr) => prev + curr,
0
);
// const slope = SSxy / SSxx;
// const intercept = avgY - slope * avgX;
// return (x) => intercept + slope * x;
return SSxy / SSxx
}
async function makeRequest(url) {
let response = await fetch(url)
if (response.status === 403) {
// http Forbidden, исправляется перезагрузкой страницы и просмотром окошка "Требуется авторизация"
window.location.reload()
return {}
} else if (response.status !== 200) {
console.log('fetch(' + url + ') failed. Status Code: ' + response.status);
return null;
}
return await response.json()
}
async function loadChartData() {
let chartTime = localStorage.getItem("settings-chart-time")
return (await makeRequest('/fetch/tank-chart?days=' + chartTime))['tank_chart']
}
async function loadLastUpdates() {
return (await makeRequest('/fetch/stats'))['stats']
}
/**
* Функция, устанавливающая классы CSS 'value-good' и 'value-bad'.
* @param element
* @param good
* @private
*/
function _setIndicatorClass(element, good) {
if (good) {
element.classList.remove("value-bad")
element.classList.add("value-good")
} else {
element.classList.remove("value-good")
element.classList.add("value-bad")
}
}
const updateFunctions = {
// последнее обновление резервуара
'tank-last-update': (element, dataset) => {
const now = Date.now() / 1000
element.innerHTML = moment(new Date(dataset['tank']['last_update'] * 1000)).format(DATETIME_FORMAT)
// для резервуара нормально, если обновление было меньше получаса назад
_setIndicatorClass(element, now - dataset['tank']['last_update'] < 1800)
},
// последнее обновление насосной
'pump-last-update': (element, dataset) => {
const now = Date.now() / 1000
element.innerHTML = moment(new Date(dataset['pump']['last_update'] * 1000)).format(DATETIME_FORMAT)
// для насосной можно допустить последнее обновление 10 минут назад
_setIndicatorClass(element, now - dataset['pump']['last_update'] < 600)
},
// Уровень воды <span id="tank-level-dir"></span>
'tank-level-dir': (element, dataset) => {
const last_radar_values = dataset['tank']['last_radar_values']
if (last_radar_values.length < 3) {
element.innerHTML = "(?)"
} else {
let ap = approximateWithTimestamps(last_radar_values)
if (Math.abs(ap) < 0.02) {
element.innerHTML = '→'
} else {
element.innerHTML = ap < 0 ? '↘' : '↗'
}
}
},
// Текущий уровень воды: <span id="tank-level-now"></span>%
'tank-level-now': (element, dataset) => {
element.innerHTML = dataset['tank']['level']
// тут все хорошо если влезаем в установленные рамки +-2% (69-80%)
_setIndicatorClass(element, 67 <= dataset['tank']['last_percentage'] <= 82)
},
// Текущее значение с радара
'tank-raw-now': (element, dataset) => {
element.innerHTML = dataset['tank']['radar']
},
// Статус: <span id="tank-status"></span>
'tank-status': (element, dataset) => {
const shur_status_bits = ['нужна вода', 'поплавок нижний', 'поплавок верхний', 'поплавок аварийный']
element.innerHTML = unpackBits(dataset['tank']['status'], shur_status_bits) + " (" + dataset['tank']['status'] + ")"
// тут все хорошо, пока нет аварийного поплавка
_setIndicatorClass(element, (dataset['tank']['status'] & 0x8) === 0)
},
// Частота ПЧ: <span id="pump-vfd-freq"></span>Гц
'pump-vfd-freq': (element, dataset) => {
element.innerHTML = dataset['pump']['vfd_freq']
},
// Ток ПЧ: <span id="pump-vfd-curr"></span>А
'pump-vfd-curr': (element, dataset) => {
document.getElementById("pump-vfd-curr").innerHTML = dataset['pump']['vfd_curr']
},
// Ошибка ПЧ: <span id="pump-vfd-error"></span>
'pump-vfd-error': (element, dataset) => {
const vfdErrorsDescription = {
5: "превышение напряжения",
8: "пониженное напряжение",
11: "обрыв фазы питания",
12: "отказ выходной цепи",
15: "перегрев ПЧ",
17: "замыкание двигателя на землю",
19: "двигатель без нагрузки",
34: "перегрузка по току"
}
if (dataset['pump']['vfd_err'] in vfdErrorsDescription) {
element.innerHTML = dataset['pump']['vfd_err'] + " (" + vfdErrorsDescription[dataset['pump']['vfd_err']] + ")"
} else {
element.innerHTML = dataset['pump']['vfd_err']
}
_setIndicatorClass(element, dataset['pump']['vfd_err'] === 0)
},
// Регистр аварий: <span id="pump-alarms"></span>
'pump-alarms': (element, dataset) => {
const pumpAlarmRegister = [
"общая авария",
"реле контроля фаз",
"насос",
"ошибка ПЧ",
"датчик потока",
"датчик уровня воды",
"датчик наличия воды",
"задвижки"
]
if (dataset['pump']['alarms'] === 0) {
element.innerHTML = "ok"
} else {
element.innerHTML = unpackBits(dataset['pump']['alarms'], pumpAlarmRegister) + " (" + dataset['pump']['alarms'] + ")"
}
_setIndicatorClass(element, dataset['pump']['alarms'] === 0)
},
// Состояние КА: <span id="pump-stage"></span>
'pump-stage': (element, dataset) => {
const pumpStageDescription = {
0: "отключен",
2: "инициализация: установка задвижек в начальное состояние",
21: "инициализация: закрытие задвижек 23.5 и 23.6",
31: "инициализация: открытие задвижек 23.5 и 23.6",
99: "<span class=\"value-bad\">авария</span>",
100: "ожидание сигнала на перекачку воды",
102: "запуск: открытие задвижки 23.6",
110: "запуск: ожидание сигнала от датчика уровня поз.36",
121: "запуск: открытие задвижек насоса",
131: "запуск: закрытие задвижки 23.6",
141: "запуск: пуск ПЧ",
200: "<span class=\"value-good\">работает</span>",
202: "остановка: закрытие задвижек 23.3 и 23.4",
211: "остановка: ожидание остановки ПЧ",
221: "остановка: перевод запорной арматуры в исходное состояние",
231: "остановка: работа компрессора",
235: "остановка: сброс конденсата клапанами 25.*"
}
if (dataset['pump']['pump_stage'] in pumpStageDescription) {
element.innerHTML = dataset['pump']['pump_stage'] + " (" + pumpStageDescription[dataset['pump']['pump_stage']] + ")"
} else {
element.innerHTML = dataset['pump']['pump_stage']
}
},
// Текущий расход: <span id="pump-flow-meter"></span>м³
'pump-flow-meter': (element, dataset) => {
element.innerHTML = dataset['pump']['flow_meter']
},
// 'id': (element, dataset) => {},
}
async function updateStatus() {
let dataset = await loadLastUpdates()
for (let id in updateFunctions) {
let element = document.getElementById(id)
if (element !== null) {
updateFunctions[id](element, dataset)
}
}
}