Plafonnier controlé via ethernet par arduino

Bonjour,

J’ai un plafonnier à LED, contrôlable par une télécommande 2.4Ghz, utilisant un protocole non reconnu.

Pour le contrôler, j’ai utilisé un arduino pour gérer la télécommande, via des transistors qui simulent l’appui sur un bouton.

L’arduino est connecté au réseau via un module ethernet.
Le code que j’ai créé agit de la façon suivante :

  • Au démarrage, il déclenche l’allumage de la lampe et une mise à 100% du niveau de luminosité.
  • Ensuite, il écoute en réseau pour récupérer via l’url des paramètres d’allumage, luminosité, et chaleur
    http://192.168.XX.YY/?Status=on&Level=100&Hue=250
#include <SPI.h>
#include <Ethernet.h>
#include <ArduinoJson.h>

// MAC address for shield
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x5E, 0xED};
IPAddress ip(192,168,XX,YY);
EthernetServer server(80); // Using port 80
int led[] = {2,3,4,5,6,7}; // LED attached to pin 7
float chaud = 50;
bool Status = true;
int Level = 100;
int Hue = 255;
bool nStatus = true;
int nLevel = 100;
int nHue = 255;
int pStatus;
int pLevel;
int pHue;
String sBuffer;
void setup() {
  for (int i=2; i<=7; i++) {
    pinMode(led[i], OUTPUT); // Led set as an output
  }    
  Ethernet.begin(mac,ip); // Start the Ethernet shield
  server.begin();
  Serial.begin(9600); // Start serial communication
  Serial.println("Server address:"); // Print server address
  // (Arduino shield)
  Serial.println(Ethernet.localIP());
  //Allume la lampe
  digitalWrite(2, HIGH);
  delay(300);
  digitalWrite(2, LOW); 
  //Lumière à fond
  digitalWrite(5, HIGH);
  delay(3000);
  digitalWrite(5, LOW); 
  //Couleur chaude 
  digitalWrite(7, HIGH);
  delay(3000);
  digitalWrite(7, LOW);
  nStatus = 1;
  nLevel = 100;
  nHue = 255;  
  Status = 1;
  Level = 100;
  Hue = 255;      
}
void loop() {
  EthernetClient client = server.available();
  if (client) {
    boolean currentLineIsBlank = true;
    String buffer = "";
    while (client.connected()) {
      if (client.available()) {
        char c = client.read(); // Read from the Ethernet shield
        buffer += c; // Add character to string buffer
        // Client sent request, now waiting for response
        if (c == '\n' && currentLineIsBlank) {
          client.println("HTTP/1.1 200 OK"); // HTTP response
          client.println("Content-Type: application/json");
          client.println(); // HTML code
          client.print('{"State": "' + Status);
          client.print('", "Level": "' + Level);
          client.print('", "Hue": "' + Hue);
          client.print('"}');
          break;
        }
        if (c == '\n') {
          currentLineIsBlank = true;
          buffer = "";
        }
        else if (c == '\r') { // Command from webpage
          //Serial.println(buffer); 
          //Serial.println(buffer.indexOf("?on"));
          //Serial.println(buffer.indexOf("?hue"));
          //Position des informations
          pStatus = buffer.indexOf("Status=");       
          pLevel = buffer.indexOf("Level=");       
          pHue = buffer.indexOf("Hue=");
          sBuffer = buffer;           
          //Nouvelles infos transmises
          if (pStatus >= 0) 
          {
            if (buffer.length() >= pStatus + 8 ) {
              //Serial.println(buffer[pStatus + 8]);              
              if (buffer[pStatus + 8] == 'n') {
                nStatus = 1; 
              }             
              else {
                nStatus = 0;
              }
            }
          }      
          if (pLevel >= 0) 
          {
            if (buffer.length() >= pLevel + 9 ) {
              nLevel = sBuffer.substring(pLevel+6,pLevel+9).toInt();
              //Serial.println(nLevel);
            }
          } 
          if (pHue >= 0) 
          {
            if (buffer.length() >= pHue + 7 ) {
              nHue = sBuffer.substring(pHue+4,pHue+7).toInt();
              //Serial.println(nHue);
            }
          }
          //Vérification que les données sont nouvelles :
              //Serial.println(nStatus);
              //Serial.println(nLevel);
          if (nStatus != Status) {
            //Extinction de la lampe
            if (nStatus == 0) {            
              digitalWrite(3, HIGH);
              delay(300);
              digitalWrite(3, LOW);
            }
            //Allumage de la lampe
            else {        
              digitalWrite(2, HIGH);
              delay(300);
              digitalWrite(2, LOW);              
            }
            //MàJ des valeurs
            Status = nStatus;
          }
          if (nStatus == 1 && nLevel != Level) {
              Serial.println(nLevel);
              Serial.println(Level);
            if (nLevel >= Level) {
              digitalWrite(5, HIGH);
              delay((nLevel-Level)*32);
              //Serial.println((nLevel-Level)*35);
              digitalWrite(5, LOW);              
            }
            else {
              digitalWrite(6, HIGH);
              delay((Level-nLevel)*32);
              //Serial.println((Level-nLevel)*35);
              digitalWrite(6, LOW);               
            }
            //MàJ des valeurs
            Level = nLevel;  
          }
          if (nStatus == 1 && nHue != Hue) {
            if (nHue >= Hue) {
              digitalWrite(7, HIGH);
              delay((nHue-Hue)*12);
              digitalWrite(7, LOW);              
            }
            else {
              digitalWrite(4, HIGH);
              delay((Hue-nHue)*12);
              digitalWrite(4, LOW);               
            }
            //MàJ des valeurs
            Hue = nHue;      
          }  
        }
        else {
          currentLineIsBlank = false;
        }
      }
    }
    client.stop(); // End server
  }
}

A présent, je souhaiterais le connecter à HASS, pour le contrôler et l’utiliser dans des automatismes, et récupérer les paramètres au démarrage.

J’ai essayé de créer un light template dans mon fichier yaml, qui agit sur un switch command_line.
Je me suis inspiré de ce tuto : https://www.jamesridgway.co.uk/using-a-template-light-to-control-a-custom-light-in-home-assistant/
Dans cet article, l’auteur utilise un switch rest. Mais ça ne me semble pas compatible avec l’architecture programmée dans mon arduino (nécessite une modification complète du code), pour qu’il digère un json qui lui serait envoyé.
Autre problème, le template light n’intègre que la luminosité mais pas la couleur.

J’ai donc créé des entités correspondant au niveau de luminosité et couleur, ainsi qu’un interrupteur, et je souhaite que lorsque l’interrupteur est activé, une url soit formatée et lancée, en intégrant les différents paramètres.

Je bloque sur cette dernière étape, je ne vois pas graphiquement des outils permettant de le faire, et je ne trouve pas dans le manuel comment ajouter des paramètres à une url.

Je vous remercie par avance pour votre aide, et vos commentaires sur la démarche.

Salut,

Connais-tu Nodered ?

Je pense qu’il y a moyen de faire ce que tu veux avec sans que ce soit trop complexe.

Salut,

Le template Light intègre la gestion de la couleur.
Passer par des appels d’urls avec des commande REST c’est totalement faisable sans utiliser de JSON, en passant les paramètres par l’url, il y a un exemple dans la doc HA.

Je sais que tu ne veux pas forcément changer to code, mais une façon pas trop compliquée de faire ça c’est en passant par MQTT. Si tu en as déjà installé dans ton réseau sans trop de protocole à écrire. Mais bon faut tout changer dans le code.
J’ai fait ça pour contrôler des rubans led adressables, c’était plutôt facile. Ca tourne bien et c’est super réactif.

Merci pour vos retours!

@AlexHass dans la doc, je n’ai pas trouvé d’exemple où ils adaptent l’url avec les paramètres. Ils passent seulement des variables par post, ce qui n’est pas mon cas actuellement. Si tu as un exemple je suis preneur, ça me permettrait d’arriver plus rapidement à mes fins.

Sinon je n’ai pas de problème pour changer le code, je me doutais qu’il faudrait surement adapter un peu… voire beaucoup!

Je n’ai pas de MQTT d’installer, alors passer par ce protocole me parait déployer une usine à gaz… mais je le garde dans un coin de la tête, en backup.
Même chose pour NodeRed.

Salut,

Une solution serait de créer des commandes rest et non un switch rest.
Comme tu as déjà créé tes entités pour les différents paramètres, tu peux créer une commande par exemple qui sera appelée quand tu fais un « turn_on » de ta lumière et qui enverra la bonne URL.
Et une 2ème commande qui sera reliée au 'turn_off", qui j’imagine est une commande fixe.
Le tout attachés aux action on & off d’un switch template.

Ca pourrait ressembler à ça (a noter je n’ai pas testé, y’a ptet une erreur de syntaxe dedans):

rest_command:
  plafonnier_on:
    url: "http://192.168.XX.YY/?Status=on&Level={{states.input_number.plafonnier_level.state}}&Hue={{states.input_number.plafonnier_hue.state}}"
  plafonnier_off:
    url: "http://192.168.XX.YY/?Status=off"

Bonjour,

Après beaucoup d’acharnement, voici le résultat final :

J’ai repris le code de l’arduino pour qu’il lise du JSON, et renvoi du JSON.

Au démarrage, il va chercher sur HASS les infos du niveau de lumière.

#include <SPI.h>
#include <Ethernet.h>
#include <ArduinoJson.h>
#include <StreamUtils.h>

//Variable pour ethernet
EthernetClient client;
// MAC address for shield
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x5E, 0xED};
IPAddress ip(192,168,XX,YY);
EthernetServer server(80); // Using port 80
//Variable pour Pins
int led[] = {2,3,4,5,6,7}; // LED attached to pin 7
//Variable pour tampon et analyse
String sBuffer;
int sBlen;
//Variable pour lampe
struct infoLight {
  bool state;
  int brightness;
  int hue;
};
struct infoLight curLight;
struct infoLight newLight;

//API HASS:
// Name address for Open Weather Map API
const unsigned long HTTP_TIMEOUT = 10000;  // max respone time from server
const size_t MAX_CONTENT_SIZE = 512;       // max size of the HTTP response
const char* HASSserver = "192.168.XX.ZZ";
const int* HASSport = 8123;
const char* HASSresourceState = "/api/states/input_boolean.salon_light_switch";
const char* HASSresourceBrightness = "/api/states/input_number.salon_light_brightness";
const char* HASSresourceHue = "/api/states/input_number.salon_light_hue";
struct clientData {
  char state[8];
};
const char* HASSstate = "";
int HASSstateInt;


//SETUUUUUUP
void setup() {
  //INIT CONSOLE
  Serial.begin(9600); // Start serial communication
  //INIT PINS
  for (int i=2; i<=7; i++) {
    pinMode(led[i], OUTPUT); // Led set as an output
  }
  ////REINITIALISATION DE LA LAMPE
  //digitalWrite(2, HIGH);
  //delay(300);
  //digitalWrite(2, LOW); 
  //Lumière à fond
  //digitalWrite(5, HIGH);
  //delay(3000);
  //digitalWrite(5, LOW); 
  //Couleur chaude 
  //digitalWrite(7, HIGH);
  //delay(3000);
  //digitalWrite(7, LOW);   
  //INITIALISATION DU SHIELD ETHERNET  
  Ethernet.begin(mac,ip);
  
  //Recupération des valeurs HASS :
  if(connect(HASSserver,HASSport)) {
      if(sendRequest(HASSserver, HASSresourceState) && skipResponseHeaders()) {
        clientData clientData;
        if(readReponseContent(&clientData)) {
          //printclientData(&clientData);
          Serial.print(HASSstate);   
          if (strcmp(HASSstate,"on")==1) {
            curLight.state = true;
          }
          else {
            curLight.state = false;            
          }
      }
    }
  }
  disconnect();
  if(connect(HASSserver,HASSport)) {
      if(sendRequest(HASSserver, HASSresourceBrightness) && skipResponseHeaders()) {
        clientData clientData;
        if(readReponseContent(&clientData)) {
          //printclientData(&clientData);
          HASSstateInt = cToI(HASSstate);
          Serial.print(HASSstateInt);     
          curLight.brightness = cToI(HASSstate);  
      }
    }
  }
  disconnect();
  if(connect(HASSserver,HASSport)) { 
      if(sendRequest(HASSserver, HASSresourceHue) && skipResponseHeaders()) {
        clientData clientData;
        if(readReponseContent(&clientData)) {
          //printclientData(&clientData);     
          curLight.hue =  cToI(HASSstate);  
      }
    }
  }
  disconnect();
  
  //Print donnée récup sur HASS
  Serial.println("State :");  
  Serial.println(curLight.state); 
  Serial.println("Hue :");  
  Serial.println(curLight.hue);  
  Serial.println("Brightness :");
  Serial.println(curLight.brightness); 

     

  //SERVEUR
  server.begin();  
  Serial.println("Server address:"); // Print server address
  // (Arduino shield)
  Serial.println(Ethernet.localIP());  
}


//LOOOOOOOOOOP
void loop() {
  EthernetClient client = server.available();
  
  //Client?
  if (!client) return;
  //Oui!
  Serial.println(F("Nouveau Client"));
  //Init du tampon
  boolean currentLineIsBlank = true;
  String buffer = "";
  //Lecture du client
  while (client.available()) {
    //Lecture depuis le Shield Ethernet
    char c = client.read();    
    //Ajout du caractère au tampon 
    buffer += c;   
    //Dernier caractère? Envoi de la réponse
    if (c == '\n' && currentLineIsBlank) {
      //Création du JSON
      StaticJsonDocument<500> docResponse;
      //Ajout des valeurs
      docResponse["state"] = newLight.state;
      docResponse["hue"] = newLight.hue;
      docResponse["brightness"] = newLight.brightness;
      //Preview du JSON 
      Serial.print(F("Sending: "));
      serializeJson(docResponse, Serial);
      Serial.println();
      // Write response headers      
      client.println(F("HTTP/1.0 200 OK"));
      client.println(F("Content-Type: application/json"));
      client.println(F("Connection: close"));
      client.print(F("Content-Length: "));
      client.println(measureJsonPretty(docResponse));
      client.println();

      // Write JSON document
      serializeJsonPretty(docResponse, client);

      // Disconnect
      disconnect();

      //RefreshLight
      refreshLight(); 
    }
    if (c == '\n') {
      currentLineIsBlank = true;
      buffer = "";
    }
    else if (c == '\r') { // Command from webpage
      //Position des informations
      sBuffer = buffer;  
      if (buffer.indexOf("state") >=0 && buffer.indexOf("brightness") >=0 && buffer.indexOf("hue") >=0) {
        //Création d'un JSOn
        StaticJsonDocument<200> inputJson;
        //Extraction des données
        sBuffer = buffer.substring(buffer.indexOf("state")-2); 
        
        // Length (with one extra character for the null terminator)
        int sBlen = sBuffer.length() + 1; 
        // Prepare the character array (the buffer) 
        char cBuffer[sBlen];
        // Copy it over 
        sBuffer.toCharArray(cBuffer, sBlen);
        Serial.println(cBuffer);

        //ReadLoggingStream loggingStream(client, Serial);
        DeserializationError error = deserializeJson(inputJson, cBuffer);
        if (error) {
          Serial.print(F("deserializeJson() failed: "));
          Serial.println(error.f_str());
          Serial.println(cBuffer);
          return;
        }
        //Inscription dans le struct
        newLight.state = inputJson["state"];
        newLight.brightness = inputJson["brightness"];
        newLight.hue = inputJson["hue"];

      }           
    }
    else {
      currentLineIsBlank = false;
    }
  }
}


//FUNCTIONS
// Open connection to the HTTP server
bool connect(const char* hostName, const int* port) {
  Serial.print("Connect to ");
  Serial.println(hostName);

  bool ok = client.connect(hostName, port);

  Serial.println(ok ? "Connected" : "Connection Failed!");
  return ok;
}

// Send the HTTP GET request to the server
bool sendRequest(const char* host, const char* resource) {
  Serial.print("GET ");
  Serial.println(resource);

  client.print("GET ");
  client.print(resource);
  client.println(" HTTP/1.1");
  client.println(F("Content-Type: application/json"));
  client.println(F("Authorization: Bearer xxxxxxTOKENxxxxxxxx"));
  client.print("Host: ");
  client.println(host);
  client.println("Connection: close");
  client.println();

  return true;
}

// Skip HTTP headers so that we are at the beginning of the response's body
bool skipResponseHeaders() {
  // HTTP headers end with an empty line
  char endOfHeaders[] = "\r\n\r\n";

  client.setTimeout(HTTP_TIMEOUT);
  bool ok = client.find(endOfHeaders);

  if (!ok) {
    Serial.println("No response or invalid response!");
  }
  return ok;
}

bool readReponseContent(struct clientData* clientData) {
  // Compute optimal size of the JSON buffer according to what we need to parse.
  const size_t bufferSize = JSON_ARRAY_SIZE(1) + JSON_OBJECT_SIZE(1) + 
      2*JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(4) + JSON_OBJECT_SIZE(5) + 
      JSON_OBJECT_SIZE(6) + JSON_OBJECT_SIZE(12) + 390;
  StaticJsonDocument<512> doc;
  //ReadLoggingStream loggingStream(client, Serial);
  DeserializationError err = deserializeJson(doc, client);//remplacer client par loggingStream pour debug

  if (err) {
    Serial.println("JSON parsing failed!");
    Serial.println(err.f_str());
    return false;
  }

  // Here were copy the strings we're interested in using to your struct data
  HASSstate = doc["state"]; // "on"
  Serial.println(err.f_str()); 
  //serializeJsonPretty(doc, Serial);
  //Serial.println(HASSstate);
  return true;
}

// Print the data extracted from the JSON
void printclientData(const struct clientData* clientData) {
  Serial.print("Status = ");
  Serial.println(clientData->state);
  //HASSstate = clientData->state;
}

// Close the connection with the HTTP server
void disconnect() {
  Serial.println("Disconnect");
  client.stop();
}

// Pause for a 1 minute
void wait() {
  Serial.println("Wait 60 seconds");
  delay(60000);
}

void refreshLight() {  
      //Vérification que les données sont nouvelles :
      if (newLight.state != curLight.state) {
        //Extinction de la lampe
        if (newLight.state == false) {            
          digitalWrite(3, HIGH);
          delay(300);
          digitalWrite(3, LOW);
        }
        //Allumage de la lampe
        else {        
          digitalWrite(2, HIGH);
          delay(300);
          digitalWrite(2, LOW);              
        }
        //MàJ des valeurs
        curLight.state = newLight.state;
      }
      if (newLight.state == 1 && newLight.brightness != curLight.brightness) {
        if (newLight.brightness >= curLight.brightness) {
          digitalWrite(5, HIGH);
          delay((newLight.brightness-curLight.brightness)*32);
          digitalWrite(5, LOW);              
        }
        else {
          digitalWrite(6, HIGH);
          delay((curLight.brightness-newLight.brightness)*32);
          digitalWrite(6, LOW);               
        }
        //MàJ des valeurs
        curLight.brightness = newLight.brightness;
      }
      if (newLight.state == 1 && newLight.hue != curLight.hue) {
        if (newLight.hue >= curLight.hue) {
          digitalWrite(7, HIGH);
          delay((newLight.hue-curLight.hue)*12);
          digitalWrite(7, LOW);              
        }
        else {
          digitalWrite(4, HIGH);
          delay((curLight.hue-newLight.hue)*12);
          digitalWrite(4, LOW);               
        }
        //MàJ des valeurs
        curLight.hue = newLight.hue;
      }
}

int cToI(const char* aChar) {
    String sChar = String(aChar);// converting a constant char into a String
    return sChar.toInt();
}

Côté HASS, j’ai réussi à transmettre les infos via les commandes suivantes :
sur configuration.yaml :


rest_command:
  salon_light_on:
    #url: "http://192.168.94.89/?Status={{state}}&Level={{brightness}}&Hue={{hue}}"
    url: "http://192.168.94.89/"
    method: POST
    content_type:  'application/json'
    headers:
        json: '{"state":true,"brightness":{{brightness}},"hue":{{hue}}}'
  #salon_light_off:
    #url: "http://192.168.94.89/?Status=off"
  salon_light_off:
    url: "http://192.168.XX.YY/"
    method: POST
    content_type:  'application/json'
    headers:
        json: '{"state":false,"brightness":{{brightness}},"hue":{{hue}}}'

et une automatisation dès que les variables changent d’état :

alias: Lumière salon
description: ""
trigger:
  - platform: state
    entity_id:
      - input_boolean.salon_light_switch
  - platform: state
    entity_id:
      - input_number.salon_light_brightness
  - platform: state
    entity_id:
      - input_number.salon_light_hue
condition: []
action:
  - if:
      - condition: state
        entity_id: input_boolean.salon_light_switch
        state: "on"
    then:
      - service: rest_command.salon_light_on
        data: 
          state: "{{ states('input_boolean.salon_light_switch') }}"
          hue: "{{ states('input_number.salon_light_hue') }}"
          brightness: "{{ states('input_number.salon_light_brightness') }}"
    else:
      - service: rest_command.salon_light_off
        data:  
          state: "{{ states('input_boolean.salon_light_switch') }}"
          hue: "{{ states('input_number.salon_light_hue') }}"
          brightness: "{{ states('input_number.salon_light_brightness') }}"
mode: single

Merci pour votre aide!

Vu que tu l’as fait sans, effectivement on peut s’en passer!
Mais, c’est en fait plus simple en MQTT. Les librairies arduino existent et ça marche en callback. A la réception d’un message MQTT tu peux déclencher le bout de code arduino que tu veux. C’est (assez) simple.

1 « J'aime »