harbour-aira 0.1.0: client Sailfish per pompa di calore Aira (bridge HTTP + stato e comandi ACS)
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
import QtQuick 2.2
|
||||
import Sailfish.Silica 1.0
|
||||
import "../components"
|
||||
|
||||
// Dashboard dell'impianto. Legge api.summary (QVariantMap dal C++), quindi non
|
||||
// dipende dallo scope delle property del main qml.
|
||||
Page {
|
||||
id: page
|
||||
|
||||
allowedOrientations: Orientation.All
|
||||
|
||||
// obiettivo ACS scelto dall'utente (non sovrascritto dal refresh finche'
|
||||
// non lo tocca, altrimenti lo slider "salta" a ogni lettura)
|
||||
property int targetAcs: 55
|
||||
property bool targetAcsTouched: false
|
||||
property string notice: ""
|
||||
property bool noticeIsError: false
|
||||
|
||||
function openSettings() {
|
||||
pageStack.push(Qt.resolvedUrl("SettingsPage.qml"))
|
||||
}
|
||||
|
||||
function showNotice(msg, isError) {
|
||||
page.notice = msg
|
||||
page.noticeIsError = isError === true
|
||||
noticeTimer.restart()
|
||||
}
|
||||
|
||||
function raw(key) {
|
||||
return api.summary[key]
|
||||
}
|
||||
|
||||
function temp(key) {
|
||||
return api.formatValue(page.raw(key))
|
||||
}
|
||||
|
||||
function flag(key) {
|
||||
return page.raw(key) ? qsTr("sì") : qsTr("no")
|
||||
}
|
||||
|
||||
function count(key) {
|
||||
var v = page.raw(key)
|
||||
if (v === undefined || v === null)
|
||||
return "—"
|
||||
return String(Math.round(Number(v)))
|
||||
}
|
||||
|
||||
// "2026-10-09 18:52:17.199340" -> "18:52"
|
||||
function shortTime(s) {
|
||||
if (!s)
|
||||
return "—"
|
||||
var parts = String(s).split(" ")
|
||||
if (parts.length < 2)
|
||||
return String(s)
|
||||
return parts[1].substring(0, 5)
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: noticeTimer
|
||||
interval: 4000
|
||||
onTriggered: page.notice = ""
|
||||
}
|
||||
|
||||
RemorseItem { id: remorse }
|
||||
|
||||
Connections {
|
||||
target: api
|
||||
onErrorOccurred: page.showNotice(message, true)
|
||||
onCommandFinished: page.showNotice(message, !ok)
|
||||
onSummaryChanged: {
|
||||
if (!page.targetAcsTouched) {
|
||||
var v = page.raw("dhw_target")
|
||||
if (v !== undefined && v !== null)
|
||||
page.targetAcs = Math.round(Number(v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SilicaFlickable {
|
||||
id: flick
|
||||
anchors.fill: parent
|
||||
contentHeight: col.height + Theme.paddingLarge
|
||||
|
||||
// I menu pulley stanno DENTRO la vista: come figli diretti della Page
|
||||
// fanno deref su null (TypeError + crash).
|
||||
PullDownMenu {
|
||||
MenuItem {
|
||||
text: qsTr("Aggiorna")
|
||||
onClicked: api.refresh()
|
||||
}
|
||||
MenuItem {
|
||||
text: qsTr("Impostazioni")
|
||||
onClicked: page.openSettings()
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: col
|
||||
width: parent.width
|
||||
spacing: Theme.paddingSmall
|
||||
|
||||
PageHeader { title: qsTr("Aira") }
|
||||
|
||||
// ---- avvisi ---------------------------------------------------- //
|
||||
Label {
|
||||
visible: page.notice.length > 0
|
||||
x: Theme.horizontalPageMargin
|
||||
width: parent.width - 2 * Theme.horizontalPageMargin
|
||||
text: page.notice
|
||||
color: page.noticeIsError ? Theme.errorColor : Theme.highlightColor
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// ---- bridge non configurato ------------------------------------ //
|
||||
Label {
|
||||
visible: !api.configured
|
||||
x: Theme.horizontalPageMargin
|
||||
width: parent.width - 2 * Theme.horizontalPageMargin
|
||||
text: qsTr("Bridge non configurato. Apri le impostazioni e inserisci l'indirizzo del server e il token.")
|
||||
color: Theme.secondaryColor
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Button {
|
||||
visible: !api.configured
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: qsTr("Impostazioni")
|
||||
onClicked: page.openSettings()
|
||||
}
|
||||
|
||||
// ---- acqua calda ---------------------------------------------- //
|
||||
Label {
|
||||
visible: api.configured
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: qsTr("Acqua calda sanitaria")
|
||||
color: Theme.secondaryColor
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: api.configured
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: page.temp("dhw_current") + " °C"
|
||||
color: Theme.primaryColor
|
||||
font.pixelSize: Theme.fontSizeHuge
|
||||
}
|
||||
|
||||
ProgressBar {
|
||||
visible: api.configured
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width - 4 * Theme.horizontalPageMargin
|
||||
minimumValue: 20
|
||||
maximumValue: 65
|
||||
value: {
|
||||
var v = page.raw("dhw_current")
|
||||
return (v === undefined || v === null) ? 0 : Number(v)
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: api.configured
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: qsTr("obiettivo %1 °C").arg(page.temp("dhw_target"))
|
||||
color: Theme.secondaryColor
|
||||
font.pixelSize: Theme.fontSizeExtraSmall
|
||||
}
|
||||
|
||||
// ---- stato impianto ------------------------------------------- //
|
||||
SectionHeader { visible: api.configured; text: qsTr("Impianto") }
|
||||
|
||||
InfoRow { visible: api.configured; label: qsTr("Temperatura esterna"); value: page.temp("outdoor_temperature") + " °C" }
|
||||
InfoRow { visible: api.configured; label: qsTr("Temperatura interna"); value: page.temp("indoor_temperature") + " °C" }
|
||||
InfoRow { visible: api.configured; label: qsTr("Modalità"); value: api.modeLabel(page.raw("operating_status")) }
|
||||
InfoRow { visible: api.configured; label: qsTr("Assenza"); value: page.flag("away_mode") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Night mode"); value: page.flag("night_mode") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Riscaldamento forzato"); value: page.flag("force_heating") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Resistenze integrative"); value: page.flag("inline_heater") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Zone"); value: page.count("zones") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Errori"); value: page.count("errors_count") }
|
||||
InfoRow { visible: api.configured; label: qsTr("Aggiornato alle"); value: page.shortTime(page.raw("pump_updated")) }
|
||||
|
||||
// ---- comandi --------------------------------------------------- //
|
||||
SectionHeader { visible: api.configured; text: qsTr("Comandi") }
|
||||
|
||||
Slider {
|
||||
visible: api.configured
|
||||
width: parent.width
|
||||
label: qsTr("Obiettivo acqua calda")
|
||||
minimumValue: 45
|
||||
maximumValue: 60
|
||||
stepSize: 1
|
||||
value: page.targetAcs
|
||||
valueText: value + " °C"
|
||||
onValueChanged: {
|
||||
page.targetAcsTouched = true
|
||||
page.targetAcs = Math.round(value)
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: setAcsButton
|
||||
visible: api.configured
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
leftMargin: Theme.horizontalPageMargin
|
||||
rightMargin: Theme.horizontalPageMargin
|
||||
}
|
||||
text: qsTr("Imposta obiettivo %1 °C").arg(page.targetAcs)
|
||||
onClicked: remorse.execute(setAcsButton,
|
||||
qsTr("Obiettivo ACS a %1 °C").arg(page.targetAcs),
|
||||
function() { api.sendCommand("set_hot_water_target", page.targetAcs) })
|
||||
}
|
||||
|
||||
Button {
|
||||
id: boostOnButton
|
||||
visible: api.configured
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
leftMargin: Theme.horizontalPageMargin
|
||||
rightMargin: Theme.horizontalPageMargin
|
||||
}
|
||||
text: qsTr("Avvia boost ACS")
|
||||
onClicked: remorse.execute(boostOnButton, qsTr("Avvio del boost ACS"),
|
||||
function() { api.sendCommand("hot_water_boost_on") })
|
||||
}
|
||||
|
||||
Button {
|
||||
id: boostOffButton
|
||||
visible: api.configured
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
leftMargin: Theme.horizontalPageMargin
|
||||
rightMargin: Theme.horizontalPageMargin
|
||||
}
|
||||
text: qsTr("Ferma boost ACS")
|
||||
onClicked: remorse.execute(boostOffButton, qsTr("Arresto del boost ACS"),
|
||||
function() { api.sendCommand("hot_water_boost_off") })
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
visible: api.busy
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
size: BusyIndicatorSize.Small
|
||||
running: true
|
||||
}
|
||||
|
||||
Label {
|
||||
visible: api.configured
|
||||
x: Theme.horizontalPageMargin
|
||||
width: parent.width - 2 * Theme.horizontalPageMargin
|
||||
text: qsTr("I comandi vengono inviati al bridge e da lì all'impianto: possono richiedere qualche secondo.")
|
||||
color: Theme.secondaryColor
|
||||
font.pixelSize: Theme.fontSizeExtraSmall
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (api.configured)
|
||||
api.refresh()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user