Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Airwell - toggling power on every state change #2035

Open
mbrevda opened this issue Sep 3, 2023 · 6 comments
Open

Airwell - toggling power on every state change #2035

mbrevda opened this issue Sep 3, 2023 · 6 comments

Comments

@mbrevda
Copy link

mbrevda commented Sep 3, 2023

Version/revision of the library used

2.8.6

Describe the bug

Airwell commands all toggle power - even when it's already on.

To Reproduce

See sample code, triggered like

curl -X PUT -d '{"temp":23,"power":true,"mode":1,"power":true}' 192.168.1.51/state

When sending this multiple times, power will toggle on and off

Example code used

Include all relevant code snippets or links to the actual code files. Tip: How to quote your code so it is still readable in the report.

/* Copyright 2019 Motea Marius

  This example code will create a webserver that will provide basic control to AC units using the web application
  build with javascript/css. User config zone need to be updated if a different class than Collix need to be used.
  Javasctipt file may also require minor changes as in current version it will not allow to set fan speed if Auto mode
  is selected (required for Airwell).

*/
#include "Web-AC-control.h"
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <ESP8266HTTPUpdateServer.h>
#include <ESP8266WebServer.h>
#endif  // ESP8266
#if defined(ESP32)
#include <ESPmDNS.h>
#include <WebServer.h>
#include <WiFi.h>
#include <Update.h>
#endif  // ESP32
#include <WiFiUdp.h>
#include <WiFiManager.h>
#include <ArduinoJson.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>

//// ###### User configuration space for AC library classes ##########

#include <ir_Airwell.h>  //  replace library based on your AC unit model, check https://github.com/crankyoldgit/IRremoteESP8266

#define AUTO_MODE kAirwellAuto
#define COOL_MODE kAirwellCool
#define DRY_MODE kAirwellDry
#define HEAT_MODE kAirwellHeat
#define FAN_MODE kAirwellFan

#define FAN_AUTO kAirwellFanAuto
#define FAN_MIN kAirwellFanLow
#define FAN_MED kAirwellFanMedium
#define FAN_HI kAirwellFanAuto

// ESP8266 GPIO pin to use for IR blaster.
const uint16_t kIrLed = 4;
// Library initialization, change it according to the imported library file.
IRAirwellAc ac(kIrLed);

/// ##### End user configuration ######


struct state {
  uint8_t temperature = 22, fan = 0, operation = 0;
  bool powerStatus;
};

File fsUploadFile;

// core

state acState;

// settings
char deviceName[] = "AC Remote Control";

#if defined(ESP8266)
ESP8266WebServer server(80);
ESP8266HTTPUpdateServer httpUpdateServer;
#endif  // ESP8266
#if defined(ESP32)
WebServer server(80);
#endif  // ESP32

bool handleFileRead(String path) {
  //  send the right file to the client (if it exists)
  // Serial.println("handleFileRead: " + path);
  if (path.endsWith("/")) 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 (FILESYSTEM.exists(pathWithGz) || FILESYSTEM.exists(path)) {
    // If the file exists, either as a compressed archive, or normal
    // If there's a compressed version available
    if (FILESYSTEM.exists(pathWithGz))
      path += ".gz";  // Use the compressed verion
    File file = FILESYSTEM.open(path, "r");
    //  Open the file
    server.streamFile(file, contentType);
    //  Send it to the client
    file.close();
    // Close the file again
    Serial.println(String("\tSent file: ") + path);
    return true;
  }
  Serial.println(String("\tFile Not Found: ") + path);
  // If the file doesn't exist, return false
  return false;
}

String getContentType(String filename) {
  // convert the file extension to the MIME type
  if (filename.endsWith(".html")) return "text/html";
  else if (filename.endsWith(".css")) return "text/css";
  else if (filename.endsWith(".js")) return "application/javascript";
  else if (filename.endsWith(".ico")) return "image/x-icon";
  else if (filename.endsWith(".gz")) return "application/x-gzip";
  return "text/plain";
}

void handleFileUpload() {  // upload a new file to the FILESYSTEM
  HTTPUpload& upload = server.upload();
  if (upload.status == UPLOAD_FILE_START) {
    String filename = upload.filename;
    if (!filename.startsWith("/")) filename = "/" + filename;
    // Serial.print("handleFileUpload Name: ");
    // Serial.println(filename);
    // Serial.println(upload.contentLength);
    fsUploadFile = FILESYSTEM.open(filename, "w+");
    // Open the file for writing in FILESYSTEM (create if it doesn't exist)
    filename = 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.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");
    }
  }
}


void handleNotFound() {
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i = 0; i < server.args(); i++) {
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
}

void setup() {
  Serial.begin(115200);
  Serial.println();
  ac.begin();
  


  Serial.println("mounting " FILESYSTEMSTR "...");

  if (!FILESYSTEM.begin()) {
     Serial.println("Failed to mount file system");
    return;
  }

  WiFiManager wifiManager;


  if (!wifiManager.autoConnect(deviceName)) {
    delay(3000);
    ESP.restart();
    delay(5000);
  }


#if defined(ESP8266)
  httpUpdateServer.setup(&server);
#endif  // ESP8266



  server.on("/state", HTTP_PUT, []() {
    DynamicJsonDocument root(1024);
    DeserializationError error = deserializeJson(root, server.arg("plain"));
    if (error) {
      server.send(404, "text/plain", "FAIL. " + server.arg("plain"));
    } else {
      if (root.containsKey("temp")) {
        acState.temperature = (uint8_t) root["temp"];
      }

      if (root.containsKey("fan")) {
        acState.fan = (uint8_t) root["fan"];
      }

      if (root.containsKey("power")) {
        acState.powerStatus = root["power"];
      }

      if (root.containsKey("mode")) {
        acState.operation = root["mode"];
      }

      String output;
      serializeJson(root, output);
      server.send(200, "text/plain", output);

      delay(200);
      Serial.print("received power state: ");
      Serial.println(acState.powerStatus ? "true" : "false");
      Serial.print("current power state: ");
      Serial.println(acState.powerStatus ? "true" : "false");

      Serial.print("received temperature: ");
      Serial.println(acState.temperature);
      Serial.print("current temperature: ");
      Serial.println(ac.getTemp());

      Serial.print("received operation: ");
      Serial.println(acState.operation);
      Serial.print("current operation: ");
      Serial.println(ac.getMode());
      
      if (acState.powerStatus) {
        //if (!ac.getPowerToggle()) {
           //Serial.println("here");
          ac.setPowerToggle(true);
        //}


        // if (ac.getTemp() != acState.temperature) {
        //   ac.setTemp(acState.temperature);
        // }


        // if (acState.operation != ac.getMode()) {
        //   if (acState.operation == 1) {
        //     ac.setMode(COOL_MODE);
        //   } else if (acState.operation == 2) {
        //     ac.setMode(HEAT_MODE);
        //   } else if (acState.operation == 3) {
        //     ac.setMode(AUTO_MODE);
        //   } else if (acState.operation == 4) {
        //     ac.setMode(DRY_MODE);
        //   } else if (acState.operation == 5) {
        //     ac.setMode(FAN_MODE);
        //   }
        // }

        if (acState.operation != 3) {
          // if (acState.fan == 0) {
          //   ac.convertFan(FAN_AUTO);
          // } else if (acState.fan == 1) {
          //   ac.convertFan(FAN_MIN);
          // } else if (acState.fan == 2) {
          //   ac.convertFan(FAN_MED);
          // } else if (acState.fan == 3) {
          //   ac.convertFan(FAN_HI);
          // }
        }
      } else {
        ac.setPowerToggle(false);
      }
      ac.send();
      Serial.println("state: " + ac.toString());
    }
  });

  server.on("/file-upload", HTTP_POST,
  // if the client posts to the upload page
  []() {
    // Send status 200 (OK) to tell the client we are ready to receive
    server.send(200);
  },
  handleFileUpload);  // Receive and save the file

  server.on("/file-upload", HTTP_GET, []() {
    // if the client requests the upload page

    String html = "<form method=\"post\" enctype=\"multipart/form-data\">";
    html += "<input type=\"file\" name=\"name\" >";
    html += "<input class=\"button\" type=\"submit\" value=\"Upload\">";
    html += "</form>";
    server.send(200, "text/html", html);
  });

  server.on("/", []() {
    server.sendHeader("Location", String("ui.html"), true);
    server.send(302, "text/plain", "");
  });

  server.on("/state", HTTP_GET, []() {
    DynamicJsonDocument root(1024);
    root["mode"] = acState.operation;
    root["fan"] = acState.fan;
    root["temp"] = acState.temperature;
    root["power"] = acState.powerStatus;
    String output;
    serializeJson(root, output);
    server.send(200, "text/plain", output);
  });


  server.on("/restart", []() {
    server.send(200, "text/html", "reset");
    delay(100);
    ESP.restart();
  });

  server.on("/reset", []() {
    server.send(200, "text/html", "reset");
    delay(100);
    FILESYSTEM.format();
    ESP.restart();
  });

  server.serveStatic("/", FILESYSTEM, "/", "max-age=86400");

  server.onNotFound(handleNotFound);

  server.begin();
}


void loop() {
  server.handleClient();
}

Expected behavior

Mode to be set as sent

Output of raw data from IRrecvDumpV2.ino or V3 (if applicable)

Include some serial text of the raw dumps of what the device saw.

Power On:

Timestamp : 000053.825
Library   : v2.8.6

Protocol  : AIRWELL
Code      : 0x256C80002 (34 Bits)
Mesg Desc.: Power Toggle: On, Mode: 1 (Cool), Fan: 1 (Medium), Temp: 24C
uint16_t rawData[169] = {3030, 3744,  1930, 1018,  902, 1914,  1798, 1956,  1930, 1914,  882, 970,  1866, 1958,  1014, 928,  1774, 1062,  858, 1958,  1928, 930,  856, 1064,  898, 1024,  898, 1022,  856, 974,  904, 1018,  856, 1064,  898, 1002,  854, 976,  852, 1070,  852, 1068,  852, 1070,  918, 914,  916, 1004,  918, 1002,  918, 1004,  918, 1828,  1856, 1072,  2956, 3818,  1858, 1072,  918, 1918,  1768, 1984,  1926, 1918,  874, 956,  1926, 1918,  1010, 912,  1788, 1048,  876, 1962,  1882, 954,  914, 1008, 906, 1016,  904, 1018,  904, 928,  902, 1020,  900, 1022,  900, 1038,  842, 988,  842, 1078,  842, 1080,  844, 1078,  842, 1904,  1850, 1078,  2950, 3824,  1850, 1080,  842, 1994,  1758, 1994,  1850, 1994,  842, 988,  1848, 1996,  934, 986,  1758, 1080,  842, 1994,  1848, 988,  840, 1080,  842, 1080,  842, 1080,  842, 988,  842, 1080,  842, 1080,  842, 1080,  842, 988,  842, 1080,  842, 1080,  820, 1104,  840, 988,  842, 1080,  842, 1080,  842, 1080,  842, 1904,  1850, 1080,  3864};  // AIRWELL 256C80002
uint64_t data = 0x256C80002;


What brand/model IR demodulator are you using?

Athom IR

I have followed the steps in the Troubleshooting Guide & read the FAQ

Yes

Has this library/code previously worked as expected for you?

no

@mbrevda
Copy link
Author

mbrevda commented Sep 3, 2023

Playing around with this a bit more, I noticed that after receiving a power on, subsequent commands are perceived as power off. Perhaps the encoding/decoding is slightly off for this remote?

Power on:

Timestamp : 001387.563
Library   : v2.8.6

Protocol  : AIRWELL
Code      : 0x256C80002 (34 Bits)
Mesg Desc.: Power Toggle: On, Mode: 1 (Cool), Fan: 1 (Medium), Temp: 24C
uint16_t rawData[169] = {3052, 3722,  1950, 1022,  830, 1962,  1790, 1986,  1858, 1986,  922, 928,  1840, 1984,  944, 998,  1792, 1044,  902, 1914,  1862, 996,  900, 1022,  902, 1020,  898, 1022,  900, 930,  834, 1088,  900, 1020,  902, 1020,  902, 928,  900, 1022,  900, 1022,  900, 1022,  900, 932,  900, 1000,  920, 1002,  918, 1002,  916, 1830,  1858, 1070,  2956, 3816,  1858, 1072,  916, 1922,  1832, 1920,  1858, 1986,  850, 980,  1858, 1986,  1012, 910,  1836, 1000,  920, 1918,  1926, 910,  874, 1048,  876, 1044,  920, 1002,  918, 914,  910, 1010,  904, 1018,  878, 1042,  904, 926,  908, 1014,  904, 1016,  904, 1018,  902, 946,  882, 1040,  882, 1038,  882, 1040,  842, 1904,  1850, 1078,  2950, 3802,  1872, 1080,  842, 1976,  1778, 1994,  1850, 1994,  844, 986,  1848, 1994,  934, 988,  1756, 1080,  842, 1994,  1850, 988,  842, 1080,  842, 1080,  842, 1080,  842, 988,  842, 1080,  840, 1082,  840, 1080,  844, 986,  842, 1080,  842, 1080,  840, 1080,  842, 990,  840, 1080,  842, 1082,  842, 1078,  842, 1904,  1848, 1080,  3862};  // AIRWELL 256C80002
uint64_t data = 0x256C80002;

Temp up:

Timestamp : 001393.191
Library   : v2.8.6

Protocol  : AIRWELL
Code      : 0x56D00002 (34 Bits)
Mesg Desc.: Power Toggle: Off, Mode: 1 (Cool), Fan: 1 (Medium), Temp: 25C
uint16_t rawData[175] = {3018, 2840,  898, 1046,  876, 1050,  886, 1948,  1808, 1946,  1878, 1966,  892, 958,  1856, 1944,  1006, 962,  1786, 1946,  1878, 1070,  870, 960,  868, 1054,  866, 1054,  870, 1050,  872, 960,  894, 1026,  872, 1050,  894, 1028,  870, 938,  870, 1072,  894, 1024,  898, 1026,  898, 932,  896, 1026,  896, 1026,  896, 1024,  896, 1830,  1856, 1092,  2978, 2858,  918, 1024,  896, 1024,  898, 1898,  1788, 1988,  1858, 1986,  920, 932,  1838, 1986,  942, 1000,  1814, 1918,  1858, 1090,  894, 916,  914, 1008,  912, 1008,  914, 1008,  914, 916,  912, 1008,  914, 1008,  914, 1008,  914, 918,  914, 1008,  914, 1006,  914, 1008,  914, 892,  872, 1072,  918, 1004,  852, 1072,  916, 1828,  1880, 1048,  2980, 2878,  918, 1004,  916, 1006,  914, 1898,  1842, 1934,  1910, 1936,  078,  844, 988,  842, 1078,  844, 1078,  844, 1078,  844, 988,  844, 1078,  844, 1078,  842, 1080,  842, 988,  844, 1078,  842, 1080,  842, 1078,  844, 1902,  1850, 1080,  3864};  // AIRWELL 56D00002
uint64_t data = 0x56D00002;


Timestamp : 001393.487
Library   : v2.8.6

Protocol  : AIRWELL
Code      : 0x56D00002 (34 Bits)
Mesg Desc.: Power Toggle: Off, Mode: 1 (Cool), Fan: 1 (Medium), Temp: 25C
uint16_t rawData[175] = {3010, 2852,  864, 1076,  844, 1078,  844, 1992,  1760, 1994,  1850, 1992,  844, 986,  1852, 1992,  936, 986,  1760, 1994,  1850, 1078,  844, 986,  844, 1076,  844, 1078,  844, 1078,  844, 986,  844, 1078,  844, 1078,  844, 1078,  844, 966,  864, 1078,  844, 1078,  844, 1078,  844, 986,  844, 1078,  842, 1080,  844, 1078,  844, 1902,  1852, 1076,  2948, 2890,  862, 1078,  844, 1076,  844, 1992,  1760, 1994,  1850, 1994,  844, 964,  1872, 1994,  936, 986,  1760, 1994,  1850, 1078,  844, 986,  842, 1078,  844, 1078,  844, 1078,  844, 988,  842, 1080,  842, 1078,  844, 1080,  842, 986,  844, 1078,  844, 1080,  840, 1080,  844, 988,  842, 1078,  842, 1080,  842, 1078,  842, 1904,  1848, 1080,  2948, 2910,  842, 1080,  842, 1080,  842, 1994,  1758, 1994,  1850, 1994,  842, 988,  1848, 1994,  934, 988,  1756, 1996,  1850, 1080,  842, 988,  842, 1080,  842, 1080,  840, 1080,  842, 988,  842, 1080,  842, 1080,  840, 1080,  842, 988,  842, 1080,  818, 1132,  790, 1132,  790, 1040,  790, 1132,  790, 1132,  788, 1132,  790, 1954,  1798, 1132,  3812};  // AIRWELL 56D00002
uint64_t data = 0x56D00002;

@NiKiZe
Copy link
Collaborator

NiKiZe commented Sep 3, 2023

A toggle means that the AC should switch state (this is for those that implements a horrible protocol on their ACs)
Other ACs has a state for power.

Not exactly following where the issue is perceived. In the server you are using. (Code which I don't really have time to try and understand) is the power toggle set or not?

You will have to make sure it doesn't set the power toggle on each command.

@mbrevda
Copy link
Author

mbrevda commented Sep 4, 2023

The main line is this:

ac.setPowerToggle(true);

Calling it (or setting temp or mode.) toggels the power mode, it doesn't set the AC to on. Interestingly, setting ac.setPowerToggle(false); does seem to consistently turn off the ac.

You will have to make sure it doesn't set the power toggle on each command.

But how?

@NiKiZe
Copy link
Collaborator

NiKiZe commented Sep 4, 2023

Then you need to collect data from your original remote to see if they have introduce a power state (and not just the toggle)

That is collecting the code for both power on and off as well as some more data, then comparing each bit to what is known to figure out exactly what is needed.

@jbrandek
Copy link

This happen to me also with a Whirlpool AC it does not follow what normally does a AC Power on command in other ACs will make the power goes on does not matter state, with this ACs a power on command will toggle the AC

@NiKiZe
Copy link
Collaborator

NiKiZe commented Oct 12, 2023

@jbrandek how are you using the library?
If the protocol only supports power toggle, then the toggle needs to be sent or not based on the "known state", if the wanted state is on and "known" is off then the toggle needs to be sent, so you need to track this or use the internal states that does the tracking for you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants