Un quatrieme tableau, pour le telephone
L'application Grafana de l'App Store ne parle qu'a Grafana Cloud ; une instance auto-hebergee se consulte au navigateur, epingle sur l'ecran d'accueil. Reste que les trois tableaux existants sont dessines pour un ecran large. Contrainte de conception, verifiee et notee dans le README : sous ~770 px, Grafana passe CHAQUE panneau en pleine largeur et les empile. Le nombre de panneaux devient le nombre d'ecrans a faire defiler. D'ou six panneaux seulement, et les chiffres regroupes dans deux panneaux "stat" — plusieurs valeurs dans un meme panneau restent cote a cote, la ou six panneaux separes auraient fait six ecrans. La reponse d'abord : batterie, marge (ce qu'elle a moins ce qu'il lui faut), excedent. Puis l'energie a couvrir et l'heure du relais solaire. Puis les deux courbes qui expliquent ces chiffres, la frise du reseau, et les huit derniers evenements. 26/26 requetes verifiees contre les bases reelles. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
@@ -226,6 +226,16 @@ Mot de passe perdu :
|
||||
mode déclaré par chacun des sept chargeurs.
|
||||
- **Prévision contre réel** — le biais de forme de la prévision, question
|
||||
ouverte qui demande des semaines de données.
|
||||
- **Sur le téléphone** — une seule colonne, six panneaux, et la réponse en
|
||||
premier. À épingler sur l'écran d'accueil de l'iPhone :
|
||||
`http://10.10.10.22:3001/d/evcs-telephone?kiosk` (Safari → Partager → Sur
|
||||
l'écran d'accueil). `?kiosk` retire les menus.
|
||||
|
||||
⚠️ Contrainte de conception à connaître avant d'y ajouter quoi que ce soit :
|
||||
sous ~770 px de large, Grafana passe **chaque** panneau en pleine largeur et
|
||||
les empile. Le nombre de panneaux est donc le nombre d'écrans à faire
|
||||
défiler — d'où les chiffres regroupés dans deux panneaux « stat » plutôt que
|
||||
répartis en six.
|
||||
|
||||
Les événements de la table `evenement` apparaissent en **marqueurs verticaux
|
||||
orange sur tous les graphiques** : une courbe sans ses événements ne
|
||||
|
||||
674
deploy/historisation/grafana/dashboards/telephone.json
Normal file
674
deploy/historisation/grafana/dashboards/telephone.json
Normal file
@@ -0,0 +1,674 @@
|
||||
{
|
||||
"uid": "evcs-telephone",
|
||||
"title": "Sur le telephone",
|
||||
"description": "Une colonne, pensee pour un ecran de telephone. Repond a une seule question : est-ce que la nuit qui vient va bien se passer ?",
|
||||
"tags": [
|
||||
"evcs"
|
||||
],
|
||||
"timezone": "Europe/Paris",
|
||||
"editable": true,
|
||||
"schemaVersion": 39,
|
||||
"version": 1,
|
||||
"refresh": "1m",
|
||||
"time": {
|
||||
"from": "now-12h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {
|
||||
"refresh_intervals": [
|
||||
"30s",
|
||||
"1m",
|
||||
"5m",
|
||||
"15m",
|
||||
"1h"
|
||||
]
|
||||
},
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"name": "Annotations & Alerts",
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"type": "dashboard"
|
||||
},
|
||||
{
|
||||
"name": "Evenements",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"enable": true,
|
||||
"iconColor": "semi-dark-orange",
|
||||
"hide": false,
|
||||
"target": {
|
||||
"format": "table",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "SELECT ts AS time, resume AS text, genre || ',' || coalesce(source,'?') AS tags FROM evenement WHERE $__timeFilter(ts) ORDER BY 1"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"panels": [
|
||||
{
|
||||
"type": "stat",
|
||||
"title": "Maintenant",
|
||||
"description": "Marge = ce que la batterie a, moins ce qu'il lui faut pour tenir jusqu'au relais du solaire. Negative, la nuit ne passe pas sans reseau.",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"format": "table",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "SELECT\n (SELECT valeur FROM mesure WHERE cle = 'sensor.victron_battery_soc'\n ORDER BY ts DESC LIMIT 1) AS \"Batterie\",\n (SELECT soc_pct - soc_requis_pct FROM bilan\n WHERE soc_requis_pct IS NOT NULL ORDER BY ts DESC LIMIT 1) AS \"Marge\",\n (SELECT valeur FROM mesure WHERE cle = 'sensor.victron_pv_power_system'\n ORDER BY ts DESC LIMIT 1)\n- (SELECT valeur FROM mesure WHERE cle = 'sensor.victron_consumption_power_l1'\n ORDER BY ts DESC LIMIT 1) AS \"Excedent\""
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"w": 24,
|
||||
"h": 5
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "text",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"mappings": []
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Batterie"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "percent"
|
||||
},
|
||||
{
|
||||
"id": "thresholds",
|
||||
"value": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "red",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "orange",
|
||||
"value": 25
|
||||
},
|
||||
{
|
||||
"color": "yellow",
|
||||
"value": 40
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
"value": 60
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Marge"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "percent"
|
||||
},
|
||||
{
|
||||
"id": "decimals",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"id": "thresholds",
|
||||
"value": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "red",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Excedent"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "watt"
|
||||
},
|
||||
{
|
||||
"id": "decimals",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"id": "thresholds",
|
||||
"value": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "blue",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"orientation": "horizontal",
|
||||
"textMode": "auto",
|
||||
"colorMode": "value",
|
||||
"graphMode": "none",
|
||||
"justifyMode": "center",
|
||||
"percentChangeColorMode": "standard",
|
||||
"wideLayout": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "stat",
|
||||
"title": "La nuit qui vient",
|
||||
"description": "Energie a couvrir d'ici l'heure ou la production repasse au-dessus de la consommation, et le niveau de batterie qu'il faudrait pour y arriver.",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"format": "table",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "SELECT round(energie_a_couvrir_kwh::numeric, 1) AS \"A couvrir\",\n round(soc_requis_pct::numeric) AS \"SOC requis\",\n heure_croisement AS \"Relais du solaire\"\nFROM bilan ORDER BY ts DESC LIMIT 1"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 5,
|
||||
"w": 24,
|
||||
"h": 5
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "text",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"mappings": []
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "A couvrir"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "kwatth"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "SOC requis"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "unit",
|
||||
"value": "percent"
|
||||
},
|
||||
{
|
||||
"id": "thresholds",
|
||||
"value": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": null
|
||||
},
|
||||
{
|
||||
"color": "yellow",
|
||||
"value": 60
|
||||
},
|
||||
{
|
||||
"color": "orange",
|
||||
"value": 85
|
||||
},
|
||||
{
|
||||
"color": "red",
|
||||
"value": 100
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"orientation": "horizontal",
|
||||
"textMode": "auto",
|
||||
"colorMode": "value",
|
||||
"graphMode": "none",
|
||||
"justifyMode": "center",
|
||||
"percentChangeColorMode": "standard",
|
||||
"wideLayout": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"title": "Ce qu'elle a, ce qu'il lui faut",
|
||||
"description": "Quand le vert passe sous l'orange, le reseau sera necessaire.",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"format": "time_series",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "SELECT ts AS time,\n soc_pct AS \"Batterie\",\n soc_requis_pct AS \"Requis\"\nFROM bilan WHERE $__timeFilter(ts) ORDER BY 1"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 10,
|
||||
"w": 24,
|
||||
"h": 7
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "percent",
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"lineWidth": 2,
|
||||
"fillOpacity": 8,
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
"axisBorderShow": false,
|
||||
"gradientMode": "opacity"
|
||||
},
|
||||
"min": 0,
|
||||
"max": 100
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Batterie"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "green"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "custom.lineWidth",
|
||||
"value": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Requis"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "orange"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "custom.fillOpacity",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true,
|
||||
"calcs": []
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "timeseries",
|
||||
"title": "Ce qui reste apres la maison",
|
||||
"description": "Au-dessus de zero, la voiture peut charger sans rien couter. En dessous, la charger revient a vider la batterie.",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"format": "time_series",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "WITH par_minute AS (\n SELECT date_trunc('minute', ts) AS t,\n avg(valeur) FILTER (WHERE cle = 'sensor.victron_pv_power_system') AS Production,\n avg(valeur) FILTER (WHERE cle = 'sensor.victron_consumption_power_l1') AS Maison\n FROM mesure WHERE $__timeFilter(ts) GROUP BY 1\n),\npaliers AS (\n SELECT t, Production, Maison,\n count(Production) OVER (ORDER BY t) AS g_Production,\n count(Maison) OVER (ORDER BY t) AS g_Maison\n FROM par_minute\n),\nreport AS (\n SELECT t,\n first_value(Production) OVER (PARTITION BY g_Production ORDER BY t) AS Production,\n first_value(Maison) OVER (PARTITION BY g_Maison ORDER BY t) AS Maison\n FROM paliers\n)\nSELECT t AS time,\n Production AS \"Production\",\n Maison AS \"Maison\",\n Production - Maison AS \"Excedent\"\nFROM report ORDER BY t"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 17,
|
||||
"w": 24,
|
||||
"h": 7
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"unit": "watt",
|
||||
"color": {
|
||||
"mode": "palette-classic"
|
||||
},
|
||||
"custom": {
|
||||
"lineWidth": 2,
|
||||
"fillOpacity": 8,
|
||||
"showPoints": "never",
|
||||
"spanNulls": true,
|
||||
"axisBorderShow": false,
|
||||
"gradientMode": "opacity"
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Production"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "yellow"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Maison"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "custom.fillOpacity",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Excedent"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "color",
|
||||
"value": {
|
||||
"mode": "fixed",
|
||||
"fixedColor": "green"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "custom.lineWidth",
|
||||
"value": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"legend": {
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": true,
|
||||
"calcs": []
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "multi",
|
||||
"sort": "desc"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "state-timeline",
|
||||
"title": "Le reseau",
|
||||
"description": "Une seule bascule par nuit : c'est le but de la session unique.",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"format": "table",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "SELECT ts AS time,\n source_mode AS \"Source\",\n CASE WHEN session_engagee THEN 'engagee' ELSE '-' END AS \"Session\"\nFROM decision WHERE $__timeFilter(ts) ORDER BY 1"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 24,
|
||||
"w": 24,
|
||||
"h": 5
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "continuous-GrYlRd"
|
||||
},
|
||||
"custom": {
|
||||
"lineWidth": 0,
|
||||
"fillOpacity": 80,
|
||||
"insertNulls": false,
|
||||
"spanNulls": true
|
||||
},
|
||||
"mappings": [
|
||||
{
|
||||
"type": "value",
|
||||
"options": {
|
||||
"-": {
|
||||
"color": "dark-blue",
|
||||
"index": 0
|
||||
},
|
||||
"engagee": {
|
||||
"color": "orange",
|
||||
"index": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"overrides": []
|
||||
},
|
||||
"options": {
|
||||
"showValue": "auto",
|
||||
"mergeValues": true,
|
||||
"alignValue": "left",
|
||||
"rowHeight": 0.85,
|
||||
"legend": {
|
||||
"displayMode": "list",
|
||||
"placement": "bottom",
|
||||
"showLegend": false
|
||||
},
|
||||
"tooltip": {
|
||||
"mode": "single"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "table",
|
||||
"title": "Ce qui s'est passe",
|
||||
"description": "Les huit derniers evenements, quelle que soit la fenetre de temps.",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"refId": "A",
|
||||
"datasource": {
|
||||
"type": "grafana-postgresql-datasource",
|
||||
"uid": "evcs-pg"
|
||||
},
|
||||
"format": "table",
|
||||
"rawQuery": true,
|
||||
"editorMode": "code",
|
||||
"rawSql": "SELECT to_char(ts, 'DD/MM HH24:MI') AS \"Quand\",\n resume AS \"Quoi\"\nFROM evenement ORDER BY ts DESC LIMIT 8"
|
||||
}
|
||||
],
|
||||
"gridPos": {
|
||||
"x": 0,
|
||||
"y": 29,
|
||||
"w": 24,
|
||||
"h": 7
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"custom": {
|
||||
"align": "left",
|
||||
"cellOptions": {
|
||||
"type": "auto"
|
||||
},
|
||||
"filterable": true
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"matcher": {
|
||||
"id": "byName",
|
||||
"options": "Quand"
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"id": "custom.width",
|
||||
"value": 110
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"showHeader": true,
|
||||
"cellHeight": "sm",
|
||||
"footer": {
|
||||
"show": false
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -73,6 +73,27 @@ def ts(title, targets, ds, x, y, w, h, unit="", desc="", extra=None, legend="bot
|
||||
p[k] = v
|
||||
return p
|
||||
|
||||
def stat(title, targets, ds, x, y, w, h, desc="", overrides=None, texte="auto"):
|
||||
"""Grands chiffres. Sur telephone, Grafana empile TOUS les panneaux en
|
||||
pleine largeur sous ~770 px : mettre plusieurs valeurs dans UN panneau est
|
||||
donc le seul moyen de les garder cote a cote et de limiter le defilement."""
|
||||
return {"type": "stat", "title": title, "description": desc,
|
||||
"datasource": ds, "targets": targets,
|
||||
"gridPos": {"x": x, "y": y, "w": w, "h": h},
|
||||
"fieldConfig": {"defaults": {
|
||||
"color": {"mode": "thresholds"},
|
||||
"thresholds": {"mode": "absolute",
|
||||
"steps": [{"color": "text", "value": None}]},
|
||||
"mappings": []}, "overrides": overrides or []},
|
||||
"options": {"reduceOptions": {"calcs": ["lastNotNull"],
|
||||
"fields": "", "values": False},
|
||||
"orientation": "horizontal", "textMode": texte,
|
||||
"colorMode": "value", "graphMode": "none",
|
||||
"justifyMode": "center",
|
||||
"percentChangeColorMode": "standard",
|
||||
"wideLayout": True}}
|
||||
|
||||
|
||||
def timeline(title, targets, ds, x, y, w, h, desc="", mappings=None):
|
||||
return {"type": "state-timeline", "title": title, "description": desc,
|
||||
"datasource": ds, "targets": targets,
|
||||
@@ -466,8 +487,119 @@ prevision = dashboard("evcs-prevision", "Prevision contre reel", (
|
||||
"a ete faite. C'est la raison d'etre de cette table."),
|
||||
])
|
||||
|
||||
# =====================================================================
|
||||
# 4. TELEPHONE — une colonne, peu de panneaux, la reponse d'abord
|
||||
# =====================================================================
|
||||
# Contrainte de conception : sous ~770 px de large, Grafana passe CHAQUE
|
||||
# panneau en pleine largeur et les empile. Le nombre de panneaux devient donc
|
||||
# le nombre d'ecrans a faire defiler. D'ou le choix de regrouper les chiffres
|
||||
# dans deux panneaux « stat » plutot que d'en faire six.
|
||||
|
||||
def seuils(*paires):
|
||||
return {"mode": "absolute", "steps": [{"color": c, "value": v} for c, v in paires]}
|
||||
|
||||
def sur(nom, props):
|
||||
return {"matcher": {"id": "byName", "options": nom}, "properties": props}
|
||||
|
||||
telephone = dashboard("evcs-telephone", "Sur le telephone", (
|
||||
"Une colonne, pensee pour un ecran de telephone. Repond a une seule "
|
||||
"question : est-ce que la nuit qui vient va bien se passer ?"),
|
||||
frm="now-12h", panels=[
|
||||
|
||||
stat("Maintenant", [
|
||||
sql("A",
|
||||
"SELECT\n"
|
||||
" (SELECT valeur FROM mesure WHERE cle = 'sensor.victron_battery_soc'\n"
|
||||
" ORDER BY ts DESC LIMIT 1) AS \"Batterie\",\n"
|
||||
" (SELECT soc_pct - soc_requis_pct FROM bilan\n"
|
||||
" WHERE soc_requis_pct IS NOT NULL ORDER BY ts DESC LIMIT 1) AS \"Marge\",\n"
|
||||
" (SELECT valeur FROM mesure WHERE cle = 'sensor.victron_pv_power_system'\n"
|
||||
" ORDER BY ts DESC LIMIT 1)\n"
|
||||
"- (SELECT valeur FROM mesure WHERE cle = 'sensor.victron_consumption_power_l1'\n"
|
||||
" ORDER BY ts DESC LIMIT 1) AS \"Excedent\"", fmt="table")],
|
||||
PG, 0, 0, 24, 5,
|
||||
"Marge = ce que la batterie a, moins ce qu'il lui faut pour tenir jusqu'au "
|
||||
"relais du solaire. Negative, la nuit ne passe pas sans reseau.",
|
||||
overrides=[
|
||||
sur("Batterie", [{"id": "unit", "value": "percent"},
|
||||
{"id": "thresholds", "value": seuils(
|
||||
("red", None), ("orange", 25), ("yellow", 40), ("green", 60))}]),
|
||||
sur("Marge", [{"id": "unit", "value": "percent"},
|
||||
{"id": "decimals", "value": 1},
|
||||
{"id": "thresholds", "value": seuils(("red", None), ("green", 0))}]),
|
||||
sur("Excedent", [{"id": "unit", "value": "watt"},
|
||||
{"id": "decimals", "value": 0},
|
||||
{"id": "thresholds", "value": seuils(
|
||||
("blue", None), ("green", 0))}])]),
|
||||
|
||||
stat("La nuit qui vient", [
|
||||
sql("A",
|
||||
"SELECT round(energie_a_couvrir_kwh::numeric, 1) AS \"A couvrir\",\n"
|
||||
" round(soc_requis_pct::numeric) AS \"SOC requis\",\n"
|
||||
" heure_croisement AS \"Relais du solaire\"\n"
|
||||
"FROM bilan ORDER BY ts DESC LIMIT 1", fmt="table")],
|
||||
PG, 0, 5, 24, 5,
|
||||
"Energie a couvrir d'ici l'heure ou la production repasse au-dessus de la "
|
||||
"consommation, et le niveau de batterie qu'il faudrait pour y arriver.",
|
||||
overrides=[
|
||||
sur("A couvrir", [{"id": "unit", "value": "kwatth"}]),
|
||||
sur("SOC requis", [{"id": "unit", "value": "percent"},
|
||||
{"id": "thresholds", "value": seuils(
|
||||
("green", None), ("yellow", 60), ("orange", 85), ("red", 100))}])]),
|
||||
|
||||
ts("Ce qu'elle a, ce qu'il lui faut", [
|
||||
sql("A", "SELECT ts AS time,\n"
|
||||
" soc_pct AS \"Batterie\",\n"
|
||||
" soc_requis_pct AS \"Requis\"\n"
|
||||
"FROM bilan WHERE $__timeFilter(ts) ORDER BY 1")],
|
||||
PG, 0, 10, 24, 7, "percent",
|
||||
"Quand le vert passe sous l'orange, le reseau sera necessaire.",
|
||||
legend="bottom",
|
||||
extra={"defaults": {"min": 0, "max": 100},
|
||||
"overrides": [
|
||||
sur("Batterie", [{"id": "color", "value": {"mode": "fixed", "fixedColor": "green"}},
|
||||
{"id": "custom.lineWidth", "value": 3}]),
|
||||
sur("Requis", [{"id": "color", "value": {"mode": "fixed", "fixedColor": "orange"}},
|
||||
{"id": "custom.fillOpacity", "value": 0}])]}),
|
||||
|
||||
ts("Ce qui reste apres la maison", [
|
||||
sql_grandeurs("A", [
|
||||
('sensor.victron_pv_power_system', 'Production'),
|
||||
('sensor.victron_consumption_power_l1', 'Maison')],
|
||||
calculees=[('Production - Maison', 'Excedent')])],
|
||||
PG, 0, 17, 24, 7, "watt",
|
||||
"Au-dessus de zero, la voiture peut charger sans rien couter. En dessous, "
|
||||
"la charger revient a vider la batterie.",
|
||||
extra={"overrides": [
|
||||
sur("Production", [{"id": "color", "value": {"mode": "fixed", "fixedColor": "yellow"}}]),
|
||||
sur("Maison", [{"id": "color", "value": {"mode": "fixed", "fixedColor": "text"}},
|
||||
{"id": "custom.fillOpacity", "value": 0}]),
|
||||
sur("Excedent", [{"id": "color", "value": {"mode": "fixed", "fixedColor": "green"}},
|
||||
{"id": "custom.lineWidth", "value": 3}])]}),
|
||||
|
||||
timeline("Le reseau", [
|
||||
sql("A", "SELECT ts AS time,\n"
|
||||
" source_mode AS \"Source\",\n"
|
||||
" CASE WHEN session_engagee THEN 'engagee' ELSE '-' END AS \"Session\"\n"
|
||||
"FROM decision WHERE $__timeFilter(ts) ORDER BY 1", fmt="table")],
|
||||
PG, 0, 24, 24, 5,
|
||||
"Une seule bascule par nuit : c'est le but de la session unique.",
|
||||
mappings=[{"type": "value", "options": {
|
||||
"-": {"color": "dark-blue", "index": 0},
|
||||
"engagee": {"color": "orange", "index": 1}}}]),
|
||||
|
||||
table("Ce qui s'est passe", [
|
||||
sql("A", "SELECT to_char(ts, 'DD/MM HH24:MI') AS \"Quand\",\n"
|
||||
" resume AS \"Quoi\"\n"
|
||||
"FROM evenement ORDER BY ts DESC LIMIT 8", fmt="table")],
|
||||
PG, 0, 29, 24, 7,
|
||||
"Les huit derniers evenements, quelle que soit la fenetre de temps.",
|
||||
overrides=[sur("Quand", [{"id": "custom.width", "value": 110}])]),
|
||||
])
|
||||
|
||||
base = pathlib.Path('deploy/historisation/grafana/dashboards')
|
||||
for nom, d in [('nuit.json', nuit), ('solaire.json', solaire),
|
||||
('prevision.json', prevision)]:
|
||||
('prevision.json', prevision),
|
||||
('telephone.json', telephone)]:
|
||||
(base / nom).write_text(json.dumps(d, indent=2, ensure_ascii=False) + '\n')
|
||||
print(f"{nom:16s} {len(d['panels'])} panneaux")
|
||||
|
||||
Reference in New Issue
Block a user