Synology et secours à chaud

J'utilise plusieurs gros NAS Synology dont certains dans un data center. De fait pas facilement accessibles, même si chez Scaleway il existe un support de proximité ou l'on peut demander à deux heures du matin au technicien d'astreinte d'aller redémarrer une machine, débrancher un câble ou sortir un disque...

En pleine nuit je me suis retrouvé avec un RS2414RP+ injoignable. Moment d'angoisse assuré. Je demande donc au technicien de le redémarrer. Et s'il a bien démarré, c'est avec un disque en panne. C'est probablement ce redémarrage sauvage qui a provoqué cette panne sur un disque qui devait déjà être fatigué. Quant à savoir pourquoi ce NAS qui tourne depuis des années s'est planté, mystère, les logs étant peu loquaces

Sur ce NAS il y a 12 disques en RAID SHR répartis dans deux groupes de stockage. Chaque groupe utilise 5 disques (4 + 1 en parité) et sur l'ensemble je dispose de 2 disques de secours à chaud (hotspare).

Je pensais naïvement qu'en cas de panne, un des disques configurés en secours à chaud allait prendre automatiquement le relais comme cela se fait sur certains serveurs. Mais il n'en était rien. J'ai alors pensé que ce relais se ferait quand la longue vérification des volumes serait terminée, mais niet.

Le stress montant j'ai alors commencé à fouiller le net sans pour autant trouver l'information idoine. En fait la seule information trouvée étant l'explication d'un utilisateur à qui le support Synology aurait répondu que pour qu'un disque de secours prenne le relais, il faut retirer physiquement le disque du NAS. Peu pratique à distance, même si j'ai pensé un temps demander au technicien d'astreinte de le faire pour moi.

En parcourant les menus j'ai remarqué que l'on pouvait désactiver un disque et au fond d'un forum un utilisateur explique que c'est la procédure pour retirer un disque proprement. Je me suis donc dit que si le disque était désactivé logiquement, le disque de secours devrait prendre le relais. J'ai donc tenté, et c'est visiblement ce qu'il convient de faire car un de mes disques de secours a alors été affecté automatiquement à mon groupe de stockage et la reconstruction est en cours.

Synology a très certainement de bonnes raisons de faire ainsi, mais je trouve leur documentation un peu légère sur ce point et quelques lignes d'explications plus claires m'auraient permises de gagner un peu de temps et de moins stresser. Avoir le choix entre cette façon d efaire et un mode automatique serait bien sur souhaitable ! J'espère donc que ces quelques lignes en aideront certains.

 

Home Assistant & Thermostats

Sous Home Assistant le thermostat de base (climate:) est plutôt "basic", et c'est le moins que l'on puisse dire ! Fort heureusement il y a des personnes géniales qui savent coder et se sont attelées à le modifier.

Simple Thermostat

Ce premier fork reprend simplement le thermostat de base en y ajoutant les "presets" que tout le monde attendait. En gros il y a deux façons d'utiliser un thermostat, soit on modifie dynamiquement la valeur de consigne avec des automations, le Scheduler ou Schedy et c'est ce que je fais, soit on joue la carte des presets à la française avec des modes ECO, CONFORT, Etc.. Et c'est ce que fait Simple Thermostat de façon très simple. Par exemple si on se positionne en BOOST et que l'on lui indique une consigne de 22°, il la gardera en mémoire. Ensuite on pourra rappeler le preset avec une automation

service: climate.set_preset_mode
target:
  entity_id: climate.thermostat_lionel
data:
  preset_mode: boost

Ou encore via l'interface :

Smart Thermostat

On est ici face à un thermostat intelligent (j'ai pas non plus dit AI !) qui se base sur un contrôleur PID afin de déterminer la période pendant laquelle le convecteur devra être ON afin de profiter de l'inertie de celui ci. Le capteur cible mesure la température ambiante tandis que l'interrupteur contrôle un système de chauffage ON/OFF. Le contrôleur PID calcule la durée pendant laquelle le convecteur doit rester allumé pendant la période PWM pour atteindre le point de consigne, par exemple avec PWM réglé sur 15 minutes, si la sortie est de 100 %, le réchauffeur restera allumé pendant les 15 prochaines minutes. Si la sortie PID est de 33 %, le réchauffeur sera allumé pendant 5 minutes seulement.

Vous n'avez pas tout compris ? J'avoue que moi non plus. Mais le fait est que ça fonctionne, et d'ailleurs pour preuve en voici la formule :

Plus sérieusement, ce système sera plus particulièrement efficace avec des convecteurs (ou un autre système de chauffage) avec une forte inertie. Donc exit les convecteurs du genre grille pain...

Pour ceux qui aimerait en savoir plus je vous conseille la lecture du projet ou le mainteneur nous donne pas mal d'explications ainsi que quelques liens :

Ensuite voici la configuration type. On notera qu'ici l'idée des presets est reprise, mais qu'il faut les figer dans la configuration alors qu'ils sont dynamiques dans Simple Thermostat (Adrien si tu nous suit..).

climate:
  - platform: smart_thermostat
    name: "Thermostat : Lionel"
    heater: switch.sw01_lionel
    target_sensor: sensor.mi_lionel_temp
    min_temp: 10
    max_temp: 26
    ac_mode: False
    target_temp: 19
    keep_alive:
      seconds: 60
    precision: 0.1
    away_temp: 14
    eco_temp: 18
    boost_temp: 22
    comfort_temp: 20
    home_temp: 21
    sleep_temp: 17
    activity_temp: 20
    kp : 75
    ki : 0.001
    kd : 70000
    pwm : 00:15:00

Bien sur ces réglages sont à affiner en fonction du convecteur et de la pièce à chauffer. Pour cela il existe un mode AutoTune :

    autotune: "ciancone-marlin"

Dixit son auteur ce mode n'est pas encore tout à fait sur. J'y reviendrait donc plus tard au fil de mes tests.

En attendant on peu constater que là ou le convecteur aurait été ON jusqu'à attendre la température de consigne avec le thermostat standard (la première ligne), il y a eu plusieurs interruptions avec ce thermostat (la seconde ligne). Et  vous l'aurez compris, plus les interruptions sont importantes, moins la consommation le sera, et la facture d'autant plus réduite.

Multizone Thermostat

Il existe une autre variante qui intègre ces différents modes et bien qu'utilisable par zones indépendantes permet en plus d'ajuster la température voulue sur plusieurs zones adjacentes. Je vous laisse lire la description compète sur son Git, je l'ai quand à moi installé dans mon bureau afin d'essayer de déterminer que mode sera le plus adapté entre cout et confort. et je verrais dans quelques jours si le résultat est probant.

Il propose plusieurs modes de fonctionnement :

  • ON/OFF : proche du thermostat de base mais en prenant compte des valeurs d'hystérésis. Dans ce mode il peut également gérer les modes chauffage et refroidissement.
  • Proportionnel : PID avec possibilité de compensation météo et gestion de plusieurs valves. Les possibilité sont énormes mais la mise au point sera délicate.

Conclusion

Ces thermostats évolués sont très intéressants sur le papier et remplacent avantageusement le thermostat de base en fonction des besoins. Par contre ils restent encore complexes à mettre au point et il faudra peut être tout l'hiver pour en tirer des conclusions...

J'y reviendrais dans les prochains jours, mais ces deux approches constituent une avancée importante pour Home Assistant et j'espère que tout cela sera un jour intégré au Core.

Echanger

Voici deux fils ou échanger sur ce sujet :

 

Augmenter la puissance d'un Shelly RGBW2

Voilà un sujet qui me fait galérer depuis un un bon moment. J'ai des luminaires équipés de 4 lampes halogène G53 en 12 V / 50 W. Ces lampes sont alimentées par deux transformateurs intégrés au luminaire. Ils étaient commandés par un variateur Legrand qui accepte la charge de 200 W sur transformateur et pour le second un Fibaro Dimmer 2 non connecté à Home Assistant car je n'ai rien en ZWave. Le Fibaro supporte les 200 W, pas le dimmer Shelly.

Alors vous allez me dire qu'à notre époque on remplace les halogènes par des leds. Oui bien sur, et j'ai essayé. Le résultat est vraiment très moche, et même avec de de lampes led en G53 le rendu est très désagréable dès lors que l'on diminue la luminosité, quant à la directivité très inégale. Par exemple des lampes led G53 Philips données pour un faisceau de 24° débordent totalement... A la limite ces lampes sont exploitables en mode full dans une vitrine, mais absolument pas pour créer une ambiance. Et comme dans mon cas elles ne sont pas souvent allumées, le gain d'économie ne fait pas partie de mes préoccupations. J'ai toutefois testé et je vais vous décrire les écueils d'installation.

Remplacement de 4 G53 de 50 W par 4 G53 Led de 11 W

On pourrait penser qu'il suffit de remplacer le dimmer Legrand par un Dimmer 2 Shelly et de remplacer les lampes.

Trop simple, il va également falloir remplacer les deux transformateurs d'origine AC 220/12 V qui alimentent chacun deux lampes par un transformateur adapté aux lampes led. On m'a conseillé le HTM 70/230 de chez Osram, mais il en existe plein d'autres. Une fois celui-ci installé ça semble fonctionner, mais il est impossible de faire l'étalonnage, la variation n'est pas linéaire et à certains niveaux ça scintille, c'est donc inexploitable. Et là on commence à fatiguer...

En fait il y a une solution qu'un collègue a trouvée au fond d'un forum, peu orthodoxe certes, il faut rétrograder le firmware du Dimmer2 en 1.8.3. Et là miracle, ça fonctionne, on peut à peu près étalonner et la variation est linéaire. Attention toutefois à fixer un seuil minimum dans la configuration sans quoi si on baisse trop la luminosité la lampe ne se rallumera pas. Il est toutefois étonnant que Shelly n'ait toujours pas apporté de solution durable.

Bref, c'est exploitable, mais comme je le disait plus haut, c'est vraiment très moche à faible luminosité, en gros pendre une ampoule nue au plafond donnerait le même résultat..

Alternative...

Je vais donc réinstaller les G53 halogène d'origine et essayer de les faire varier, et que cela soit possible via la domotique afin de créer des scènes. 

Si j'avais 3 fils au plafond j'aurais installé deux Dimmer2 Shelly, c'est ce que j'ai fait dans une autre pièce avec les deux Dimmer Shelly sous les interrupteurs de type poussoir et ça fonctionne, un peu poussivement à l'allumage, mais de de façon autonome (j'y tiens et c'est un principe de base), mais aussi via Home Assistant. Sauf que dans le cas qui me préoccupe je n'ai pas de neutre sous l'interrupteur (d'où le Dimmer2).

Entre temps François Bergeret (Quintium, le distributeur Shelley pour la France), qui est toujours de bon conseil dans cet environnement, m'a suggéré d'utiliser un Shelly RGBW2. En fait je ne savais pas que le RGBW2 a un mode qui permet en fait d'obtenir 4 dimmers indépendants. On va toutefois se retrouver face à deux obstacles :

  1. Le RGBW2 s'alimente en DC, il va falloir utiliser un transformateur 220AC/12VDC supportant plus de 200 W et suffisamment petit pour s'insérer dans le luminaire.
  2. Le RGBW2 supporte bien 288 W mais en 24 V. Donc en 12 V ce sera 144 W, ce qui sera insuffisant pour nos 4 lampes de 50 W. J'avais bien pensé à un bricolage consistant à mettre les lampes en série de deux afin d'exploiter le tout en 24 V, mais la charge reposerait uniquement sur deux sorties du RGBW2, et je pense que ce n'est pas fait pour...

Et là Francois m'a suggéré d'utiliser un amplificateur. Je ne connaissait pas non plus cette technologie qui semble très utilisée dans le monde des bandes led. Cet amplificateur traite en entrée 4 signaux issus du driver led (ici le RGBW2) s'alimente séparément et ressort le signal à la puissance souhaitée.

J'ai donc commandé dans la nuit un modèle suffisamment petit pour pouvoir d'insérer dans le luminaire et suffisamment puissant pour alimenter les 4 G53 de 50 W. Et par magie Amazon me l'a livré avant midi !

  • Tension d'entrée : DC12-24V
  • Sortie : 4 circuits, courant de charge maximal: 6A pour chaque canal.
  • Puissance totale de sortie : 24A.
  • Puissance de sortie : max 288 W (12 V), max 576 W (24 V)

Il en existe une pléthore de modèles, à vous de chercher (par principe je ne mets pas de liens (et encore moins sponsorisés) vers les gros sites de vente sur ce blog). 

J'ai donc rapidement fait le test en me servant d'une grosse alimentation 12 V DC. Il faudra que je trouve une alimentation de plus petite taille pour l'insérer dans le luminaire. Le résultat est parfait, que ce soit avec des lampes halogène de 35 W ou 50 W, ou encore avec des lampes led de 6.5 W ou 11 W. 

Et cerise sur le gâteau je vais disposer de 4 dimmers séparés ce qui est un vrai plus pour créer une ambiance. Autre avantage, la puissance utilisée sur le RGBW2 est minime (0.26 W), ça devrait potentiellement augmenter sa durée de vie, mais aussi permettre au besoin son installation plus loin des lampes (limitation de base sur la longueur des câbles en 12 V).

Et l'interrupteur mural dans tout ça ?

Je tiens à ce que tout éclairage fixe soit actionnable par son interrupteur à son emplacement d'origine. Avantage ça fonctionne sans domotique et n'importe qui peut l'utiliser. Et si on cède le logement on peut même tout laisser en place (un petit dossier technique sera bien sur un plus pour l'acquéreur...).

Il y a plusieurs solutions possibles :

  1. Une télécommande gérée par Home Assistant (Xiaomi, Ikea en Zigbee ou un i3) = pas de domotique, pas de lumière.
  2. Utiliser un Shelly i3 pour interagir directement avec le RGBW3 = pas de WI-FI, pas de lumière.
  3. Un simple interrupteur ON/OFF. Ca tombe bien car je n'ai pas de neutre, mais en configurant le RGBW2 de façon idoine les lampes s'allumeront par défaut en actionnant cet interrupteur.

Oui, mais...

Bien sur c'était trop beau ! En fait sur le montage final je me suis aperçu que les lampes G53 sifflent. Avec ou sans l'ampli. Différemment selon les alims que j'utilise et selon la charge. Ces alimentations ne sont surement pas d'excellente qualité et de plus le form factor est important. Caser une alim qui chauffe (toutes les alims DC chauffent) dans le socle n'est pas une bonne idée, et à l'extérieur la seule que j'ai trouvée qui soit acceptable provoque un sifflement qui rend l'usage impossible.

Tout ça pour ça !

Me voici donc à la case départ à devoir éliminer la solution RGBW2 + Ampli.

Je suis donc repartit sur un Dimmer2. Avec les deux transformateurs AC 105W d'origine ça ne tient pas. Avec un seul de ces transformateur et deux ou trois lampes oui. Pas logique, je me suis dit qu'il était surdimensionné. J'ai donc commandé un transformateur AC220V/AC12V vendu pour une charge de 210W, avec 4 lampes de 50W il n'est pas possible d'étalonner, par contre avec 3 lampes oui avec une conso affichée de 130W. Et si ensuite on ajoute la quatrième lampe ça fonctionne très bien et la conso de 160W. Il y a tout de même des choses qu'il me faudra expliquer...

Home Assistant & Schedy, encore...

L'hiver approche et on ouvre à nouveau ce sujet...

Pour mémoire, afin de planifier un peu finement pièce par pièce un chauffage électrique par convecteurs, on a principalement trois possibilités dans Home Assistant :

  • Faire une multitude de d'automations en YAML, j'ai fait lors de mes débuts avec Home Assistant et j'en ai parlé ici et .
  • Utiliser le Scheduler de Niels, j'en ai parlé ici et c'est une bonne solution GUI, mais il y a des lacunes (pas de replanification à la volée, pas de gestion ECO entre les plages).
  • Et enfin Schedy dont j'ai parlé plusieurs fois ( 1 | 2 ), qui lui ne dispose pas d'interface. On va donc s'en servir de moteur et lui construire une interface Lovelace. En fait j'ai géré ma clim avec tout l'été sur la base de ce que j'avais fait et c'est parfait. Je vais maintenant l'adapter afin d'en faire profiter mon frère, avec d'autres contraintes et le souhait qu'il soit le plus autonome possible.

Cahier des charges

On veut pouvoir gérer :

  • 4 plages d'horaires par jour pour 3 types de journées :
    1. Jour de semaine travaillé.
    2. Samedi matin travaillé.
    3. Dimanche, jours fériés et vacances à la maison...
  • Activation / désactivation possible de chacune des plages et faire en sorte qu'elles ne soient affichées que si elles sont actives. Le but étant de ne pas afficher les plages inutiles pour ne pas prêter à confusion. Par exemple, le dimanche si on chauffe le séjour de 10:00 à minuit, on a besoin que d'une seule plage.
  • Une température différente pour chacune des plages, en opposition à un classique ECO/CONFORT à la française. 
  • Une température ECO commune à toutes les périodes entre les plages; mais propre à chaque pièce et une température ABSCENT (HG).
  • La présence de l'occupant de la pièce (en entrant ses dates d'arrivée et de départ ou par géolocalisation, présence dans un rayon de...). Cette fonction ne sera utile que pour les chambres des enfants qui vont et viennent...
  • La planification des vacances de l'occupant principal, avec passage en mode week-end si présent.
  • Boost dynamique : température t pendant n minutes. Utile pour une dérogation temporaire, genre un occupant ne va pas travailler un lundi après-midi, il serait dommage qu'il se gèle...
  • Désactivation en cas d'ouverture d'une fenêtre pendant n minutes. Inutile de chauffer quand on aère les pièces...
  • Un mode télétravail, je ne l'ai pas encore vraiment défini, mais il s'agira probablement d'un script qui va apporter quelques dérogation sur le principe du boost.
  • Un mode dormir (utile pour passer en ECO les pièce de vie quand on va se coucher plus tôt que ce qui est programmé, s'insère dans une automation plus globale qui désactive également les éclairages).
  • Un mode absence temporaire (on ne passe pas le chauffage en mode ABSCENT HG comme une absence de longue durée, mais en mode ECO (binary sur géo loc ou via les fonction de l'alarme).

Philosophie

Toutes les plages sont gérés par des binary_sensor via différentes sources (input_datetime, input_number, input_boolean, et d'autres binary_sensor...) et Schedy ne fera qu'affecter la température de consigne correspondant à la plage au thermostat (climate:) quand le binary passera à ON. Il nous faut donc 1 binary par plage, ce qui fait 3 groupes de 4 = 12 pour une pièce. Ces binary constituent la partie centrale, ils risquent donc d'évoluer et je publierait les mise à jour sur GitHub. Ne cherchez pas à recopier ces bouts de code, il ne sont là que pour expliquer la philosophie et proviennent de plusieurs tests, vous trouverez mes sources sur GitHub. et ici pour un second projet plus complet. (je vais essayer de les tenir à jour).

Si pour gérer ces binary_sensor les variables que sont les input_boolean sont simple, il faut préparer les binary_sensor qui vont nous servir à déterminer le type de journée :

binary_sensor:
  - platform: template
    sensors:
      workday_saturday_working:
        friendly_name: "Samedi travaillé"
        value_template: >
          {% if now().isoweekday() in (6,) and states.binary_sensor.workday_sensor.state == 'on' and states.input_boolean.holidays_andre.state == 'off' %}
            true
          {%else%}
            false
          {% endif %}

      workday_weekday_working:
        friendly_name: "Jour de semaine travaillé"
        value_template: >
          {% if now().isoweekday() in (1,2,3,4,5,) and states.binary_sensor.workday_sensor.state == 'on' and states.input_boolean.holidays_andre.state == 'off'%}
            true
          {%else%}
            false
          {% endif %}

      workday_weekend_holidays:
        friendly_name: "Week-End, Vacances ou jour Férié"
        value_template: >
          {% if states.binary_sensor.workday_sensor.state == 'off' or states.input_boolean.holidays_andre.state == 'on'%}
            true
          {%else%}
            false
          {% endif %}

Mais à y réfléchir on peut envisager quelque chose de plus simple avec un template sensor :

template:
  - sensor:
      - name: "Day Type"
        state: >
          {% if now().isoweekday() in (6,) and states.binary_sensor.workday_sensor.state == 'on' and states.input_boolean.holidays_andre.state == 'off' %}
            saturday_working
          {% elif now().isoweekday() in (1,2,3,4,5,) and states.binary_sensor.workday_sensor.state == 'on' and states.input_boolean.holidays_andre.state == 'off'%}
            weekday_working
          {% elif states.binary_sensor.workday_sensor.state == 'off' or states.input_boolean.holidays_andre.state == 'on'%}
            weekend_and_holidays
          {% else %}
            failed
          {% endif %}

Ce qui va nous donner :

    binary_sensor:
      heating_antoine_2_s:
        entity_id: sensor.time    <<< IL FAUT SUPPRIMER CETTE LIGNE DESORMAIS OBSOLETE
        friendly_name: Mode Confort
        value_template: >
          {% set t = states('sensor.time') %}
          {% set start = states('input_datetime.heating_antoine_start_2_s') [0:5] %}
          {% set stop = states('input_datetime.heating_antoine_end_2_s') [0:5] %}
          {{ (start <= t < stop if start < stop else (start <= t or t < stop)) == true   # Slot horaire
            and states('sensor.day_type') == 'saturday_working'                          # Type de journée
            and states('input_boolean.presence_antoine') == 'on'                         # Présence de l'occupant
            and states('input_boolean.heating_antoine_enabled_2_s') == 'on'              # Activation de la plage    
          }}

Ce qui va se traduire dans Schedy par :

  - x: state("input_number.heating_antoine_temperature_confort_2_s") if (is_on("binary_sensor.heating_antoine_2_s")) else Next()

Et bien sur dans Schedy on va gérer d'autres contraintes plus globales :

  - x: "Break() if is_off('input_boolean.thermostats_on_off') else Next()"    # Etat général du chauffage
  - x: "Break() if is_on('binary_sensor.antoine_window_delayed') else Next()" # Fenêtre ouverte

Ainsi que le mode ECO si aucune plage confort n'est active : 

  - x: state("input_number.heating_antoine_temperature_eco") if (is_on("input_boolean.presence_antoine")) else Next()

Et ABSCENT si les contraintes générales son validées :

  - x: state("input_number.heating_antoine_temperature_away")

Et il ne faut pas oublier l'entête, avec deux types de thermostats. A noter que dans le cas du climatiseur on va gérer deux modes HVAC : off et heat_cool.

schedy_heating:
  module: hass_apps_loader
  class: SchedyApp

  actor_type: thermostat
  actor_templates:
    ac:
      # send_retry_interval: 15
      send_retries: 20
      supports_hvac_modes: true 
      # off_temp: 17
      # delta: 0
      hvac_mode_off: "off"
      hvac_mode_on: heat_cool  # J'ai demandé à pouvoir gérer ce mode via un input_select: mais le développeur ne semble pas très chaud...
      # max_temp: 95
      # min_temp: 45

    convecteur:
      send_retry_interval: 30
      send_retries: 10
      supports_hvac_modes: true

Exemples

Et voici la programmation d'une chambre avec un convecteur :

      schedule:
      - rules:
        - rules:
          # CONTRAINTES
          - x: "Break() if is_off('binary_sensor.marie_home') else Next()"          # Absence temporaire, on passe en ECO
          - x: "Break() if is_off('input_boolean.thermostats_on_off') else Next()"  # ON / OFF global du chauffage, on passe le thermostat à OFF
          - x: "Break() if is_on('input_boolean.thermostats_away') else Next()"     # Absence globale prolongée (on ferme la maison), on passe en consigne AWAY
          - x: "Break() if is_on('binary_sensor.marie_window_delayed') else Next()" # Contrainte ouverture de fenêtre, on passe en consigne AWAY
          # CONFORT SEMAINE
          - x: state("input_number.heating_marie_temperature_confort_1") if (is_on("binary_sensor.heating_marie_1")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_2") if (is_on("binary_sensor.heating_marie_2")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_3") if (is_on("binary_sensor.heating_marie_3")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_4") if (is_on("binary_sensor.heating_marie_4")) else Next()
          # CONFORT SAMEDI
          - x: state("input_number.heating_marie_temperature_confort_1_s") if (is_on("binary_sensor.heating_marie_1_s")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_2_s") if (is_on("binary_sensor.heating_marie_2_s")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_3_s") if (is_on("binary_sensor.heating_marie_3_s")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_4_s") if (is_on("binary_sensor.heating_marie_4_s")) else Next()
          # CONFORT DIMANCHE & FERIE
          - x: state("input_number.heating_marie_temperature_confort_1_d") if (is_on("binary_sensor.heating_marie_1_d")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_2_d") if (is_on("binary_sensor.heating_marie_2_d")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_3_d") if (is_on("binary_sensor.heating_marie_3_d")) else Next()
          - x: state("input_number.heating_marie_temperature_confort_4_d") if (is_on("binary_sensor.heating_marie_4_d")) else Next()
          # ECO
          - x: state("input_number.heating_marie_temperature_eco") if (is_on("input_boolean.presence_marie")) else Next()
          - x: "Break(2)"
      # REGLES LIES AUX CONTRAINTES (Attention à l'ordre des règles)
      - x: Mark(OFF, Mark.OVERLAY) if (is_off("input_boolean.thermostats_on_off")) else Next()
      - x: state("input_number.heating_marie_temperature_away") if (is_on("input_boolean.thermostats_away")) or (is_on('binary_sensor.marie_window_delayed')) else Next()
      - x: state("input_number.heating_marie_temperature_eco") if (is_on("input_boolean.presence_marie")) and (is_off("binary_sensor.marie_home")) else Next()

La contrainte fenêtre ouverte est gérée par un binary_sensor: avec une temporisation :

binary_sensor:
  - platform: template
    sensors:
      marie_window_delayed:
        friendly_name: "Fenêtre (delayed)"
        device_class: window
        #window_room: bedroom
        delay_on: 
          seconds: 300
        delay_off:
          seconds: 300
        value_template: >-
          {{ is_state('binary_sensor.fenetre_marie', 'on') }}
        icon_template: >-
          {% if is_state('binary_sensor.fenetre_marie', 'on') %}
            mdi:window-open
          {% else %}
            mdi:window-closed
          {% endif %}  

Quant à l'absence temporaire on va la gérer avec l'intégration proximity: qui va facilement nous donner la distance entre la maison et ma fille, ainsi et surtout sa direction. L'idée est de passer en ECO lorsque ma fille est ici pour quelques jours mais qu'elle est de sortie. Il me faudra temporiser la sortie afin de ne pas passer en ECO trop rapidement (genre elle va chercher du pain...), mais également le retour afin que sa chambre soit chauffée lors du retour effectif... 

- id: '5e1b6bdd-dee7-4a63-b8cb-3b9d01719b2b'
  alias: GEO - Lionel en depart
  mode: single
  trigger:
  - platform: numeric_state
    entity_id:
      - proximity.lionel
    above: 3
  condition:
  - condition: and
    conditions:
    - condition: template
      value_template: '{{ trigger.to_state.attributes.dir_of_travel  == "away_from" }}'
  action:
  - service: input_boolean.turn_off
    target:
      entity_id: input_boolean.presence_lionel_geo
  - service: notify.slack_hass_canaletto
    data:
      message: "{{ states.sensor.date_time.state}} > Lionel s'éloigne de sa maison | Distance : {{ states.proximity.lionel.state }} Km."

Et le même en approche. On bascule un input_boolean: que l'on pourra basculer manuellement en cas de défaillance...

- id: 'a6bbcd6c-8c18-4ea5-8992-8dae0d63c24c'
  alias: GEO - Lionel en approche
  mode: single
  trigger:
  - platform: numeric_state
    entity_id:
      - proximity.lionel
    below: 3
  condition:
  - condition: and
    conditions:
    - condition: template
      value_template: '{{ trigger.to_state.attributes.dir_of_travel  == "towards" }}'
  action:
  - service: input_boolean.turn_on
    target:
      entity_id: input_boolean.presence_lionel_geo
  - service: notify.slack_hass_canaletto
    data:
      message: "{{ states.sensor.date_time.state}} > Lionel s'approche de sa maison | Distance : {{ states.proximity.lionel.state }} Km."

Et pour plus d'élégance et pour ne pas afficher le boolean dans le Lovelace de l'utilisateur on en fait un binary (pour les test le boolean reste plus pratique que d'aller faire un tour en voiture...) :

binary_sensor:
  - platform: template
    sensors:
       lionel_geo:
         friendly_name: Lionel (Présence relative sur Géoloc)
         device_class: presence
         icon_template: >-
           {% if is_state('binary_sensor.antoine_away_from_home','on') %} mdi:home-account
           {% else %} mdi:home-outline
           {% endif %}
         value_template: >-
           {{ is_state('input_boolean.presence_lionel_geo', 'on') }}

Un autre exemple est celui du climatiseur. Dans mon cas il est dans un large couloir et arrose plus ou moins toutes les pièces. C'est bien sur à adapter à la région, l'installation et au mode de vie, mais dans mon cas on va interagir différemment selon la saison .Afin de ne pas tout dupliquer il sera nécessaire d'adapter les consignes en début d'été et en début d'hiver.

  • En été on va avoir des périodes de refroidissement et des périodes ou le climatiseur sera OFF.
  • En hiver on va avoir des périodes CONFORT et des périodes ECO. 

En fait Schedy est fait pour faire fonctionner les climatiseurs en mode Heat/Cool. Ca ne me convient pas car en mode demie saison parfois il se mets en route alors que c'est inutile. J'ai demandé au développeur de faire des modifications, mais ce n'est pas sa préoccupation du moment. J'ai donc dupliqué deux rooms pour lui en faisant de sorte que seule l'une puisse fonctionner. C'est un pis aller en attendant mieux. Ca génère une erreur dans le log, mais elle n'est qu'indicative.

    ac_heat:
      allow_manual_changes: true
      rescheduling_delay: 1
      actors:
        climate.daikin:
          send_retries: 20
          supports_hvac_modes: true
          hvac_mode_off: "off"
          hvac_mode_on: heat
      watched_entities:
      - input_number.heating_ac_temperature_confort_1
      - input_number.heating_ac_temperature_confort_2
      - input_number.heating_ac_temperature_confort_3
      - input_number.heating_ac_temperature_confort_4
      - input_number.heating_ac_temperature_confort_1_d
      - input_number.heating_ac_temperature_confort_2_d
      - input_number.heating_ac_temperature_confort_3_d
      - input_number.heating_ac_temperature_confort_4_d
      - input_number.heating_ac_temperature_eco
      - input_number.heating_ac_temperature_away
      - binary_sensor.heating_ac_1
      - binary_sensor.heating_ac_2
      - binary_sensor.heating_ac_3
      - binary_sensor.heating_ac_4
      - binary_sensor.heating_ac_1_d
      - binary_sensor.heating_ac_2_d
      - binary_sensor.heating_ac_3_d
      - binary_sensor.heating_ac_4_d
      - input_boolean.presence_ac
      - binary_sensor.life_windows_and_doors_delayed
      - input_boolean.heating_enabled
      - input_boolean.cooling_enabled
      - input_boolean.thermostats_ac_on_off

      schedule:
      - rules:
        - x: "Break() if is_off('input_boolean.thermostats_ac_on_off') else Next()"         # Etat général du chauffage
        - x: "Abort() if is_off('input_boolean.heating_enabled') else Next()"               # On stop toute planification si on n'est pas dans ce mode
        - rules:
          # CONTRAINTES
          - x: "Break() if is_off('input_boolean.presence_ac') else Next()"                   # Présence au sens callendrier actif
          - x: "Break() if is_on('binary_sensor.life_windows_and_doors_delayed') else Next()" # Fenêtre ouverte
          
          - rules:
            - x: "Break() if is_on('input_boolean.to_sleep') else Next()"                       # Je vais dormir...
            - x: "Break() if is_off('binary_sensor.lionel_geo') else Next()"                    # Je vais au ciné...
            # CONFORT SEMAINE
            - x: state("input_number.heating_ac_temperature_confort_1") if (is_on("binary_sensor.heating_ac_1")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_2") if (is_on("binary_sensor.heating_ac_2")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_3") if (is_on("binary_sensor.heating_ac_3")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_4") if (is_on("binary_sensor.heating_ac_4")) else Next()
            # CONFORT WEEK-END & FERIE
            - x: state("input_number.heating_ac_temperature_confort_1_d") if (is_on("binary_sensor.heating_ac_1_d")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_2_d") if (is_on("binary_sensor.heating_ac_2_d")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_3_d") if (is_on("binary_sensor.heating_ac_3_d")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_4_d") if (is_on("binary_sensor.heating_ac_4_d")) else Next()
            # ECO
            - x: state("input_number.heating_ac_temperature_eco") if (is_on("input_boolean.presence_ac")) and (is_on("input_boolean.heating_enabled")) else Next()
            - x: "Break(2)"
        # REGLES LIES AUX CONTRAINTES
        - x: state("input_number.heating_ac_temperature_eco") if (is_on("input_boolean.to_sleep")) and (is_on("input_boolean.heating_enabled")) else Next()
        - x: state("input_number.heating_ac_temperature_eco") if (is_off("binary_sensor.lionel_geo")) and (is_on("input_boolean.heating_enabled")) else Next()

      - x: Mark(OFF, Mark.OVERLAY) if (is_off("input_boolean.thermostats_ac_on_off")) else Next()
      - x: Mark(OFF, Mark.OVERLAY) if (is_off("input_boolean.presence_ac")) else Next()      
      - x: Mark(OFF, Mark.OVERLAY) if (is_on("binary_sensor.life_windows_and_doors_delayed")) else Next()


    ac_cool:
      allow_manual_changes: true
      rescheduling_delay: 1
      actors:
        climate.daikin:
          send_retries: 20
          supports_hvac_modes: true
          hvac_mode_off: "off"
          hvac_mode_on: cool
      watched_entities:
      - input_number.heating_ac_temperature_confort_1
      - input_number.heating_ac_temperature_confort_2
      - input_number.heating_ac_temperature_confort_3
      - input_number.heating_ac_temperature_confort_4
      - input_number.heating_ac_temperature_confort_1_d
      - input_number.heating_ac_temperature_confort_2_d
      - input_number.heating_ac_temperature_confort_3_d
      - input_number.heating_ac_temperature_confort_4_d
      - input_number.heating_ac_temperature_eco
      - input_number.heating_ac_temperature_away
      - binary_sensor.heating_ac_1
      - binary_sensor.heating_ac_2
      - binary_sensor.heating_ac_3
      - binary_sensor.heating_ac_4
      - binary_sensor.heating_ac_1_d
      - binary_sensor.heating_ac_2_d
      - binary_sensor.heating_ac_3_d
      - binary_sensor.heating_ac_4_d
      - input_boolean.presence_ac
      - binary_sensor.life_windows_and_doors_delayed
      - input_boolean.heating_enabled
      - input_boolean.cooling_enabled
      - input_boolean.thermostats_ac_on_off

      schedule:
      - rules:
        - x: "Break() if is_off('input_boolean.thermostats_ac_on_off') else Next()"
        - x: "Abort() if is_off('input_boolean.cooling_enabled') else Next()"
        - rules:
          # CONTRAINTES
          - x: "Break() if is_off('input_boolean.presence_ac') else Next()"
          - x: "Break() if is_on('binary_sensor.life_windows_and_doors_delayed') else Next()"
          
          - rules:
            - x: "Break() if is_on('input_boolean.to_sleep') else Next()"
            - x: "Break() if is_off('binary_sensor.lionel_geo') else Next()"
            # CONFORT SEMAINE
            - x: state("input_number.heating_ac_temperature_confort_1") if (is_on("binary_sensor.heating_ac_1")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_2") if (is_on("binary_sensor.heating_ac_2")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_3") if (is_on("binary_sensor.heating_ac_3")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_4") if (is_on("binary_sensor.heating_ac_4")) else Next()
            # CONFORT WEEK-END & FERIE
            - x: state("input_number.heating_ac_temperature_confort_1_d") if (is_on("binary_sensor.heating_ac_1_d")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_2_d") if (is_on("binary_sensor.heating_ac_2_d")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_3_d") if (is_on("binary_sensor.heating_ac_3_d")) else Next()
            - x: state("input_number.heating_ac_temperature_confort_4_d") if (is_on("binary_sensor.heating_ac_4_d")) else Next()
            # ECO
            - x: Mark(OFF, Mark.OVERLAY) if (is_on("input_boolean.presence_ac")) and (is_on("input_boolean.cooling_enabled")) else Next()
            - x: "Break(2)"
        # REGLES LIES AUX CONTRAINTES
        - x: Mark(OFF, Mark.OVERLAY) if (is_on("input_boolean.to_sleep")) and (is_on("input_boolean.cooling_enabled")) else Next()
        - x: Mark(OFF, Mark.OVERLAY) if (is_off("binary_sensor.lionel_geo")) and (is_on("input_boolean.cooling_enabled")) else Next()

      - x: Mark(OFF, Mark.OVERLAY) if (is_off("input_boolean.thermostats_ac_on_off")) else Next()
      - x: Mark(OFF, Mark.OVERLAY) if (is_off("input_boolean.presence_ac")) else Next()      
      - x: Mark(OFF, Mark.OVERLAY) if (is_on("binary_sensor.life_windows_and_doors_delayed")) else Next()

Les deux modes du climatiseur sont gérés par deux input_boolean: et deux automations de façon à ce qu'un seul mode puisse être ON à un instant t.

  - id: 'd1bdf1ac-4d43-4f04-9b09-03e7a5e9ff59'
    alias: 'CLIMATE - AC Chaud'
    description: ''
    trigger:
      - platform: state
        entity_id: input_boolean.heating_enabled
        to: 'on'
      - platform: state
        entity_id: climate.daikin
        # attribute: hvac_mode
        to: 'heat'
    action:
    - service: input_boolean.turn_off
      target:
        entity_id: input_boolean.cooling_enabled
    - service: input_boolean.turn_on
      target:
        entity_id: input_boolean.heating_enabled
    - service: climate.set_hvac_mode
      target:
        entity_id: climate.daikin
      data:
        hvac_mode: heat

  - id: '8fbf1387-04db-44e8-a210-472e4ff80501'
    alias: 'CLIMATE - AC Froid'
    description: ''
    trigger:
      - platform: state
        entity_id: input_boolean.cooling_enabled
        to: 'on'
      - platform: state
        entity_id: climate.daikin
        # attribute: hvac_mode
        to: 'cool'
    action:
    - service: input_boolean.turn_off
      target:
        entity_id: input_boolean.heating_enabled
    - service: input_boolean.turn_on
      target:
        entity_id: input_boolean.cooling_enabled
    - service: climate.set_hvac_mode
      target:
        entity_id: climate.daikin
      data:
        hvac_mode: cool

On pourrait compléter ces deux automations avec pour chacune 12 input_number.set_value afin de d'adapter les consigne à la saison.... On pourrait

service: input_number.set_value
target:
  entity_id: input_number.heating_ac_temperature_confort_1
data:
  value: 23

Et tant qu'à y être on pourrait aussi déclencher tout ça avec un trigger basé sur la saison ou appliquer un condition...

  trigger:
    - platform: state
      entity_id: sensor.season
      to: summer  # A vérifier, je ne suis pas certain que le "to" passe

# Ou en condition....

  condition:
    - condition: state
      entity_id: sensor.season
      state: summer

Ne cherchez pas à recopier ces bouts de code, il ne sont là que pour expliquer la philosophie, vous trouverez mes sources sur GitHub. (je vais essayer de les tenir à jour).

L'interface

C'est là toute la difficulté, créer une interface simple pour un utilisateur lambda. Personnellement taper dans le YAML de Schedy me va bien, mais pour mon frère que je souhaite autonome dans sa maison il n'en est pas question.

Il ne faut pas que cette interface soit compliquée. J'ai donc choisit de masquer tout ce que n'est pas utile au quotidien. Pour y parvenir j'ai utilisé des cartes conditionnelles, l'intégration Fold-Entity et bien sur Multiple-Entity-Row que j'utilise régulièrement.. Un onglet sera dédié à chaque pièce, onglet dans lequel on trouve deux cartes Vertical Stack. Une première pour les fonctions et informations générales et la seconde ou vont se déplier les plages actives

Outre la visualisation du thermostat graphique, on va retrouver ici des informations telles une fenêtre ouverte ou le type de journée :

Ensuite si on déroule les options, on retrouve les consignes ECO et ABSENCE, le retour Schedy n'est là que pour du debug, il nous indique l'information reçue par Schedy. Les 3 commutateurs suivant seront des contraintes générales que nous pourrons utiliser ultérieurement dans Schedy.

On va ensuite déplier le boost. On choisit ici la température et la durée. Un reset est prévu afin d'annuler un boost lancé malencontreusement.

Le boost est géré par un script qui va demander à Schedy de passer outre les planifications prévues pendant la durée choisie. De base j'ai demandé à Schedy de replanifier toute les minutes, en actionnant ce script on va mettre en pause la replanification interne.

script:
  heating_antoine_boost:
    alias: Boost
    icon: mdi:timer-outline
    sequence:
    - event: schedy_set_value
      event_data:
        app_name: schedy_heating
        room: chambre_antoine
        v: '{{ states.input_number.heating_antoine_temperature_boost.state }}'
        rescheduling_delay: '{{ states.input_number.heating_antoine_time_boost.state }}'

La carte suivante de notre stack est consacrée à la présence de l'occupant (avec ses dates d'arrivée et de départ cachées, sachant que ces dates, principalement liées aux chambres des enfants, pourront être gérées autrement (calendrier scolaire, géoloc sur approche, ...) ainsi que le choix des plages à activer pour chaque type de journée :

Ensuite on passe à la Vertical Stack des différentes plages. Il s'agit ici d'une suite de 12 cartes conditionnelles qui ne s'affichent que lorsqu'elle sont choisies dans la carte précédente. J'ai nommé ces plages NUIT, MATIN, MIDI et SOIR, mais c'est purement informel, 1, 2, 3, et 4 aurait pu faire l'affaire et rien n'empêche d'en créer plus ou moins... Si une plage n'est pas activée elle ne sera ni visible, ni fonctionnelle, et quand une plage passe en mode confort un indicateur est présent.

Attention : Je n'ai pas fait de contrôle pour prévenir une éventuelle superposition des plages. On part donc du principe qu'elles sont consécutives (plus pratique visuellement, mais sans conséquence), mais surtout qu'elles ne se superposent pas. Il doit être possible de gérer ça, mais ça me semble bien compliqué...

Déploiement

J'ai déjà expliqué comment installer Schedy dans les articles précédents.

Pour le reste j'ai mis le code sur GitHub. Il y a d'une part le fichier de configuration schedy_heating.yaml qui trouvera sa place dans le répertoire /config/appdaemon et la partie HA avec un fichier global et fichier propre à chaque pièce fait l'objet d'un fichier dans le répertoire /packages. On y trouvera donc heating_global.yaml et heating_antoine.yaml pour cette première pièce.

Pour créer une nouvelle pièce on va dupliquer heating_antoine.yaml en heating_cuisine.yaml par exemple, et ensuite faire un cherche et remplace (case sensitive) dans un éditeur afin de remplacer toutes occurrences antoine par cuisine et Antoine par Cuisine. Et vous l'aurez compris, même punition pour les cartes Lovelace.

Je vous conseille toutefois de bien fignoler et debugger une première pièce avant de déployer les suivantes. J'ai joué la carte du fonctionnel sans trop me préoccuper de l'esthétique, et chaque pièce va comporter des particularités. Inutile par exemple de déployer les fonctions Arrivée / Départ sur les pièces communes. On fait ici du sur mesure, ça sous entend également d'y passer un peu de temps....

Voilà ! Et j'espère ne pas y revenir....

 

 
 

 

 

Home Assistant : Alertes

Les cordonniers sont bien souvent les plus mal chaussés. Une adage qui se vérifie une fois de plus. J'ai laissé la porte d'un congélateur ouverte et malgré le fait qu'il y ait une sonde connectée à l'intérieur je ne m'en suis aperçu que deux heures plus tard et j'ai du jeter pas mal de choses... Pourquoi ? Simplement parce que je je n'ai jamais pris le temps de configurer les alertes ! Alors on va le faire, rien de sorcier mais il faut le faire !

On par du principe que l'on a des sondes en place. Il faut éviter les Aqara ou Xiaomi sur piles. Si cela convient pour un réfrigérateur, à -18 °C les piles ne tiennent pas très longtemps. J'ai donc opté pour une extension Shelly avec des sondes DS18B20. Après cette source peut être différente mais il faut que la sonde soit déportée. On peut aisément utiliser un câble plat de type téléphone pour passer la porte. On pourait également installer un détecteur d'ouverture, mais ça n'aura aucun intérêt en cas de panne de l'appareil...

On commence par faire des binary_sensor pour gérer les seuil et on recharge les templates sans avoir à redémarrer. On pourrait régler le seuil dynamiquement avec un input_number si on voulait faire genre.... On configure un seuil un peu plus haut afin de ne pas déclencher des faux positifs à chaque ouverture.

- platform: template
  sensors:
    alert_freezer_haier_2:
      value_template: "{{ states('sensor.shelly1_55eaea_temperature_3') | float > -12 }}"
      friendly_name: "Alerte Congelateur Haier 2"

    alert_freezer_haier_1:
      value_template: "{{ states('sensor.shelly1_55eaea_temperature_1') | float > -12 }}"
      friendly_name: "Alerte Congelateur Haier 1"

    alert_fridge_garage:
      value_template: "{{ states('sensor.shelly1_55eaea_temperature_2') | float > 8 }}"
      friendly_name: "Alerte Réfrigérateur Garage"

    alert_fridge_kitchen:
      value_template: "{{ states('sensor.mi_refrigerateur_temp') | float > 9 }}"
      friendly_name: "Alerte Réfrigérateur Cuisine"

On pourrait aussi créer nos binary_sensor avec la plateforme Threshold. C'est plus smart, mais pour tester il me fallait redémarrer On pourrait également s'intéresser à la plateforme Derivative...

- platform: threshold # will switch state not at 0°C/min but 0.1°C/min or -0.1°C/min depending on the current state of the sensor, respectively
  entity_id: sensor.shelly1_55eaea_temperature_3
  upper: -10
  hysteresis: 0.1 # sensor
  name: Temperature rising   

Ensuite il faut gérer les alertes et ça on va le faire avec la plateforme Alert que j'utilise déjà pour les coupures électriques :

fridge_garage:
  name: 'Alerte Réfrigérateur Garage'
  entity_id: binary_sensor.alert_fridge_garage
  state: 'on'
  repeat:
    - 5
    - 10
    - 30
    - 45
    - 60
    - 90
    - 120
    - 240
    - 300
  can_acknowledge: true  # Optional, default is true
  skip_first: true  # Optional, false is the default
  message: "{{ states.sensor.date_time.state}} > Réfrigérateur Garage | Température excessive : {{ states.sensor.shelly1_55eaea_temperature_2.state }}°" 
  done_message: "{{ states.sensor.date_time.state}} > Réfrigérateur Garage | Température normale : {{ states.sensor.shelly1_55eaea_temperature_2.state }}°" 
  notifiers:
    - slack_hass_canaletto
    - Free_Mobile

A partir de là on reçoit une alerte (ici deux) dès lors que la température grimpe. J'ai toutefois éliminé la première alerte afin d'éviter les faux positifs. Et rien n'empêcherait en complément de diffuser une annonce vocale en TTS ou de faire clignoter en rouge quelques ampoules...

 

Redirection de port sous Linux : 2/2

Dans la partie précédente, en bon béotien, j'ai exploré pas mal de techniques de redirection de port, mais j'aurais du commencer par l'exploitation de ce qui est depuis un certain temps de base sans Linux, à savoir IPTables.

Pour cette manipulation on va utiliser un routeur Ubiquiti ER-X avec la dernière version de EdgeOS (2.0.9-hotfix.2). Et sur ce routeur on va commencer par y installer Wireguard (pour changer un peu, mais Zerotier ou les VPN disponibles de base auraient pu faire l'affaire).

On va se rendre compte que la philosophie de WireGuard est bien différente de Zerotier (ou même de TailScale). Ici il faut tout borner, c'est la version barbus et les ACL ne se gèrent pas sur une console d'admin, il faudra pour ça utiliser le firewall du point d'entrée, ce qui à mon gout est bien moins souple et rend impossible certaines restrictions plus granulaires au niveau de l'utilisateur client...

Je ne suis pas expert, mais contrairement à Zerotier ou le client génère un ID propre à la machine, sous WireGuard un simple copié collé suffit à exporter et utiliser la configuration ailleurs... On peut certes utiliser une clé supplémentaire (PresharedKey), mais ça ne changera rien. Un mot de passe qui se transmet vocalement aurait été préférable. Cela ne sera pas trop gênant dans une configuration fermée (la mienne, un serveur verrouillé vers un routeur qui sera tout autant), par contre dans le cadre de l'utilisation pour un utilisateur lambda itinérant et souvent inconscient cela pose question, tout comme le non support apparent d'une clé physique...

WireGuard coté serveur :

On se connecter en SSH et on installer la dernière version que l'on va trouver ici avec les commandes suivantes :

curl -OL https://github.com/WireGuard/wireguard-vyatta-ubnt/releases/download/1.0.20210606-1/e50-v2-v1.0.20210606-v1.0.20210424.deb
sudo dpkg -i e50-v2-v1.0.20210606-v1.0.20210424.deb

Attention à bien remplacer par la dernière version et faire attention car il existe des versions spécifiques à chaque modèle de routeur et pour chaque version de EdgeOS (v1 et v2).

Si on manque de place (df) on fait un show system image et on efface celle qui ne sert à rien après une mise à jour avec un delete system image. Et oui il y a du vécu.

Quand WireGuard est installé on va générer nos clés :

sudo wg genkey | tee /dev/tty | wg pubkey

Ce qui va nous donner deux clés, la première est la clé privée, la seconde la clé publique.

0GbmWkPkYB9y2s5aIaAxUrAPoSnsDFnuhHjRnujEsm8=
KsVzrtWPGWDbuCLPUyTsTL6pQOfiS+96VOXsMnPo+SI=

On sauvegarde ces clés au chaud et on passe à la configuration de l'interface CLI propre à EdgeOS (configure, commit, save et exit)

configure

On y associe un subnet privé, un port UDP et la clé privée :

set interfaces wireguard wg0 address 192.168.33.1/24 # Ici on choisit l'IP de notre serveur WireGuard
set interfaces wireguard wg0 listen-port 51833       # Ici le port UDP qu'il conviendra de redirigier si on est pas en Bridge ou DMZ...
set interfaces wireguard wg0 route-allowed-ips true  # Pour sortir de ce subnet....
set interfaces wireguard wg0 private-key 0GbmWkPkYB9y2s5aIaAxUrAPoSnsDFnuhHjRnujEsm8=

On va ensuite déclarer les pairs (peers) autorisés en associant notre interface à leur clé publique et une IP autorisée. 

set interfaces wireguard wg0 peer N4cuA0WPkMt+3hfidlemsI/VGcupv96NtrkwA/esf2E= allowed-ips 192.168.33.2/32
set interfaces wireguard wg0 peer u2w/+ZNI2RwbYTdft9yggPGnff8QexY9UjjvdvVf0gM= allowed-ips 192.168.33.3/32

On ajoute une règle sur le firewall (Il est également possible aussi de faire ça depuis l'interface du routeur)

set firewall name WAN_LOCAL rule 20 action accept
set firewall name WAN_LOCAL rule 20 protocol udp
set firewall name WAN_LOCAL rule 20 description 'WireGuard'
set firewall name WAN_LOCAL rule 20 destination port 51833

Et on termine par :

commit
save
exit

Il est bien sur possible de faire ça en plusieurs fois, mais dans ce cas là il ne faudra pas oublier de rentrer dans le CLI propre à EdgeOS (configure, commit, save et exit).

Si je fais un scan des ports coté WAN, je ne dois rien voir le seul port ouvert étant le 51833 en UDP.

WireGuard coté client

Le client peut être sous n'importe quel OS, dans mon cas ce sera Windows et on va faire la configuration manuellement (il y a moyen de préparer des fichiers de configuration à importer pour un déploiement conséquent). Plus haut on a ajouté la clé publique fournie par le client dans les pairs autorisés. Cette clé est propre à chaque tunnel défini coté client. Idem pour la clé privée du client que l'on reporte ci dessous, elle est crée lors de l'ajout d'un tunnel sur le client. Ensuite on renseigne le pair du client, c-a-d le serveur que l'on a créé plus haut, sa clé publique, les IP autorisées pour ce tunnel ainsi que l'IP ou le TLD du serveur.

[Interface]
PrivateKey = IIczTA5sdrdcg4+VQNnudslgnveoR5ZDD3ZyL0ZXonU=
ListenPort = 15092
Address = 192.168.33.2/32

[Peer]
PublicKey = KsVzrtWPGWDbuCLPUyTsTL6pQOfiS+96VOXsMnPo+SI=
AllowedIPs = 192.168.33.0/24
Endpoint = 69.69.69.69:51833
PersistentKeepalive = 25

Ensuite on active le tunnel et normalement à ce stade on doit pouvoir faire un ping sur l'IP LAN du routeur distant (notre serveur WireGuard) ainsi que les IP de son subnet pour peu que les routes inverses soient configurées.

Si sous Linux l'activation / désactivation d'un tunnel en CLI coule de source, il m'a fallut un peu chercher pour Windows. Mon but étant de permettre à une application de supervision d'éventuellement réactiver un tunnel cas de défaillance.

Donc pour activer un tunnel,  CMD en mode admin... (-h pour en savoir plus..) :

C:\>"C:\Program Files\WireGuard\wireguard.exe" /installtunnelservice "C:\Program Files\WireGuard\Data\Configurations\Nom du Tunnel.conf.dpapi"

Et pour le désactiver :

C:\>"C:\Program Files\WireGuard\wireguard.exe" /uninstalltunnelservice "Nom du Tunnel"

Pour information et pour les afficionados, on peut très bien installer un serveur WireGuard sous Windows, les explications sont ici, ce n'est pas officiellement supporté et ça a l'air bien plus compliqué.

Sauf que dans le cas qui me préoccupe je ne peux justement pas disposer des routes inverses pour une question de sécurité chez mon client. La seule IP autorisée sera l'IP LAN du routeur. Il faut donc que les services que je doit joindre me reconnaissent avec cette IP. Et c'ets ici qu'interviennent les IPTables.

IPTables

Pour utiliser les IPTables il n'y a rien à installer car cela fait partie de l'O/S. Ca tombe bien car sur ce routeur la place est limitée. Par contre il faut que l'IP Forwarding soit activé, on peut vérifier avec sysctl net.ipv4.ip_forward qui va nous répondre net.ipv4.ip_forward = 0 ou 1 si c'est activé.

Ensuite, toujours en SSH : 

sudo iptables -F
sudo iptables -F -t nat
sudo echo 1 >| /proc/sys/net/ipv4/ip_forward  # En cas de besoin....
sudo iptables -t nat -A  PREROUTING -p tcp -d  192.168.33.1 --dport 2525 -j DNAT --to 192.168.169.22:25
sudo iptables -t nat -A  PREROUTING -p tcp -d  192.168.33.1 --dport 8080 -j DNAT --to 192.168.150.20:80
sudo iptables -t nat  -A POSTROUTING -j MASQUERADE

Depuis le client on va faire pointer nos requetés sur la première IP de WireGuard à laquelle j'ai associé un serveur web sur 192.168.150.20 et un serveur SMTP sur 192.168.169.22. A noter que si le port de destination est bien sur le port sur lequel répond le service, le port source peut lui être identique ou défini différemment (surtout qu'en 80 on a l'interface du routeur...).

Maintenant, depuis mon client, le serveur web de destination répondra sur http://192.168.33.1:8080 et si je fais un telnet 192.168.33.1 25 j'obtiendrait la mire de mon serveur SMTP. Et ces deux derniers ne verront en IP source que l'IP LAN de mon routeur, donc une IP autorisée.

Mais on ne peut pas tout à fait aller dîner... En effet ces IPTables vont disparaitre  au premier redémarrage !

Persistance

Mon premier réflexe était d'utiliser le paquet iptables-persistent. Sauf que j'ai remarqué, que pour une raison que je n'explique pas, mais qui a certainement sa logique, il est impossible d'ajouter une règle au firewall (tout au moins depuis l'interface du routeur) dès lors que ce paquet est activé. Je vais donc créer un script qui se lancera au démarrage du routeur et que je pourrais désactiver au besoin... (si un barbu passe par la merci de me dire sil y a mieux à faire).

Sous EdgeOS on a un emplacement spécifique pour les scripts devant s'exécuter après le démarrage du routeur et de ses services : /config/scripts/post-config.d

sudo vi /config/scripts/post-config.d/myfwd

Et dans ce fichier on va insérer nos commande  :

#!/bin/bash
iptables -F
iptables -F -t nat
iptables -t nat -A PREROUTING -p tcp -d  192.168.33.1 --dport 2525 -j DNAT --to 192.168.169.22:25
iptables -t nat -A PREROUTING -p tcp -d  192.168.33.1 --dport 8080 -j DNAT --to 192.168.150.20:80
iptables -t nat -A POSTROUTING -j MASQUERADE

Ensuite on va rendre ce script exécutable :

chmod +x /config/scripts/post-config.d/myfwd

Mais, car il y a toujours un mais. Il se peut que la résolution DNS ne se fasse pas au lancement. Donc si vous devez obtenir l’adresse IP à partir d’un nom de domaine, vous devez utiliser dig que vous obtiendrez avec :

sudo apt-get install dnsutils

Ensuite il faudra modifier légèrement le script...

sudo IP_ADDR=$(dig +short smtp.mondomaine.com| awk 'NR==1') 
sudo iptables -t nat -A  PREROUTING -p tcp -d  192.168.33.1 --dport 2525 -j DNAT --to $IP_ADDR::25

Firewall

Attention : dès lors que l'on ajoute ces IPTables il ne sera plus possible de modifier les règles du firewall depuis l'interface, et comme l'interface qui ne fait que lancer des lignes de CLI, idem en CLI. Afin de pouvoir créer ou modifier des règles il faudra temporairement désactiver ce script et redémarrer. En ce qui me concerne je l'ai fait sauvagement en renommant le répertoire qui le contient :

cd /config/scripts
sudo mv post-config.d post-config.d.off

Pour le reste on laisse par défaut, mais on va tout de même éviter de laisser des ports ouverts. On commence par désactiver le service Ubiquiti Device Discovery qui expose en UDP/TCP le port 10001 :

configure
set service ubnt-discover-server disable
commit ; save 

On désactive l'interface GUI et le SSH depuis le WAN. Pour ça il est possible de forcer sur l'IP LAN :

set service gui listen-address 192.168.0.1
set service ssh listen-address 192.168.0.1

Mais en fait ça ne m'intéresse pas car je veux que ce soit accessible également via WireGuad, et ici c'est thé ou café. Donc je vais créer des règles de drop sur ces ports au niveau du WAN_LOCAL, ce qui va nous donner quelque chose dans le genre, mais qui sera plus simple de faire en GUI :

set firewall name WAN_LOCAL rule 50 action drop
set firewall name WAN_LOCAL rule 50 protocol tcp
set firewall name WAN_LOCAL rule 50 description 'Drop GUI & SSH'
set firewall name WAN_LOCAL rule 50 destination port 22,80,443

Ensuite on fait un scan (avec ça par exemple) sur l'IP WAN afin de constater la fermeture effective. Il ne reste que l'ICMP et WireGuard en UDP. Si on souhaite encore renforcer la sécurité on peut aussi restreindre l'ICMP et le port UDP de Wireguard avec des IP sources... Le même scan depuis un client WireGuard continuera à présenter les mêmes ports que l'interface LAN. Pour en savoir plus sur ces règles c'est ici, et vous allez trouver une petite bible dédiée aux EdgeRouter, et notamment pour tout ce qui concerne leur sécurité.

Voilà !

EDIT 25/09/2021: La bonne nouvelle c'es que Free a ajouté WireGuard dans ses Freebox et que ça fait tout le travail... Quand on a une Freebox bien sûr !

EDIT 4/11/2021: Ajustement du script pour EdgeOS.

Redirection de port sous Linux : 1/2

J'ai depuis quelques temps une problématique à résoudre pour un client : accéder depuis l'extérieur à un sous-réseau, ou tout au moins à certains services, sur un réseau pour lequel je ne peux pas être routé. Pour Microsoft la chose était facile, on installe un serveur PPTP sur le réseau autorisé (routé) et à partir de là tout devient facile. Sauf qu'en 2021 même Microsoft déconseille le PPTP, et pour cause ! Et bien sur il n'est pas question d'exposer ces services sur internet via une simple redirection de ports...

J'ai pensé bien sur à une solution de type SDN (Zerotier, Wireguard ou Tailscale pour faire facile). Tous les 3 vont me permettre de router des réseaux distants, mais ça ne fonctionnera pas car je ne peux pas disposer du routage inverse, donc dans le meilleur des cas je pourrais atteindre le réseau distant. De plus je serais vu comme une adresse source suspecte, l'IP Zerotier par exemple...

Afin qu'elle soit valide sur les sous réseaux distants, il faut que mon adresse source soit une IP autorisée, donc une IP du réseau distant.

L'alternative pourrait être de faire un bridge (niveau 2) à l'entrée du réseau distant. C'est possible (ici par exemple avec Zerotier), mais, comme chacun le sait, l'inconvénient d'un bridge est de laisser transiter beaucoup trop de saloperies, à moins de sérieusement les filtrer. A explorer. Bref j'en était là peu avant minuit en explorant comment me dépatouiller de cette affaire, je suivait une piste TCP PROXY quand je suis tombé sur un site d'un gentil hacker qui expose sa trousse à outils dans laquelle on trouve RineTD. A noter que TCPProxy sous OpenWRT ou UbuProxy sous Ubuntu sont des déclinaisons plus ou moins améliorées et supportant IPV6, permettant même l'encapsulation dans un tunnel IPV6..

RineTD

Dans la pratique RineTD agit plus ou moins comme les redirecteur de ports que l'on trouve sur un classique routeur grand public, sauf qu'ici il est capable de le faire sur une machine Linux. Il peut s'utiliser sur tous les ports, sauf le FTP qui lui demande des sockets sur les ports différents. Il présente cependant un défaut qui pour moi devient une qualité : l'adresse source sera toujours l'IP LAN de la machine sur laquelle il est installée. Et c'est justement ce que je souhaite.

On part du principe que Zerotier est configuré et on installe :

sudo apt-get install rinetd

Et ensuite on va éditer le fichier de configuration : 

sudo nano /etc/rinetd.conf
#localip localport remoteip remoteport

0.0.0.0      80  192.168.1.90  8080  # Ecoute sur toutes les IP sur le port 80 et redirection vers 192.168.1.90 sur le port 8080
10.146.50.50 25  192.168.1.50  25    # Ecoute sur toutes l'IP 10.146.50.50 sur le port 25 et redirection vers 192.168.1.50 sur le port 25

logfile /var/log/rinetd.log          # Le fichier de log...

allow IP                             # Ici on gère les IP source autorisée à utiliser le service...
deny 192.168.2.15
allow 10.146.50.*

Et il faudra relancer le service à chaque changement de configuration :

sudo /etc/init.d/rinetd restart

Et c'est tout !

Maintenant si depuis la machine 10.146.50.10 de mon réseau Zerotier je fais un telnet 10.146.50.50 25 je vais tomber sur le serveur SMTP qui se trouve en 192.168.1.50 et lui me verra comme étant 192.168.100.50 qui est l'IP LAN de la machine sur laquelle tourne RineTD.

TCP Proxy sous OpenWRT

Bon, maintenant j'aimerais bien faire la même chose sur OpenWRT. Ici ce qui m'intéresse c'est de faire tourner ça sur un micro routeur, le GL-MT300N car avec sa taille proche d'une boite d'allumettes pour une vingtaine d'€uros on va pouvoir le caser l'importe ou...

On le mets donc à jour dans sa dernière version et on commence par lui installer Zerotier en SSH :

opkg update
opkg install zerotier

Ensuite on édite le fichier de configuration idoine avec vi. (Confidence, je crois que c'est ma première avec VI ! ESC + :wq! pour sauvegarder et ESC + :q! pour juste quitter...)

vi /etc/config/zerotier
# cat /etc/config/zerotier
config zerotier 'sample_config'
    option enabled '1'
    list join 'd5e5fb6537869a7d'  # Que l'on remplace par son ID Zerotier

On passe ensuite à la configuration du firewall si on veut accéder au LAN

vi /etc/config/firewall

Et on ajoute :

config zone 'vpn_zone'
    option name 'zerotier'
    option input 'ACCEPT'
    option forward 'REJECT'
    option output 'ACCEPT'
    option device 'zt+'
    option masq '1'
    option mtu_fix '1'

config forwarding
    option dest 'zerotier'
    option src 'lan'

config forwarding
    option dest 'lan'
    option src 'zerotier'

Et on redémarre tout ça avec :

/etc/init.d/zerotier restart
/etc/init.d/firewall restart

Normalement à ce stade on doit pouvoir pinguer l'IP Zerotier et LAN du routeur depuis puis un client distant.

On passe donc à l'installation de TCPProxy :

opkg install tcpproxy

 Ensuite on édite sa configuration

vi /etc/config/tcpproxy
config listen
  option disabled 0                    # 1 ou 0 pour activer ce port car il peut y en avoir plusieurs
  option local_port '9000'             # Le port d'écoute
  option resolv 'ipv4'                 # Ca peut aussi travailler en IPV6...
  option remote_addr '192.168.210.16'  # L'IP du serveur distant
  option remote_port '8000'            # Son port
  option local_addr '10.147.80.48'     # L'IP locale sur laquelle on écoute​
Et bien sur on redémarre le service :
/etc/init.d/tcpproxy restart

Pour mon test j'ai installé un mini serveur web afin de valider l'adresse source, donc http://10.147.80.48:9000 et quand je vais dans son log je constate que l'IP source est bien 192.168.210.48 qui est l'IP LAN de mon micro routeur.

Alternative

Vous avez noté que je suis toujours passé par Zerotier, par habitude surement. Mais on peu aussi passer par WireGuard qui est de base installé sur ces micro routeurs.

Du coup j'ai acheté un autre micro routeur, le GL-AR300M qui lui est noir et un peu plus puissant. L'interface permet de configurer le serveur WireGuard et de générer la configuration du client qu'il suffira de copier sur le poste distant :

[Interface]
Address = 10.0.0.2/32
ListenPort = 13925
PrivateKey = +LsVmc6LVmdjfggmjgfmjùsgsdkfg3+A0hNncpxcHw=
DNS = 64.6.64.6

[Peer]
AllowedIPs = 192.168.10.0/0, 10.0.0.1/32  # Ici les réseaux que je vais router sur le client
Endpoint = 69.69.69.69:51820
PersistentKeepalive = 25
PublicKey = yeNCdjgfùsjdfùgjùfj*gsd*fgklsfkgsfdg k807pxs6iidhM=

Pour mon usage je pourrais me contenter de renseigner uniquement 10.0.0.1/32 dans AllowedIPs puisque TCPProxy prendra le relais...

Débit

GL-iNet nous assure qu'il est possible de soutenir 50 Mbps avec WireGuard en opposition aux 15 Mbps possibles avec OpenVPN.

Bonus

  • Si votre routeur de test n'est pas en en bridge ou en DMZ, pensez à rediriger vers lui le port UDP idoine pour Zerotier ou WireGuard.
  • Curieusement sur ces micro routeurs le serveur DHCP n'est pas facilement désactivable depuis l'interface, et dans mes bricolages j'ai déjà un serveur DHCP et je ne voudrais pas que celui ci interfère. On doit pouvoir faire ça en éditant /etc/config/dhcp, mais on peu également dans les options avancées installer LUCI, éditer l'interface LAN et lui dire d'ignorer le DHCP sur cette interface.
  • Ce n'était pas mon besoin, mais ces micro routeurs peuvent également êtres configurés en client VPN (WireGuard ou OpenVPN), en client Tor, voire même en répéteur WI-FI très pratique quand on est à l'hôtel avec une seule connexion possible. Dans ce dernier cas il vaut mieux également combiner avec le client VPN...
  • J'ai fait ces manips sur ces routeurs, mais n'importe quel Linux fera tourner WireGuard, une petit tuto ici ou en site to site ici.

Voilà !

Les barbus vont surement trouver à redire et me proposer une autre façon de faire et je serais curieux de qu'ils vont me proposer. En attendant je suis content de moi et ma pauvre culture Windows...

Il semblerait que Rinetd comporte une limitation du nombre de connexions. Il existe également Socat, plus de connexion, mais plus de RAM, ou en encore Redir qui semble moins gourmand. Et bien sur pour un usage plus conséquent il ne faut oublier HAProxy qui est surtout un équilibreur de charge http, https et TCP, mais peut être utilisé pour transférer le trafic Tcp uniquement en utilisant légèrement le processeur et la RAM. Mais HAProxy vous ne l'installerez pas sur un petit routeur...

Mais il existe une autre voie : IpTables ! Son utilisation ne consomme pas de CPU et très peu de RAM, et surtout ça fait partie du système d’exploitation ! Ce sera l'objet du prochain article...

Idées...

On l'a vu ces petits routeurs sont très discrets. Pour mes tests ils sont configurés avec deux interfaces LAN et WAN, LAN et WAN pouvant très bien être WLAN et WWAN... Mais prenons un autre cas de figure, vous devez accéder à un réseau de façon discrète (discrète mais autorisée, je vous rappelle que ce genre de délit relève du pénal et est passible de la case prison). Bref, par exemple le responsable d'une entreprise cliente vous demande (demande écrite pour vous couvrir) une telle solution à l'insu de son service informatique, vous préconfigurez un tel routeur en client DHCP sur le LAN et en serveur Wireguard et le tour est joué. Accès possible au LAN, voire plus via TCPProxy. Et bien sur l'administration du routeur en web et ssh reste possible via le client Wireguard...

 

 

Unifi UDM Pro, ZT, encore...

Longtemps j'ai disposé de deux lignes vDSL à 90 MB en étant proche du DSLAM. Il y avait un peu de bricolage, la Freebox en bridge sur le WAN1 de l'UDMP et Nerim en NAT entre le WAN2 (secours) de l'UDM Pro et vie le LAN sur un subnet différent sur MPTCP et OTB. Je sais, pas très propre, mais c'est juste un labo... Et c'est pourquoi on va optimiser un peu, d'autant plus qu'entre temps je me retrouve avec une fibre SFR et ma Freebox Révolution migrée elle aussi en fibre.

Versions utilisées, relativement stable par rapport à ces derniers mois :

  • UDMP : 1.10.0
  • Network : 6.4.47

Swap des ports WAN de l'UDM Pro

Avec l'idée migrer la Freebox Révolution vers une Delta S afin de profiter d'un lien à 8 GB, j'ai commencé par swapper la logique des deux ports Wan de l'UDMP. De base le port 9 en 1 GB (RJ45) correspond à WAN1 et le port 10 en 10 GB (SFP+) à WAN2. On peut se demander ou est leur logique tant il est évident que le lien principal sera plus performant que le lien de secours. Depuis je ne sais plus quelle beta il est possible d'inverser cette logique, mais attention il reste un bug de taille, le nom du provider (probablement lié à l'ASN) ne sera pas mis à jour et on reste avec ceux de base, et ça peut être déroutant. Comme on le voit ci dessous le port WAN1 avec la Freebox remonte OwCloud (anciennement Nerim racheté par BT), alors que le WAN2 est maintenant branché sur du SFR...). Ce bug est connu, mais comme d'autres il ne semble pas être dans les priorités des équipes Unifi qui préfèrent surement se concentrer sur une nouvelle version de l'interface...

Passer mon mélange de subnets en VLAN

C'est logique, il faut juste trouver un peu de temps. Et on va voir que du temps j'en ai perdu.

Pour créer un VLAN sur Unifi c'est dans l'absolu très simple. On crée un nouveau réseau et dans les paramètres avancés on choisit un VLAN ID et éventuellement le subnet et le mode DHCP, mais on peut faire sans et dans certains cas laisser faire l'Auto Scale Network. Ensuite on associe ce réseau à un port du switch. Sauf que j'ai passé des heures à chercher à faire compliqué car ça ne fonctionnait pas. Et finalement je me suis résolut à un reboot et là ça passe tout seul. Allez donc savoir...

Dans mon cas je voulait un VLAN sur un subnet particulier avec DHCP. Mon objectif étant d'y coller la box SFR qui sera branchée sur le WAN2 et sur ce VLAN et ainsi sera utilisable par mon fils pour ses jeux qui disposera ainsi d'une fibre rien que pour lui et sans aucune restrictions (il est grand !) et sans venir perturber la QoS du réseau principal, même si l'impact sera moindre via que fibre qu'il ne l'était en xDSL.

Sur le subnet de ce VLAN, la passerelle par défaut est donc la box SFR, comme c'est le cas sur le WAN2 de l'UDM. Les deux usages cohabitent, et la box de secours sert à quelque chose...

On peu ensuite créer un réseau WI-FI sur ce VLAN ou taguer des ports sur les switch (port profile) pour qu'ils l'utilisent.

On verra plus tard comment créer un VLAN dédié à l'IoT et surtout comment ouvrir ou bloquer le trafic entre les VLAN.

UPnP mon amour !

Toute cette partie fonctionnelle, je me suis aperçu que mon routeur VPN/SDN Zerotier (un Linux dans une VM) avait un peu de mal à sortir. Dans la configuration précédente il sortait en NAT via ma ligne de secours. Dans ma nouvelle configuration il doit sortir via l'UDMP, et le vas échéant profiter du secours. Curieusement j'ai remarqué que le passage en mode secours était lent et qu'au retour certaine destinations n'étaient pas joignables, bizzarement celles situées sur le réseau Free, Online Sacaleway pour être précis, alors que d'autres distants passaient. Et c'est critique.

On lance la commande :

[email protected]:~# zerotier-cli peers

Et l'on obtient la liste des clients distants :

1d20dgr3f3 1.6.5  LEAF      -1 RELAY
2533gtdaef 1.6.5  LEAF      -1 RELAY
338fgte636 1.4.6  LEAF      13 DIRECT 7131     2944

Si DIRECT veut dire que tout va bien, RELAY indique que le distant est injoignable directement, que le trafic va transiter par les serveurs Zerotier. Et que quand le trafic passe, ce qui n'est pas toujours le cas, cela va induire une latence aléatoirement plus importante, au minimum 35 ms. là ou en direct on passera à 12 ms. Et justement, au delà de remplir les poches des Telcos, l'un des objectifs de la fibre est de réduire la latence (même si avec le l'IPV4 encapsulée dans de l'IPV6 on en perd un peu, mais c'est un autre problème et il faudra se pencher sur du full IPV6).

Après moultes recherches j'ai trouvé que 

  1. Que l'UDMP bloquait peut être certains ports en sortie, même si on désactive toute la partie sécurité et que tout est autorisé sur le firewall (pas clair !)
  2. Qu'il est possible de configurer des ports UDP secondaires sur un client (en plus du port UDP natif 9993.

On commence donc par configurer un port secondaire (on ne touche surtout pas au primary (voir ce fil vers la fin) sur le client qui me sert de routeur ZT en créant un fichier local.conf dans le bon répertoire (détails ici) :

{
  "settings": {
    "secondaryPort": 21234
  }
}

Mais visiblement ça ne suffit pas et il va falloir activer l'UPnP pour que tout passe en direct avec un minimum de latence :

Je ne suis pas fan de l'UPnP/NAT-PMP car ce protocole d'automatisation ouvre par définition les ports à la demandes des applications. Applications qui sur un réseau peuvent êtres malveillantes. Pourtant à la maison ce service est très utilisé et quasiment indispensable. Par contre hors de question d'utiliser cette méthode en entreprise ou une règle WAN_LOCAL avec la destination UDP 9993 devrait faire l'affaire (pas clair et à creuser). (ici pour pfsense avec ACL en prime, ce qui va aider en entreprise).

ZT Failover ?

Alors là on rentre dans le coté obscur. Tant qu'à avoir deux fibres autant qu'elles servent à quelque chose. Visiblement l'UPnP de l'UDM Pro est archi buggé. Je me suis donc dit que j'allais faire le failover directement dans le routeur Zerotier. J'ai donc ajouté une carte réseau virtuelle à ma VM ZT qui me sert au routage Lan to Lan (ici et pour monter ça) sur le VLAN 110 qui correspond à la box SFR. Les deux interfaces ont une passerelle par défaut avec le même poids. 

Ensuite on édite le fichier local.conf et on ajoute et on ajoute ces paramètres : 

{
  "settings":
  {
    "defaultBondingPolicy": "active-backup",
    "peerSpecificBonds":
    {
      "f6dd3a2db3":"active-backup",
      "1d20ff53f3":"balance-xor",
      "a92cbsd6fa":"broadcast"
    }
  }
}

Pour comprendre les différentes possibilités on ne trouve pas beaucoup d'information si ce ne sont deux docs officielles ici et ! A croire que ce sujet intéresse peu. Attention, j'ai eu pas mal de difficultés en éditant avec Nano ce fichier avec un copié / collé. Peut être une question de formatage qui m'aurait échappé. Par contre je n'ai pas réussit à associer dans ces paramètres le port secondaire.

Toujours est-il que si je débranche un interface l'autre prend le relais en mode active-backup avec une légère interruption que quelque secondes ou encore plus rapidement en mode broadcast. Mais ce dernier mode semble déconseillé car il génère pas mal de garbage...

Design

Si j'étais un cador en Visio ou d'autres outils en ligne comme draw.io ou autres, je pourrais vous faire un joli dessin, mais ce n'est pas le cas. Donc :

  • WAN1 : Freebox en mode bridge (je voulais tester la Freebox Pro mais elle n'a pas ce mode).
  • WAN2: Secours en NAT sur une box SFR. NAT également accessible sur les ports tagués en VLAN 110 sur un subnet dédié et isolé. Cela permet à mon fils de jouer et faire ses bricolages sans impact sur la sécurité du LAN principal.
  • VPN vers les sites distants accessible depuis le LAN principal. On verra plus tard comment profiter des fonctionnalités Multipath / Bonding de Zerotier qui vont nous permettre d'utiliser le lien de secours sans passer par celui de l'UDM.

Vieilleries...

Et dans tout ça que devient mon installation openMPTCP ? L'objectif de cette installation était d'agréger plusieurs lignes afin de fiabiliser et d'augmenter le débit disponible. Si la question du débit se pose en en xDSL elle ne se pose plus avec une fibre. Quant à la fiabilité il faudrait pour que ça ait du sens disposer d'un VPS puissant avec un lien 10 GB. Et encore on aurait malgré tout une latence dégradée. 

Mais je vais tout de même tenter la chose pour le sport. Pour ca il faut que je crée sur mon ESXi une nouvelle interface sur le VLAN 110 afin d'accéder au NAT de la ligne de secours et éventuellement le modem 4G. L'objectif n'est pas la performance, je ne toucherait donc rien coté VPS, mais juste d'éviter le lag qui se produit quand l'UDMP passe sur le lien de secours. Mais ça reste très rare et je ne sais pas si le jeu en vaut la chandelle. Peyt être pour l'IoT.

 

 

TailScale, VPN/SDN simplifié

Avant, il y avait les VPN (PPTP, IPSec, OpenVPN, etc...) qui ne sont pas toujours très simple à implémenter. Et puis arrivent les SDN, qui dans l'absolu sont des VPN, orientés réseaux étendus et très simples à mettre en œuvre. D'aucun le savent, je sus un fan de Zerotier que j'utilise au quotidien, tant pour interconnecter 4 sites à la place d'IPSec, que pour des machines distantes ou quand je suis en déplacement.

Entre temps les barbus nous ont beaucoup parlé de Wireguard, qui peu ou prou fait la même chose en un peu plus compliqué avec de soit disant meilleures performances. Et puis arrive TailScale qui lui est basé sur Wireguard et lui apporte la simplicité. Une sorte de Wireguard pour les nuls, enfin, pas que, car TailScale apporte à WireGuard la notion d'annuaire qui lui manque (WG-Dynamic en cours de dev.). Comme pour Zerotier, au delà d'un certain nombre de nodes il faudra passer à la caisse, mais les tarifs sont comparables, tout comme les possibilités offertes par la version gratuite suffisante pour un usage home.

Comme pour Zerotier, il est possible :

  1. D'accéder depuis internet à une machine particulière avec un client dédié (MacOS, Windows, Linux, IOS, Android).
  2. D'accéder depuis internet à un sous réseau dès lors que l'on installe un node Linux qui servira de routeur
  3. D'accéder depuis internet à internet de façon sécurisée en passant par un node Linux installé en mode Exit-Node, chez vous ou sur un VPS.
  4. D'accéder depuis chez vous à d'autres sous réseaux, bon là c'est plus compliqué et il faudra passer à la caisse et avantage à Zerotier (ou Wireguard, mais je n'ai pas testé).

Le gros avantage de Zerotier est que l'on travaille en Layer-2 sur un subnet dédié avec la possibilité de figer des IP là ou TailScale affecte des IP aléatoires et travaille en Layer-3. Mais les deux peuvent cohabiter, et ça peut être intéressant dans certains cas. Vous trouverez ici un comparatif des deux solutions.

Ce qui est certain, c'est que si moi j'y trouve des limitations, TailScale a pour lui une simplicité qui en fera un bon choix pour ceux qui débutent et veulent juste un usage limité. On va donc se consacrer aux trois premiers points d'usage.

Accéder depuis internet à une machine distante

Je ne vais pas vous expliquer comment créer un compte (il suffit d'aller sur leur site), ou comment se faire coucou entre deux machines clic clic (MacOS, Windows, IOS ou Android), il suffit de lancer l'installation et de faire un ping. Par contre je vais le faire pour la machine Linux qui nous servira dans les deux cas qui suivent.

On part du principe que la machine existe dans sa config minimale et qu'elle communique avec Internet. On en fait un client Tailsacle, ça se passe ici et c'est un peu différent selon les distributions. J'utilise Ubuntu et on commence par ajouter les clés et le repository :

curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.gpg | sudo apt-key add -
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.list | sudo tee /etc/apt/sources.list.d/tailscale.list

On fait les mise à jour et on installe :

sudo apt-get update
sudo apt-get install tailscale

On lance le client et on copie l'url pour l'autoriser :

sudo tailscale up

Et voici notre IP :

ip addr show tailscale0

A ce stade si on a un autre client TailScale on peu faire un ping... A noter que sur la console d'admin on va pouvoir définir si la clé expire ou pas...

Si votre but est d'accéder à Home Assistant, il existe un addon qui fera le travail pour vous.

Accéder depuis internet à un sous réseau

Afin de ne pas devoir installer TailScale sur toutes vos machines, et surtout accéder à celle ou il n'est pas possible de l'installer (IoT par exemple), on va installer une machine en mode routeur. Pour l'instant ce n'est faisable que sous Linux, avec un petit RPI ou une VM par exemple... On continue sur la même machine.

Première chose on active l'IP Forwarding :

echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p /etc/sysctl.conf

Ensuite on active le routage du subnet :

sudo tailscale up --advertise-routes=192.168.210.0/24

Et pour terminer on va dans la console d'admin indiquer que cette machine servira de routeur pour ce subnet (sous réseau) (les ... au bout de la ligne) :

A partir de là un client TailScale pourra accéder à ce sous réseau.

Accéder depuis internet à internet de façon sécurisée

Quand vous vous connectez en WI-FI sur un hotspot public tout le monde vous dira que ce n'est pas très sécure et qu'il faut utiliser un VPN. Ici plutôt que de payer pour un VPN on va utiliser notre propre connexion, à la maison, ou encore dans un VPS. Pour ça on ajoute la fonction Exit-Node à notre machine :

sudo tailscale up --advertise-exit-node

Et on la configure de facon idoine au même endroit

Ensuite dans le client on choisit l'option Exit-Node afin que tout le trafic transite par notre nouveau point de sortie.

A noter que si l'in veut combiner les deux fonctions, subnet + Exit-Node la commande sera plutôt celle ci :

 sudo tailscale up --advertise-routes=192.168.210.0/24 --advertise-exit-node

Voilà, c'est pas plus compliqué, c'est gratuit et c'est sur. En parlant de gratuit sachez que Free a démarré une beta afin d'intégrer Wireguard dans les Freebox et que Wireguard tout comme Zerotier peut être intégré dans certains routeurs (Unifi, Asus, etc...) ce qui vous évitera la petite machine Linux. Mais ce n'est pas forcément aussi simple.

Sources :

 

Home Assistant & Hygrostat

Longtemps j'ai utilisé l'intégration custom Hygrostat pour forcer la VMC lorsque quelqu'un prend une douche. C'est un détournement en ce sens qu'un hygrostat est plutôt fait pour fonctionner de façon contestante, come par exemple dans une cave à vin. 

La dernière version de Home Assistant (2021-08) apporte dans son lot de nouveautés un hygrostat intégré. J'ai donc naturellement voulut l'utiliser. Mais celui ci, bien que plous évolué, ne me semble pas fait pour travailler sur un écart rapide d'humidité. Hors le niveau d'humidité dans une maison varie en fonction des saisons et de la la météo. Donc, à moins de l'ajuster manuellement tous les jours, si je règle la consigne sur une valeur arbitraire, le résultat sera aléatoire.

Mon idée, il y a surement d'autres façons de faire, est de calculer la moyenne de la valeur humidité sur les 8 heures passées et d'ajuster dynamiquement la consigne de mon hygrostat toutes les heures (ces deux valeurs restent à affiner).

Mise en œuvre

On commence par déclarer l'intégration dans le fichier de configuration (attention à bien supprimer le custom hygrostat si vous l'utilisiez avant car il a le même nom) :

generic_hygrostat:
  - name: "Hygrostat : SdB"
    humidifier: switch.ipx800_7_vmc
    target_sensor: sensor.rpi_mi_h_sdb
    min_humidity: 20
    max_humidity: 80
    target_humidity: 67
    dry_tolerance: 3
    wet_tolerance: 0
    device_class: "dehumidifier"
    min_cycle_duration:
      seconds: 5
    keep_alive:
      minutes: 3
    initial_state: true
    away_humidity: 35
    away_fixed: True
    sensor_stale_duration: 00:15:00

Pour les détails de sa configuration ça se passe ici.

Je ne me suis pas vraiment intéressé aux autres valeurs possibles, mais dans notre cas on veut déshumidifier, donc attention du device class...

Ensuite je vais utiliser l'intégration Statistics afin de calculer la moyenne du taux d'humidité de la salle de bain durant les 8 dernières heures :

    - platform: statistics
      name: 'Humidité Salle de Bain (moyenne)'
      entity_id: sensor.rpi_mi_h_sdb
      sampling_size: 700     
      max_age:
        hours: 8

Et pour terminer je vais créer une petite automation qui va ajuster la consigne en fonction de cette moyenne en ajoutant + 10 :de façon à ce que la VMC se mette en route si l'humidité monte lors d'une douche :

- alias: '000 : Set Hygrostat'
  trigger:
  - platform: time_pattern
    hours: '1'
  action:
  - service: humidifier.set_humidity
    target:
      entity_id: humidifier.hygrostat_sdb
    data_template:
      humidity: "{{ states('sensor.humidite_salle_de_bain_moyenne') | float + 10 }}"

Il ne reste plus qu'à tester... Et surement à affiner les valeurs.

Alternatives

L'objectif étant de forcer la VMC lors d'une douche il y a d'autres alternatives à explorer :

  • Détecter si quelqu'un prend une douche : ça pourrait se faire en détectant le débit de l'eau... un peu compliqué ! Ou encore avec un détecteur de présence dans la cabine de douche. A creuser.
  • Par un scénario : en hiver quand je dis à Alexa que j'ai l'intention d'aller me doucher, elle lance un script qui va lancer une play list sur Sonos et un radiateur soufflant, ensuite elle me dit (c'est bien la seule à répondre à touys mes souhaits !) quand la température est idéale pour me doucher. Je pourrait inclure la VMC.

Enjoy !

PS : idées et corrections bienvenues...