absalom-muc / MHI-AC-Ctrl

Reads and writes data (e.g. power, mode, fan status etc.) from/to a Mitsubishi Heavy Industries (MHI) air conditioner (AC) via SPI controlled by MQTT
MIT License
279 stars 62 forks source link

Added inbuilt Webserver, httpupdater, InfluxDB Support, LED status #162

Closed kARTechnology closed 1 year ago

kARTechnology commented 1 year ago

I have been using since project since a long time with webserver and influxdb v1 data logging which can be visualised with free chronograf, both are opensource. also the webserver makes it easier to make any schedules using cron. request to please merge and enhance the project as very little changes are necessary.

the inbuilt led is flashed momentarily when there is data tx/rx from the ac..

output:

image

main ino file: https://www.diffchecker.com/podzoiXc/

changes made in files: support.h:

extern int WIFI_lost;
extern int MQTT_lost;

#include <ESP8266WebServer.h>
#include <ESP8266HTTPUpdateServer.h>
extern ESP8266WebServer server;
extern ESP8266HTTPUpdateServer httpUpdater;
#include <InfluxDbClient.h>
extern InfluxDBClient* iclient; 
void startHttpServer();

changes made in support.cpp:

Point point_mhiac("mhi_status");
void buzz(int speed) {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(speed);
  digitalWrite(LED_BUILTIN, LOW);
  delay(speed);
  digitalWrite(LED_BUILTIN, HIGH);
}

changes made to function in support.cpp:

void output_P(const ACStatus status, PGM_P topic, PGM_P payload) {
  const int mqtt_topic_size = 100;
  char mqtt_topic[mqtt_topic_size];
  Serial.printf_P(PSTR("status=%i topic=%s payload=%s\n"), status, topic, payload);
  if ((status & 0xc0) == type_status)
    strncpy_P(mqtt_topic, PSTR(MQTT_PREFIX), mqtt_topic_size);
  else if ((status & 0xc0) == type_opdata)
    strncpy_P(mqtt_topic, PSTR(MQTT_OP_PREFIX), mqtt_topic_size);
  else if ((status & 0xc0) == type_erropdata)
    strncpy_P(mqtt_topic, PSTR(MQTT_ERR_OP_PREFIX), mqtt_topic_size);
  strncat_P(mqtt_topic, topic, mqtt_topic_size - strlen(mqtt_topic));
  MQTTclient.publish_P(mqtt_topic, payload, true);

  if (!isdigit(String(payload)[0]))
    point_mhiac.addField(String(mqtt_topic), String(payload));
  else {
    String dat = String(payload);
    point_mhiac.addField(String(mqtt_topic), dat.toFloat());
  }
  if (!iclient->writePoint(point_mhiac)) Serial.print(F("InfluxDB write failed: "));
  point_mhiac.clearFields();
  buzz(15);
}

new http.ino


void startHttpServer() {
  server.on("/reset", handle_reset);
  server.on("/upload", HTTP_GET, []() {  // if the client requests the upload page
    server.send(200, "text/html", F("<html><body>  <form method=\"POST\" enctype=\"multipart/form-data\" action=\"/uploadfile\"><input type=\"file\" name=\"data\"><input class=\"button\" type=\"submit\" value=\"Upload\"></form></body></html>"));
  });

  server.on("/cmd", HTTP_GET, []() {
    server.send(200, "text/html", String(" <html><head><meta http-equiv=\"refresh\" content=\"1;url=/\" /> <html> </head><body> <h1>" + server.arg("payload") + "-" + server.arg("topic") + " </body></html>"));

    char buf[10];  //make this the size of the String
    server.arg("payload").toCharArray(buf, 10);

    char buf2[25];  //make this the size of the String
    server.arg("topic").toCharArray(buf2, 25);

    MQTT_subscribe_callback(buf2, (byte*)buf, sizeof(buf));
  });

  server.on(
    "/uploadfile", HTTP_POST, []() {
      server.send(200);
    },
    handleFileUpload);
  server.on("/", HTTP_GET, []() {
    server.chunkedResponseModeStart(200, F("text/html"));
    server.sendContent("<html><head><title>" + String(HOSTNAME) + "</title>");
     server.sendContent("<style>body {  font-family: Verdana, sans-serif;} </style></head>");
     server.sendContent("<body> <h1>MHI-AC-CONTROL: " + String(HOSTNAME));
    switch (power_status) {
      case on: server.sendContent("<br>Power: On"); break;
      case off: server.sendContent("<br>Power: Off"); break;
      case unknown: server.sendContent("<br>Power: Unknown"); break;
    }
    server.sendContent(
        "<br> Mode: " + String(acmode)
      + "<br> Set Temp: " + String(settemp)
      + "<br> Return Air: " + String(returnair)
      + "<br> Fan Speed: " + String(fanstate) 
       + "<br> <button onclick=\"document.location='/cmd?payload=on&topic=" + String(HOSTNAME) + "/set/Power'\">On</button>"
      + "     <button onclick=\"document.location='/cmd?payload=off&topic=" + String(HOSTNAME) + "/set/Power'\">Off</button>"

      + "<br> <button onclick=\"document.location='/cmd?payload=Auto&topic=" + String(HOSTNAME) + "/set/Mode'\">Auto</button>"
      + "     <button onclick=\"document.location='/cmd?payload=Cool&topic=" + String(HOSTNAME) + "/set/Mode'\">Cool</button>"
      + "     <button onclick=\"document.location='/cmd?payload=Dry&topic=" + String(HOSTNAME) + "/set/Mode'\">Dry</button>"
      + "     <button onclick=\"document.location='/cmd?payload=Fan&topic=" + String(HOSTNAME) + "/set/Mode'\">Fan</button>"

      + "<br> <button onclick=\"document.location='/cmd?payload=Auto&topic=" + String(HOSTNAME) + "/set/Fan'\">Fan Auto</button>"
      + "     <button onclick=\"document.location='/cmd?payload=1&topic=" + String(HOSTNAME) + "/set/Fan'\">Fan 1</button>"
      + "     <button onclick=\"document.location='/cmd?payload=2&topic=" + String(HOSTNAME) + "/set/Fan'\">Fan 2</button>"
      + "     <button onclick=\"document.location='/cmd?payload=3&topic=" + String(HOSTNAME) + "/set/Fan'\">Fan 3</button>"
      + "     <button onclick=\"document.location='/cmd?payload=4&topic=" + String(HOSTNAME) + "/set/Fan'\">Fan 4</button>"

      + "<br> <button onclick=\"document.location='/cmd?payload=1&topic=" + String(HOSTNAME) + "/set/Vanes'\">Vane 1</button>"
      + "     <button onclick=\"document.location='/cmd?payload=2&topic=" + String(HOSTNAME) + "/set/Vanes'\">Vane 2</button>"
      + "     <button onclick=\"document.location='/cmd?payload=3&topic=" + String(HOSTNAME) + "/set/Vanes'\">Vane 3</button>"
      + "     <button onclick=\"document.location='/cmd?payload=4&topic=" + String(HOSTNAME) + "/set/Vanes'\">Vane 4</button>"
      + "     <button onclick=\"document.location='/cmd?payload=Swing&topic=" + (HOSTNAME) + "/set/Vanes'\">Swing</button>"

      + "<br> <button onclick=\"document.location='/cmd?payload=1&topic=" + String(HOSTNAME) + "/set/VanesLR'\"><<</button>"
      + "     <button onclick=\"document.location='/cmd?payload=2&topic=" + String(HOSTNAME) + "/set/VanesLR'\"><</button>"
      + "     <button onclick=\"document.location='/cmd?payload=3&topic=" + String(HOSTNAME) + "/set/VanesLR'\">|</button>"
      + "     <button onclick=\"document.location='/cmd?payload=4&topic=" + String(HOSTNAME) + "/set/VanesLR'\">></button>"
      + "     <button onclick=\"document.location='/cmd?payload=4&topic=" + String(HOSTNAME) + "/set/VanesLR'\">>></button>"
      + "     <button onclick=\"document.location='/cmd?payload=4&topic=" + String(HOSTNAME) + "/set/VanesLR'\"><></button>"
      + "     <button onclick=\"document.location='/cmd?payload=4&topic=" + String(HOSTNAME) + "/set/VanesLR'\">><</button>"
      + "     <button onclick=\"document.location='/cmd?payload=Swing&topic=" + (HOSTNAME) + "/set/VanesLR'\">Swing</button>"

      + "<br> <button onclick=\"document.location='/cmd?payload=On&topic=" + String(HOSTNAME) + "/set/3Dauto'\">3D Auto On</button>"
      + "     <button onclick=\"document.location='/cmd?payload=Off&topic=" + String(HOSTNAME) + "/set/3Dauto'\">3D Auto Off</button>"

      + "<br> <input type=\"number\" min='18' max='30' id=\"temp\" value='" + String(settemp) + "'>  </input>"

      + "    <button onclick=\"document.location='/cmd?payload='+ document.getElementById('temp').value +'&topic=" + (HOSTNAME) + "/set/Tsetpoint'\">Set Temp</button>"

      + "<br> <br> <br> <button onclick=\"document.location='/update'\">Update</button>"
      + "     <button onclick=\"document.location='/cmd?payload=reset&topic=" + (HOSTNAME) + "/set/reset'\">Reset</button>"
      + "     <button onclick=\"document.location='/cmd?payload=ErrOpData&topic=" + (HOSTNAME) + "/set/ErrOpData'\">Error Operating Data to MQTT Client</button>");

    server.sendContent("<br> WIFI Strength:" + String(WiFi.RSSI()));
    server.sendContent("<br> IP: " + WiFi.localIP().toString());
    server.sendContent("<br> WIFI_lost:" + String(WIFI_lost));
    server.sendContent("<br> MQTT_lost:" + String(MQTT_lost));
    server.sendContent("</body></html>");
    server.chunkedResponseFinalize();
  });

  server.onNotFound(handleNotFound);
  server.begin();
  Serial.println(F("HTTP server started."));
}

void handle_cmd() {
  if (server.arg("state") == "sleep") {

  } else if (server.arg("state") == "off") {
  }

  server.sendHeader(F("Access-Control-Allow-Origin"), F("*"));
  server.send(200, F("text/plain"), "hi");
}

String formatBytes(size_t bytes) {  // convert sizes in bytes to KB and MB
  if (bytes < 1024) {
    return String(bytes) + "B";
  } else if (bytes < (1024 * 1024)) {
    return String(bytes / 1024.0) + "KB";
  } else if (bytes < (1024 * 1024 * 1024)) {
    return String(bytes / 1024.0 / 1024.0) + "MB";
  }
}

bool startSPIFFS() {     // Start the SPIFFS and list all contents
  if (SPIFFS.begin()) {  // Start the SPI Flash File System (SPIFFS)
    Serial.println(F("SPIFFS started. Contents:"));
    Dir dir = SPIFFS.openDir("/");
    while (dir.next()) {  // List the file system contents
      String fileName = dir.fileName();
      size_t fileSize = dir.fileSize();
      Serial.printf("\tFS File: %s, size: %s\r\n", fileName.c_str(), formatBytes(fileSize).c_str());
    }
    Serial.printf("\n");
    return true;
  }
  return false;
}

bool handleFileRead(String path) {  // send the right file to the client (if it exists)
  // Serial.println("handleFileRead: " + path);
  if (path.endsWith(F("/"))) path += "index.html";  // If a folder is requested, send the index file
  String contentType = getContentType(path);        // Get the MIME type
  String pathWithGz = path + ".gz";
  if (SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)) {  // If the file exists, either as a compressed archive, or normal
    if (SPIFFS.exists(pathWithGz)) path += ".gz";          // If there's a compressed version available Use the compressed verion
    File file = SPIFFS.open(path, "r");                    // Open the file
    server.streamFile(file, contentType);                  // Send it to the client
    file.close();                                          // Close the file again
    //   Serial.println("\tSent file: " + path);
    return true;
  }
  //  Serial.println("\tFile Not Found: " + path);          // If the file doesn't exist, return false
  return false;
}
void handleNotFound() {                 // if the requested file or page doesn't exist, return a 404 not found error
  if (!handleFileRead(server.uri())) {  // check if the file exists in the flash memory (SPIFFS), if so, send it
    server.send(404, F("text/plain"), F("404: File Not Found"));
  }
}

String getContentType(String filename) {  // determine the filetype of a given filename, based on the extension
  if (filename.endsWith(F(".html"))) return F("text/html");
  else if (filename.endsWith(F(".css"))) return F("text/css");
  else if (filename.endsWith(F(".js"))) return F("application/javascript");
  else if (filename.endsWith(F(".ico"))) return F("image/x-icon");
  else if (filename.endsWith(F(".gz"))) return F("application/x-gzip");
  return F("text/plain");
}
void handle_reset() {
  server.send(200, F("text/plain"), F("resetting..."));
  ESP.restart();
}
File fsUploadFile;
void handleFileUpload() {  // upload a new file to the SPIFFS

  Serial.println("handleFileUpload: ");

  HTTPUpload& upload = server.upload();
  String path;

  if (upload.status == UPLOAD_FILE_START) {
    path = upload.filename;
    Serial.println("Uplod: " + path);
    if (!path.startsWith("/")) path = "/" + path;
    if (!path.endsWith(".gz")) {         // The file server always prefers a compressed version of a file
      String pathWithGz = path + ".gz";  // So if an uploaded file is not compressed, the existing compressed
      if (SPIFFS.exists(pathWithGz))     // version of that file must be deleted (if it exists)
        SPIFFS.remove(pathWithGz);
    }
    Serial.print("handleFileUpload Name: ");
    Serial.println(path);
    fsUploadFile = SPIFFS.open(path, "w");  // Open the file for writing in SPIFFS (create if it doesn't exist)
    path = String();
  } else if (upload.status == UPLOAD_FILE_WRITE) {
    if (fsUploadFile)
      fsUploadFile.write(upload.buf, upload.currentSize);  // Write the received bytes to the file
  } else if (upload.status == UPLOAD_FILE_END) {
    if (fsUploadFile) {      // If the file was successfully created
      fsUploadFile.close();  // Close the file again
      Serial.print("handleFileUpload Size: ");
      Serial.println(upload.totalSize);
      server.send(200, "text/plain", "Upload Success");

      //    server.sendHeader("Location", "/success.html");     // Redirect the client to the success page
      //    server.send(303);
    } else {
      server.send(500, "text/plain", "500: couldn't create file");
    }
  }
}
glsf91 commented 1 year ago

Looks nice but I'm not going to integrate this. I (and also absalom-muc I think) want to keep this module as clean as possible. Too much not related stuff will be added. You can make your own fork if you want.

glsf91 commented 1 year ago

I will close this one.