//======================================================================================================================================================================== // 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: "" + symbole_unite + "" 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.
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 !

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.

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.
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
%1
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; } }