Files
shipheart-qt/capteur.qml
T

1169 lines
53 KiB
QML

//========================================================================================================================================================================
// CAPTEUR.QML
// Ce fichier traite l'affichage des détails d'un capteur. La page affiche notamment :
// - La valeur actuelle
// - les valeurs mini et maxi sur la période avec leur date, la moyenne sur la période et le delta @1h
// - la courbe historique de niveau
// La page est appelée depuis la page HOME, en cliquant sur l'une des zones de bas de page ou depuis la page CAPTEURS.
//========================================================================================================================================================================
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
//import QtCharts 2.3
import QtCharts
import qt.koriolan.shipheart 1.0
import"fonctions.js" as Fonctions
import"fonctions_date_heure.js" as Fonctions_date_heure
Shipheart_page {
id: page_capteur
// Mis en forme :
readonly property double pourcentage_largeur_colonne_gauche: 0.5
readonly property double pourcentage_largeur_graphique: 0.85
readonly property double espace_colonnes_principales: 20
readonly property double affichage_hauteur_utile: shipheart_hauteur_page - shipheart_hauteur_bandeau_haut - shipheart_hauteur_bandeau_bas - (2 * espace_lignes_principales)
readonly property double pourcentage_hauteur_ligne_haut: 0.6
readonly property double pourcentage_hauteur_ligne_erreur: 0.3 // Part de 'pourcentage_hauteur_ligne_haut'
readonly property double espace_lignes_principales: 20
readonly property double donnees_capteur_pourcentage_largeur_donnee: 0.33
readonly property double donnees_capteur_pourcentage_largeur_valeur: 0.33
readonly property double donnees_capteur_pourcentage_largeur_heure: 1 - (donnees_capteur_pourcentage_largeur_donnee + donnees_capteur_pourcentage_largeur_valeur)
readonly property double nb_boutons_periode: 4
readonly property int periode_init_raffraichissement_parametres: 1 // En ms
readonly property int periode_raffraichissement_parametres: 1000 // En ms
readonly property int periode_init_raffraichissement_graphiques: 20 // En ms
readonly property int periode_raffraichissement_graphiques: (15 * 1000) // en ms
// Affichage :
property bool courbe_flag_vide: false // Flag indiquant s'il y a des données à afficher ou pas
property int courbe_nombre_principal_graduations_axe_x: 13
property int courbe_nombre_zoom_1_graduations_axe_x: 8
property int courbe_nombre_graduations_axe_y: 5
property string courbe_format_principal_axe_x: "HH:mm" // exemple : "yyyy-MM-dd HH:mm:ss"
property string courbe_format_zoom_1_axe_x: "HH:mm" // exemple : "yyyy-MM-dd HH:mm:ss"
property bool flag_zoom_en_cours: false
property real zoomx_en_cours: 1
// Données :
property bool flag_init: true // Permet de bloquer certains affichage tant que l'init n'est pas terminée
property int code_capteur: shipheart_capteur_predefini_aucun
property int id_capteur: 0
property int etat_systeme: 0
property string nom_capteur: "---"
property int type_entrees: -1
property string entrees: ""
property int id_variable: -1
property int etat: 0
property string chaine_valeur: "---"
property string symbole_unite: ""
property int type_unite: 0
property int precision_affichage: 1
property int conversion_unite: 0
property int nouvelle_unite: 0
property int periode_analyse: 0
property string texte_alerte: ""
property int nb_events: -1 // -1: aucun event n'est lié à ce capteur, 0: au moins 1 event est lié à ce capteur mais aucun n'a Etat_test>0, 1: au moins un event lié et au moins un Etat_test=1
property date xMin_date
property date xMax_date
property real yMin_valeur: 0
property real yMax_valeur: 1
property int precision_affichage_axe_y_courbe: 1
// Modèles :
// Note : les modèles chargés dans 'main.qml' n'ont pas à être rechargés dans les pages suivantes
//===========================================================
// Timer periode raffraichissement parametres capteur :
//===========================================================
Timer {
id: timer_raffraichissement_parametres
interval: periode_init_raffraichissement_parametres
triggeredOnStart: false
running: true
repeat: true
onTriggered: {
if (interval === periode_init_raffraichissement_parametres) // Premier passage, 1ms après le premier chargement
{
interval = periode_raffraichissement_parametres;
busy_indicator.running = true;
init_page();
}
else
{
mise_a_jour_valeurs_instantanees();
}
}
}
//===========================================================
// Timer periode raffraichissement graphiques :
//===========================================================
Timer {
id: timer_raffraichissement_graphiques
interval: periode_raffraichissement_graphiques
triggeredOnStart: false
running: true
repeat: true
onTriggered: {
if (interval === periode_init_raffraichissement_graphiques) interval = periode_raffraichissement_graphiques;
if (!flag_init) // La gestion du premier calcul des courbes est géré directement par 'init()' car le séquençage des 2 timers au démarrage n'est pas garanti !!
{
if (!flag_zoom_en_cours) // On ne met pas à jour les courbes si on est en train de zoomer
{
mise_a_jour_courbes();
}
}
}
}
// Titre de la page :
Shipheart_titre_1 {titre:nom_capteur; hauteur: shipheart_hauteur_bandeau_haut}
//-------------------------------------------------------
// Ecran haut - gauche :
//-------------------------------------------------------
Rectangle {
id: zone_affichage_haut_gauche
x: 0
y: shipheart_hauteur_bandeau_haut;
width: (shipheart_largeur_page * pourcentage_largeur_colonne_gauche) - (espace_colonnes_principales / 2);
height: affichage_hauteur_utile * pourcentage_hauteur_ligne_haut;
color: "transparent"
visible: (etat_systeme === 0)
//-------------------------------------------------------
// Valeur si aucune unité :
Rectangle {
id: valeur_capteur_sans_unite
anchors.fill: parent
color: "transparent"
visible: (symbole_unite === "")
Text {
anchors.fill: parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
padding: 10
text: chaine_valeur
font.family: "Montserrat"
fontSizeMode: Text.HorizontalFit;
minimumPixelSize: 30;
font.pixelSize: 80
elide: Text.ElideRight
wrapMode: Text.Wrap
font.weight: Font.Bold
color: couleur_blanc_casse
}
}
//-------------------------------------------------------
// Valeur si unité :
Rectangle {
id: valeur_capteur_avec_unite
anchors.fill: parent
color: "transparent"
visible: (symbole_unite !== "")
Text {
id: valeur_actuelle
anchors {
verticalCenter: parent.verticalCenter
horizontalCenter: parent.horizontalCenter
horizontalCenterOffset: -20
}
text: chaine_valeur
font.family: "Montserrat"
font.pixelSize: 120
font.weight: Font.Bold
color: (nb_events > 0) ? couleur_rouge : couleur_blanc_casse
}
//-------------------------------------------------------
// Unite :
Text {
anchors {
baseline: valeur_actuelle.baseline
left: valeur_actuelle.right
leftMargin: 10
}
text: symbole_unite
font.pixelSize: 20
color: couleur_blanc_casse
}
}
//-----------------------------------
// Alarmes :
Rectangle {
id: zone_alarmes
width: parent.width
height: 50
color: shipheart_couleur_fond_bouton
radius: shipheart_rayon_bouton
anchors {
left: parent.left
bottom: parent.bottom
}
visible: nb_events >= 0
Image {
source: (nb_events === 0) ? "/images/cloche_sombre.png" : "/images/cloche_rouge.png"
sourceSize.height: 20
anchors {
verticalCenter: zone_alarmes.verticalCenter
horizontalCenter: zone_alarmes.horizontalCenter
}
}
//-----------------------------------
// Zone de clics dédiée aux alarmes :
MouseArea{
anchors.fill: zone_alarmes
onClicked: {
shipheart_clic();
boite_dialogue_liste_alarmes.open();
}
}
}
Component.onCompleted: {
mise_a_jour_valeurs_instantanees();
}
}
//-------------------------------------------------------
// Ecran haut - droite :
//-------------------------------------------------------
Rectangle {
id: zone_affichage_haut_droite
x: (shipheart_largeur_page * pourcentage_largeur_colonne_gauche) + (espace_colonnes_principales / 2);
y: shipheart_hauteur_bandeau_haut
width: ((shipheart_largeur_page * (1 - pourcentage_largeur_colonne_gauche)) - (espace_colonnes_principales / 2));
height: (affichage_hauteur_utile * pourcentage_hauteur_ligne_haut * pourcentage_hauteur_ligne_erreur) - (espace_lignes_principales / 2)
color: "transparent"
border.width: 3
border.color: couleur_rouge
radius: shipheart_rayon_bouton
visible: (etat_systeme === 0) && (texte_alerte !== "")
Image {
id: picto_alerte
source: "/images/ak14.png"
sourceSize.height: 30;
anchors {
left: parent.left
leftMargin: 20
verticalCenter: parent.verticalCenter
}
}
Text { // Affiche parfaitement 1, 2 ou 3 lignes de texte. A partir de 4 et 5, ça mort sur le contour du rectangle mais c'est encore lisible.
width: parent.width - picto_alerte.width - picto_alerte.anchors.leftMargin - anchors.leftMargin;
wrapMode: Text.Wrap
horizontalAlignment: Text.AlignHCenter
leftPadding: 10 // Dégage le pictogramme sur la gauche
rightPadding: 10 // Evite de se caler sur le bord droit du rectangle
text: texte_alerte;
anchors {
left: picto_alerte.right
verticalCenter: parent.verticalCenter
}
font.pixelSize: 16
color: couleur_blanc_casse
}
}
//-------------------------------------------------------
// Ecran milieu - droite :
//-------------------------------------------------------
Rectangle {
id: zone_affichage_milieu_droite
x: zone_affichage_haut_droite.x
y: zone_affichage_haut_droite.y + zone_affichage_haut_droite.height + espace_lignes_principales
width: zone_affichage_haut_droite.width
height: (affichage_hauteur_utile * pourcentage_hauteur_ligne_haut * (1 - pourcentage_hauteur_ligne_erreur)) - (espace_lignes_principales / 2)
color: "transparent"
visible: (etat_systeme === 0)
ColumnLayout {
width: parent.width
anchors.verticalCenter: parent.verticalCenter
spacing: 10
ListModel {
id: modele_donnees_capteur
ListElement { donnee: qsTr("Max"); valeur: "---"; heure: "--:--" ; ligne_sup: 1; ligne_inf: 1; hauteur:50; }
ListElement { donnee: qsTr("Moyen"); valeur: "---"; heure: "" ; ligne_sup: 0; ligne_inf: 1; hauteur:50; }
ListElement { donnee: qsTr("Min"); valeur: "---"; heure: "--:--" ; ligne_sup: 0; ligne_inf: 1; hauteur:50; }
ListElement { donnee: qsTr("Delta (Max - Min)"); valeur: "---"; heure: "-----" ; ligne_sup: 0; ligne_inf: 1; hauteur:80; }
// ListElement { donnee: qsTr("Delta (24h)"); valeur: "---"; heure: "-----" ; ligne_sup: 0; ligne_inf: 1; hauteur:40; }
}
Repeater {
model: modele_donnees_capteur
Rectangle {
Layout.alignment: Qt.AlignCenter
Layout.preferredWidth: parent.width
Layout.preferredHeight: model.hauteur
color: "transparent"
Rectangle {
id: ligne_sup
height:1
width:parent.width
color: couleur_gris_tres_fonce_transparent
visible: model.ligne_sup ? 1 : 0;
}
Text {
id: titre_ligne
width: parent.width * donnees_capteur_pourcentage_largeur_donnee;
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignLeft
leftPadding: 10
text: model.donnee
font.pixelSize: 18
color: couleur_blanc_casse
}
Text {
id: valeur_ligne
anchors.left: titre_ligne.right
width: parent.width * donnees_capteur_pourcentage_largeur_valeur;
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignHCenter
text: model.valeur
font.pixelSize: 18
color: couleur_blanc_casse
}
Text {
id: heure_ligne
anchors.left: valeur_ligne.right
width: parent.width * donnees_capteur_pourcentage_largeur_heure;
anchors.verticalCenter: parent.verticalCenter
horizontalAlignment: Text.AlignRight
rightPadding: 10
text: model.heure
font.pixelSize: 18
color: couleur_blanc_casse
}
Rectangle {
id: ligne_inf
anchors.top: parent.bottom;
height:1
width:parent.width
color: couleur_gris_tres_fonce_transparent
visible: model.ligne_inf ? 1 : 0;
}
}
}
}
}
//-------------------------------------------------------
// Ecran bas :
//-------------------------------------------------------
Rectangle {
id: zone_affichage_bas
x: 0
y: zone_affichage_haut_gauche.y + zone_affichage_haut_gauche.height + espace_lignes_principales;
width: shipheart_largeur_page;
height: affichage_hauteur_utile * (1 - pourcentage_hauteur_ligne_haut)
color: "transparent"
visible: (etat_systeme === 0)
//========================
// Graphique :
ChartView {
id: graphique
antialiasing: true
anchors {
top: parent.top;
left:parent.left;
bottom:parent.bottom;
margins: -15 // Un peu compliqué mais permet, avec la ligne suivante, ...
}
margins { right: 0; bottom: 0; left: 5; top: 0 } // ... d'annuler les marges.
width: parent.width * pourcentage_largeur_graphique;
backgroundColor: "transparent"
// animationDuration: 10000
// animationOptions: ChartView.SeriesAnimations
legend {visible: false }
visible: (id_variable !== 0)
DateTimeAxis {
id: axisX
format: (zoomx_en_cours > 0.5) ? courbe_format_principal_axe_x : courbe_format_zoom_1_axe_x
min: xMin_date
max: xMax_date
tickCount: (zoomx_en_cours > 0.5) ? courbe_nombre_principal_graduations_axe_x : courbe_nombre_zoom_1_graduations_axe_x
//labelsColor: (!flag_zoom_en_cours) ? couleur_blanc_casse : couleur_green_yellow
labelsColor: couleur_blanc_casse
labelsFont: Qt.font({pointSize: 10})
color: couleur_gris_moyen
shadesColor: courbes_couleur_shades_fond
shadesVisible: true
gridVisible: false
// gridLineColor: "green"
}
ValueAxis {
id: axisY1
min: yMin_valeur
max: yMax_valeur
tickCount: courbe_nombre_graduations_axe_y
labelFormat: "%." + precision_affichage_axe_y_courbe + "f"
// labelsColor: (!flag_zoom_en_cours) ? couleur_blanc_casse : couleur_green_yellow
labelsColor: couleur_blanc_casse
labelsFont:Qt.font({pointSize: 10})
color: couleur_gris_moyen
// shadesColor: courbes_couleur_shades_fond
shadesVisible: false
gridVisible: true
gridLineColor: couleur_gris_tres_fonce
titleText: "<font color='" + couleur_blanc_casse + "'>" + symbole_unite + "</font>"
titleFont {
pointSize: 20
bold: true
}
}
LineSeries { // Utilisé si on veut renforcer la courbe mais sans avoir tout le tour de l'area renfocée (qui se fait dans AreaSeries pour le coup)
id: courbe_principale
name: "Courbe principale"
axisX: axisX
axisY: axisY1
capStyle: Qt.RoundCap
style: Qt.SolidLine
width: courbes_epaisseur_bordure
color: shipheart_couleur_bordure_courbes
}
// Affichage des 2 points Min et Maxi :
ScatterSeries {
id: courbe_principale_mini_maxi
axisX: axisX
axisY: axisY1
markerSize: 10
borderColor: "transparent"
borderWidth: 0
color: couleur_green_yellow
}
Text {
id: texte_courbe_vide
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Aucune donnée n'est disponible pour cette période")
font.pixelSize: 20
font.weight: Font.Bold
color: "red"
visible: courbe_flag_vide ? true : false;
}
//---------------------------------
// Rectangle de sélection de zoom :
Rectangle {
id: rectangle_zoom
x: pinch_area.zoom_x
y: pinch_area.zoom_y
width: pinch_area.zoom_w
height: pinch_area.zoom_h
radius: shipheart_rayon_zoom
color: couleur_bleu_koriolan_tres_transparent
}
//---------------------------------
// Zone de pinch :
PinchArea{
id: pinch_area
// Propriété de la eone de zoom pendant toute la phase de sélection :
property real zoom_x: 0
property real zoom_y: 0
property real zoom_w: 0
property real zoom_h: 0
anchors.fill: parent // Calé sur 'graphique'.
// Attention : la zone de traçage à l'intérieur de 'graphique' est 'graphique.plotArea'.
// Toutes les coordonnées sont données dans 'graphique' mais on limite à l'usage les coordonnées intérieures à 'graphique.plotArea'.
//-----------------------------------------------
// Mis à jour en cours de déplacement du pinch :
onPinchUpdated: {
if ((pinch.point1.x !== pinch.point2.x) || // Il arrive, en fin de pinch, que les 2 points renvoyés soient les mêmes...
(pinch.point1.y !== pinch.point2.y)) // ... Il faut éviter de les traiter sous peine de ramener W et H à 0.
{
// Récupération des données en s'affranchissant de la position des 2 points :
zoom_x = Math.min(pinch.point1.x, pinch.point2.x)
zoom_y = Math.min(pinch.point1.y, pinch.point2.y)
zoom_w = Math.abs(pinch.point1.x - pinch.point2.x)
zoom_h = Math.abs(pinch.point1.y - pinch.point2.y)
// Limitation de 'x' et 'w' à la largeur de la zone de traçage :
if (zoom_x < graphique.plotArea.x) zoom_x = graphique.plotArea.x;
if (zoom_x > (graphique.plotArea.x + graphique.plotArea.width)) zoom_x = graphique.plotArea.x + graphique.plotArea.width
if ((zoom_x + zoom_w) > (graphique.plotArea.x + graphique.plotArea.width)) zoom_w = (graphique.plotArea.x + graphique.plotArea.width) - zoom_x;
// Limitation de 'y' et 'h' à la hauteur de la zone de traçage :
if (zoom_y < graphique.plotArea.y) zoom_y = graphique.plotArea.y;
if (zoom_y > (graphique.plotArea.y + graphique.plotArea.height)) zoom_y = graphique.plotArea.y + graphique.plotArea.height
if ((zoom_y + zoom_h) > (graphique.plotArea.y + graphique.plotArea.height)) zoom_h = (graphique.plotArea.y + graphique.plotArea.height) - zoom_y;
}
}
//-------------------------
// Valeur de fin du pinch :
onPinchFinished: {
// Préparation du zoom sur le rectangle sélectionné :
var r = Qt.rect(zoom_x, zoom_y, zoom_w, zoom_h);
graphique.zoomIn(r);
flag_zoom_en_cours = true;
zoomx_en_cours *= (zoom_w / graphique.plotArea.width);
// Effacement du rectangle :
zoom_x = 0
zoom_y = 0
zoom_w = 0
zoom_h = 0
}
// Déplacement de la courbe :
MouseArea{
anchors.fill: parent
drag.target: dragTarget
drag.axis: Drag.XAndYAxis
onClicked: {
shipheart_clic();
if (!flag_zoom_en_cours) // Si le zoom est en cours, il ne faut PAS lancer le raffraichissement...!
{
busy_indicator.running = true;
timer_raffraichissement_graphiques.interval = periode_init_raffraichissement_graphiques; // Cela permet de lancer le "busy_indicator' avant de faire la mise à jour de la courbe
}
}
onPressAndHold: {
raz_zoom();
}
}
Item {
id: dragTarget
property real oldX : x // Permet de mémoriser la dernière position d'arrivée
property real oldY : y
property real cumulX : 0 // Permet de revenir à un déclage nul lorsque nécessaire
property real cumulY : 0
onXChanged: {
graphique.scrollLeft( x - oldX );
cumulX -= (x - oldX); // Le signe '-' permettra ensuite de revenir à le position de départ
oldX = x;
}
onYChanged: {
graphique.scrollUp( y - oldY );
cumulY -= (y - oldY); // Le signe '-' permettra ensuite de revenir à le position de départ
oldY = y;
}
}
}
}
//========================
// Busy Indicator :
Shipheart_busy_indicator {
id: busy_indicator
}
//========================
// Boutons de pếriode :
ColumnLayout {
spacing: 10
anchors {
top: parent.top;
right:parent.right;
rightMargin: 5;
bottom:parent.bottom;
}
width: (parent.width * (1 - pourcentage_largeur_graphique)) - anchors.rightMargin;
ListModel {
id: modele_periodes_graphique
}
Repeater {
model: modele_periodes_graphique
Rectangle {
color: "transparent"
Layout.preferredWidth: parent.width;
Layout.preferredHeight: (parent.height / nb_boutons_periode) - parent.spacing;
Text {
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
text: model.libelle
font.pixelSize: 18
font.weight: (model.index === periode_analyse) ? Font.Bold : Font.Normal
color: couleur_blanc_casse
}
MouseArea { // Zone d'interaction. On est obligé de la mettre dans la grille pour que Rectangle et MouseArea soient frères ou parent/enfant ET qu'on n'occupe qu'un espace dans la grille.
anchors.fill: parent
onClicked: {
shipheart_clic();
raz_zoom(); // Au-delà de la fonctionnalité, c'est obligatoire sinon, le raffraichissement des la courbe est bloqué au niveau du timer
periode_analyse = model.index;
busy_indicator.running = true;
timer_raffraichissement_graphiques.interval = periode_init_raffraichissement_graphiques; // Cela permet de lancer le "busy_indicator' avant de faire la mise à jour de la courbe
}
}
// Fond du bouton :
Rectangle { // Permet d'avoir un fond et de pouvoir régler son opacité sans changer celle des textes au-dessus
width: parent.width
height: parent.height
color: (model.index === periode_analyse) ? (flag_zoom_en_cours ? couleur_green_yellow_transparent : shipheart_couleur_fond_bouton_actif) : shipheart_couleur_fond_bouton;
opacity: 0.5
radius: shipheart_rayon_bouton
}
}
}
}
Component.onCompleted: {
// mise_a_jour_courbes(); // Si on met la MAJ là, on va attendre la dispo des valeurs avant d'afficher le Rectangle. En la mettant dans un timer séparé, la page est affichée et on charge ensuite les valeurs.
}
}
//-------------------------------------------------------
// Message d'erreur si capteur Absent
// (le module sur lequel le capteur est configuré n'est pas physiquement présent)
//-------------------------------------------------------
Rectangle {
id: zone_message_absence
x: 0
y: shipheart_hauteur_bandeau_haut + espace_lignes_principales
width: shipheart_largeur_page
height: shipheart_hauteur_page - shipheart_hauteur_bandeau_haut - shipheart_hauteur_bandeau_bas - espace_lignes_principales
color: "transparent"
visible: (etat_systeme !== 0)
Rectangle { // Rectangle entouré de rouge
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -50
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
height: parent.height / 2
radius: shipheart_rayon_bouton
border.width: 3
border.color: couleur_rouge
color: couleur_rouge_transparent//"transparent"
Image { // Pictogramme DANGER
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -100
anchors.horizontalCenter: parent.horizontalCenter
source: "/images/ak14.png"
sourceSize.height: 70;
}
Text { // Texte d'info
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: 50
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
height : parent.height / 2
horizontalAlignment: Qt.AlignCenter
verticalAlignment: Qt.AlignVCenter
padding: 50
text: (type_entrees === shipheart_capteur_type_entrees_ow)
? qsTr("La sonde One Wire correspondant à ce capteur n'est pas accessible.<br />Vérifier son câblage entre le port One Wire à l'arrière de ShipBase et la sonde.")
: qsTr("Le module assurant la supervision de ce capteur n'est pas accessible !<br /><br />Si des ajouts/suppression/déplacements de modules ont eu lieu récemment, mettez à jour la configuration correspondant dans le menu MODULES de PARAMETRES AVANCES.<br /><br />Dans le cas où aucune modification n'a été effectuée récemment, vérifier les connexions de tous les modules.")
color: couleur_blanc_casse
font.pixelSize: 18
wrapMode: Label.Wrap
}
}
}
//-------------------------------------------------------
// Bandeau bas :
//-------------------------------------------------------
Row {
x: 0
y: shipheart_hauteur_page - shipheart_hauteur_bandeau_bas;
width: shipheart_largeur_page;
height: shipheart_hauteur_bandeau_bas;
spacing: 5
padding: 5
//.................................................
// Bouton retour
//.................................................
Rectangle {
id: bandeau_bas_zone_retour
width: parent.width - (2 * parent.padding)
height: shipheart_hauteur_bandeau_bas - parent.topPadding - parent.bottomPadding;
radius: 5
color: shipheart_couleur_fond_bouton
Image {
source: "/images/icone_retour.png"
sourceSize.height: 30
anchors {
verticalCenter: parent.verticalCenter
horizontalCenter: parent.horizontalCenter
}
}
MouseArea { // Zone d'interaction. On est obligé de la mettre dans la grille pour que Rectangle et MouseArea soient frères ou parent/enfant ET qu'on n'occupe qu'un espace dans la grille.
id: mouse_area_bandeau_bas_zone_retour
anchors.fill: parent
onClicked: {
shipheart_clic();
stackView.pop()
}
}
}
}
//-------------------------------------------------------
// Boite d'affichage de la liste des alarmes :
//-------------------------------------------------------
Shipheart_boite_dialogue_liste_alarmes {
id: boite_dialogue_liste_alarmes
titre: nom_capteur
type_source: shipheart_source_mesure_capteur
id_source: id_capteur
id_variable: page_capteur.id_variable
titre_widget: nom_capteur
unite: symbole_unite
}
//===========================================================
// Economiseur : Doit être placé en fin de page pour fonctionner
//===========================================================
MouseArea {
anchors.fill: parent
preventStealing: true // Permet que cela fonctionne même si un élément flickable (notamment) soit derrière et "vole" le click
enabled: flag_economiseur_actif
onClicked: {
mise_en_veille(0);
}
}
//=============================================================================================================
// Init de la page
//=============================================================================================================
function init_page()
{
if (id_capteur === 0)
{
console.log("La page 'capteur.qml' a été appelée sans 'id_capteur'. L'affichage de la page est impossible");
return;
}
//====================================================================
// Init du nom des périodes du graphique :
modele_periodes_graphique.append({ "libelle": Fonctions_date_heure.nom_periode_analyse(0), "actif": true });
modele_periodes_graphique.append({ "libelle": Fonctions_date_heure.nom_periode_analyse(1), "actif": true });
modele_periodes_graphique.append({ "libelle": Fonctions_date_heure.nom_periode_analyse(2), "actif": true });
modele_periodes_graphique.append({ "libelle": Fonctions_date_heure.nom_periode_analyse(3), "actif": true });
// Première mise à jour :
mise_a_jour_valeurs_instantanees();
mise_a_jour_courbes();
// Libération du flag :
flag_init = false;
}
//=============================================================================================================
// Mise à jour de la valeur instantannée
//=============================================================================================================
function mise_a_jour_valeurs_instantanees()
{
var req, res;
var nb;
var i;
var liste_valeurs_textes;
var age;
var valeur;
req = 'SELECT Cle_primaire, ';
req += ' Etat_systeme, ';
req += ' Nom, ';
req += ' Type_entrees, ';
req += ' Entrees, ';
req += ' ID_Variable, ';
req += ' Etat, ';
req += ' Valeur, ';
req += ' Precision_affichage_mesures, ';
req += ' Liste_valeurs_textes, ';
req += ' IF ( (Type_entrees=' + shipheart_capteur_type_entrees_nmea + '), ';
req += ' ( SELECT ( UNIX_TIMESTAMP() - UNIX_TIMESTAMP(Date_maj) ) ';
req += ' FROM TB_Nmea2000 WHERE ';
req += ' Pgn=SUBSTRING_INDEX(Entrees, \':\', 1) AND Field=SUBSTRING_INDEX(Entrees, \':\', -1) ), ';
req += ' (UNIX_TIMESTAMP() - UNIX_TIMESTAMP(Date_maj)) )';
req += ' AS Age ';
req += ' FROM TB_Capteurs WHERE Cle_primaire="' + id_capteur + '" ';
//console.log("Requête = [" + req + "]");
res = bdd.requete_unique(req);
i = 0;
id_capteur = Number(res[i++]);
etat_systeme = Number(res[i++]);
nom_capteur = res[i++];
type_entrees = Number(res[i++]);
entrees = res[i++];
id_variable = Number(res[i++]);
etat = Number(res[i++]);
valeur = Number(res[i++]);
precision_affichage = Number(res[i++]);
liste_valeurs_textes = res[i++];
age = Number(res[i++]);
texte_alerte = "";
// Recherche infos sur variable :
req = ' SELECT Unite, Conversion_unite FROM TB_Variables WHERE Cle_primaire="' + id_variable + '" ';
res = bdd.requete_unique(req);
i = 0;
symbole_unite = res[i++];
conversion_unite = Number(res[i++]);
// Traitement des conversions d'unités :
switch(conversion_unite)
{
case shipheart_type_unite_volume:
nouvelle_unite = shipheart_unite_volume;
symbole_unite = shipheart_unite_volume_symbole;
type_unite = shipheart_type_unite_volume;
break;
case shipheart_type_unite_temperature:
nouvelle_unite = shipheart_unite_temperature;
symbole_unite = shipheart_unite_temperature_symbole;
type_unite = shipheart_type_unite_temperature;
break;
case shipheart_type_unite_latitude:
nouvelle_unite = 0;
symbole_unite = "";
type_unite = shipheart_type_unite_latitude;
break;
case shipheart_type_unite_longitude:
nouvelle_unite = 0;
symbole_unite = "";
type_unite = shipheart_type_unite_longitude;
break;
default:
nouvelle_unite = 0;
type_unite = 0;
break;
}
// Cas spécifiques en fonction du type de capteur :
chaine_valeur = Fonctions.conversion_unites( valeur, nouvelle_unite, "", precision_affichage, type_unite);
switch(type_entrees)
{
//-----------------------------------------------------------------
case shipheart_capteur_type_entrees_aux:
case shipheart_capteur_type_entrees_calculee_booleen:
// Traitement des listes :
var chaines_valeurs_textes = liste_valeurs_textes.split(';');
for (let chaine_valeur_texte of chaines_valeurs_textes)
{
var chaine = chaine_valeur_texte.split('/');
if (chaine.length === 2) // Evite de traiter les cas invalides (notamment, une chaine totalement vide qui arrive ici avec un length de 1...)
{
if (valeur === Number(chaine [0])) // Si l'une des valeurs de la liste correspond à la valeur actuelle :
{
chaine_valeur = chaine[1]; // La valeur affichée sera le texte correspondant.
break; // Et il est inutile de chercher d'autres valeurs...
}
}
}
break;
//-----------------------------------------------------------------
case shipheart_capteur_type_entrees_level_sensor:
break;
//-----------------------------------------------------------------
case shipheart_capteur_type_entrees_ow:
if (etat !== shipheart_capteur_etat_normal)
{
texte_alerte = qsTr("La sonde One Wire du capteur est détectée en défaut.<br />Vérifier son câblage depuis l'arrière de ShipBase.")
}
break;
//-----------------------------------------------------------------
case shipheart_capteur_type_entrees_predefinie:
break;
//-----------------------------------------------------------------
case shipheart_capteur_type_entrees_nmea:
if (age > shipheart_periode_obsolescence_capteur_nmea)
{
texte_alerte = qsTr("Le capteur n'a pas reçu de mise à jour sur le réseau NMEA depuis<br />%1<br />Les données affichées sont donc probablement obsolètes.").arg(Fonctions_date_heure.chaine_duree(age * 1000));
}
break;
//-----------------------------------------------------------------
default :
break;
}
// Recherche des events liés à ce capteur et leur valeur :
nb_events = -1;
req = 'SELECT Etat_test ';
req += ' FROM TB_Events ';
req += ' WHERE Validite=1 ';
req += ' AND Alarme_source>0 ';
req += ' AND Type_source=' + shipheart_source_mesure_capteur + ' ';
req += ' AND ID_Source=' + id_capteur + ' ';
//console.log("Requête = [ " + req + "; ]");
nb = bdd.requete(req);
for (i = 0; i < nb; i ++)
{
res = bdd.lecture_ligne();
if (!res.isEmpty)
{
if (nb_events < Number(res[0])) nb_events = Number(res[0]);
}
}
// En sorttant, on a donc nb_events=-1 si aucun event lié, 0 si au moins un event lié mais tous leurs Etat_test=0, et 1 si au moins un event et au moins un a Etat_test=1.
}
//=============================================================================================================
// Mise à jour des données des courbes
//=============================================================================================================
function mise_a_jour_courbes()
{
var req, res;
var nb;
var date_debut, date_fin;
var courbe_principale_duree;
var mini_maxi;
var ymin_x, ymin_y, ymax_x, ymax_y;
var delta_x, delta_y;
var gradient;
var chaine_valeur_min, chaine_valeur_moyenne, chaine_valeur_max;
var format_chaine_heure; // 0=format court ("hh:mm") et 1=format long ("MM/dd hh:mm")
console.time('mise_a_jour_courbes')
// Paramètres de la courbe dépendant de la période d'analyse :
switch(periode_analyse)
{
default:
case 0: // 1 heure
courbe_nombre_principal_graduations_axe_x = 7 // Nombre de bandes verticales + 1
courbe_nombre_zoom_1_graduations_axe_x = 7 // Utilisé lorsque le taux de zoom passe en-dessous de 0.5
courbe_format_principal_axe_x = "HH:mm" // exemple : "yyyy-MM-dd HH:mm:ss"
courbe_format_zoom_1_axe_x = "HH:mm" // Utilisé lorsque le taux de zoom passe en-dessous de 0.5
break;
case 1: // 24 heures
courbe_nombre_principal_graduations_axe_x = 13 // Nombre de bandes verticales + 1
courbe_nombre_zoom_1_graduations_axe_x = 13 // Utilisé lorsque le taux de zoom passe en-dessous de 0.5
courbe_format_principal_axe_x = "HH:mm" // exemple : "yyyy-MM-dd HH:mm:ss"
courbe_format_zoom_1_axe_x = "HH:mm" // Utilisé lorsque le taux de zoom passe en-dessous de 0.5
break;
case 2: // 7 jours
courbe_nombre_principal_graduations_axe_x = 8 // Nombre de bandes verticales + 1
courbe_nombre_zoom_1_graduations_axe_x = 8 // Utilisé lorsque le taux de zoom passe en-dessous de 0.5
courbe_format_principal_axe_x = "dd/MM HH:mm" // exemple : "yyyy-MM-dd HH:mm:ss"
courbe_format_zoom_1_axe_x = "HH:mm" // Utilisé lorsque le taux de zoom passe en-dessous de 0.5
break;
case 3: // 30 jours
courbe_nombre_principal_graduations_axe_x = 31 // Nombre de bandes verticales + 1
courbe_nombre_zoom_1_graduations_axe_x = 13 // Utilisé lorsque le taux de zoom passe en-dessous de 0.5
courbe_format_principal_axe_x = "dd" // exemple : "yyyy-MM-dd HH:mm:ss"
courbe_format_zoom_1_axe_x = "HH:mm" // Utilisé lorsque le taux de zoom passe en-dessous de 0.5
break;
}
// Dates et durée :
courbe_principale_duree = Fonctions_date_heure.duree_periode_analyse(periode_analyse); // En secondes
date_fin = new Date(); // En UTC
date_debut = Fonctions_date_heure.date_add_secondes(date_fin, -(courbe_principale_duree)); // En UTC
//console.log("Date de début initiale UTC = " + date_debut + " et date de fin UTC = " + date_fin)
// Mise à jour des données de la courbe principale :
console.log("Type_unite="+type_unite+", et nouvelle_unite="+nouvelle_unite);
res = tbmesuresModel.maj_data(graphique.series("Courbe principale"), "TB_Mesures_capteurs", id_capteur, id_variable, date_debut, date_fin, 1, 1, (shipheart_fuseau_horaire + shipheart_heure_ete), type_unite, nouvelle_unite);
// Traitements spécifiques en focntion du type de capteur :
chaine_valeur_min = Number(res[6]).toFixed(precision_affichage) + " " + symbole_unite;
chaine_valeur_moyenne = Number(res[9]).toFixed(precision_affichage) + " " + symbole_unite;
chaine_valeur_max = Number(res[8]).toFixed(precision_affichage) + " " + symbole_unite;
switch(type_entrees)
{
//-----------------------------------------------------------------
case shipheart_capteur_type_entrees_aux:
break;
//-----------------------------------------------------------------
case shipheart_capteur_type_entrees_level_sensor:
break;
//-----------------------------------------------------------------
case shipheart_capteur_type_entrees_ow:
break;
//-----------------------------------------------------------------
case shipheart_capteur_type_entrees_predefinie:
if (Number(entrees) === shipheart_capteur_predefini_lat)
{
chaine_valeur_min = Fonctions.chaine_latitude(Number(res[6]));
chaine_valeur_moyenne = Fonctions.chaine_latitude(Number(res[9]));
chaine_valeur_max = Fonctions.chaine_latitude(Number(res[8]));
}
if (Number(entrees) === shipheart_capteur_predefini_lon)
{
chaine_valeur_min = Fonctions.chaine_longitude(Number(res[6]));
chaine_valeur_moyenne = Fonctions.chaine_longitude(Number(res[9]));
chaine_valeur_max = Fonctions.chaine_longitude(Number(res[8]));
}
break;
//-----------------------------------------------------------------
default :
break;
}
//-----------------------------------------------------------------------------------------------------
// Cas où aucune donnée n'est disponible sur la période :
if (Number(res[0]) === 0)
{
courbe_principale_flag_vide = true; // Permet d'afficher le texte "Aucune donnée disponible..." sur la zone graphique
modele_donnees_capteur.set(0, {"valeur": "---", heure : "--:--"}) // valeurs par défaut pour supprimer les anciennes valeurs
modele_donnees_capteur.set(1, {"valeur": "---", heure : "--"})
modele_donnees_capteur.set(2, {"valeur": "---", heure : "--:--"})
modele_donnees_capteur.set(3, {"valeur": "---", heure : "--:--"})
// modele_donnees_capteur.set(4, {"valeur": "---", heure : "--:--"})
}
//-----------------------------------------------------------------------------------------------------
// Cas où des données sont disponibles sur la période (cas normal donc...) :
else
{
courbe_flag_vide = false;
xMin_date = new Date(Number(res[1])); // Nécessaire pour la Min de l'axe X (NB : les dates retournées par 'maj_data' sont en heure locale)
xMax_date = new Date(Number(res[3])); // Nécessaire pour la Max de l'axe X (NB : les dates retournées par 'maj_data' sont en heure locale)
mini_maxi = Fonctions.ajustement_mini_maxi(Number(res[6]), Number(res[8]), courbe_nombre_graduations_axe_y);
yMin_valeur = mini_maxi.mini // Nécessaire pour le Min de l'axe Y
yMax_valeur = mini_maxi.maxi // Nécessaire pour le Max de l'axe Y
precision_affichage_axe_y_courbe = Fonctions.precision_affichage_mini_maxi(Number(res[6]), Number(res[8]), courbe_nombre_graduations_axe_y);
//console.log("Min val=" + Number(res[6]) + " et mini=" + yMin_valeur + " - Max val=" + Number(res[8]) + " et maxi=" + yMax_valeur);
// Mise à jour des données numériques :
var chaine_heure;
if (format_chaine_heure === 0) chaine_heure = Fonctions_date_heure.chaine_heure_courte(new Date(Number(res[7]))); // Date MAX format court...
else chaine_heure = Fonctions_date_heure.chaine_date_heure_courte(new Date(Number(res[7]))); // ... ou format long
modele_donnees_capteur.set(0, {"valeur": chaine_valeur_max, "heure": "[" + chaine_heure + "]"}) // Valeur MAX
modele_donnees_capteur.set(1, {"valeur": chaine_valeur_moyenne}) // Valeur MOYENNE
if (format_chaine_heure === 0) chaine_heure = Fonctions_date_heure.chaine_heure_courte(new Date(Number(res[5]))); // Date MIN format court...
else chaine_heure = Fonctions_date_heure.chaine_date_heure_courte(new Date(Number(res[5]))); // ... ou format long
modele_donnees_capteur.set(2, {"valeur": chaine_valeur_min, "heure": "[" + chaine_heure + "]"}) // Valeur MIN
// Calcul du delta X :
ymin_x = Number(res[5]);
ymax_x = Number(res[7]);
delta_x = ymax_x - ymin_x;
// Calcul du delta Y :
ymin_y = Number(res[6]);
ymax_y = Number(res[8]);
delta_y = ymax_y - ymin_y;
// Ajout du delta :
modele_donnees_capteur.set(3, {"valeur": ((ymax_x > ymin_x) ? "+" : "-") + delta_y.toFixed(precision_affichage + 1) + " " + symbole_unite, "heure": Fonctions_date_heure.chaine_duree(delta_x)})
// Ajout des points de Min/Max sur la courbe :
courbe_principale_mini_maxi.clear();
courbe_principale_mini_maxi.append(ymin_x, ymin_y);
courbe_principale_mini_maxi.append(ymax_x, ymax_y);
}
busy_indicator.running = false; // Arrêt du tourniquet s'il est en marche
console.timeEnd('mise_a_jour_courbes')
}
//=============================================================================================================
// RAZ du zoom
//=============================================================================================================
function raz_zoom()
{
graphique.zoomReset(); // RAZ du zoom proprement dit
graphique.scrollLeft(dragTarget.cumulX); // RAZ du déplacement horizontal par annulation du cumul
graphique.scrollUp(dragTarget.cumulY); // RAZ du déplacement vertical par annulation du cumul
dragTarget.cumulX = 0; // RAZ du cumul après retour à la position d'origine
dragTarget.cumulY = 0;
flag_zoom_en_cours = false;
zoomx_en_cours = 1;
}
}