314 lines
10 KiB
QML
314 lines
10 KiB
QML
import QtQuick 2.12
|
|
import QtQuick.Controls 2.12
|
|
import QtQuick.Layouts 1.12
|
|
|
|
Dialog {
|
|
id: dlg
|
|
modal: true
|
|
parent: Overlay.overlay
|
|
title: "Калибровка"
|
|
anchors.centerIn: parent
|
|
width: Math.min(460, parent ? parent.width * 0.92 : 460)
|
|
closePolicy: Popup.CloseOnEscape
|
|
standardButtons: Dialog.NoButton
|
|
|
|
property string step: "pick"
|
|
property string mac: ""
|
|
property string sensorName: ""
|
|
property int minMm: 0
|
|
property int capturedMm: 0
|
|
property double baseline: 0
|
|
property string waitError: ""
|
|
property var live: []
|
|
|
|
readonly property color fg: "#ffffff"
|
|
readonly property color muted: "#aaaaaa"
|
|
|
|
background: Rectangle {
|
|
color: "#161628"
|
|
radius: 4
|
|
}
|
|
|
|
onOpened: begin()
|
|
onClosed: waitTimeout.stop()
|
|
|
|
function begin() {
|
|
waitTimeout.stop()
|
|
waitError = ""
|
|
minMm = 0
|
|
capturedMm = 0
|
|
refreshLive()
|
|
if (live.length === 0) {
|
|
mac = ""
|
|
sensorName = ""
|
|
step = "empty"
|
|
} else if (live.length === 1) {
|
|
selectSensor(live[0])
|
|
step = "minInstruct"
|
|
} else {
|
|
mac = ""
|
|
sensorName = ""
|
|
step = "pick"
|
|
}
|
|
}
|
|
|
|
function refreshLive() {
|
|
var out = []
|
|
var slots = sensorsBridge.sensorSlots
|
|
for (var i = 0; i < slots.length; ++i) {
|
|
if (!slots[i].empty && slots[i].mac)
|
|
out.push(slots[i])
|
|
}
|
|
live = out
|
|
}
|
|
|
|
function selectSensor(s) {
|
|
mac = String(s.mac).toUpperCase()
|
|
sensorName = s.name && String(s.name).length ? s.name : mac
|
|
}
|
|
|
|
function slotOf(addr) {
|
|
var slots = sensorsBridge.sensorSlots
|
|
var want = String(addr).toUpperCase()
|
|
for (var i = 0; i < slots.length; ++i) {
|
|
if (!slots[i].empty && String(slots[i].mac).toUpperCase() === want)
|
|
return slots[i]
|
|
}
|
|
return null
|
|
}
|
|
|
|
function startWait(which) {
|
|
var s = slotOf(mac)
|
|
var seen = s && s.lastSeen !== undefined ? Number(s.lastSeen) : 0
|
|
baseline = isNaN(seen) ? 0 : seen
|
|
waitError = ""
|
|
step = which === "max" ? "maxWait" : "minWait"
|
|
waitTimeout.restart()
|
|
}
|
|
|
|
function checkWait() {
|
|
if (step !== "minWait" && step !== "maxWait")
|
|
return
|
|
var s = slotOf(mac)
|
|
if (!s)
|
|
return
|
|
var seen = Number(s.lastSeen)
|
|
var dist = Number(s.distRaw)
|
|
if (isNaN(seen) || isNaN(dist))
|
|
return
|
|
if (seen > baseline && dist >= 0) {
|
|
waitTimeout.stop()
|
|
capturedMm = dist
|
|
step = step === "minWait" ? "minConfirm" : "maxConfirm"
|
|
}
|
|
}
|
|
|
|
function instructionText() {
|
|
if (step === "empty")
|
|
return "Нет подключённых датчиков."
|
|
if (step === "pick")
|
|
return "Выберите датчик для калибровки."
|
|
if (step === "minInstruct")
|
|
return "Поставьте минимум.\nНажмите «Снять» и дождитесь нового показания датчика."
|
|
if (step === "minWait")
|
|
return "Ожидание минимума.\nНовый отчёт может прийти через несколько минут."
|
|
if (step === "minConfirm")
|
|
return "Минимум: " + capturedMm + " мм.\nЭто значение будет 0%."
|
|
if (step === "maxInstruct")
|
|
return "Поставьте максимум.\nНажмите «Снять» и дождитесь нового показания датчика."
|
|
if (step === "maxWait")
|
|
return "Ожидание максимума.\nНовый отчёт может прийти через несколько минут."
|
|
if (step === "maxConfirm")
|
|
return "Максимум: " + capturedMm + " мм.\nЭто значение будет 100%.\nМинимум: "
|
|
+ minMm + " мм — 0%."
|
|
return ""
|
|
}
|
|
|
|
Connections {
|
|
target: sensorsBridge
|
|
onChanged: {
|
|
if (dlg.step === "pick" || dlg.step === "empty")
|
|
dlg.refreshLive()
|
|
dlg.checkWait()
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: waitTimeout
|
|
interval: 300000
|
|
onTriggered: {
|
|
dlg.waitError = "Нет нового показания"
|
|
dlg.step = dlg.step === "maxWait" ? "maxInstruct" : "minInstruct"
|
|
}
|
|
}
|
|
|
|
contentItem: ColumnLayout {
|
|
spacing: 10
|
|
|
|
Text {
|
|
Layout.fillWidth: true
|
|
visible: dlg.sensorName.length > 0 && dlg.step !== "pick"
|
|
text: dlg.sensorName
|
|
color: dlg.muted
|
|
font.pixelSize: 12
|
|
elide: Text.ElideRight
|
|
}
|
|
|
|
Text {
|
|
Layout.fillWidth: true
|
|
text: (dlg.step, dlg.capturedMm, dlg.minMm, dlg.instructionText())
|
|
color: dlg.fg
|
|
font.pixelSize: 15
|
|
wrapMode: Text.Wrap
|
|
}
|
|
|
|
Text {
|
|
Layout.fillWidth: true
|
|
visible: dlg.waitError.length > 0
|
|
text: dlg.waitError
|
|
color: "#ff6666"
|
|
font.pixelSize: 13
|
|
wrapMode: Text.Wrap
|
|
}
|
|
|
|
Column {
|
|
Layout.fillWidth: true
|
|
visible: dlg.step === "pick"
|
|
spacing: 6
|
|
Repeater {
|
|
model: dlg.live
|
|
delegate: Button {
|
|
width: parent.width
|
|
implicitHeight: 36
|
|
text: (modelData.name && String(modelData.name).length
|
|
? modelData.name : "—") + " " + modelData.mac
|
|
onClicked: {
|
|
dlg.selectSensor(modelData)
|
|
dlg.waitError = ""
|
|
dlg.step = "minInstruct"
|
|
}
|
|
background: Rectangle {
|
|
radius: 3
|
|
color: parent.down ? "#3a3a58" : "#2a2a40"
|
|
border.color: "#555570"
|
|
}
|
|
contentItem: Text {
|
|
text: parent.text
|
|
color: dlg.fg
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
font.pixelSize: 13
|
|
elide: Text.ElideRight
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: 6
|
|
|
|
Button {
|
|
readonly property bool shown: dlg.step === "minInstruct" || dlg.step === "maxInstruct"
|
|
visible: shown
|
|
text: "Снять"
|
|
Layout.fillWidth: shown
|
|
Layout.preferredWidth: shown ? 80 : 0
|
|
Layout.maximumWidth: shown ? 10000 : 0
|
|
Layout.preferredHeight: 36
|
|
onClicked: dlg.startWait(dlg.step === "maxInstruct" ? "max" : "min")
|
|
background: Rectangle {
|
|
radius: 3
|
|
color: parent.down ? "#3a3a58" : "#2a2a40"
|
|
border.color: "#555570"
|
|
}
|
|
contentItem: Text {
|
|
text: parent.text
|
|
color: dlg.fg
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
font.pixelSize: 13
|
|
}
|
|
}
|
|
|
|
Button {
|
|
readonly property bool shown: dlg.step === "minConfirm" || dlg.step === "maxConfirm"
|
|
visible: shown
|
|
text: "Подтвердить"
|
|
Layout.fillWidth: shown
|
|
Layout.preferredWidth: shown ? 80 : 0
|
|
Layout.maximumWidth: shown ? 10000 : 0
|
|
Layout.preferredHeight: 36
|
|
onClicked: {
|
|
if (dlg.step === "minConfirm") {
|
|
dlg.minMm = dlg.capturedMm
|
|
dlg.waitError = ""
|
|
dlg.step = "maxInstruct"
|
|
} else if (!sensorsBridge.saveCalibration(dlg.mac, dlg.minMm, dlg.capturedMm)) {
|
|
dlg.waitError = "Не удалось сохранить"
|
|
} else {
|
|
dlg.close()
|
|
}
|
|
}
|
|
background: Rectangle {
|
|
radius: 3
|
|
color: parent.down ? "#3a3a58" : "#2a2a40"
|
|
border.color: "#555570"
|
|
}
|
|
contentItem: Text {
|
|
text: parent.text
|
|
color: dlg.fg
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
font.pixelSize: 13
|
|
}
|
|
}
|
|
|
|
Button {
|
|
readonly property bool shown: dlg.step === "minConfirm" || dlg.step === "maxConfirm"
|
|
visible: shown
|
|
text: "Снять заново"
|
|
Layout.fillWidth: shown
|
|
Layout.preferredWidth: shown ? 80 : 0
|
|
Layout.maximumWidth: shown ? 10000 : 0
|
|
Layout.preferredHeight: 36
|
|
onClicked: {
|
|
dlg.waitError = ""
|
|
dlg.step = dlg.step === "maxConfirm" ? "maxInstruct" : "minInstruct"
|
|
}
|
|
background: Rectangle {
|
|
radius: 3
|
|
color: parent.down ? "#3a3a58" : "#2a2a40"
|
|
border.color: "#555570"
|
|
}
|
|
contentItem: Text {
|
|
text: parent.text
|
|
color: dlg.fg
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
font.pixelSize: 13
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: "Отмена"
|
|
Layout.fillWidth: true
|
|
Layout.preferredHeight: 36
|
|
onClicked: dlg.close()
|
|
background: Rectangle {
|
|
radius: 3
|
|
color: parent.down ? "#3a3a58" : "#2a2a40"
|
|
border.color: "#555570"
|
|
}
|
|
contentItem: Text {
|
|
text: parent.text
|
|
color: dlg.fg
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
font.pixelSize: 13
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|