Differences

This shows you the differences between two versions of the page.

Link to this comparison view

iothings:proiecte:2023sric:esp32_home_automation [2024/05/30 00:43]
valentin.radu2005
iothings:proiecte:2023sric:esp32_home_automation [2024/05/30 01:55] (current)
valentin.radu2005
Line 4: Line 4:
   * Email: valentin.radu@valinet.ro   * Email: valentin.radu@valinet.ro
   * Master: SRIC   * Master: SRIC
-  * [[https://​youtu.be/​|Demo]] 
  
 ===== Introduction ===== ===== Introduction =====
Line 10: Line 9:
 This project implements two functionalities for my smart home setup: This project implements two functionalities for my smart home setup:
  
-  * Speaker volume control using TV remote +  * Speaker volume control using TV remote ​[[https://​youtu.be/​j-plKCrtVP4|Demo]] 
-  * Smart Air Conditioning+  * Smart Air Conditioning ​[[https://​youtu.be/​R3kvEPo8lK0|Demo]]
  
 Both tasks are centered around an ESP32-WROOM board. Both tasks are centered around an ESP32-WROOM board.
Line 19: Line 18:
 The goal for this task was to control the volume for the stereo system using the TV's remote control. The goal for this task was to control the volume for the stereo system using the TV's remote control.
  
-=== About the current situation ​===+=== About ===
  
 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. 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.
Line 33: Line 32:
  
 So, for this task, I have decided to use and ESP32 board to which I connected a TSOP4838 IR receiver [[https://​www.vishay.com/​docs/​82459/​tsop48.pdf| ]] 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. So, for this task, I have decided to use and ESP32 board to which I connected a TSOP4838 IR receiver [[https://​www.vishay.com/​docs/​82459/​tsop48.pdf| ]] 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.
 +
 +{{:​iothings:​proiecte:​2023sric:​esp32s.png?​200|}}
 +
 +=== Pinout ===
 +
 +  * TSOP4838 pin 1 > ESP32 pin 14
 +  * TSOP4838 pin 2 > ESP32 5V pin
 +  * TSOP4838 pin 3 > ESP32 GND pin
  
 === Software === === Software ===
Line 40: Line 47:
 === Gotchas === === Gotchas ===
  
-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 the middle ​(31), 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. +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: 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:
Line 83: Line 92:
 uint32_t command = 0x17; uint32_t command = 0x17;
 uint64_t data = 0x43A2; uint64_t data = 0x43A2;
- 
- 
- 
  
  
Line 150: Line 156:
 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: 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:
  
-  * When I receive a code for SHARP, is_held is set to 1. +  * When I receive a code for SHARP, is_held is set to 1 and I memorize the command
-  * When I receive ​+  * When I receive ​an UNKNOWN code, if is_held is 1, I repeat the last command. 
 +  * After 300ms since the last command, I reset is_held back to 0. 
 + 
 +=== Code === 
 + 
 +Below is the complete listing for the code.ino file with comments: 
 + 
 +<​code>​ 
 +#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) 
 +    */ 
 +  } 
 +
 +</​code>​
  
 ===== Smart Air Conditioning ===== ===== Smart Air Conditioning =====
Line 157: Line 352:
 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 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.
  
-=== About the AC units ===+=== About ===
  
 The AC units are Bosch Climate 3000i [[https://​www.bosch-industrial.com/​gb/​en/​ocs/​commercial-industrial/​climate-3000i-18569608-p/​| ]]. These are actually manufactured by Midea and sold under the Bosch brand in Romania [[https://​community.home-assistant.io/​t/​bosch-5000i-air-conditioning/​367752/​9| ]]. The units feature an OEM dongle option, but the functionality is limited and it comes with a steep price [[https://​www.emag.ro/​modul-wi-fi-aparat-de-aer-conditionat-bosch-seria-3000-5000i-7736606215/​pd/​DJV5GHMBM/​| ]]. The AC units are Bosch Climate 3000i [[https://​www.bosch-industrial.com/​gb/​en/​ocs/​commercial-industrial/​climate-3000i-18569608-p/​| ]]. These are actually manufactured by Midea and sold under the Bosch brand in Romania [[https://​community.home-assistant.io/​t/​bosch-5000i-air-conditioning/​367752/​9| ]]. The units feature an OEM dongle option, but the functionality is limited and it comes with a steep price [[https://​www.emag.ro/​modul-wi-fi-aparat-de-aer-conditionat-bosch-seria-3000-5000i-7736606215/​pd/​DJV5GHMBM/​| ]].
Line 165: Line 360:
 === Hardware === === Hardware ===
  
-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). ​+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).
  
-=== Circuit diagram ​===+{{:​iothings:​proiecte:​2023sric:​esp32s.png?​200|}} 
 + 
 +=== Pinout ​===
  
   * USB red wire (USB 5V) > ESP32 5V pin   * USB red wire (USB 5V) > ESP32 5V pin
Line 175: Line 372:
   * IR LED long pin > ESP32 GPIO19 pin   * IR LED long pin > ESP32 GPIO19 pin
   * IR LED short pin > ESP32 GND pin   * IR LED short pin > ESP32 GND pin
 +
 +
 +{{:​iothings:​proiecte:​2023sric:​valinet_ac_control.png?​200|}}
  
 === Software === === Software ===
Line 195: Line 395:
 esphome.exe run --device COM1 config.yaml esphome.exe run --device COM1 config.yaml
 </​code>​ </​code>​
 +
 +  * "​uart"​ section describes the pins that the module uses for UART communication
 +  * "​remote_transmitter"​ section describes which pins the IR LED uses
 +  * "​climate"​ section configures a Midea control module. This automatically finds and uses the "​uart"​ and "​remote_transmitter"​ modules to achieve its functionality.
 +
 +Below is the full listing for the config.yaml file:
  
 <code yaml> <code yaml>
Line 352: Line 558:
 </​code>​ </​code>​
  
-===== 2. Design ​===== +===== Conclusion ​=====
- +
-==== a. Hardware ==== +
-{{:​iothings:​proiecte:​2023sric:​esp32s.png?​200|}} +
-  * ESP32-WROOM-S microcontroller +
- +
-==== b. Software ==== +
- +
-=== Title === +
- +
-<code vbnet> +
-uint8_t i[]; +
-</​code>​ +
- +
-<​code>​ +
-const unsigned int t; +
-</​code>​ +
- +
  
-===== 3Conclusion =====+Overall, a useful implementation for my home automation goals.
  
-===== 4. References ===== +===== References =====
-  *https://​ocw.cs.pub.ro/​courses/​iothings/​laboratoare/​2022/​lab1 +
-  ​+
  
 +  * https://​ocw.cs.pub.ro/​courses/​iothings/​laboratoare/​2022/​lab1
 +  * http://​forum.microprocesoare.ro/​index.php?​topic=9179.0
 +  * https://​vestelvisualsolutions.com/​fr/​products/​interactive-flat-panel/​ifm65th752-4/​files/​vestel-visual-solutions-rs232-lan-customer-control-v1-1-c6ab95453035.pdf
 +  * https://​www.vishay.com/​docs/​82459/​tsop48.pdf
 +  * https://​github.com/​crankyoldgit/​IRremoteESP8266/​blob/​master/​examples/​IRrecvDumpV3/​IRrecvDumpV3.ino
 +  * https://​www.bosch-industrial.com/​gb/​en/​ocs/​commercial-industrial/​climate-3000i-18569608-p/​
 +  * https://​community.home-assistant.io/​t/​bosch-5000i-air-conditioning/​367752/​9
 +  * https://​www.emag.ro/​modul-wi-fi-aparat-de-aer-conditionat-bosch-seria-3000-5000i-7736606215/​pd/​DJV5GHMBM/​
 +  * https://​community.home-assistant.io/​t/​bosch-5000i-air-conditioning/​367752/​17
 +  * https://​github.com/​mac-zhou/​midea-msmart
 +  * https://​github.com/​NeoAcheron/​midea-ac-py
 +  * https://​github.com/​reneklootwijk/​midea-uart
  
iothings/proiecte/2023sric/esp32_home_automation.1717019027.txt.gz · Last modified: 2024/05/30 00:43 by valentin.radu2005
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0