-------------------------------------------------
[10/12/2024] - ~10mins - #domotique #graph #monitoring
-------------------------------------------------
J'aime beaucoup les graphs.
Je graph tout un tas de trucs et notamment ma conso d'eau, de gaz et d'électricité en relevant manuellement les compteurs.
Je graph aussi mes kilomètres à vélo, la température et l'humidité bref, ça m'amuse vraiment bien.
Mais un pote me tease depuis des années avec son installation domotique.
Il fait mumuse avec des capteurs des scénaris et tout le toutim.
Globalement la domotique me fait pas vraiment rêver, je n'en vois pas trop l'intéret et j'ai pas envie de dépendre de l'ordi pour ouvrir un volet, encore moins de devoir dégainer un smartphone pour interagir avec la lumière.
Et ne partons pas non plus dans le délire d'avoir un compte chez un fabricant ou ne serait-ce qu'une applie dédiée.
Je suis trop parano et pas suffisamment scotché à mon smartphone pour ce genre de conneries.
Mais par contre avoir des prises électriques connectées qui permettent de grapher en live la consommation électrique, **LÀ** ça me titille vraiment plus.
Et quand ce pote me dit qu'il existe une marque de produits connectés qui est compatible avec mes exigences… bha j'ai eu du mal à résister !
J'ai acheté un pack de **Shelly Plug S** qui sont des prises connectées avec du wifi et du bluetooth.
Elles tournent grâce à des ptits ESP et donc peuvent fonctionner sans être reliées au net, ont toutes leurs fonctionnalités disponibles sans appli, il y a une grande doc pour les utiliser via leur API.
Bref, c'est fait pour un bricolo de mon genre !
Bon alors, ni une ni deux, je déballe, je branche.
Là un réseau wifi apparait.
On s'y connecte et ensuite on se rend sur l'interface web à l'adresse http://192.168.33.1 et là on arrive sur un truc plutôt sobre, propre et surtout très réactif.
J'avais jamais utilisé d'appareil à base d'ESP mais je pensais vraiment que ces trucs étaient vraiment si peu puissants que rien que l'interface web serait lente.
Que nenni !
Les pages s'ouvrent de suite en sans bug !
Du coup, dans les paramètres, je lui file mon wifi, une adresse statique et ça me demande un reboot.
Hop, j'accepte, je me fous sur la nouvelle adresse et c'est parti pour explorer un peu tout ça.
- Access point : On peut faire en sorte que réactiver son AP (je vois pas l'intéret mais bon)
- Wifi : Bon bha la conf réseau, on peut lui en donner 2 d'ailleurs et ça se fout sur le plus fort.
- Bluetooth : Ouai on peut y accéder en bluetooth notamment pour une app je crois. On peut aussi s'en servir en tant que gateway pour les autres appareils bluetooth (ils font des capteurs, des boutons …), pas con du tout.
- Cloud : c'est du opt-in. J'ai pas testé et ne le ferais pas mais c'est possible de le faire.
- MQTT : Pour se raccorder à un serveur MQTT (j'en ai pas même si j'avoue que ça m'intrigue).
- RPC over UDP : je me suis pas penché dessus mais c'est visiblement pour rendre l'api dispo comme ça (ça consomme moins ?)
- Outbound websocket : la même mais pour du websocket.
- Range Extender : Chsais po mais vu le nom j'imagine que c'est pour augmenter la couverture wifi ( ou bluetooth ?).
- Device Name : Histoire de lui donner un charmant ptit nom.
- Reboot
- Factory Reset
- Location and time : On y fout le fuseau horaire et la géoloc (ça sert pour déterminer les heures de levé/couché de soleil)
- Authentication
- Firmware : Pour les mises à jour. Ça peut aller direct piocher chez le fabricant ou via uplpad de fichier voir s'auto-update.
- Debug : un menu pour sortir des logs
- TLS configuration : un menu pour coller les certifs TLS (à générer de son côté) pour avoir de l'https (je m'y attendais pas !)
- Eco mode : un menu permettant d'activer ce mode qui downclock le bousin pour consommer moins.
C'est plutôt pas mal.
Mine de rien, c'est juste une prise électrique, c'est à peine plus gros qu'une prise classique.
Bon, premier truc à faire, changer le nom, foutre l'heure et la localisation puis mettre à jour à la dernière version (ça se fait en plusieurs fois).
Une fois fait, je fous une passerelle bidon au niveau réseau histoire que la prise n'ait plus accès à Internet et surtout qu'elle ne soit pas joignable depuis Internet.
À terme d'ailleurs j'isolerai ce subnet histoire d'être sûr.
Ces prises ont plusieurs fonctionnalités.
La première c'est de les controler manuellement.
Pourquoi pas, la seconde c'est de les automatiser à base d'un simili-chron.
Donc définir une heure d'allumage, une d'extinction en fonction du jour, de la date…
Un poil plus exotique, on peut aussi lui définir ça en fonction de l'heure de couché/levé du soleil grâce à l'information de localisation.
J'ai notamment des guirlandes qui me servent d'éclairage dans le salon (ça consomme peu, ça éclaire de partout, pour du tamisé c'est parfait et bonus si vous êtes myopes : en enlevant les lunettes on est en plein ciel étoilé !) qui ont chacune leur ptit programmateur intégré qui permet de se démarrer pour 6h, s'éteindre 18h et recommencer.
C'est bien gentil mais 6h en hiver c'est pas ouf.
En plus la nuit ne tombant pas au même moment c'est chiant à re-régler continuellement.
Ha et en plus, ça garde pas très bien l'heure, certaines se décalent dans un sens, d'autre dans l'autre, bref c'est merdique.
{{}}
Du coup, j'ai fait une ptite programmation d'éclairage 10 minutes avant le couché de soleil et extinction à heure fixe.
Ça prend deux minutes à faire.
Voilà rien de bien fifou, ça fait un truc basique sans surprise.
Et ça gère aussi si le truc est déjà allumé ou éteint.
Mais bon ce que je veux surtout c'est des jolis graphs !
L'interface web affiche la conso en live.
C'est gentil mais j'ai pas envie d'avoir ça ouvert en permanence.
On peut aussi récup ces infos à coup de curl !
curl http://ip.de.la.prise/rpc/Switch.GetStatus?id=0 | jq
{
"id": 0,
"source": "init",
"output": true,
"apower": 1.3,
"voltage": 232.4,
"current": 0.037,
"aenergy": {
"total": 1861.903,
"by_minute": [
22.407,
21.976,
21.976
],
"minute_ts": 1733083620
},
"temperature": {
"tC": 22.5,
"tF": 72.4
}
}
Voilà une seconde source pour atteindre notre objectif.
Mais j'ai pas trop envie de me faire chier et surtout j'ai fouillé un peu et j'ai trouvé mieux !
Il y a un menu **Scripts** permettant de créer des scripts en javascript qui peuvent faire tout un tas de trucs tout en s'executant quand même sur la prise elle-même et non dans votre navigateur (je précise au cas où…).
Et ils proposent une collection de scripts pré-existants (également trouvable sur leur github [1]).
Dans le lot, il y a un exporteur prometheus !
Du coup, tout content je le sélectionne l'active et le fout au boot.
Je configure mon ptit **prometheus** pour aller picorer dedans.
Et ça fonctionne pas.
Je vérifie dans mon curl voir si la page de metrics fonctionne et oui c'est bon.
🤷
Du coup, je dégaine mon **telegraf** qui sait aussi aller piocher dans du prometheus.
Et ça ne fonctionne pas non plus, mais il est sympa en me filant une erreur explicite d'un problème de syntaxe.
Je modifie le fichier avec ce qui me semble être une meilleure syntaxe (j'y connais rien mais deux ptites recherches vite fait sur le web) et je reteste et là boum !
Ça fonctionne, enfin j'ai plus d'erreur.
Je me rue sur **grafana** et je crée un nouveau panel et bingo, il me propose bien les nouvelles metrics.
Bon, bha c'est tout bon (ou presque).
Un truc qui n'est pas remonté dans les metrics c'est la force du signal wifi.
Vu que je m'amuse un peu avec, en ce moment, j'installe la prise dans des endroits reculés et du coup autant en profiter pour indiquer comment fonctionne le wifi.
Du coup j'édite un peu le fameux script prometheus pour y foutre également les infos du wifi et voilà.
<summary>Bref voilà le script complet pour l'export prometheus
{{}}
/*
* This script exposes a "/status" endpoint that returns Prometheus metrics that can be scraped.
* It will be reachable under "/script//metrics". Id will be 1 if this is your first script.
*
* Example Prometheus config:
*
* scrape_configs:
* - job_name: 'shelly'
* metrics_path: /script/1/metrics
* static_configs:
* - targets: ['']
*
* Replace with the ip address of the device and adjust the script id if needed.
*
* If you use Grafana to display metrics scraped, you can give a try to the example dashboard in prometheus-grafana-example-dashboard.json
* available via the url https://raw.githubusercontent.com/ALLTERCO/shelly-script-examples/main/prometheus-grafana-example-dashboard.json
*
* Note: This script assumes you have one switch, but you can configure that in the Configuration section
*/
// ---------------------------------------------------------------------------------------------------------------------
// Configuration (you can change / adapt here)
// ---------------------------------------------------------------------------------------------------------------------
// Prefix added to all metrics name
const metric_prefix = "shelly_"
// url of metrics. will be used for the last part of "/script//<url>"
// where ip = your plug ip/hostname; id the script id or number (1 if this is the first); url the value below
const url = "metrics"
// List of internal switch to monitor using the shelly's id naming scheme in the form of switch: (ex switch:0)
const monitored_switches = ["switch:0"]
// ---------------------------------------------------------------------------------------------------------------------
// Prometheus exporter
// ---------------------------------------------------------------------------------------------------------------------
const TYPE_GAUGE = "gauge"
const TYPE_COUNTER = "counter"
var info = Shelly.getDeviceInfo();
function promLabel(label, value) {
return [label, "=", '"', value, '"'].join("");
}
// Will be added to every metrics
var defaultLabels = [
["name", info.name],
["id", info.id],
["mac", info.mac],
["app", info.app]
]
.map(function (data) {
return promLabel(data[0], data[1]);
});
/**
* Generate one metric output with all the shenanigans around it
* @param name The name of the metrics (Will be prefixed by metric_prefix value)
* @param type One of the TYPE_* of metrics. Usually Counter, Gauge, Histogram (not supported yes)
* @param specificLabels Array of labels generated by promLabel() specific to this metric.
* @param description An Human description of the metric
* @param value The actual metric numeric value
* @returns {string} The metric string to include in final response
*/
function printPrometheusMetric(name, type, specificLabels, description, value) {
return [
"# HELP ", metric_prefix, name, " ", description, "\n",
"# UNIT ", metric_prefix, name, " ", type, "\n",
metric_prefix, name, "{", defaultLabels.join(","), specificLabels.length > 0 ? "," : "", specificLabels.join(","), "}", " ", value, "\n\n"
].join("");
}
/**
* HTTP handler that will be called when the url will be accessed
* @param request
* @param response
*/
function httpServerHandler(request, response) {
response.body = [
generateMetricsForSystem(),
monitored_switches.map(function (switch_string_id) {
return generateMetricsForSwitch(switch_string_id)
}).join("")
].join("")
response.code = 200;
response.send();
}
/**
* Generate metrics for the system part
* @returns {string}
*/
function generateMetricsForSystem() {
const sys = Shelly.getComponentStatus("sys")
const wifi = Shelly.getComponentStatus("wifi")
return [
printPrometheusMetric("uptime_seconds", TYPE_COUNTER, [], "power level in watts", sys.uptime),
printPrometheusMetric("ram_size_bytes", TYPE_GAUGE, [], "Internal board RAM size in bytes", sys.ram_size),
printPrometheusMetric("ram_free_bytes", TYPE_GAUGE, [], "Internal board free RAM size in bytes", sys.ram_free),
printPrometheusMetric("rssi", TYPE_GAUGE, [], "Wifi strength", wifi.rssi)
].join("")
}
/**
* generate metrics for one switch with the name given as parameter
* @returns {string}
*/
function generateMetricsForSwitch(string_id) {
const sw = Shelly.getComponentStatus(string_id);
return [
printPrometheusMetric("switch_power_watts", TYPE_GAUGE, [promLabel("switch", sw.id)], "Instant power consumption in watts", sw.apower),
printPrometheusMetric("switch_voltage_volts", TYPE_GAUGE, [promLabel("switch", sw.id)], "Instant voltage in volts", sw.voltage),
printPrometheusMetric("switch_current_amperes", TYPE_GAUGE, [promLabel("switch", sw.id)], "Instant current in amperes", sw.current),
printPrometheusMetric("switch_temperature_celsius", TYPE_GAUGE, [promLabel("switch", sw.id)], "temperature of the plug in celsius", sw.temperature.tC),
printPrometheusMetric("switch_power_total", TYPE_COUNTER, [promLabel("switch", sw.id)], "accumulated energy consumed in watts hours", sw.aenergy.total),
printPrometheusMetric("switch_output", TYPE_GAUGE, [promLabel("switch", sw.id)], "is switch (1)on or (0)off", sw.output ? 1 : 0)
].join("");
}
HTTPServer.registerEndpoint(url, httpServerHandler);
{{}}
Les lignes surlignées sont celles que j'ai modifié pour que ça fonctionne.
Pour choper ça il faut configurer telegraf
<summary>détail de la conf telegraf pour ça
{{}}
[[inputs.prometheus]]
urls = ["http://ip.de.la.prise1/script/1/metrics","http://ip.de.la.prise2/script/1/metrics","http://ip.de.la.prise3/script/1/metrics","http://ip.de.la.prise4/script/1/metrics"]
metric_version = 1
{{}}
Et hop yapuka partir du côté de Grafana !
Un premier panel pour voir la puissance délivrée par les ptites shellies.
{{}}
> SELECT mean("value") FROM "shelly_switch_power_watts" WHERE $timeFilter GROUP BY time($__interval), "name"::tag fill(null)
Vraiment c'est le genre de truc qui me faisait de l'œil depuis des années.
Le seul truc qui me titille c'est la colonne "Total" qui n'a aucune vraie signification.
Il faudrait faire un ptit calcul pour en extraire une quantité d'énergie exploitable (et notamment pouvoir en dériver le coût) mais je ne sais pas comment le faire 🤷.
Je vous ferai une micro-suite où je listerai quelques consos de différents appareils de la maison. :-)
[1] leur github (https://github.com/ALLTERCO/shelly-script-examples)
------------------------------------
------------------------------------
[10/12/2024] - #domotique #graph #monitoring
------------------------------------