Daikin + ESP32 + ESPHome = Local Control!

Hello everybody,
I want to share with you this small ESP32 powered board that I designed myself that was inspired by these projects on the web:
https://github.com/revk/ESP32-Faikin

It’s just a tiny board that I installed inside my Daikin aircon and connected to the S21 port.
Loaded with ESPHome code found at https://github.com/joshbenner/esphome-daikin-s21 it allows full local control over my aircon like everyone of us like.

I also connected standard I2C BME280 and CCS811 modules to get more data and a bunch of IR led to control devices in the room.
The IR led are driven by a transistor on the cathode, while the anode is connected to the main S21 power supply through a resistor to limit the current flow ( the only through hole resistor in the picture ).
The position of the inside aircon unit is just perfect for the scope because can beam ir signals all around.
A footprint for an IR receiver is available, but I was not confident with drilling the aircon plastic chassis, so in the end I didn’t use it.

The board is connected to the S21 port with the correct JST connector, that provides supply and proper level shifting will prevent damage to the ESP.

Onboard general purpose blue led available and standard programming port for the ESP32.

There is also an NTC on the board because my original idea was to avoid any BME280, but I connected it to GPIO14 of the ESP, that can not be used as analog input with ESPHome…my fault of doing things before reading.

Some pictures of the installation where you can see how tiny is the board; I used some double side scotch tape to stick in this position so I can always have a look at it by just opening the front cover.

The final result is very clean and finally another device is cloud free.

I got 5 pieces with 5 JST cables, but I just need two of them.

If somebody from Italy is interested to get one just contact me in pm and we will find a solution, I will be happy to free others Daikin aircons from the cloud!!

Best Regards

7 Likes

What an amazing job! I am a beginner with ESP (just finished my first project: a D1 mini based LUX-meter in Home Assistant (#proud) and would like to add my Daikin in HA. I am not in Italy (currently), but would like to take your project as an inspiration. I have a Daikin FTXP35 but don’t have the Wifi module. Is that required for this setup or can I just connect your solution to the S21 port?

if your split has the S21 port, it’s likely gonna work. No need for the wifi module at all
you can find a lot of different works on s21 port, here on github, honestly you would not even need a JST cable to connect (i used plain dupont cables to connect a common esp8266 mini)

Edit:
check here

1 Like

Hmmm… mine is a Daikin Comfora, looking in the compatibility list it seems to be a bit troublesome due to power supply. Gonna have to read a bit deeper into this.

it doesn’t seem a power supply issue, probably you just need to use open drain output (mine works with 3.3V logic level, so can’t assure you)

I used this github code with board ESP32-C3 Supermini. My heatpump is FTXS serries, When set mode to Auto / HeatCool the heatpump are acknowledge but board show mode : “OFF”. Other mode setting is OK.
Any one have that error ???

Hi @vercellino ,

This is great, thank you for sharing. Would you mind sharing some details about that pcb you designed?

Thank you!

Hi, I’m trying to use an ESP32-C3 Supermini as well. I’ve connected directly to ports 1 and 5 of the S21 to power it with 5V, and ports 20 and 21 of the GPIO for RX and TX communications. Could you tell me what I’m doing wrong? I’m using the esphome-daikin-s21 integration.

[22:36:58][E][uart:015]: Reading from UART timed out at byte 0!
[22:36:58][W][daikin_s21:257]: Timeout waiting for F1 response
[22:36:58][E][uart:015]: Reading from UART timed out at byte 0!
[22:36:58][W][daikin_s21:257]: Timeout waiting for F5 response
[22:36:58][E][uart:015]: Reading from UART timed out at byte 0!
[22:36:58][W][daikin_s21:257]: Timeout waiting for RH response
[22:36:58][E][uart:015]: Reading from UART timed out at byte 0!
[22:36:58][W][daikin_s21:257]: Timeout waiting for RI response
[22:36:58][E][uart:015]: Reading from UART timed out at byte 0!
[22:36:58][W][daikin_s21:257]: Timeout waiting for Ra response
[22:36:58][E][uart:015]: Reading from UART timed out at byte 0!
[22:36:58][W][daikin_s21:257]: Timeout waiting for RL response
[22:36:58][E][uart:015]: Reading from UART timed out at byte 0!
[22:36:58][W][daikin_s21:257]: Timeout waiting for Rd response
[22:36:58][W][component:232]: Component daikin_s21 took a long time for an operation (899 ms).
[22:36:58][W][component:233]: Components should block for at most 30 ms.

I don’t used pin 1 on S21 connector. My wiring as follow:

  • PIN 4, 5 → DC-DC buck → PIN Gnd, VCC on ESP32-C3
  • PIN 2, 3–> Shift Level → PIN 20,21 on ESP32-C3

Yes, I also used pins 4 and 5 with a Mini 560, but for some reason, I couldn’t achieve a 5V output. However, when I tested it with a 9V battery, it worked. That’s why I opted for pin 1.
Is really necessary a level shifter?

My setting:

....
uart:
  - id: s21_uart
    tx_pin: GPIO20
    rx_pin: GPIO21
    baud_rate: 2400
    data_bits: 8
    parity: EVEN
    stop_bits: 2
...

S21 connector:
Pin2 (TX) → HV1( Shifter) → LV1( Shifter) → GPIO20
Pin3 (RX) → HV2( Shifter) → LV2( Shifter) -->GPIO21

Can you check mode HeatCool ?

Checked and it does not work properly. The problem is that when you activate it, the HVAC works, but on the frontend of HA, it shows as OFF

If you don’t want to buy/order a ‘special’ pcb, it can be done with off-the-shelf parts as well:
M5stamp + Dc-Dc 5V output + Levelshifter 5V-3V3 + a little soldering and heat shrink tubing.
Configure ESPHome with pins: RX 32 and TX 33 and S21 2 green - 3 yellow - 4 red - 5 black


I am trying figure out if I could get Faikin working on this setup with a non-standard pinout.

Other direction that can also go ‘real Faikin’ software:

I am wanting to try something similar to yours with just a ESP 32 Wroom board and the DC - DC buck converter and Level Shifter. Was that all that was needed as I see in the PCB Parts list for the Faikin that there are a bunch of resistors and capacitors.

If you could let me know how your project worked out, did you just have to change the pin outs in the code?

Both hardware versions work as expected!

Hardware and Software pins must of course be matched.

ESPHome runs on most ESP32 versions, while Faikin, on the other hand, is limited in choice; ESP32 minimum HW rev3 required.
Additional components were installed to have the inverting options of the RX/TX line (which I did not need for my S21 unit).

Hi, I’m interested of JST cable, I live in Rome, how can I contact you?

Write me in PM

I tried to install daikin_s21 integration onto Seeedstudio Xiao ESP32-C6.

The esphome config is:

esphome:
  name: test-ac
  platformio_options:
    board_build.f_cpu: 160000000L
    board_build.f_flash: 80000000L
    board_build.flash_size: 4MB
    board_build.mcu: esp32c6
    build_flags: "-DBOARD_HAS_PSRAM"
    board_build.arduino.memory_type: qio_opi

esp32:
  board: esp32-c6-devkitc-1
  variant: ESP32C6
  framework:
    type: esp-idf
    version: 5.2.2    
    sdkconfig_options:
      CONFIG_ESPTOOLPY_FLASHSIZE_4MB: y    
    platform_version: 6.7.0    

external_components:
  - source: github://joshbenner/esphome-daikin-s21@main
    components: [ daikin_s21 ]

uart:
  - id: s21_uart
    tx_pin: GPIO16
    rx_pin: GPIO17
    baud_rate: 2400
    data_bits: 8
    parity: EVEN
    stop_bits: 2
    debug:

# The base UART communication hub.
daikin_s21:
  tx_uart: s21_uart
  rx_uart: s21_uart

climate:
  - name: Daikin Test
    platform: daikin_s21
    visual:
      temperature_step: 0.5

However, when I tried to compile the firmware, I get all sorts of errors from the component:

In file included from src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp:3:
src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp: In member function 'virtual void esphome::daikin_s21::DaikinS21Climate::dump_config()':

src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp:27:22: error: format '%u' expects argument of type 'unsigned int', but argument 5 has type 'uint32_t' {aka 'long unsigned int'} [-Werror=format=]
   27 |   ESP_LOGCONFIG(TAG, "  Update interval: %u", this->get_update_interval());
      |                      ^~~~~~~~~~~~~~~~~~~~~~~
src/esphome/core/log.h:72:36: note: in definition of macro 'ESPHOME_LOG_FORMAT'
   72 | #define ESPHOME_LOG_FORMAT(format) format
      |                                    ^~~~~~
src/esphome/core/log.h:153:33: note: in expansion of macro 'esph_log_config'
  153 | #define ESP_LOGCONFIG(tag, ...) esph_log_config(tag, __VA_ARGS__)
      |                                 ^~~~~~~~~~~~~~~
src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp:27:3: note: in expansion of macro 'ESP_LOGCONFIG'
   27 |   ESP_LOGCONFIG(TAG, "  Update interval: %u", this->get_update_interval());
      |   ^~~~~~~~~~~~~
src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp:27:43: note: format string is defined here
   27 |   ESP_LOGCONFIG(TAG, "  Update interval: %u", this->get_update_interval());
      |                                          ~^
      |                                           |
      |                                           unsigned int
      |                                          %lu
src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp: In member function 'void esphome::daikin_s21::DaikinS21Climate::save_setpoint(float)':

src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp:132:12: error: enumeration value 'Disabled' not handled in switch [-Werror=switch]
  132 |     switch (mode) {
      |            ^
src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp:132:12: error: enumeration value 'Dry' not handled in switch [-Werror=switch]

src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp:132:12: error: enumeration value 'Fan' not handled in switch [-Werror=switch]

src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp: In member function 'esphome::optional<float> esphome::daikin_s21::DaikinS21Climate::load_setpoint(esphome::daikin_s21::DaikinClimateMode)':

src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp:156:10: error: enumeration value 'Disabled' not handled in switch [-Werror=switch]
  156 |   switch (this->s21->get_climate_mode()) {
      |          ^
src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp:156:10: error: enumeration value 'Dry' not handled in switch [-Werror=switch]

src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp:156:10: error: enumeration value 'Fan' not handled in switch [-Werror=switch]

src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp: In member function 'void esphome::daikin_s21::DaikinS21Climate::set_s21_climate()':

src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp:410:17: error: format '%s' expects argument of type 'char*', but argument 5 has type 'const esphome::LogString*' [-Werror=format=]
  410 |   ESP_LOGI(TAG, "  Mode: %s", climate::climate_mode_to_string(this->mode));
      |                 ^~~~~~~~~~~~
src/esphome/core/log.h:72:36: note: in definition of macro 'ESPHOME_LOG_FORMAT'
   72 | #define ESPHOME_LOG_FORMAT(format) format
      |                                    ^~~~~~
src/esphome/core/log.h:151:28: note: in expansion of macro 'esph_log_i'
  151 | #define ESP_LOGI(tag, ...) esph_log_i(tag, __VA_ARGS__)
      |                            ^~~~~~~~~~
src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp:410:3: note: in expansion of macro 'ESP_LOGI'
  410 |   ESP_LOGI(TAG, "  Mode: %s", climate::climate_mode_to_string(this->mode));
      |   ^~~~~~~~
src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp:410:27: note: format string is defined here
  410 |   ESP_LOGI(TAG, "  Mode: %s", climate::climate_mode_to_string(this->mode));
      |                          ~^
      |                           |
      |                           char*
Compiling .pioenvs/sens-1st-bedroom-ac/src/esphome/components/esp32/core.cpp.o
cc1plus: some warnings being treated as errors
*** [.pioenvs/sens-1st-bedroom-ac/src/esphome/components/daikin_s21/climate/daikin_s21_climate.cpp.o] Error 1
In file included from src/esphome/components/uart/uart.h:6,
                 from src/esphome/components/daikin_s21/s21.h:3,
                 from src/esphome/components/daikin_s21/s21.cpp:1:
src/esphome/components/daikin_s21/s21.cpp: In member function 'void esphome::daikin_s21::DaikinS21::check_uart_settings()':
src/esphome/components/daikin_s21/s21.cpp:107:11: error: format '%u' expects argument of type 'unsigned int', but argument 6 has type 'uint32_t' {aka 'long unsigned int'} [-Werror=format=]
  107 |           "  Invalid baud_rate: Integration requested baud_rate %u but you "
      |           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  108 |           "have %u!",
      |           ~~~~~~~~~~
src/esphome/core/log.h:72:36: note: in definition of macro 'ESPHOME_LOG_FORMAT'
   72 | #define ESPHOME_LOG_FORMAT(format) format
      |                                    ^~~~~~
src/esphome/core/log.h:149:28: note: in expansion of macro 'esph_log_e'
  149 | #define ESP_LOGE(tag, ...) esph_log_e(tag, __VA_ARGS__)
      |                            ^~~~~~~~~~
src/esphome/components/daikin_s21/s21.cpp:105:7: note: in expansion of macro 'ESP_LOGE'
  105 |       ESP_LOGE(
      |       ^~~~~~~~
src/esphome/components/daikin_s21/s21.cpp:108:18: note: format string is defined here
  108 |           "have %u!",
      |                 ~^
      |                  |
      |                  unsigned int
      |                 %lu
src/esphome/components/daikin_s21/s21.cpp: In member function 'virtual void esphome::daikin_s21::DaikinS21::dump_config()':
src/esphome/components/daikin_s21/s21.cpp:136:22: error: format '%u' expects argument of type 'unsigned int', but argument 5 has type 'uint32_t' {aka 'long unsigned int'} [-Werror=format=]
  136 |   ESP_LOGCONFIG(TAG, "  Update interval: %u", this->get_update_interval());
      |                      ^~~~~~~~~~~~~~~~~~~~~~~
src/esphome/core/log.h:72:36: note: in definition of macro 'ESPHOME_LOG_FORMAT'
   72 | #define ESPHOME_LOG_FORMAT(format) format
      |                                    ^~~~~~
src/esphome/core/log.h:153:33: note: in expansion of macro 'esph_log_config'
  153 | #define ESP_LOGCONFIG(tag, ...) esph_log_config(tag, __VA_ARGS__)
      |                                 ^~~~~~~~~~~~~~~
src/esphome/components/daikin_s21/s21.cpp:136:3: note: in expansion of macro 'ESP_LOGCONFIG'
  136 |   ESP_LOGCONFIG(TAG, "  Update interval: %u", this->get_update_interval());
      |   ^~~~~~~~~~~~~
src/esphome/components/daikin_s21/s21.cpp:136:43: note: format string is defined here
  136 |   ESP_LOGCONFIG(TAG, "  Update interval: %u", this->get_update_interval());
      |                                          ~^
      |                                           |
      |                                           unsigned int
      |                                          %lu
cc1plus: some warnings being treated as errors
*** [.pioenvs/sens-1st-bedroom-ac/src/esphome/components/daikin_s21/s21.cpp.o] Error 1
========================= [FAILED] Took 161.07 seconds =========================

Any ideas?

I managed to compile the component by making the following alterations…

Within the climate/daikin_s21_climate.cpp file, find/replace:

void DaikinS21Climate::dump_config() {
  ESP_LOGCONFIG(TAG, "DaikinS21Climate:");
  ESP_LOGCONFIG(TAG, "  Update interval: %lx", this->get_update_interval());

(Note: %u becomes %lx)

Within the same file, three more conditions (Disabled/Fan/Dry) are added to the following:

void DaikinS21Climate::save_setpoint(float value) {
  auto mode = this->s21->get_climate_mode();
  optional<float> prev = this->load_setpoint(mode);
  // Only save if value is diff from what's already saved.
  if (abs(value - prev.value_or(0.0)) >= SETPOINT_STEP) {
    switch (mode) {
      case DaikinClimateMode::Auto:
        this->save_setpoint(value, this->auto_setpoint_pref);
        break;
      case DaikinClimateMode::Cool:
        this->save_setpoint(value, this->cool_setpoint_pref);
        break;
      case DaikinClimateMode::Heat:
        this->save_setpoint(value, this->heat_setpoint_pref);
        break;
      case DaikinClimateMode::Dry:
        //this->save_setpoint(value, this->heat_setpoint_pref);
        break;
	  case DaikinClimateMode::Fan:
        //this->save_setpoint(value, this->heat_setpoint_pref);
        break;
      case DaikinClimateMode::Disabled:
        //this->save_setpoint(value, this->heat_setpoint_pref);
        break;		
    }
  }
}

optional<float> DaikinS21Climate::load_setpoint(DaikinClimateMode mode) {
  optional<float> loaded;
  switch (this->s21->get_climate_mode()) {
    case DaikinClimateMode::Auto:
      loaded = this->load_setpoint(this->auto_setpoint_pref);
      break;
    case DaikinClimateMode::Cool:
      loaded = this->load_setpoint(this->cool_setpoint_pref);
      break;
    case DaikinClimateMode::Heat:
      loaded = this->load_setpoint(this->heat_setpoint_pref);
      break;
    case DaikinClimateMode::Dry:
      //loaded = this->load_setpoint(this->heat_setpoint_pref);
      break;
	case DaikinClimateMode::Fan:
      //loaded = this->load_setpoint(this->heat_setpoint_pref);
      break;
    case DaikinClimateMode::Disabled:
      //loaded = this->load_setpoint(this->heat_setpoint_pref);
      break;	  
  }
  return loaded;
}

Furthermore, within the s21.cpp file, find & replace the following:

void DaikinS21::check_uart_settings() {
  for (auto uart : {this->tx_uart, this->rx_uart}) {
    if (uart->get_baud_rate() != S21_BAUD_RATE) {
      ESP_LOGE(
          TAG,
          "  Invalid baud_rate: Integration requested baud_rate %u but you "
          "have %lx!",
          S21_BAUD_RATE, uart->get_baud_rate());

and

void DaikinS21::dump_config() {
  ESP_LOGCONFIG(TAG, "DaikinS21:");
  ESP_LOGCONFIG(TAG, "  Update interval: %lx", this->get_update_interval());
  this->check_uart_settings();
}

(Note: %u becomes %lx)

can you also power it fom the 5 volt?
pin 1?
my pin 4 does not give any voltage