This project implements two functionalities for my smart home setup:
Both tasks are centered around an ESP32-WROOM board.
The goal for this task was to control the volume for the stereo system using the TV's remote control.
The speakers are connected using the TV's headphone jack. Unfortunately, the software on my TV model is particularly dumb, in that it only allows changing the volume of its internal speakers using the volume controls on the remote. Initially, I wanted to use optical audio, but that does not allow for volume control due to its nature (optical audio transmits digital audio over infrared which simply carries the soundwave without any volume information - traditionally, it is the job of the receiver unit to regulate the volume). Then, I looked into using the HDMI ARC (audio return channel) feature, but, again, this is poorly implemented and does not allow for volume change. Lastly, I chose to connect the speakers via the headphone jack, which allows change via the “Sound” section of the TV menu, but not directly using the IR remote control.
Out of sheer luck, while researching something totally unrelated, I found out that the Sharp 42LE756EN TV that I have is actually made by Vestel . It is an old TV, but it has a 3D panel, which are basically impossible to buy nowadays, so that's why I keep it around. Anyway, Vestel is a pretty large enterprise and they also sell panels for usage in industrial/commercial signage for example. In those kinds of applications, the panels are not controlled via an IR remote control, but rather using some protocol over some sort of physical link, like serial or Ethernet. Turns out, Vestel actually ships similar software on all of their panels and just skin it according to the service are of the product; thus, their consumer LCDs still have the “customer control” implemented, which allows controlling the TV functionality via Ethernet (or an RS-232 header which I do not really have access to and an easy way to interface with). The “customer control” protocol is a VERY simple protocol: connect (telnet) via port 1986, and then send some commands in plain text and that's it. The commands required are:
The protocol described in the PDF is not totally accurate to what is experienced on my TV unit: there is no reply back, but the command actually works, it does actually change the output volume, which is great.
So, for this task, I have decided to use and ESP32 board to which I connected a TSOP4838 IR receiver I have taken out from some smart IR controlled light bulb that I had no use for (I was experiencing a shortage of IR receivers in my parts inventory). This receives data from my TV's remote control. Everything else that is required is already embedded on the ESP32 board.
The board is connected to my home network. The IR sensor receives codes from my TV's remote control - when the codes for the volume buttons are received, a connection to the TV is made over the network (the TV is connected as well to my home network using an Ethernet cable and a static local IP address) and the two commands described above are sent to the TV: the internal speakers are muted and the desired headphone volume level is set in the TV.
Changing the headphone volume produces no UI on the TV. I have experimented with the OSD_PRINT command in the protocol which adds text in the top left corner of the TV image, but once the command is used, the TV crashes and reboots approximately 2 minutes after the command is used. The only UI produced corresponds to the the VOLUME 0 command, which displays the volume bar for the internal speakers with the volume set to 0. This is just aesthetic, the end result is that the headphone volume does change, so the actual sound we hear gets louder/softer, only that the change is not reflected via any UI on the screen .
As I said, not all features described in the protocol work on my TV. For example, there is a “GETHEADPHONEVOLUME” command which is supposed to return the current level for the headphone volume, but unfortunately it doesn't work on my model. This kind of is required when the ESP32 board cold starts - it would fetch the current headphone volume of the TV and start from there. As it is at the moment, I reset the volume to a sane default (16), which is an acceptable compromise. This scenario only happens after a power outage, which is VERY rare. Otherise, the ESP32 board runs continously even when the TV is off, so it “remembers” what the volume level was when the TV closed and so does the TV when it turns back on.
Another workaround for this minor inconvenience would be to memorize the current headphone level in ESP32's EEPROM and restore it when the TV is started fresh. I have decided to not implement this because of the following reasons:
I think that such feature is not really that necessary, helping in only a fraction of the use cases, and for the considerations above I decided that I can live without it.
Here is a sample of the data received from my TV remote when holding down the volume buttons:
Timestamp : 000113.193 Library : v2.8.6 Protocol : SHARP Code : 0x40A2 (15 Bits) uint16_t rawData[95] = {336, 1664, 346, 704, 316, 728, 344, 706, 316, 706, 318, 704, 318, 706, 316, 1708, 314, 680, 344, 1706, 316, 706, 318, 706, 314, 708, 316, 1768, 316, 698, 316, 47580, 340, 1682, 340, 706, 316, 708, 316, 706, 316, 706, 292, 1732, 300, 1724, 312, 714, 310, 1712, 292, 732, 296, 1724, 318, 1680, 344, 1704, 294, 730, 316, 1708, 292, 43308, 338, 1680, 344, 732, 292, 708, 342, 696, 294, 730, 292, 734, 290, 732, 314, 1706, 316, 710, 292, 1706, 342, 704, 294, 730, 292, 730, 290, 1732, 292, 862, 316}; // SHARP 40A2 uint32_t address = 0x1; uint32_t command = 0x14; uint64_t data = 0x40A2; Timestamp : 000114.055 Library : v2.8.6 Protocol : SHARP Code : 0x42A2 (15 Bits) uint16_t rawData[95] = {338, 1670, 342, 704, 318, 706, 318, 704, 318, 706, 314, 1706, 318, 706, 318, 1714, 314, 708, 318, 1694, 320, 702, 318, 682, 340, 706, 316, 1706, 318, 708, 318, 46674, 364, 1682, 318, 708, 318, 706, 320, 694, 316, 706, 318, 706, 316, 1706, 318, 706, 316, 1706, 318, 706, 316, 1706, 318, 1706, 316, 1708, 316, 708, 316, 1706, 320, 44288, 342, 1678, 344, 680, 344, 706, 318, 706, 320, 678, 342, 1706, 316, 712, 316, 1698, 316, 706, 316, 1710, 316, 706, 316, 708, 316, 706, 318, 1704, 318, 684, 342}; // SHARP 42A2 uint32_t address = 0x1; uint32_t command = 0x15; uint64_t data = 0x42A2; Timestamp : 000116.245 Library : v2.8.6 Protocol : SHARP Code : 0x43A2 (15 Bits) uint16_t rawData[95] = {336, 1692, 318, 708, 314, 706, 292, 730, 292, 732, 298, 1724, 318, 1682, 342, 1706, 316, 706, 316, 1710, 290, 732, 316, 710, 300, 722, 294, 1730, 292, 732, 290, 45722, 316, 1702, 320, 706, 308, 716, 314, 708, 316, 684, 342, 708, 290, 734, 290, 732, 316, 1710, 314, 708, 314, 1706, 318, 1704, 318, 1706, 318, 706, 318, 1696, 294, 45432, 314, 1680, 344, 706, 292, 730, 316, 706, 292, 732, 292, 1708, 340, 1706, 318, 1706, 316, 694, 292, 1734, 290, 732, 318, 706, 316, 710, 290, 1732, 292, 732, 314}; // SHARP 43A2 uint32_t address = 0x1; uint32_t command = 0x17; uint64_t data = 0x43A2; Timestamp : 000332.349 WARNING: IR code is too big for buffer (>= 128). This result shouldn't be trusted until this is resolved. Edit & increase `kCaptureBufferSize`. Library : v2.8.6 Protocol : SHARP Code : 0x40A2 (15 Bits) uint16_t rawData[127] = {338, 1694, 320, 702, 322, 702, 320, 704, 318, 706, 318, 704, 318, 728, 318, 1708, 318, 704, 318, 1706, 318, 702, 322, 706, 318, 702, 318, 1704, 318, 704, 318, 47734, 340, 1668, 320, 704, 320, 702, 320, 702, 320, 704, 320, 1702, 320, 1702, 320, 704, 318, 1706, 320, 702, 320, 1778, 318, 1692, 322, 1704, 322, 702, 320, 1702, 320, 43230, 366, 1654, 344, 702, 320, 704, 320, 704, 318, 704, 322, 702, 320, 706, 318, 1680, 346, 704, 318, 1702, 320, 704, 320, 706, 318, 704, 318, 1706, 318, 704, 320, 47310, 342, 1678, 346, 704, 318, 704, 320, 702, 318, 702, 322, 1702, 320, 1704, 320, 704, 318, 1706, 320, 704, 318, 1710, 322, 1696, 320, 1704, 318, 704, 320, 1702, 322}; // SHARP 40A2 uint32_t address = 0x1; uint32_t command = 0x14; uint64_t data = 0x40A2; Timestamp : 000332.617 WARNING: IR code is too big for buffer (>= 128). This result shouldn't be trusted until this is resolved. Edit & increase `kCaptureBufferSize`. Library : v2.8.6 Protocol : UNKNOWN Code : 0x815AFE67 (64 Bits) uint16_t rawData[127] = {1702, 318, 704, 320, 704, 320, 702, 320, 704, 318, 704, 320, 704, 318, 1704, 320, 828, 320, 1706, 318, 704, 318, 704, 318, 704, 320, 1704, 320, 702, 320, 47182, 324, 1696, 322, 692, 320, 706, 318, 704, 320, 704, 320, 1704, 320, 1702, 320, 704, 320, 1702, 320, 704, 320, 1702, 320, 1786, 318, 1692, 320, 704, 320, 1680, 344, 43222, 340, 1700, 322, 704, 318, 706, 318, 702, 322, 704, 318, 704, 320, 704, 320, 1706, 318, 706, 320, 1836, 320, 704, 318, 704, 320, 704, 320, 1704, 318, 708, 318, 47146, 344, 1676, 346, 704, 320, 692, 320, 706, 318, 704, 320, 1702, 320, 1704, 322, 704, 316, 1704, 320, 704, 322, 1678, 344, 1706, 320, 1702, 322, 702, 320, 1704, 318, 43314}; // UNKNOWN 815AFE67 Timestamp : 000332.889 WARNING: IR code is too big for buffer (>= 128). This result shouldn't be trusted until this is resolved. Edit & increase `kCaptureBufferSize`. Library : v2.8.6 Protocol : UNKNOWN Code : 0x179D1856 (64 Bits) uint16_t rawData[127] = {342, 702, 320, 704, 318, 704, 318, 704, 318, 706, 318, 704, 320, 1704, 318, 704, 320, 1704, 320, 704, 320, 704, 320, 704, 318, 1702, 320, 704, 320, 47282, 364, 1652, 348, 702, 320, 706, 318, 696, 318, 704, 320, 1704, 320, 1704, 320, 704, 320, 1702, 320, 704, 318, 1704, 320, 1704, 320, 1704, 320, 798, 320, 1704, 318, 43198, 338, 1702, 322, 702, 320, 704, 318, 704, 322, 702, 318, 704, 320, 702, 322, 1700, 322, 702, 320, 1706, 322, 702, 322, 702, 320, 704, 320, 1702, 320, 704, 318, 47278, 366, 1678, 322, 702, 322, 704, 318, 702, 322, 706, 322, 1704, 322, 1704, 318, 702, 322, 1702, 318, 704, 322, 1702, 322, 1702, 322, 1702, 320, 706, 320, 1792, 322, 43488, 340}; // UNKNOWN 179D1856 Timestamp : 000333.155 WARNING: IR code is too big for buffer (>= 128). This result shouldn't be trusted until this is resolved. Edit & increase `kCaptureBufferSize`. Library : v2.8.6 Protocol : UNKNOWN Code : 0xB91B7E67 (64 Bits) uint16_t rawData[127] = {706, 318, 702, 320, 702, 320, 704, 318, 702, 320, 704, 320, 1704, 320, 704, 324, 1686, 322, 704, 320, 692, 322, 702, 320, 1702, 322, 704, 320, 47022, 366, 1654, 346, 702, 322, 678, 344, 704, 318, 704, 320, 1722, 320, 1704, 320, 704, 320, 1704, 318, 704, 320, 1702, 320, 1704, 320, 1702, 320, 704, 320, 1704, 320, 43286, 338, 1680, 322, 702, 322, 702, 322, 700, 322, 704, 320, 702, 320, 702, 322, 1702, 320, 702, 322, 1700, 322, 702, 322, 718, 322, 704, 320, 1704, 320, 704, 320, 47264, 364, 1678, 322, 702, 318, 704, 318, 704, 322, 702, 320, 1704, 320, 1732, 322, 702, 322, 1700, 322, 702, 320, 1702, 320, 1702, 322, 1702, 320, 702, 322, 1704, 318, 43318, 364, 1678}; // UNKNOWN B91B7E67 Timestamp : 000333.425 WARNING: IR code is too big for buffer (>= 128). This result shouldn't be trusted until this is resolved. Edit & increase `kCaptureBufferSize`. Library : v2.8.6 Protocol : UNKNOWN Code : 0xA39865A5 (64 Bits) uint16_t rawData[127] = {318, 704, 318, 704, 320, 704, 320, 702, 320, 702, 322, 1702, 320, 702, 322, 1702, 320, 704, 320, 704, 320, 704, 322, 1838, 320, 678, 344, 47166, 366, 1652, 346, 702, 320, 704, 320, 704, 318, 704, 320, 1704, 320, 1692, 318, 704, 320, 1702, 322, 704, 320, 1702, 320, 1702, 322, 1680, 342, 704, 322, 1702, 320, 43318, 340, 1680, 322, 704, 318, 704, 320, 706, 318, 702, 318, 706, 318, 702, 320, 1702, 322, 702, 322, 1702, 318, 704, 322, 702, 318, 706, 320, 1706, 320, 704, 318, 47280, 362, 1658, 344, 702, 320, 702, 322, 702, 320, 702, 320, 1702, 320, 1702, 322, 704, 320, 1702, 320, 704, 320, 1702, 320, 1704, 318, 1704, 322, 702, 320, 1702, 322, 43280, 340, 1704, 322}; // UNKNOWN A39865A5 Timestamp : 000333.624 Library : v2.8.6 Protocol : UNKNOWN Code : 0xB032D22A (45 Bits) uint16_t rawData[90] = {704, 318, 706, 318, 704, 320, 702, 320, 706, 318, 1704, 320, 704, 320, 1704, 320, 704, 320, 702, 320, 702, 322, 1702, 320, 846, 318, 47160, 318, 1702, 322, 678, 346, 702, 320, 704, 320, 704, 320, 1702, 320, 1680, 344, 702, 320, 1692, 320, 704, 320, 1704, 320, 1704, 320, 1702, 320, 702, 320, 1704, 320, 43294, 366, 1676, 324, 704, 318, 706, 318, 704, 320, 702, 320, 704, 320, 702, 320, 1704, 318, 702, 322, 1704, 318, 706, 318, 702, 320, 702, 320, 1704, 318, 694, 320 }; // UNKNOWN B032D22A
Of particular interest is what happens when a button is held down on my TV's remote: it basically repeats the same bytes over and over again and the TV acts accordingly.
Though, the way the IR library that I used works is it has an internal buffer where it collects bytes. Only when the buffer gets completely filled or the bytes stop coming (i.e. the button on the remote is depressed), does it processes the received bytes, determines a protocol and return that to the user. Unfortunately, that is really not useful, because I wanted to be notified when the button is held down and act accordingly (i.e. raise/lower the volume more the more the user holds down the button). My solution to this was to configure a smaller internal buffer that gets filled at the rate I want to be notified about the button still being held. This works really well in practice, but as can be seen, the library is not able to properly decode the subsequent bytes. My solution for this was a bit of a hack:
Below is the complete listing for the code.ino file with comments:
#include <WiFi.h> #include <Arduino.h> #include <assert.h> #include <IRrecv.h> #include <IRremoteESP8266.h> #include <IRac.h> #include <IRtext.h> #include <IRutils.h> // Network info const char* ssid = "REPLACE_WITH_YOUR_SSID"; const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // TV IP address IPAddress ADDR(10,1,1,100); #define PORT 1986 WiFiClient client; // First time, reset volume to some sane default instead of increasing/decreasing it bool first = true; // Current volume level, set to desired default initially int volume = 16; // Whether the mute button was pressed on the remote control bool muted = false; // How many "bars" to increase/decrease the volume on each button press int volSteps = 2; // Holds last button to have been pressed uint64_t lastValue = 0; // Tells whether the user is holding a button on the remote control bool isHeld = false; // Timestamp of last decode success notification unsigned long last = 0; // Pin on which TSOP3848 is connected const uint16_t kRecvPin = 14; const uint32_t kBaudRate = 115200; const uint16_t kCaptureBufferSize = 128; const uint8_t kTimeout = 50; const uint16_t kMinUnknownSize = 12; const uint8_t kTolerancePercentage = kTolerance; // kTolerance is normally 25% #define LEGACY_TIMING_INFO false IRrecv irrecv(kRecvPin, kCaptureBufferSize, kTimeout, true); decode_results results; // Somewhere to store the results void setup_wifi() { delay(10); // We start by connecting to a WiFi network Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } randomSeed(micros()); Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); } unsigned long IRAM_ATTR millis2() { return (unsigned long) (esp_timer_get_time() / 1000ULL); } void setup() { #if defined(ESP8266) Serial.begin(kBaudRate, SERIAL_8N1, SERIAL_TX_ONLY); #elif ARDUINO_USB_CDC_ON_BOOT Serial.begin(kBaudRate); #else // ESP8266 Serial.begin(kBaudRate, SERIAL_8N1); #endif // ESP8266 while (!Serial) delay(50); assert(irutils::lowLevelSanityCheck() == 0); setup_wifi(); Serial.printf("\n" D_STR_IRRECVDUMP_STARTUP "\n", kRecvPin); #if DECODE_HASH irrecv.setUnknownThreshold(kMinUnknownSize); #endif irrecv.setTolerance(kTolerancePercentage); irrecv.enableIRIn(); } void actOnValue(uint64_t value) { /*Serial.println(String("Client status: ") + client.status()); while (client.available()) { char c = client.read(); Serial.print(c); } if (!client.connected()) { client.setTimeout(1); Serial.println("Connecting to client..."); client.connect(ADDR, PORT); }*/ if (value == 0x40A2) { if (first) first = false; else volume += volSteps; if (volume > 63) volume = 63; client.setTimeout(1); client.connect(ADDR, PORT); client.println("VOLUME 0"); client.println(String("HEADPHONEVOLUME ") + volume); client.stop(); Serial.println("volume up"); } else if (value == 0x42A2) { if ((volSteps == 2 || volSteps == 4) && volume == 63) volume = 64; if (first) first = false; else volume -= volSteps; if (volume < 0) volume = 0; client.setTimeout(1); client.connect(ADDR, PORT); client.println("VOLUME 0"); client.println(String("HEADPHONEVOLUME ") + volume); client.stop(); Serial.println("volume down"); } else if (value == 0x43A2) { muted = !muted; client.setTimeout(1); client.connect(ADDR, PORT); client.println("VOLUME 0"); if (muted) client.println("HEADPHONEVOLUME 0"); else client.println(String("HEADPHONEVOLUME ") + volume); client.stop(); Serial.println("mute"); } } void loop() { if (irrecv.decode(&results)) { unsigned long now = millis2(); if (now - last > 300) isHeld = false; if (isHeld && results.decode_type == UNKNOWN) { actOnValue(lastValue); } if (results.decode_type == SHARP) { actOnValue(results.value); lastValue = results.value; isHeld = true; } last = now; // Code to print info about the received bytes: /* // Display a crude timestamp. uint32_t now = millis(); Serial.printf(D_STR_TIMESTAMP " : %06u.%03u\n", now / 1000, now % 1000); // Check if we got an IR message that was to big for our capture buffer. if (results.overflow) Serial.printf(D_WARN_BUFFERFULL "\n", kCaptureBufferSize); // Display the library version the message was captured with. Serial.println(D_STR_LIBRARY " : v" _IRREMOTEESP8266_VERSION_STR "\n"); // Display the tolerance percentage if it has been change from the default. if (kTolerancePercentage != kTolerance) Serial.printf(D_STR_TOLERANCE " : %d%%\n", kTolerancePercentage); // Display the basic output of what we found. Serial.print(resultToHumanReadableBasic(&results)); // Display any extra A/C info if we have it. String description = IRAcUtils::resultAcToString(&results); if (description.length()) Serial.println(D_STR_MESGDESC ": " + description); yield(); // Feed the WDT as the text output can take a while to print. #if LEGACY_TIMING_INFO // Output legacy RAW timing info of the result. Serial.println(resultToTimingInfo(&results)); yield(); // Feed the WDT (again) #endif // LEGACY_TIMING_INFO // Output the results as source code Serial.println(resultToSourceCode(&results)); Serial.println(); // Blank line between entries yield(); // Feed the WDT (again) */ } }
The goal for this task was to be able to smart control the AC unit that I have in my home, bypassing the included remote control. The end goal was to have them controlled from anywhere around the globe.
The AC units are Bosch Climate 3000i . These are actually manufactured by Midea and sold under the Bosch brand in Romania . The units feature an OEM dongle option, but the functionality is limited and it comes with a steep price .
The logic board of the AC is hidden away by a plastic tray underneath the front lid. A standard Phillips screw and a set of plastic clips hold the tray and have to be carefully removed. The logic board features a female USB-A receptacle. Despite this, the electrical wiring is not for USB, but for UART . Communication with the AC unit can happen over this port using the Midea protocol .
So, for this task, I have decided to use an ESP32 board which is wired for communication to the UART port on the AC unit logic board. I have determined that both boards use the same logic voltage levels, so there is no need for level shifters when wiring the RX/TX pins. Since some of the commands of the protocol do not work over the UART connection, I have also connected a standard 940nm infrared LED to the board, through which the remote control's commands are replicated in order to send the unsupported commands. I decided for this hybrid solution instead of going with the IR alone since the UART communication also offers feedback about the current status of the AC unit (current room temperature and other sensor data, for example).
Since a standalone dashboard would introduce too much friction, I have decided to have this board connected to my WiFi network and integrate it with my Home Assistant setup as an additional “service”. This is best achieved by writing an ESPHome description file and having the framework glue modules together in order to produce the desired functionality. Getting a usable prototype is accelerated compared to the traditional approach of writing boiler code that links various libraries together at a high level. ESPHome is a mature ecosystem supported by Nabu Casa, the same team that delivers the world renowned Home Assistant ecosystem.
Home Assistant offers a HomeKit bridge service that integrates it within the Apple Home. Apple Home can be managed remotely if it contains a home hub device, such as an Apple TV or a HomePod speaker. An alternative outside the Apple ecosystem is an yearly subscription to Nabu Casa which allows access to your Home Assistant instance via their web site (75 EUR). Or the totally DIY way is to enable port forwarding on your ISP's AP/router combo, and use a service like DuckDNS on Home Assistant, along with switching it to using HTTPS using a free Let's Encrypt certificate instead and access your instance over the public web directly.
When crafting the male receptacle that connects with the female one on the AC's logic board, usually by sacrificing some cheap USB cable laying around, pay extra attention and I recommend to verify the pinout of the connector against the wires: cheap Chinese cables often use different colors for the wires or use the traditional colors (red/green/white/black), but connect them in a random order (I have seen 5V via the black wire and GND via the red wire, for example). Use a multimeter and check each wire against the connector - failure to do so may lead to damaged equipment, as things aren't usually protected against reverse connection, for example. You have been warned!
This command can be used to compile based on the YAML description and flash a connected ESP32 board:
esphome.exe run --device COM1 config.yaml
Below is the full listing for the config.yaml file:
esphome: name: ac-esp friendly_name: AC_ESP esp32: board: esp32dev framework: type: arduino logger: baud_rate: 0 api: encryption: key: "fjkhgusyfhygskfhgbvfxkhvbjkdhgxukhxkghxkxrg=" ota: password: "d76v87687db8ebmdbfjdjb8d789bdbd7" wifi: ssid: REPLACE_WITH_YOUR_SSID password: REPLACE_WITH_YOUR_PASSWORD ap: ssid: "Fallback Hotspot" password: "473958869456" uart: tx_pin: 1 rx_pin: 3 baud_rate: 9600 remote_transmitter: pin: GPIO19 # For iot-uni-stick. carrier_duty_percent: 50% # 50% for IR LED, 100% for direct # connect to TSOP IR receiver output. climate: - platform: midea name: AC_ESP # Use a unique name. period: 1s # Optional timeout: 2s # Optional num_attempts: 3 # Optional autoconf: true # Autoconfigure most options. beeper: false # Beep on commands. visual: # Optional. min_temperature: 17 °C # min: 17 max_temperature: 30 °C # max: 30 temperature_step: 1 °C # min: 0.5 supported_modes: # Optional. - FAN_ONLY - HEAT_COOL - COOL - HEAT - DRY custom_fan_modes: # Optional - SILENT - TURBO supported_presets: # Optional. - ECO - BOOST - SLEEP custom_presets: # Optional. - FREEZE_PROTECTION supported_swing_modes: # Optional - VERTICAL - HORIZONTAL - BOTH outdoor_temperature: # Optional. name: Temp switch: - platform: template name: Beeper icon: mdi:volume-source restore_mode: 'RESTORE_DEFAULT_OFF' optimistic: true turn_on_action: midea_ac.beeper_on: turn_off_action: midea_ac.beeper_off: binary_sensor: - platform: status name: Connection Status id: climate_AC_ESP_connection_status text_sensor: - platform: template name: Uptime id: uptime_human icon: mdi:clock-start - platform: version name: ESPHome Version id: climate_AC_ESP_esphome_version - platform: wifi_info ip_address: name: IP id: climate_AC_ESP_ip_address icon: mdi:ip-network sensor: - platform: uptime name: Uptime Sensor id: uptime_sensor update_interval: 60s on_raw_value: then: - text_sensor.template.publish: id: uptime_human state: !lambda |- int seconds = round(id(uptime_sensor).raw_state); int days = seconds / (24 * 3600); seconds = seconds % (24 * 3600); int hours = seconds / 3600; seconds = seconds % 3600; int minutes = seconds / 60; seconds = seconds % 60; return ( (days ? to_string(days) + "d " : "") + (hours ? to_string(hours) + "h " : "") + (minutes ? to_string(minutes) + "m " : "") + (to_string(seconds) + "s") ).c_str(); - platform: wifi_signal name: WiFi Signal id: climate_AC_ESP_wifi_signal update_interval: 60s button: - platform: restart name: Reboot id: climate_AC_ESP_restart icon: "mdi:restart" - platform: shutdown name: Shutdown id: climate_AC_ESP_shutdown - platform: safe_mode name: Reboot (Safe Mode) id: climate_AC_ESP_safe_mode - platform: template name: Display Toggle id: climate_AC_ESP_display_toggle icon: mdi:theme-light-dark on_press: midea_ac.display_toggle: - platform: template name: Swing Step icon: mdi:tailwind on_press: midea_ac.swing_step:
Overall, a useful implementation for my home automation goals.