├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md └── components └── mitsubishi_heatpump ├── __init__.py ├── climate.py ├── espmhp.cpp ├── espmhp.h └── mitsubishi_ac_select.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Python cache 35 | __pycache__ 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | This project uses (or at least makes a vague hand-waving attempt at using) 2 | the git-flow release model. 3 | 4 | As such, please keep the following in mind: 5 | 6 | * New features should be based on the `develop` branch. 7 | * Bug fixes should be based on the `master` branch. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2020, Geoff Davis 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esphome-mitsubishiheatpump 2 | 3 | Wirelessly control your Mitsubishi Comfort HVAC equipment with an ESP8266 or 4 | ESP32 using the [ESPHome](https://esphome.io) framework. 5 | 6 | ## DEPRECATION NOTICE 7 | 8 | > [!IMPORTANT] 9 | > As of 2025, it's clear that I'm not able to dedicate the necessary time to keep 10 | > this project running. Thanks to all of the contributors that have made it 11 | > popular and brought it kicking and screaming out of the ESP8266 era. 12 | 13 | ### Recommended replacement 14 | 15 | [Eric Chavet](https://github.com/echavet/) has rewritten this code and expanded on it's features over at [echavet/MitsubishiCN105ESPHome](https://github.com/echavet/MitsubishiCN105ESPHome) 16 | 17 | Note that Eric's project "maintains all functionalities of (this) project", per the README there. 18 | 19 | ## Features 20 | 21 | * Instant feedback of command changes via RF Remote to HomeAssistant or MQTT. 22 | * Direct control without the remote. 23 | * Uses the [SwiCago/HeatPump](https://github.com/SwiCago/HeatPump) Arduino 24 | libary to talk to the unit directly via the internal `CN105` connector. 25 | 26 | ## Requirements 27 | 28 | * [SwiCago/HeatPump](https://github.com/SwiCago/HeatPump) 29 | * ESPHome 1.19.1 or greater 30 | 31 | ## Supported Microcontrollers 32 | 33 | This library should work on most ESP8266 or ESP32 platforms. It has been tested 34 | with the following MCUs: 35 | 36 | * Generic ESP-01S board (ESP8266) 37 | * WeMos D1 Mini (ESP8266) 38 | * Generic ESP32 Dev Kit (ESP32) 39 | 40 | ## Supported Mitsubishi Climate Units 41 | 42 | The underlying HeatPump library works with a number of Mitsubishi HVAC 43 | units. Basically, if the unit has a `CN105` header on the main board, it should 44 | work with this library. The [HeatPump 45 | wiki](https://github.com/SwiCago/HeatPump/wiki/Supported-models) has a more 46 | exhaustive list. 47 | 48 | The same `CN105` connector is used by the Mitsubishi KumoCloud remotes, which 49 | have a 50 | [compatibility list](https://www.mitsubishicomfort.com/kumocloud/compatibility) 51 | available. 52 | 53 | The whole integration with this libary and the underlying HeatPump has been 54 | tested by the author on the following units: 55 | 56 | * `MSZ-GL06NA` 57 | * `MFZ-KA09NA` 58 | * `MSZ-FH35V` 59 | * `MSZ-LN35VG2W` 60 | 61 | ## Usage 62 | 63 | ### Step 1: Build a control circuit 64 | 65 | Build a control circuit with your MCU as detailed in the [SwiCago/HeatPump 66 | README](https://github.com/SwiCago/HeatPump/blob/master/README.md#demo-circuit). 67 | You can use either an ESP8266 or an ESP32 for this. 68 | 69 | Note: several users have reported that they've been able to get away with 70 | not using the pull-up resistors, and just [directly connecting a Wemos D1 mini 71 | to the control 72 | board](https://github.com/SwiCago/HeatPump/issues/13#issuecomment-457897457) 73 | via CN105. 74 | 75 | ### Step 2: Use ESPHome 1.18.0 or higher 76 | 77 | The code in this repository makes use of a number of features in the 1.18.0 78 | version of ESPHome, including various Fan modes and 79 | [external components](https://esphome.io/components/external_components.html). 80 | 81 | ### Step 3: Add this repository as an external component 82 | 83 | Add this repository to your ESPHome config: 84 | 85 | ```yaml 86 | external_components: 87 | - source: github://geoffdavis/esphome-mitsubishiheatpump 88 | ``` 89 | 90 | #### Step 3a: Upgrading from 1.x releases 91 | 92 | Version 2.0 and greater of this libary use the ESPHome `external_components` 93 | feature, which is a huge step forward in terms of usability. In order to make 94 | things compile correctly, you will need to: 95 | 96 | 1. Remove the `libraries` section that imports 97 | `https://github.com/SwiCago/HeatPump`, as this is handled by the 98 | `external_component` section of manifest. 99 | 2. Remove the `includes` section that imports `src/esphome-mitsubishiheatpump` 100 | 3. Delete the old checkout of this repository under 101 | `src/esphome-mitsubishiheatpump`. 102 | 4. Clean your old ESPHome build directories out (3-dot menu, "Clean Build 103 | Files") 104 | 5. You may also have to delete the _esphomenodename_ directory that 105 | corresponds with your _esphomenodename.yaml_ configuration file 106 | completely. This directory may exist in your base config directory, 107 | or in `config/.esphome/build`. Testing with ESPHome 0.18.x showed this 108 | to be necessary to get the cached copy of src/esphome-mitsubishiheatpump to 109 | go away entirely, as the "Clean Build Files" isn't as thorough as one would like. 110 | 111 | *Note:* Failure to delete the old source directory and remove the `includes` 112 | and `libraries` lines will likely result in compilation errors complaining 113 | about duplicate declarations of `MitsubishiHeatPump::traits()`. 114 | 115 | ##### Example error 116 | 117 | ```none 118 | Linking /data/bedroom_east_heatpump/.pioenvs/bedroom_east_heatpump/firmware.elf 119 | /root/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/4.8.2/../../../../xtensa-lx106-elf/bin/ld: /data/bedroom_east_heatpump/.pioenvs/bedroom_east_heatpump/src/esphome/components/mitsubishi_heatpump/espmhp.cpp.o: in function `MitsubishiHeatPump::traits()': 120 | espmhp.cpp:(.text._ZN18MitsubishiHeatPump6traitsEv+0x4): multiple definition of `MitsubishiHeatPump::traits()'; /data/bedroom_east_heatpump/.pioenvs/bedroom_east_heatpump/src/esphome-mitsubishiheatpump/espmhp.cpp.o:espmhp.cpp:(.text._ZN18MitsubishiHeatPump6traitsEv+0x80): first defined here 121 | ``` 122 | 123 | ### Step 4: Configure the heatpump 124 | 125 | Add a `mitsubishi_heatpump` to your ESPHome config: 126 | 127 | ```yaml 128 | climate: 129 | - platform: mitsubishi_heatpump 130 | name: "My Heat Pump" 131 | 132 | # Optional 133 | hardware_uart: UART0 134 | baud_rate: 4800 135 | 136 | # Optional 137 | update_interval: 500ms 138 | ``` 139 | 140 | #### ESP8266 platforms 141 | 142 | On ESP8266 you'll need to disable logging to serial because it conflicts with 143 | the heatpump UART: 144 | 145 | ```yaml 146 | logger: 147 | baud_rate: 0 148 | ``` 149 | 150 | #### ESP32 platforms 151 | 152 | On ESP32 you can change `hardware_uart` to `UART1` or `UART2` and keep logging 153 | enabled on the main serial port. This may require specifying `baud_rate` on some 154 | ESP32 boards. 155 | 156 | #### UART Notes 157 | 158 | *Note:* this component DOES NOT use the ESPHome `uart` component, as it 159 | requires direct access to a hardware UART via the Arduino `HardwareSerial` 160 | class. The Mitsubishi Heatpump units use an atypical serial port setting ("even 161 | parity"). Parity bit support is not implemented in any of the existing 162 | software serial libraries, including the one in ESPHome. There's currently no 163 | way to guarantee access to a hardware UART nor retrieve the `HardwareSerial` 164 | handle from the `uart` component within the ESPHome framework. 165 | 166 | ## Example configurations 167 | 168 | Below is an example configuration which will include wireless strength 169 | indicators and permit over the air updates. You'll need to create a 170 | `secrets.yaml` file inside of your `esphome` directory with entries for the 171 | various items prefixed with `!secret`. 172 | 173 | ### ESP8266 Example Configuration 174 | 175 | ```yaml 176 | substitutions: 177 | name: hptest 178 | friendly_name: Test Heatpump 179 | 180 | 181 | esphome: 182 | name: ${name} 183 | platform: ESP8266 184 | board: esp01_1m 185 | # Boards tested: ESP-01S (ESP8266), Wemos D1 Mini (ESP8266); ESP32 Wifi-DevKit2 186 | 187 | wifi: 188 | ssid: !secret wifi_ssid 189 | password: !secret wifi_password 190 | 191 | # Enable fallback hotspot (captive portal) in case wifi connection fails 192 | ap: 193 | ssid: "${friendly_name} Fallback Hotspot" 194 | password: !secret fallback_password 195 | 196 | # Note: if upgrading from 1.x releases of esphome-mitsubishiheatpump, be sure 197 | # to remove any old entries from the `libraries` and `includes` section. 198 | #libraries: 199 | # Remove reference to SwiCago/HeatPump 200 | 201 | #includes: 202 | # Remove reference to src/esphome-mitsubishiheatpump 203 | 204 | captive_portal: 205 | 206 | # Enable logging 207 | logger: 208 | # ESP8266 only - disable serial port logging, as the HeatPump component 209 | # needs the sole hardware UART on the ESP8266 210 | baud_rate: 0 211 | 212 | # Enable Home Assistant API 213 | api: 214 | 215 | ota: 216 | 217 | # Enable Web server. 218 | web_server: 219 | port: 80 220 | 221 | # Sync time with Home Assistant. 222 | time: 223 | - platform: homeassistant 224 | id: homeassistant_time 225 | 226 | # Text sensors with general information. 227 | text_sensor: 228 | # Expose ESPHome version as sensor. 229 | - platform: version 230 | name: ${name} ESPHome Version 231 | # Expose WiFi information as sensors. 232 | - platform: wifi_info 233 | ip_address: 234 | name: ${name} IP 235 | ssid: 236 | name: ${name} SSID 237 | bssid: 238 | name: ${name} BSSID 239 | 240 | # Sensors with general information. 241 | sensor: 242 | # Uptime sensor. 243 | - platform: uptime 244 | name: ${name} Uptime 245 | 246 | # WiFi Signal sensor. 247 | - platform: wifi_signal 248 | name: ${name} WiFi Signal 249 | update_interval: 60s 250 | 251 | external_components: 252 | - source: github://geoffdavis/esphome-mitsubishiheatpump 253 | 254 | climate: 255 | - platform: mitsubishi_heatpump 256 | name: "${friendly_name}" 257 | 258 | horizontal_vane_select: 259 | name: Horizontal Vane 260 | vertical_vane_select: 261 | name: Vertical Vane 262 | 263 | # ESP32 only - change UART0 to UART1 or UART2 and remove the 264 | # logging:baud_rate above to allow the built-in UART0 to function for 265 | # logging. 266 | # Some ESP32 boards will require the baud_rate setting if hardware_uart is specified. 267 | hardware_uart: UART0 268 | baud_rate: 4800 269 | ``` 270 | 271 | ### ESP32 Example Configuration 272 | 273 | ```yaml 274 | substitutions: 275 | name: hptest 276 | friendly_name: Test Heatpump 277 | 278 | 279 | esphome: 280 | name: ${name} 281 | 282 | esp32: 283 | board: lolin_s2_mini 284 | variant: ESP32S2 285 | framework: 286 | type: arduino 287 | version: 2.0.3 288 | platform_version: 5.0.0 289 | 290 | wifi: 291 | ssid: !secret wifi_ssid 292 | password: !secret wifi_password 293 | 294 | # Enable fallback hotspot (captive portal) in case wifi connection fails 295 | ap: 296 | ssid: "${friendly_name} Fallback Hotspot" 297 | password: !secret fallback_password 298 | 299 | captive_portal: 300 | 301 | # Enable logging 302 | logger: 303 | 304 | # Enable Home Assistant API 305 | api: 306 | 307 | ota: 308 | 309 | # Enable Web server. 310 | web_server: 311 | port: 80 312 | 313 | # Sync time with Home Assistant. 314 | time: 315 | - platform: homeassistant 316 | id: homeassistant_time 317 | 318 | # Text sensors with general information. 319 | text_sensor: 320 | # Expose ESPHome version as sensor. 321 | - platform: version 322 | name: ${name} ESPHome Version 323 | # Expose WiFi information as sensors. 324 | - platform: wifi_info 325 | ip_address: 326 | name: ${name} IP 327 | ssid: 328 | name: ${name} SSID 329 | bssid: 330 | name: ${name} BSSID 331 | 332 | # Sensors with general information. 333 | sensor: 334 | # Uptime sensor. 335 | - platform: uptime 336 | name: ${name} Uptime 337 | 338 | # WiFi Signal sensor. 339 | - platform: wifi_signal 340 | name: ${name} WiFi Signal 341 | update_interval: 60s 342 | 343 | external_components: 344 | - source: github://geoffdavis/esphome-mitsubishiheatpump 345 | 346 | climate: 347 | - platform: mitsubishi_heatpump 348 | name: "${friendly_name}" 349 | 350 | horizontal_vane_select: 351 | name: Horizontal Vane 352 | vertical_vane_select: 353 | name: Vertical Vane 354 | 355 | # ESP32 only - change UART0 to UART1 or UART2 and remove the 356 | # logging:baud_rate above to allow the built-in UART0 to function for 357 | # logging. 358 | hardware_uart: UART1 359 | ``` 360 | 361 | ### Advanced configuration 362 | 363 | Some models of heat pump require different baud rates or don't support all 364 | possible modes of operation. You can configure mulitple climate "traits" in 365 | YAML to match what your hardware supports. For example: 366 | 367 | ```yaml 368 | climate: 369 | - platform: mitsubishi_heatpump 370 | name: "My heat pump" 371 | hardware_uart: UART2 372 | baud_rate: 9600 373 | rx_pin: 9 374 | tx_pin: 10 375 | supports: 376 | mode: ["HEAT_COOL", "COOL", "HEAT", "FAN_ONLY"] 377 | fan_mode: ["AUTO", "LOW", "MEDIUM", "HIGH"] 378 | swing_mode: ["OFF", "VERTICAL", "HORIZONTAL", "BOTH"] 379 | visual: 380 | min_temperature: 16 381 | max_temperature: 31 382 | temperature_step: 1.0 383 | ``` 384 | 385 | ## Configuration variables that affect this library directly 386 | 387 | * `hardware_uart` (_Optional_): the hardware UART instance to use for 388 | communcation with the heatpump. On ESP8266, only `UART0` is usable. On ESP32, 389 | `UART0`, `UART1`, and `UART2` are all valid choices. Default: `UART0` 390 | * `baud_rate` (_Optional_): Serial BAUD rate used to communicate with the 391 | HeatPump. Most systems use the default value of `4800` baud, but some use 392 | `2400` or `9600`. Check [here](https://github.com/SwiCago/HeatPump/issues/13) 393 | to find discussion of whether your particular model requires a non-default baud rate. 394 | Some ESP32 boards will require the baud_rate setting if 395 | hardware_uart is specified. Default: `4800`. 396 | * `rx_pin` (_Optional_): pin number to use as RX for the specified hardware 397 | UART (ESP32 only - ESP8266 hardware UART's pins aren't configurable). 398 | * `tx_pin` (_Optional_): pin number to use as TX for the specified hardware 399 | UART (ESP32 only - ESP8266 hardware UART's pins aren't configurable). 400 | * `update_interval` (_Optional_, range: 0ms to 9000ms): How often this 401 | component polls the heatpump hardware, in milliseconds. Maximum usable value 402 | is 9 seconds due to underlying issues with the HeatPump library. Default: 500ms 403 | * `supports` (_Optional_): Supported features for the device. 404 | * `mode` (_Optional_, list): Supported climate modes for the HeatPump. Default: 405 | `['HEAT_COOL', 'COOL', 'HEAT', 'DRY', 'FAN_ONLY']` 406 | * `fan_mode` (_Optional_, list): Supported fan speeds for the HeatPump. 407 | Default: `['AUTO', 'DIFFUSE', 'LOW', 'MEDIUM', 'MIDDLE', 'HIGH']` 408 | * `swing_mode` (_Optional_, list): Supported fan swing modes. Most Mitsubishi 409 | units only support the default. Default: `['OFF', 'VERTICAL']` 410 | * `remote_temperature_operating_timeout_minutes` (_Optional_): The number of 411 | minutes before a set_remote_temperature request becomes stale, while the 412 | heatpump is heating or cooling. Unless a new set_remote_temperature 413 | request was made within the time duration, the heatpump will revert back to it's 414 | internal temperature sensor. 415 | * `remote_temperature_idle_timeout_minutes` (_Optional_): The number of 416 | minutes before a set_remote_temperature request becomes stale while the heatpump 417 | is idle. Unless a new set_remote_temperature request is made within the time duration, 418 | the heatpump will revert back to it's internal temperature sensor. 419 | * `remote_temperature_ping_timeout_minutes` (_Optional_): The number of 420 | minutes before a set_remote_temperature request becomes stale, if a ping 421 | request wasn't received from your ESPHome controller. This will result 422 | in the heatpump reverting to it's internal temperature sensor if the heatpump 423 | loses it's WiFi connection. 424 | equest.) 425 | 426 | ## Other configuration 427 | 428 | * `id` (_Optional_): used to identify multiple instances, e.g. "denheatpump" 429 | * `name` (_Required_): The name of the climate component, e.g. "Den Heatpump" 430 | * `visual` (_Optional_): The core `Climate` component has several *visual* 431 | options that can be set. See the [Climate 432 | Component](https://esphome.io/components/climate/index.html) documentation for 433 | details. 434 | 435 | ### Remote temperature 436 | 437 | It is possible to use an external temperature sensor to tell the heat pump what 438 | the room temperature is, rather than relying on its internal temperature 439 | sensor. This is helpful if you want to make sure that a particular room, or part 440 | of the room, reaches the desired temperature—rather than just the area near the 441 | heat pump or the thermostat. You can do this by calling `set_remote_temperature(float temp)` 442 | on the `mitsubishi_heatpump` object in a lambda. (If needed, you can call 443 | `set_remote_temperature(0)` to switch back to the internal temperature sensor.) 444 | 445 | There are several ways you could make use of this functionality. One is to use 446 | a sensor automation: 447 | 448 | ```yaml 449 | climate: 450 | - platform: mitsubishi_heatpump 451 | name: "Lounge heat pump" 452 | id: hp 453 | 454 | sensor: 455 | # You could use a Bluetooth temperature sensor as the source... 456 | - platform: atc_mithermometer 457 | mac_address: "XX:XX:XX:XX:XX:XX" 458 | temperature: 459 | name: "Lounge temperature" 460 | on_value: 461 | then: 462 | - lambda: 'id(hp).set_remote_temperature(x);' 463 | 464 | # ...or you could use a Home Assistant sensor as the source 465 | - platform: homeassistant 466 | name: "Temperature Sensor From Home Assistant" 467 | entity_id: sensor.temperature_sensor 468 | on_value: 469 | then: 470 | - lambda: 'id(hp).set_remote_temperature(x);' 471 | ``` 472 | One issue that you might have here is that, after some amount of time with no update from the 473 | external temperature sensor, the heat pump will revert back to its internal temperature. 474 | You can prevent this by [adding a `heartbeat` filter](https://github.com/geoffdavis/esphome-mitsubishiheatpump/issues/31#issuecomment-1207115352) 475 | to the sensor, which will keep reminding the heat pump of the external sensor value. 476 | 477 | Also, if your external sensor is in Fahrenheit, you will have to [convert the value to Celsius](https://github.com/geoffdavis/esphome-mitsubishiheatpump/issues/31#issuecomment-1207115352). 478 | 479 | 480 | Alternatively, you could define a 481 | [service](https://www.esphome.io/components/api.html#user-defined-services) 482 | that Home Assistant can call: 483 | 484 | ```yaml 485 | api: 486 | services: 487 | - service: set_remote_temperature 488 | variables: 489 | temperature: float 490 | then: 491 | - lambda: 'id(hp).set_remote_temperature(temperature);' 492 | 493 | - service: use_internal_temperature 494 | then: 495 | - lambda: 'id(hp).set_remote_temperature(0);' 496 | ``` 497 | 498 | It's also possible to configure timeouts which will revert the heatpump 499 | back to it's internal temperature sensor in the event that an external sensor 500 | becomes unavailable. All three settings are optional, but it's recommended 501 | that you configure both operating and idle timeout. Both can be configured to the same 502 | value. 503 | 504 | ```yaml 505 | climate: 506 | - platform: mitsubishi_heatpump 507 | remote_temperature_operating_timeout_minutes: 65 508 | remote_temperature_idle_timeout_minutes: 120 509 | remote_temperature_ping_timeout_minutes: 20 510 | 511 | api: 512 | services: 513 | - service: ping 514 | then: 515 | - lambda: 'id(hp).ping();' 516 | ``` 517 | 518 | There is an explicit distinction between an operating timeout and an idle timeout. 519 | 520 | * **Operating timeout** The heatpump is currently pumping heat, and the expectation is that 521 | the temperature should shift within a certain time period. Recommended value: 60 minutes. 522 | * **Idle timeout** The heatpump is not currently pumping heat, so temperature shifts are expected 523 | to happen less frequently. Recommended value depends on the implementation details of your temperature 524 | sensor. Some will only provide updates on temperature changes, others such as Aqara will provide 525 | an update at least once every hour. 526 | * **Ping timeout** Detects if a connection is lost between HomeAssistant and the heatpump, or if your 527 | home assistant instance is down. Recommended value is 20 minutes, with a ping being sent every 5 minutes. 528 | 529 | Do not enable ping timeout until you have the logic in place to call the ping service at a regular interval. You 530 | can view the ESPHome logs to ensure this is taking place. 531 | 532 | ## See Also 533 | 534 | ### Other Implementations 535 | 536 | The [gysmo38/mitsubishi2MQTT](https://github.com/gysmo38/mitsubishi2MQTT) 537 | Arduino sketch also uses the `SwiCago/HeatPump` 538 | library, and works with MQTT directly. The author of this implementation found 539 | `mitsubishi2MQTT`'s WiFi stack to not be particularly robust, but the controls 540 | worked fine. Like this ESPHome repository, `mitsubishi2MQTT` will automatically 541 | register the device in your HomeAssistant instance if you have HA configured to do so. 542 | 543 | There's also the built-in to ESPHome 544 | [Mitsubishi](https://github.com/esphome/esphome/blob/dev/esphome/components/mitsubishi/mitsubishi.h) 545 | climate component. 546 | The big drawback with the built-in component is that it uses Infrared Remote 547 | commands to talk to the Heat Pump. By contrast, the approach used by this 548 | repository and it's underlying `HeatPump` library allows bi-directional 549 | communication with the Mitsubishi system, and can detect when someone changes 550 | the settings via an IR remote. 551 | 552 | ### Reference documentation 553 | 554 | The author referred to the following documentation repeatedly: 555 | 556 | * [ESPHome Custom Sensors Reference](https://esphome.io/components/sensor/custom.html) 557 | * [ESPHome Custom Climate Components Reference](https://esphome.io/components/climate/custom.html) 558 | * [ESPHome External Components Reference](https://esphome.io/components/external_components.html) 559 | * [Source for ESPHome's Climate Component](https://github.com/esphome/esphome/tree/master/esphome/components/climate) 560 | -------------------------------------------------------------------------------- /components/mitsubishi_heatpump/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffdavis/esphome-mitsubishiheatpump/0db179b57bc4bd61c454fa9c772c16c991355dae/components/mitsubishi_heatpump/__init__.py -------------------------------------------------------------------------------- /components/mitsubishi_heatpump/climate.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | import esphome.config_validation as cv 3 | from esphome.components import climate, select 4 | from esphome.components.logger import HARDWARE_UART_TO_SERIAL 5 | from esphome.const import ( 6 | CONF_ID, 7 | CONF_HARDWARE_UART, 8 | CONF_BAUD_RATE, 9 | CONF_RX_PIN, 10 | CONF_TX_PIN, 11 | CONF_UPDATE_INTERVAL, 12 | CONF_MODE, 13 | CONF_FAN_MODE, 14 | CONF_SWING_MODE, 15 | PLATFORM_ESP8266 16 | ) 17 | from esphome.core import CORE, coroutine 18 | 19 | AUTO_LOAD = ["climate", "select"] 20 | 21 | CONF_SUPPORTS = "supports" 22 | CONF_HORIZONTAL_SWING_SELECT = "horizontal_vane_select" 23 | CONF_VERTICAL_SWING_SELECT = "vertical_vane_select" 24 | DEFAULT_CLIMATE_MODES = ["HEAT_COOL", "COOL", "HEAT", "DRY", "FAN_ONLY"] 25 | DEFAULT_FAN_MODES = ["AUTO", "DIFFUSE", "LOW", "MEDIUM", "MIDDLE", "HIGH"] 26 | DEFAULT_SWING_MODES = ["OFF", "VERTICAL"] 27 | HORIZONTAL_SWING_OPTIONS = [ 28 | "auto", 29 | "swing", 30 | "left", 31 | "left_center", 32 | "center", 33 | "right_center", 34 | "right", 35 | ] 36 | VERTICAL_SWING_OPTIONS = ["swing", "auto", "up", "up_center", "center", "down_center", "down"] 37 | 38 | # Remote temperature timeout configuration 39 | CONF_REMOTE_OPERATING_TIMEOUT = "remote_temperature_operating_timeout_minutes" 40 | CONF_REMOTE_IDLE_TIMEOUT = "remote_temperature_idle_timeout_minutes" 41 | CONF_REMOTE_PING_TIMEOUT = "remote_temperature_ping_timeout_minutes" 42 | 43 | MitsubishiHeatPump = cg.global_ns.class_( 44 | "MitsubishiHeatPump", climate.Climate, cg.PollingComponent 45 | ) 46 | 47 | MitsubishiACSelect = cg.global_ns.class_( 48 | "MitsubishiACSelect", select.Select, cg.Component 49 | ) 50 | 51 | def valid_uart(uart): 52 | if CORE.is_esp8266: 53 | uarts = ["UART0"] # UART1 is tx-only 54 | elif CORE.is_esp32: 55 | uarts = ["UART0", "UART1", "UART2"] 56 | else: 57 | raise NotImplementedError 58 | 59 | return cv.one_of(*uarts, upper=True)(uart) 60 | 61 | 62 | SELECT_SCHEMA = select.SELECT_SCHEMA.extend( 63 | {cv.GenerateID(CONF_ID): cv.declare_id(MitsubishiACSelect)} 64 | ) 65 | 66 | CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend( 67 | { 68 | cv.GenerateID(): cv.declare_id(MitsubishiHeatPump), 69 | cv.Optional(CONF_HARDWARE_UART, default="UART0"): valid_uart, 70 | cv.Optional(CONF_BAUD_RATE): cv.positive_int, 71 | cv.Optional(CONF_REMOTE_OPERATING_TIMEOUT): cv.positive_int, 72 | cv.Optional(CONF_REMOTE_IDLE_TIMEOUT): cv.positive_int, 73 | cv.Optional(CONF_REMOTE_PING_TIMEOUT): cv.positive_int, 74 | cv.Optional(CONF_RX_PIN): cv.positive_int, 75 | cv.Optional(CONF_TX_PIN): cv.positive_int, 76 | # If polling interval is greater than 9 seconds, the HeatPump library 77 | # reconnects, but doesn't then follow up with our data request. 78 | cv.Optional(CONF_UPDATE_INTERVAL, default="500ms"): cv.All( 79 | cv.update_interval, cv.Range(max=cv.TimePeriod(milliseconds=9000)) 80 | ), 81 | # Add selects for vertical and horizontal vane positions 82 | cv.Optional(CONF_HORIZONTAL_SWING_SELECT): SELECT_SCHEMA, 83 | cv.Optional(CONF_VERTICAL_SWING_SELECT): SELECT_SCHEMA, 84 | # Optionally override the supported ClimateTraits. 85 | cv.Optional(CONF_SUPPORTS, default={}): cv.Schema( 86 | { 87 | cv.Optional(CONF_MODE, default=DEFAULT_CLIMATE_MODES): 88 | cv.ensure_list(climate.validate_climate_mode), 89 | cv.Optional(CONF_FAN_MODE, default=DEFAULT_FAN_MODES): 90 | cv.ensure_list(climate.validate_climate_fan_mode), 91 | cv.Optional(CONF_SWING_MODE, default=DEFAULT_SWING_MODES): 92 | cv.ensure_list(climate.validate_climate_swing_mode), 93 | } 94 | ), 95 | } 96 | ).extend(cv.COMPONENT_SCHEMA) 97 | 98 | 99 | @coroutine 100 | def to_code(config): 101 | serial = HARDWARE_UART_TO_SERIAL[PLATFORM_ESP8266][config[CONF_HARDWARE_UART]] 102 | var = cg.new_Pvariable(config[CONF_ID], cg.RawExpression(f"&{serial}")) 103 | 104 | if CONF_BAUD_RATE in config: 105 | cg.add(var.set_baud_rate(config[CONF_BAUD_RATE])) 106 | 107 | if CONF_RX_PIN in config: 108 | cg.add(var.set_rx_pin(config[CONF_RX_PIN])) 109 | 110 | if CONF_TX_PIN in config: 111 | cg.add(var.set_tx_pin(config[CONF_TX_PIN])) 112 | 113 | if CONF_REMOTE_OPERATING_TIMEOUT in config: 114 | cg.add(var.set_remote_operating_timeout_minutes(config[CONF_REMOTE_OPERATING_TIMEOUT])) 115 | 116 | if CONF_REMOTE_IDLE_TIMEOUT in config: 117 | cg.add(var.set_remote_idle_timeout_minutes(config[CONF_REMOTE_IDLE_TIMEOUT])) 118 | 119 | if CONF_REMOTE_PING_TIMEOUT in config: 120 | cg.add(var.set_remote_ping_timeout_minutes(config[CONF_REMOTE_PING_TIMEOUT])) 121 | 122 | 123 | supports = config[CONF_SUPPORTS] 124 | traits = var.config_traits() 125 | 126 | for mode in supports[CONF_MODE]: 127 | if mode == "OFF": 128 | continue 129 | cg.add(traits.add_supported_mode(climate.CLIMATE_MODES[mode])) 130 | 131 | for mode in supports[CONF_FAN_MODE]: 132 | cg.add(traits.add_supported_fan_mode(climate.CLIMATE_FAN_MODES[mode])) 133 | 134 | for mode in supports[CONF_SWING_MODE]: 135 | cg.add(traits.add_supported_swing_mode( 136 | climate.CLIMATE_SWING_MODES[mode] 137 | )) 138 | 139 | if CONF_HORIZONTAL_SWING_SELECT in config: 140 | conf = config[CONF_HORIZONTAL_SWING_SELECT] 141 | swing_select = yield select.new_select(conf, options=HORIZONTAL_SWING_OPTIONS) 142 | yield cg.register_component(swing_select, conf) 143 | cg.add(var.set_horizontal_vane_select(swing_select)) 144 | 145 | if CONF_VERTICAL_SWING_SELECT in config: 146 | conf = config[CONF_VERTICAL_SWING_SELECT] 147 | swing_select = yield select.new_select(conf, options=VERTICAL_SWING_OPTIONS) 148 | yield cg.register_component(swing_select, conf) 149 | cg.add(var.set_vertical_vane_select(swing_select)) 150 | 151 | yield cg.register_component(var, config) 152 | yield climate.register_climate(var, config) 153 | cg.add_library( 154 | name="HeatPump", 155 | repository="https://github.com/SwiCago/HeatPump#5d1e146771d2f458907a855bf9d5d4b9bf5ff033", 156 | version=None, # this appears to be ignored? 157 | ) 158 | -------------------------------------------------------------------------------- /components/mitsubishi_heatpump/espmhp.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * espmhp.cpp 3 | * 4 | * Implementation of esphome-mitsubishiheatpump 5 | * 6 | * Author: Geoff Davis 7 | * Author: Phil Genera @pgenera on Github. 8 | * Author: Barry Loong @loongyh on GitHub. 9 | * Author: @am-io on Github. 10 | * Author: @nao-pon on Github. 11 | * Author: Simon Knopp @sijk on Github 12 | * Author: Paul Murphy @donutsoft on GitHub 13 | * Last Updated: 2023-04-22 14 | * License: BSD 15 | * 16 | * Requirements: 17 | * - https://github.com/SwiCago/HeatPump 18 | * - ESPHome 1.18.0 or greater 19 | */ 20 | 21 | #include "espmhp.h" 22 | using namespace esphome; 23 | 24 | /** 25 | * Create a new MitsubishiHeatPump object 26 | * 27 | * Args: 28 | * hw_serial: pointer to an Arduino HardwareSerial instance 29 | * poll_interval: polling interval in milliseconds 30 | */ 31 | MitsubishiHeatPump::MitsubishiHeatPump( 32 | HardwareSerial* hw_serial, 33 | uint32_t poll_interval 34 | ) : 35 | PollingComponent{poll_interval}, // member initializers list 36 | hw_serial_{hw_serial} 37 | { 38 | this->traits_.set_supports_action(true); 39 | this->traits_.set_supports_current_temperature(true); 40 | this->traits_.set_supports_two_point_target_temperature(false); 41 | this->traits_.set_visual_min_temperature(ESPMHP_MIN_TEMPERATURE); 42 | this->traits_.set_visual_max_temperature(ESPMHP_MAX_TEMPERATURE); 43 | this->traits_.set_visual_temperature_step(ESPMHP_TEMPERATURE_STEP); 44 | 45 | // Assume a succesful connection was made to the ESPHome controller on 46 | // launch. 47 | this->ping(); 48 | } 49 | 50 | bool MitsubishiHeatPump::verify_serial() { 51 | if (!this->get_hw_serial_()) { 52 | ESP_LOGCONFIG( 53 | TAG, 54 | "No HardwareSerial was provided. " 55 | "Software serial ports are unsupported by this component." 56 | ); 57 | return false; 58 | } 59 | 60 | #ifdef USE_LOGGER 61 | if (this->get_hw_serial_() == logger::global_logger->get_hw_serial()) { 62 | ESP_LOGW(TAG, " You're using the same serial port for logging" 63 | " and the MitsubishiHeatPump component. Please disable" 64 | " logging over the serial port by setting" 65 | " logger:baud_rate to 0."); 66 | return false; 67 | } 68 | #endif 69 | // unless something went wrong, assume we have a valid serial configuration 70 | return true; 71 | } 72 | 73 | void MitsubishiHeatPump::banner() { 74 | ESP_LOGI(TAG, "ESPHome MitsubishiHeatPump version %s", 75 | ESPMHP_VERSION); 76 | } 77 | 78 | void MitsubishiHeatPump::update() { 79 | // This will be called every "update_interval" milliseconds. 80 | //this->dump_config(); 81 | this->hp->sync(); 82 | #ifndef USE_CALLBACKS 83 | this->hpSettingsChanged(); 84 | heatpumpStatus currentStatus = hp->getStatus(); 85 | this->hpStatusChanged(currentStatus); 86 | #endif 87 | this->enforce_remote_temperature_sensor_timeout(); 88 | } 89 | 90 | void MitsubishiHeatPump::set_baud_rate(int baud) { 91 | this->baud_ = baud; 92 | } 93 | 94 | void MitsubishiHeatPump::set_rx_pin(int rx_pin) { 95 | this->rx_pin_ = rx_pin; 96 | } 97 | 98 | void MitsubishiHeatPump::set_tx_pin(int tx_pin) { 99 | this->tx_pin_ = tx_pin; 100 | } 101 | 102 | /** 103 | * Get our supported traits. 104 | * 105 | * Note: 106 | * Many of the following traits are only available in the 1.5.0 dev train of 107 | * ESPHome, particularly the Dry operation mode, and several of the fan modes. 108 | * 109 | * Returns: 110 | * This class' supported climate::ClimateTraits. 111 | */ 112 | climate::ClimateTraits MitsubishiHeatPump::traits() { 113 | return traits_; 114 | } 115 | 116 | /** 117 | * Modify our supported traits. 118 | * 119 | * Returns: 120 | * A reference to this class' supported climate::ClimateTraits. 121 | */ 122 | climate::ClimateTraits& MitsubishiHeatPump::config_traits() { 123 | return traits_; 124 | } 125 | 126 | void MitsubishiHeatPump::update_swing_horizontal(const std::string &swing) { 127 | this->horizontal_swing_state_ = swing; 128 | 129 | if (this->horizontal_vane_select_ != nullptr && 130 | this->horizontal_vane_select_->state != this->horizontal_swing_state_) { 131 | this->horizontal_vane_select_->publish_state( 132 | this->horizontal_swing_state_); // Set current horizontal swing 133 | // position 134 | } 135 | } 136 | 137 | void MitsubishiHeatPump::update_swing_vertical(const std::string &swing) { 138 | this->vertical_swing_state_ = swing; 139 | 140 | if (this->vertical_vane_select_ != nullptr && 141 | this->vertical_vane_select_->state != this->vertical_swing_state_) { 142 | this->vertical_vane_select_->publish_state( 143 | this->vertical_swing_state_); // Set current vertical swing position 144 | } 145 | } 146 | 147 | void MitsubishiHeatPump::set_vertical_vane_select( 148 | select::Select *vertical_vane_select) { 149 | this->vertical_vane_select_ = vertical_vane_select; 150 | this->vertical_vane_select_->add_on_state_callback( 151 | [this](const std::string &value, size_t index) { 152 | if (value == this->vertical_swing_state_) return; 153 | this->on_vertical_swing_change(value); 154 | }); 155 | } 156 | 157 | void MitsubishiHeatPump::set_horizontal_vane_select( 158 | select::Select *horizontal_vane_select) { 159 | this->horizontal_vane_select_ = horizontal_vane_select; 160 | this->horizontal_vane_select_->add_on_state_callback( 161 | [this](const std::string &value, size_t index) { 162 | if (value == this->horizontal_swing_state_) return; 163 | this->on_horizontal_swing_change(value); 164 | }); 165 | } 166 | 167 | void MitsubishiHeatPump::on_vertical_swing_change(const std::string &swing) { 168 | ESP_LOGD(TAG, "Setting vertical swing position"); 169 | bool updated = false; 170 | 171 | if (swing == "swing") { 172 | hp->setVaneSetting("SWING"); 173 | updated = true; 174 | } else if (swing == "auto") { 175 | hp->setVaneSetting("AUTO"); 176 | updated = true; 177 | } else if (swing == "up") { 178 | hp->setVaneSetting("1"); 179 | updated = true; 180 | } else if (swing == "up_center") { 181 | hp->setVaneSetting("2"); 182 | updated = true; 183 | } else if (swing == "center") { 184 | hp->setVaneSetting("3"); 185 | updated = true; 186 | } else if (swing == "down_center") { 187 | hp->setVaneSetting("4"); 188 | updated = true; 189 | } else if (swing == "down") { 190 | hp->setVaneSetting("5"); 191 | updated = true; 192 | } else { 193 | ESP_LOGW(TAG, "Invalid vertical vane position %s", swing); 194 | } 195 | 196 | ESP_LOGD(TAG, "Vertical vane - Was HeatPump updated? %s", YESNO(updated)); 197 | 198 | // and the heat pump: 199 | hp->update(); 200 | } 201 | 202 | void MitsubishiHeatPump::on_horizontal_swing_change(const std::string &swing) { 203 | ESP_LOGD(TAG, "Setting horizontal swing position"); 204 | bool updated = false; 205 | 206 | if (swing == "swing") { 207 | hp->setWideVaneSetting("SWING"); 208 | updated = true; 209 | } else if (swing == "auto") { 210 | hp->setWideVaneSetting("<>"); 211 | updated = true; 212 | } else if (swing == "left") { 213 | hp->setWideVaneSetting("<<"); 214 | updated = true; 215 | } else if (swing == "left_center") { 216 | hp->setWideVaneSetting("<"); 217 | updated = true; 218 | } else if (swing == "center") { 219 | hp->setWideVaneSetting("|"); 220 | updated = true; 221 | } else if (swing == "right_center") { 222 | hp->setWideVaneSetting(">"); 223 | updated = true; 224 | } else if (swing == "right") { 225 | hp->setWideVaneSetting(">>"); 226 | updated = true; 227 | } else { 228 | ESP_LOGW(TAG, "Invalid horizontal vane position %s", swing); 229 | } 230 | 231 | ESP_LOGD(TAG, "Horizontal vane - Was HeatPump updated? %s", YESNO(updated)); 232 | 233 | // and the heat pump: 234 | hp->update(); 235 | } 236 | 237 | /** 238 | * Implement control of a MitsubishiHeatPump. 239 | * 240 | * Maps HomeAssistant/ESPHome modes to Mitsubishi modes. 241 | */ 242 | void MitsubishiHeatPump::control(const climate::ClimateCall &call) { 243 | ESP_LOGV(TAG, "Control called."); 244 | 245 | bool updated = false; 246 | bool has_mode = call.get_mode().has_value(); 247 | bool has_temp = call.get_target_temperature().has_value(); 248 | if (has_mode){ 249 | this->mode = *call.get_mode(); 250 | } 251 | 252 | if (last_remote_temperature_sensor_update_.has_value()) { 253 | // Some remote temperature sensors will only issue updates when a change 254 | // in temperature occurs. 255 | 256 | // Assume a case where the idle sensor timeout is 12hrs and operating 257 | // timeout is 1hr. If the user changes the HP setpoint after 1.5hrs, the 258 | // machine will switch to operating mode, the remote temperature 259 | // reading will expire and the HP will revert to it's internal 260 | // temperature sensor. 261 | 262 | // This change ensures that if the user changes the machine setpoint, 263 | // the remote sensor has an opportunity to issue an update to reflect 264 | // the new change in temperature. 265 | last_remote_temperature_sensor_update_ = 266 | std::chrono::steady_clock::now(); 267 | } 268 | 269 | switch (this->mode) { 270 | case climate::CLIMATE_MODE_COOL: 271 | hp->setModeSetting("COOL"); 272 | hp->setPowerSetting("ON"); 273 | 274 | if (has_mode){ 275 | if (cool_setpoint.has_value() && !has_temp) { 276 | hp->setTemperature(cool_setpoint.value()); 277 | this->target_temperature = cool_setpoint.value(); 278 | } 279 | this->action = climate::CLIMATE_ACTION_IDLE; 280 | updated = true; 281 | } 282 | break; 283 | case climate::CLIMATE_MODE_HEAT: 284 | hp->setModeSetting("HEAT"); 285 | hp->setPowerSetting("ON"); 286 | if (has_mode){ 287 | if (heat_setpoint.has_value() && !has_temp) { 288 | hp->setTemperature(heat_setpoint.value()); 289 | this->target_temperature = heat_setpoint.value(); 290 | } 291 | this->action = climate::CLIMATE_ACTION_IDLE; 292 | updated = true; 293 | } 294 | break; 295 | case climate::CLIMATE_MODE_DRY: 296 | hp->setModeSetting("DRY"); 297 | hp->setPowerSetting("ON"); 298 | if (has_mode){ 299 | this->action = climate::CLIMATE_ACTION_DRYING; 300 | updated = true; 301 | } 302 | break; 303 | case climate::CLIMATE_MODE_HEAT_COOL: 304 | hp->setModeSetting("AUTO"); 305 | hp->setPowerSetting("ON"); 306 | if (has_mode){ 307 | if (auto_setpoint.has_value() && !has_temp) { 308 | hp->setTemperature(auto_setpoint.value()); 309 | this->target_temperature = auto_setpoint.value(); 310 | } 311 | this->action = climate::CLIMATE_ACTION_IDLE; 312 | } 313 | updated = true; 314 | break; 315 | case climate::CLIMATE_MODE_FAN_ONLY: 316 | hp->setModeSetting("FAN"); 317 | hp->setPowerSetting("ON"); 318 | if (has_mode){ 319 | this->action = climate::CLIMATE_ACTION_FAN; 320 | updated = true; 321 | } 322 | break; 323 | case climate::CLIMATE_MODE_OFF: 324 | default: 325 | if (has_mode){ 326 | hp->setPowerSetting("OFF"); 327 | this->action = climate::CLIMATE_ACTION_OFF; 328 | updated = true; 329 | } 330 | break; 331 | } 332 | 333 | if (has_temp){ 334 | ESP_LOGV( 335 | "control", "Sending target temp: %.1f", 336 | *call.get_target_temperature() 337 | ); 338 | hp->setTemperature(*call.get_target_temperature()); 339 | this->target_temperature = *call.get_target_temperature(); 340 | updated = true; 341 | } 342 | 343 | //const char* FAN_MAP[6] = {"AUTO", "QUIET", "1", "2", "3", "4"}; 344 | if (call.get_fan_mode().has_value()) { 345 | ESP_LOGV("control", "Requested fan mode is %s", 346 | climate::climate_fan_mode_to_string(*call.get_fan_mode())); 347 | this->fan_mode = *call.get_fan_mode(); 348 | switch(*call.get_fan_mode()) { 349 | case climate::CLIMATE_FAN_OFF: 350 | hp->setPowerSetting("OFF"); 351 | updated = true; 352 | break; 353 | case climate::CLIMATE_FAN_DIFFUSE: 354 | hp->setFanSpeed("QUIET"); 355 | updated = true; 356 | break; 357 | case climate::CLIMATE_FAN_LOW: 358 | hp->setFanSpeed("1"); 359 | updated = true; 360 | break; 361 | case climate::CLIMATE_FAN_MEDIUM: 362 | hp->setFanSpeed("2"); 363 | updated = true; 364 | break; 365 | case climate::CLIMATE_FAN_MIDDLE: 366 | hp->setFanSpeed("3"); 367 | updated = true; 368 | break; 369 | case climate::CLIMATE_FAN_HIGH: 370 | hp->setFanSpeed("4"); 371 | updated = true; 372 | break; 373 | case climate::CLIMATE_FAN_ON: 374 | case climate::CLIMATE_FAN_AUTO: 375 | default: 376 | hp->setFanSpeed("AUTO"); 377 | updated = true; 378 | break; 379 | } 380 | } 381 | 382 | 383 | ESP_LOGV(TAG, "in the swing mode stage"); 384 | //const char* VANE_MAP[7] = {"AUTO", "1", "2", "3", "4", "5", "SWING"}; 385 | if (call.get_swing_mode().has_value()) { 386 | ESP_LOGV(TAG, "control - requested swing mode is %s", 387 | climate::climate_swing_mode_to_string(*call.get_swing_mode())); 388 | 389 | this->swing_mode = *call.get_swing_mode(); 390 | switch(*call.get_swing_mode()) { 391 | case climate::CLIMATE_SWING_OFF: 392 | hp->setVaneSetting("AUTO"); 393 | hp->setWideVaneSetting("|"); 394 | updated = true; 395 | break; 396 | case climate::CLIMATE_SWING_VERTICAL: 397 | hp->setVaneSetting("SWING"); 398 | hp->setWideVaneSetting("|"); 399 | updated = true; 400 | break; 401 | case climate::CLIMATE_SWING_HORIZONTAL: 402 | hp->setVaneSetting("3"); 403 | hp->setWideVaneSetting("SWING"); 404 | updated = true; 405 | break; 406 | case climate::CLIMATE_SWING_BOTH: 407 | hp->setVaneSetting("SWING"); 408 | hp->setWideVaneSetting("SWING"); 409 | updated = true; 410 | break; 411 | default: 412 | ESP_LOGW(TAG, "control - received unsupported swing mode request."); 413 | 414 | } 415 | } 416 | ESP_LOGD(TAG, "control - Was HeatPump updated? %s", YESNO(updated)); 417 | 418 | // send the update back to esphome: 419 | this->publish_state(); 420 | // and the heat pump: 421 | hp->update(); 422 | } 423 | 424 | void MitsubishiHeatPump::hpSettingsChanged() { 425 | heatpumpSettings currentSettings = hp->getSettings(); 426 | 427 | if (currentSettings.power == NULL) { 428 | /* 429 | * We should always get a valid pointer here once the HeatPump 430 | * component fully initializes. If HeatPump hasn't read the settings 431 | * from the unit yet (hp->connect() doesn't do this, sadly), we'll need 432 | * to punt on the update. Likely not an issue when run in callback 433 | * mode, but that isn't working right yet. 434 | */ 435 | ESP_LOGW(TAG, "Waiting for HeatPump to read the settings the first time."); 436 | esphome::delay(10); 437 | return; 438 | } 439 | 440 | /* 441 | * ************ HANDLE POWER AND MODE CHANGES *********** 442 | * https://github.com/geoffdavis/HeatPump/blob/stream/src/HeatPump.h#L125 443 | * const char* POWER_MAP[2] = {"OFF", "ON"}; 444 | * const char* MODE_MAP[5] = {"HEAT", "DRY", "COOL", "FAN", "AUTO"}; 445 | */ 446 | if (strcmp(currentSettings.power, "ON") == 0) { 447 | if (strcmp(currentSettings.mode, "HEAT") == 0) { 448 | this->mode = climate::CLIMATE_MODE_HEAT; 449 | if (heat_setpoint != currentSettings.temperature) { 450 | heat_setpoint = currentSettings.temperature; 451 | save(currentSettings.temperature, heat_storage); 452 | } 453 | this->action = climate::CLIMATE_ACTION_IDLE; 454 | } else if (strcmp(currentSettings.mode, "DRY") == 0) { 455 | this->mode = climate::CLIMATE_MODE_DRY; 456 | this->action = climate::CLIMATE_ACTION_DRYING; 457 | } else if (strcmp(currentSettings.mode, "COOL") == 0) { 458 | this->mode = climate::CLIMATE_MODE_COOL; 459 | if (cool_setpoint != currentSettings.temperature) { 460 | cool_setpoint = currentSettings.temperature; 461 | save(currentSettings.temperature, cool_storage); 462 | } 463 | this->action = climate::CLIMATE_ACTION_IDLE; 464 | } else if (strcmp(currentSettings.mode, "FAN") == 0) { 465 | this->mode = climate::CLIMATE_MODE_FAN_ONLY; 466 | this->action = climate::CLIMATE_ACTION_FAN; 467 | } else if (strcmp(currentSettings.mode, "AUTO") == 0) { 468 | this->mode = climate::CLIMATE_MODE_HEAT_COOL; 469 | if (auto_setpoint != currentSettings.temperature) { 470 | auto_setpoint = currentSettings.temperature; 471 | save(currentSettings.temperature, auto_storage); 472 | } 473 | this->action = climate::CLIMATE_ACTION_IDLE; 474 | } else { 475 | ESP_LOGW( 476 | TAG, 477 | "Unknown climate mode value %s received from HeatPump", 478 | currentSettings.mode 479 | ); 480 | } 481 | } else { 482 | this->mode = climate::CLIMATE_MODE_OFF; 483 | this->action = climate::CLIMATE_ACTION_OFF; 484 | } 485 | 486 | ESP_LOGI(TAG, "Climate mode is: %i", this->mode); 487 | 488 | /* 489 | * ******* HANDLE FAN CHANGES ******** 490 | * 491 | * const char* FAN_MAP[6] = {"AUTO", "QUIET", "1", "2", "3", "4"}; 492 | */ 493 | if (strcmp(currentSettings.fan, "QUIET") == 0) { 494 | this->fan_mode = climate::CLIMATE_FAN_DIFFUSE; 495 | } else if (strcmp(currentSettings.fan, "1") == 0) { 496 | this->fan_mode = climate::CLIMATE_FAN_LOW; 497 | } else if (strcmp(currentSettings.fan, "2") == 0) { 498 | this->fan_mode = climate::CLIMATE_FAN_MEDIUM; 499 | } else if (strcmp(currentSettings.fan, "3") == 0) { 500 | this->fan_mode = climate::CLIMATE_FAN_MIDDLE; 501 | } else if (strcmp(currentSettings.fan, "4") == 0) { 502 | this->fan_mode = climate::CLIMATE_FAN_HIGH; 503 | } else { //case "AUTO" or default: 504 | this->fan_mode = climate::CLIMATE_FAN_AUTO; 505 | } 506 | ESP_LOGI(TAG, "Fan mode is: %i", this->fan_mode.value_or(-1)); 507 | 508 | /* ******** HANDLE MITSUBISHI VANE CHANGES ******** 509 | * const char* VANE_MAP[7] = {"AUTO", "1", "2", "3", "4", "5", "SWING"}; 510 | */ 511 | if (strcmp(currentSettings.vane, "SWING") == 0 && 512 | strcmp(currentSettings.wideVane, "SWING") == 0) { 513 | this->swing_mode = climate::CLIMATE_SWING_BOTH; 514 | } else if (strcmp(currentSettings.vane, "SWING") == 0) { 515 | this->swing_mode = climate::CLIMATE_SWING_VERTICAL; 516 | } else if (strcmp(currentSettings.wideVane, "SWING") == 0) { 517 | this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; 518 | } else { 519 | this->swing_mode = climate::CLIMATE_SWING_OFF; 520 | } 521 | ESP_LOGI(TAG, "Swing mode is: %i", this->swing_mode); 522 | if (strcmp(currentSettings.vane, "SWING") == 0) { 523 | this->update_swing_vertical("swing"); 524 | } else if (strcmp(currentSettings.vane, "AUTO") == 0) { 525 | this->update_swing_vertical("auto"); 526 | } else if (strcmp(currentSettings.vane, "1") == 0) { 527 | this->update_swing_vertical("up"); 528 | } else if (strcmp(currentSettings.vane, "2") == 0) { 529 | this->update_swing_vertical("up_center"); 530 | } else if (strcmp(currentSettings.vane, "3") == 0) { 531 | this->update_swing_vertical("center"); 532 | } else if (strcmp(currentSettings.vane, "4") == 0) { 533 | this->update_swing_vertical("down_center"); 534 | } else if (strcmp(currentSettings.vane, "5") == 0) { 535 | this->update_swing_vertical("down"); 536 | } 537 | 538 | ESP_LOGI(TAG, "Vertical vane mode is: %s", currentSettings.vane); 539 | 540 | if (strcmp(currentSettings.wideVane, "SWING") == 0) { 541 | this->update_swing_horizontal("swing"); 542 | } else if (strcmp(currentSettings.wideVane, "<>") == 0) { 543 | this->update_swing_horizontal("auto"); 544 | } else if (strcmp(currentSettings.wideVane, "<<") == 0) { 545 | this->update_swing_horizontal("left"); 546 | } else if (strcmp(currentSettings.wideVane, "<") == 0) { 547 | this->update_swing_horizontal("left_center"); 548 | } else if (strcmp(currentSettings.wideVane, "|") == 0) { 549 | this->update_swing_horizontal("center"); 550 | } else if (strcmp(currentSettings.wideVane, ">") == 0) { 551 | this->update_swing_horizontal("right_center"); 552 | } else if (strcmp(currentSettings.wideVane, ">>") == 0) { 553 | this->update_swing_horizontal("right"); 554 | } 555 | 556 | ESP_LOGI(TAG, "Horizontal vane mode is: %s", currentSettings.wideVane); 557 | 558 | /* 559 | * ******** HANDLE TARGET TEMPERATURE CHANGES ******** 560 | */ 561 | this->target_temperature = currentSettings.temperature; 562 | ESP_LOGI(TAG, "Target temp is: %f", this->target_temperature); 563 | 564 | /* 565 | * ******** Publish state back to ESPHome. ******** 566 | */ 567 | this->publish_state(); 568 | } 569 | 570 | /** 571 | * Report changes in the current temperature sensed by the HeatPump. 572 | */ 573 | void MitsubishiHeatPump::hpStatusChanged(heatpumpStatus currentStatus) { 574 | this->current_temperature = currentStatus.roomTemperature; 575 | switch (this->mode) { 576 | case climate::CLIMATE_MODE_HEAT: 577 | if (currentStatus.operating) { 578 | this->action = climate::CLIMATE_ACTION_HEATING; 579 | } 580 | else { 581 | this->action = climate::CLIMATE_ACTION_IDLE; 582 | } 583 | break; 584 | case climate::CLIMATE_MODE_COOL: 585 | if (currentStatus.operating) { 586 | this->action = climate::CLIMATE_ACTION_COOLING; 587 | } 588 | else { 589 | this->action = climate::CLIMATE_ACTION_IDLE; 590 | } 591 | break; 592 | case climate::CLIMATE_MODE_HEAT_COOL: 593 | this->action = climate::CLIMATE_ACTION_IDLE; 594 | if (currentStatus.operating) { 595 | if (this->current_temperature > this->target_temperature) { 596 | this->action = climate::CLIMATE_ACTION_COOLING; 597 | } else if (this->current_temperature < this->target_temperature) { 598 | this->action = climate::CLIMATE_ACTION_HEATING; 599 | } 600 | } 601 | break; 602 | case climate::CLIMATE_MODE_DRY: 603 | if (currentStatus.operating) { 604 | this->action = climate::CLIMATE_ACTION_DRYING; 605 | } 606 | else { 607 | this->action = climate::CLIMATE_ACTION_IDLE; 608 | } 609 | break; 610 | case climate::CLIMATE_MODE_FAN_ONLY: 611 | this->action = climate::CLIMATE_ACTION_FAN; 612 | break; 613 | default: 614 | this->action = climate::CLIMATE_ACTION_OFF; 615 | } 616 | 617 | this->operating_ = currentStatus.operating; 618 | 619 | this->publish_state(); 620 | } 621 | 622 | void MitsubishiHeatPump::set_remote_temperature(float temp) { 623 | ESP_LOGD(TAG, "Setting remote temp: %.1f", temp); 624 | if (temp > 0) { 625 | last_remote_temperature_sensor_update_ = 626 | std::chrono::steady_clock::now(); 627 | } else { 628 | last_remote_temperature_sensor_update_.reset(); 629 | } 630 | 631 | this->hp->setRemoteTemperature(temp); 632 | } 633 | 634 | void MitsubishiHeatPump::ping() { 635 | ESP_LOGD(TAG, "Ping request received"); 636 | last_ping_request_ = std::chrono::steady_clock::now(); 637 | } 638 | 639 | void MitsubishiHeatPump::set_remote_operating_timeout_minutes(int minutes) { 640 | ESP_LOGD(TAG, "Setting remote operating timeout time: %d minutes", minutes); 641 | remote_operating_timeout_ = std::chrono::minutes(minutes); 642 | } 643 | 644 | void MitsubishiHeatPump::set_remote_idle_timeout_minutes(int minutes) { 645 | ESP_LOGD(TAG, "Setting remote idle timeout time: %d minutes", minutes); 646 | remote_idle_timeout_ = std::chrono::minutes(minutes); 647 | } 648 | 649 | void MitsubishiHeatPump::set_remote_ping_timeout_minutes(int minutes) { 650 | ESP_LOGD(TAG, "Setting remote ping timeout time: %d minutes", minutes); 651 | remote_ping_timeout_ = std::chrono::minutes(minutes); 652 | } 653 | 654 | void MitsubishiHeatPump::enforce_remote_temperature_sensor_timeout() { 655 | // Handle ping timeouts. 656 | if (remote_ping_timeout_.has_value() && last_ping_request_.has_value()) { 657 | auto time_since_last_ping = 658 | std::chrono::steady_clock::now() - last_ping_request_.value(); 659 | if(time_since_last_ping > remote_ping_timeout_.value()) { 660 | ESP_LOGW(TAG, "Ping timeout."); 661 | this->set_remote_temperature(0); 662 | last_ping_request_.reset(); 663 | return; 664 | } 665 | } 666 | 667 | // Handle set_remote_temperature timeouts. 668 | auto remote_set_temperature_timeout = 669 | this->operating_ ? remote_operating_timeout_ : remote_idle_timeout_; 670 | if (remote_set_temperature_timeout.has_value() && 671 | last_remote_temperature_sensor_update_.has_value()) { 672 | auto time_since_last_temperature_update = 673 | std::chrono::steady_clock::now() - last_remote_temperature_sensor_update_.value(); 674 | if (time_since_last_temperature_update > remote_set_temperature_timeout.value()) { 675 | ESP_LOGW(TAG, "Set remote temperature timeout, operating=%d", this->operating_); 676 | this->set_remote_temperature(0); 677 | return; 678 | } 679 | } 680 | } 681 | 682 | void MitsubishiHeatPump::setup() { 683 | // This will be called by App.setup() 684 | this->banner(); 685 | ESP_LOGCONFIG(TAG, "Setting up UART..."); 686 | 687 | if (!this->verify_serial()) { 688 | this->mark_failed(); 689 | return; 690 | } 691 | 692 | ESP_LOGCONFIG(TAG, "Initializing new HeatPump object."); 693 | this->hp = new HeatPump(); 694 | this->current_temperature = NAN; 695 | this->target_temperature = NAN; 696 | this->fan_mode = climate::CLIMATE_FAN_OFF; 697 | this->swing_mode = climate::CLIMATE_SWING_OFF; 698 | this->vertical_swing_state_ = "auto"; 699 | this->horizontal_swing_state_ = "auto"; 700 | 701 | #ifdef USE_CALLBACKS 702 | hp->setSettingsChangedCallback( 703 | [this]() { 704 | this->hpSettingsChanged(); 705 | } 706 | ); 707 | 708 | hp->setStatusChangedCallback( 709 | [this](heatpumpStatus currentStatus) { 710 | this->hpStatusChanged(currentStatus); 711 | } 712 | ); 713 | 714 | hp->setPacketCallback(this->log_packet); 715 | #endif 716 | 717 | ESP_LOGCONFIG( 718 | TAG, 719 | "hw_serial(%p) is &Serial(%p)? %s", 720 | this->get_hw_serial_(), 721 | &Serial, 722 | YESNO((void *)this->get_hw_serial_() == (void *)&Serial) 723 | ); 724 | 725 | ESP_LOGCONFIG(TAG, "Calling hp->connect(%p)", this->get_hw_serial_()); 726 | if (hp->connect(this->get_hw_serial_(), this->baud_, this->rx_pin_, this->tx_pin_)) { 727 | hp->sync(); 728 | } 729 | else { 730 | ESP_LOGCONFIG( 731 | TAG, 732 | "Connection to HeatPump failed." 733 | " Marking MitsubishiHeatPump component as failed." 734 | ); 735 | this->mark_failed(); 736 | } 737 | 738 | // create various setpoint persistence: 739 | cool_storage = global_preferences->make_preference(this->get_object_id_hash() + 1); 740 | heat_storage = global_preferences->make_preference(this->get_object_id_hash() + 2); 741 | auto_storage = global_preferences->make_preference(this->get_object_id_hash() + 3); 742 | 743 | // load values from storage: 744 | cool_setpoint = load(cool_storage); 745 | heat_setpoint = load(heat_storage); 746 | auto_setpoint = load(auto_storage); 747 | 748 | this->dump_config(); 749 | } 750 | 751 | /** 752 | * The ESP only has a few bytes of rtc storage, so instead 753 | * of storing floats directly, we'll store the number of 754 | * TEMPERATURE_STEPs from MIN_TEMPERATURE. 755 | **/ 756 | void MitsubishiHeatPump::save(float value, ESPPreferenceObject& storage) { 757 | uint8_t steps = (value - ESPMHP_MIN_TEMPERATURE) / ESPMHP_TEMPERATURE_STEP; 758 | storage.save(&steps); 759 | } 760 | 761 | optional MitsubishiHeatPump::load(ESPPreferenceObject& storage) { 762 | uint8_t steps = 0; 763 | if (!storage.load(&steps)) { 764 | return {}; 765 | } 766 | return ESPMHP_MIN_TEMPERATURE + (steps * ESPMHP_TEMPERATURE_STEP); 767 | } 768 | 769 | void MitsubishiHeatPump::dump_config() { 770 | this->banner(); 771 | ESP_LOGI(TAG, " Supports HEAT: %s", YESNO(true)); 772 | ESP_LOGI(TAG, " Supports COOL: %s", YESNO(true)); 773 | ESP_LOGI(TAG, " Supports AWAY mode: %s", YESNO(false)); 774 | ESP_LOGI(TAG, " Saved heat: %.1f", heat_setpoint.value_or(-1)); 775 | ESP_LOGI(TAG, " Saved cool: %.1f", cool_setpoint.value_or(-1)); 776 | ESP_LOGI(TAG, " Saved auto: %.1f", auto_setpoint.value_or(-1)); 777 | } 778 | 779 | void MitsubishiHeatPump::dump_state() { 780 | LOG_CLIMATE("", "MitsubishiHeatPump Climate", this); 781 | ESP_LOGI(TAG, "HELLO"); 782 | } 783 | 784 | void MitsubishiHeatPump::log_packet(byte* packet, unsigned int length, char* packetDirection) { 785 | String packetHex; 786 | char textBuf[15]; 787 | 788 | for (int i = 0; i < length; i++) { 789 | memset(textBuf, 0, 15); 790 | sprintf(textBuf, "%02X ", packet[i]); 791 | packetHex += textBuf; 792 | } 793 | 794 | ESP_LOGV(TAG, "PKT: [%s] %s", packetDirection, packetHex.c_str()); 795 | } -------------------------------------------------------------------------------- /components/mitsubishi_heatpump/espmhp.h: -------------------------------------------------------------------------------- 1 | /** 2 | * espmhp.h 3 | * 4 | * Header file for esphome-mitsubishiheatpump 5 | * 6 | * Author: Geoff Davis 7 | * Author: Phil Genera @pgenera on Github. 8 | * Author: @nao-pon on Github 9 | * Author: Simon Knopp @sijk on Github 10 | * Last Updated: 2021-06-23 11 | * License: BSD 12 | * 13 | * Requirements: 14 | * - https://github.com/SwiCago/HeatPump 15 | * - ESPHome 1.19.1 or greater 16 | */ 17 | 18 | #define USE_CALLBACKS 19 | 20 | #include "esphome.h" 21 | #include "esphome/components/select/select.h" 22 | #include "esphome/core/preferences.h" 23 | #include 24 | 25 | #include "HeatPump.h" 26 | 27 | #ifndef ESPMHP_H 28 | #define ESPMHP_H 29 | 30 | static const char* TAG = "MitsubishiHeatPump"; // Logging tag 31 | 32 | static const char* ESPMHP_VERSION = "2.5.0"; 33 | 34 | /* If polling interval is greater than 9 seconds, the HeatPump 35 | library reconnects, but doesn't then follow up with our data request.*/ 36 | static const uint32_t ESPMHP_POLL_INTERVAL_DEFAULT = 500; // in milliseconds, 37 | // 0 < X <= 9000 38 | static const uint8_t ESPMHP_MIN_TEMPERATURE = 16; // degrees C, 39 | // defined by hardware 40 | static const uint8_t ESPMHP_MAX_TEMPERATURE = 31; // degrees C, 41 | //defined by hardware 42 | static const float ESPMHP_TEMPERATURE_STEP = 0.5; // temperature setting step, 43 | // in degrees C 44 | 45 | class MitsubishiHeatPump : public esphome::PollingComponent, public esphome::climate::Climate { 46 | 47 | public: 48 | 49 | /** 50 | * Create a new MitsubishiHeatPump object 51 | * 52 | * Args: 53 | * hw_serial: pointer to an Arduino HardwareSerial instance 54 | * poll_interval: polling interval in milliseconds 55 | */ 56 | MitsubishiHeatPump( 57 | HardwareSerial* hw_serial, 58 | uint32_t poll_interval=ESPMHP_POLL_INTERVAL_DEFAULT 59 | ); 60 | 61 | // Print a banner with library information. 62 | void banner(); 63 | 64 | // Set the baud rate. Must be called before setup() to have any effect. 65 | void set_baud_rate(int); 66 | 67 | // Set the RX pin. Must be called before setup() to have any effect. 68 | void set_rx_pin(int); 69 | 70 | // Set the TX pin. Must be called before setup() to have any effect. 71 | void set_tx_pin(int); 72 | 73 | // print the current configuration 74 | void dump_config() override; 75 | 76 | // handle a change in settings as detected by the HeatPump library. 77 | void hpSettingsChanged(); 78 | 79 | // Handle a change in status as detected by the HeatPump library. 80 | void hpStatusChanged(heatpumpStatus currentStatus); 81 | 82 | // Set up the component, initializing the HeatPump object. 83 | void setup() override; 84 | 85 | // This is called every poll_interval. 86 | void update() override; 87 | 88 | // Configure the climate object with traits that we support. 89 | esphome::climate::ClimateTraits traits() override; 90 | 91 | // Get a mutable reference to the traits that we support. 92 | esphome::climate::ClimateTraits& config_traits(); 93 | 94 | // Debugging function to print the object's state. 95 | void dump_state(); 96 | 97 | // Handle a request from the user to change settings. 98 | void control(const esphome::climate::ClimateCall &call) override; 99 | 100 | // Use the temperature from an external sensor. Use 101 | // set_remote_temp(0) to switch back to the internal sensor. 102 | void set_remote_temperature(float); 103 | 104 | void set_vertical_vane_select(esphome::select::Select *vertical_vane_select); 105 | void set_horizontal_vane_select(esphome::select::Select *horizontal_vane_select); 106 | 107 | // Used to validate that a connection is present between the controller 108 | // and this heatpump. 109 | void ping(); 110 | 111 | // Number of minutes before the heatpump reverts back to the internal 112 | // temperature sensor if the machine is currently operating. 113 | void set_remote_operating_timeout_minutes(int); 114 | 115 | // Number of minutes before the heatpump reverts back to the internal 116 | // temperature sensor if the machine is currently idle. 117 | void set_remote_idle_timeout_minutes(int); 118 | 119 | // Number of minutes before the heatpump reverts back to the internal 120 | // temperature sensor if a ping isn't received from the controller. 121 | void set_remote_ping_timeout_minutes(int); 122 | 123 | protected: 124 | // HeatPump object using the underlying Arduino library. 125 | HeatPump* hp; 126 | 127 | // The ClimateTraits supported by this HeatPump. 128 | esphome::climate::ClimateTraits traits_; 129 | 130 | // Vane position 131 | void update_swing_horizontal(const std::string &swing); 132 | void update_swing_vertical(const std::string &swing); 133 | std::string vertical_swing_state_; 134 | std::string horizontal_swing_state_; 135 | 136 | // Allow the HeatPump class to use get_hw_serial_ 137 | friend class HeatPump; 138 | 139 | //Accessor method for the HardwareSerial pointer 140 | HardwareSerial* get_hw_serial_() { 141 | return this->hw_serial_; 142 | } 143 | 144 | //Print a warning message if we're using the sole hardware UART on an 145 | //ESP8266 or UART0 on ESP32, or if no serial was provided 146 | bool verify_serial(); 147 | 148 | // various prefs to save mode-specific temperatures, akin to how the IR 149 | // remote works. 150 | esphome::ESPPreferenceObject cool_storage; 151 | esphome::ESPPreferenceObject heat_storage; 152 | esphome::ESPPreferenceObject auto_storage; 153 | 154 | esphome::optional cool_setpoint; 155 | esphome::optional heat_setpoint; 156 | esphome::optional auto_setpoint; 157 | 158 | static void save(float value, esphome::ESPPreferenceObject& storage); 159 | static esphome::optional load(esphome::ESPPreferenceObject& storage); 160 | 161 | esphome::select::Select *vertical_vane_select_ = 162 | nullptr; // Select to store manual position of vertical swing 163 | esphome::select::Select *horizontal_vane_select_ = 164 | nullptr; // Select to store manual position of horizontal swing 165 | 166 | // When received command to change the vane positions 167 | void on_horizontal_swing_change(const std::string &swing); 168 | void on_vertical_swing_change(const std::string &swing); 169 | 170 | static void log_packet(byte* packet, unsigned int length, char* packetDirection); 171 | 172 | private: 173 | void enforce_remote_temperature_sensor_timeout(); 174 | 175 | // Retrieve the HardwareSerial pointer from friend and subclasses. 176 | HardwareSerial *hw_serial_; 177 | int baud_ = 0; 178 | int rx_pin_ = -1; 179 | int tx_pin_ = -1; 180 | bool operating_ = false; 181 | 182 | std::optional>> remote_operating_timeout_; 183 | std::optional>> remote_idle_timeout_; 184 | std::optional>> remote_ping_timeout_; 185 | std::optional> last_remote_temperature_sensor_update_; 186 | std::optional> last_ping_request_; 187 | }; 188 | 189 | #endif 190 | -------------------------------------------------------------------------------- /components/mitsubishi_heatpump/mitsubishi_ac_select.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/components/select/select.h" 4 | #include "esphome/core/component.h" 5 | 6 | namespace esphome { 7 | 8 | class MitsubishiACSelect : public select::Select, public Component { 9 | protected: 10 | void control(const std::string &value) override { 11 | this->publish_state(value); 12 | } 13 | }; 14 | 15 | } // namespace esphome 16 | --------------------------------------------------------------------------------