Files
Button/qml/CalibrationDialog.qml
T
2026-11-02 16:46:20 +03:00

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