Outils pour utilisateurs

Outils du site


domotique:retro-ingenierie_d_une_station_meteo

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
domotique:retro-ingenierie_d_une_station_meteo [03/08/2019 21:03] antoinevedomotique:retro-ingenierie_d_une_station_meteo [20/10/2019 15:22] (Version actuelle) – Alecto antoineve
Ligne 1: Ligne 1:
 +~~DISCUSSION~~
 +
 +{{ :domotique:station_meteo_header.jpg?nolink&400 |}}
 +
 ====== Rétro-ingénierie d'une station météo ====== ====== Rétro-ingénierie d'une station météo ======
  
-//TL;DR// : Le code est disponible sur GitHub : https://github.com/AntoineVe/Otio_WH510+Cette station météo, "Otio WH5100" est également connue sous le nom "Alecto ACH-2010"
 + 
 +//TL;DR// : Le code est disponible sur mon repo git : https://git.antoineve.me/Otio_WH5100/
  
 ===== Le signal radio ===== ===== Le signal radio =====
Ligne 47: Ligne 53:
  
 La ligne qui nous interresse ici est ''[00] {79} fe bc 64 74 bc 02 04 21 d4 f2''. Pour information, la station est à environ 20m du récepteur. La ligne qui nous interresse ici est ''[00] {79} fe bc 64 74 bc 02 04 21 d4 f2''. Pour information, la station est à environ 20m du récepteur.
 +
 +===== Analyse =====
 +
 +Vient ensuite le moment d'analyser cette suite hexadécimale.
 +Pour cela, j'ai laissé tourner l'enregistrement des données reçues (une redirection de la sortie de ''rtl_433'' vers un fichier).
 +J'ai ainsi pu voir quelles parties de la chaîne se modifiaient dans la journée.
 +Pour une mieux visualiser ces données, j'ai écrit un script python utilisant pylab et numpy.
 +Ce script prend en argument le fichier écrit par rtl_433 et affiche un graphique.
 +
 +<file python meteo_graph.py>
 +#!/bin/python3
 +
 +from sys import argv
 +from pylab import *
 +import numpy as np
 +
 +data_file = argv[1]
 +
 +
 +def col2array(dico, col):
 +    temp_liste = list()
 +    for line in dico:
 +        temp_liste.append(int(dico[line][col], 16))
 +    return(np.array(temp_liste))
 +
 +
 +with open(data_file, 'r') as data:
 +    data = data.read().splitlines()
 +data_dict = {}
 +count = 0
 +for line in data:
 +    if "[00] {79}" in line:
 +        data_dict.update({count: tuple(line.replace('[00] {79} ','').split())})
 +        count += 1
 +x = np.array(list(data_dict.keys()))
 +y0 = col2array(data_dict, 0)
 +y1 = col2array(data_dict, 1)
 +y2 = col2array(data_dict, 2)
 +y3 = col2array(data_dict, 3)
 +y4 = col2array(data_dict, 4)
 +y5 = col2array(data_dict, 5)
 +y6 = col2array(data_dict, 6)
 +y7 = col2array(data_dict, 7)
 +y8 = col2array(data_dict, 8)
 +y9 = col2array(data_dict, 9)
 +
 +figure(figsize=(21,10), dpi=100)
 +# plot(x, y0, label="col 0")
 +# plot(x, y1, label="col 1")
 +plot(x, y2, label="col 2 (temp)")
 +print('temp1 : ', y2)
 +plot(x, y3, label="col 3 (temp)")
 +print('temp2 : ', y3)
 +plot(x, y4, label="col 4 (hum)")
 +print('hum : ', y4)
 +plot(x, y5, label="col 5 (vent ?)")
 +plot(x, y6, label="col 6 (vent ?)")
 +plot(x, y7, label="col 7 (pluie ?)")
 +plot(x, y8, label="col 8 (?)")
 +# plot(x, y9, label="col 9")
 +legend(loc='best')
 +# savefig("/tmp/meteo-graph.svg", dpi=100)
 +show()
 +close('all')
 +</file>
 +
 +{{ :domotique:meteograph.png?nolink&720 |Graphique représentant les données reçues}}
 +
 +J'ai ensuite rapproché les relevés bruts avec les données affichées sur le terminal météo fourni.
 +Après avoir un peu tâtonné, l'analyse tend vers une relation linéaire entre données reçues et données affichées.
 +Pour cette analyse numérique, je vais faire de la [[https://fr.wikipedia.org/wiki/R%C3%A9gression_lin%C3%A9aire | régression linéaire]].
 +
 +Avec python, c'est la fonction //linregress// du module //stats// de //scipy// qui va me servir.
 +
 +<code>
 +linregress(x, y=None)
 +    Calculate a linear least-squares regression for two sets of measurements.
 +
 +    Parameters
 +    ----------
 +    x, y : array_like
 +        Two sets of measurements.  Both arrays should have the same length.
 +        If only x is given (and y=None), then it must be a two-dimensional
 +        array where one dimension has length 2.  The two sets of measurements
 +        are then found by splitting the array along the length-2 dimension.
 +
 +    Returns
 +    -------
 +    slope : float
 +        slope of the regression line
 +    intercept : float
 +        intercept of the regression line
 +    rvalue : float
 +       correlation coefficient
 +    pvalue : float
 +        two-sided p-value for a hypothesis test whose null hypothesis is
 +        that the slope is zero.
 +    stderr : float
 +        Standard error of the estimated gradient.
 +</code>
 +
 +==== Température ====
 +Pour la température, j'ai pu déduire que c'était sur les octets 3 et 4 que l'information était codée
 +(e.g. ''[00] {79} fe bc **64 74** bc 02 04 21 d4 f2'', ici //0x64// et //0x74//).
 +L'information est donc codée sur 4 octets.
 +
 +Régression linéaire :
 +
 +<code pycon>
 +>>> from scipy import stats
 +>>> raw_data = [25776, 25780, 25790, 25794, 25796, 25798]
 +>>> real_data = [20, 20.2, 20.7, 20.9, 21, 21.1]
 +>>> a, b, r, p, std_err = stats.linregress(raw_data, real_data)
 +>>> a,b
 +(0.050000000000000031, -1268.8000000000006)
 +>>> std_err
 +0.0
 +>>>
 +</code>
 +
 +On a donc : f(x) = 0,05 * x − 1268,8. Avec une erreur standard nulle, ce qui signifie que la formule « tombe juste »
 +(Si on trace la courbe à partir de la formule, elle passe parfaitement par tous les points relevés).
 +
 +En python :
 +
 +<code pycon>
 +>>> def temp(data):
 +...  return round((0.05*data-1268.8), 1)
 +...
 +>>> temp(0x6474)
 +17.0
 +</code>
 +
 +J'ai également remarqué que l'état des piles modifie l'offset de la formule, j'ai donc ajouté un dictionnaire
 +permettant d'intervertir les valeurs quant c'est nécessaire.
 +
 +==== Humidité ====
 +
 +Pour l'humidité, j'ai identifié les changements sur le 5ème octet.
 +On voit bien sur la courbe des relevés la relation qu'ont la température et l'humidité.
 +L'analyse numérique donne a = 0,5 et b = 0, avec également une erreur standard nulle.
 +La relation est donc très simple : y = 0.5 * x.
 +C'est à dire que la valeur de l'humidité est la donnée (convertie en entier décimal) divisée par deux.
 +
 +En python :
 +
 +<code pycon>
 +   >>> def hum(data):
 +   ...  return int(round(data/2))
 +   ...
 +   >>> hum(0xbc)
 +   94
 +</code>
 +
 +==== Anémomètre ====
 +
 +La station météo renvoie deux paramètres pour le vent : une moyenne (ou médiane ?)
 +et le maximum (ce qui correspond aux rafales).
 +Ceux-ci sont portés sur les octets 6 et 7.
 +L'analyse numérique :
 +
 +<code pycon>
 +>>> from scipy import stats
 +>>> raw = [0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x1c, 0x22, 0x24, 0x28, 0x2e, 0x50, 0x5a]
 +>>> read = [0, 1.1, 2.5, 3.6, 5.0, 6.1, 7.2, 8.6, 9.7, 11.2, 12.2, 13.3, 17.3, 20.9, 22, 24.5, 28.1, 49, 55.1]
 +>>> a, b, r, p, std_err = stats.linregress(raw, read)
 +>>> a, b
 +(0.61264343715451175, -0.018142655636458116)
 +>>> std_err
 +0.0009343564466014815
 +>>>
 +</code>
 +
 +J'ai essayé avec les différentes configuration : km/h, mph, knots (nœuds), ...
 +Jamais d'erreur standard nulle mais c'est avec les km/h qu'elle est le plus faible (0.00093).
 +Certainement une erreur à cause d'arrondis.
 +
 +La fonction est donc :
 +
 +<code pycon>
 +>>> def wind(data):
 +...  return round((0.61264343715451175*data-0.018142655636458116),1)
 +...
 +>>> wind(0x5a)
 +55.1
 +</code>
 +
 +==== Pluviomètre ====
 +Pour le pluviomètre, il n'y a pas de formule. C'est un compteur : à chaque fois qu'il s'incrémente de 2 unités,
 +il faut ajouter 0,3 mm de pluie. La donnée est codée sur 1 octet, le 8ème de la transmission, et donc //overflow// à //0xFF//.
 +Il faut pouvoir gérer cette situation. Pour plus de facilité, je stocke la valeur dans un fichier temporaire.
 +
 +<code python>
 +def rain(raw):
 +     current = int(raw)
 +     try:
 +         with open("/tmp/old_rain", "r") as tmp:
 +             old_rain = int(tmp.read())
 +     except FileNotFoundError:
 +         old_rain = None
 +     except Exception as err:
 +         logging.error("Error : " + str(err))
 +         return int(0)
 +     if old_rain is None:
 +         with open("/tmp/old_rain", "w") as tmp:
 +             tmp.write(str(current))
 +         return int(0)
 +     if current > old_rain:
 +         res = round(float(current-old_rain)*0.3, 1)
 +         with open("/tmp/old_rain", "w") as tmp:
 +             tmp.write(str(current))
 +     else:
 +         if (current+255)-old_rain < 254:
 +             res = round(float((current+255)-old_rain)*0.3, 1)
 +             with open("/tmp/old_rain", "w") as tmp:
 +                 tmp.write(str(current))
 +         else:
 +             res = 0
 +     return float(res)
 +</code>
domotique/retro-ingenierie_d_une_station_meteo.1564866233.txt · Dernière modification : 03/08/2019 21:03 de antoineve