mirror of
https://github.com/GrKoR/esphome_aux_ac_component.git
synced 2026-01-01 06:09:18 +03:00
Compare commits
108 Commits
v0.2.9
...
6195ba6f8a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6195ba6f8a | ||
|
|
ba75794434 | ||
|
|
2ec3deb2db | ||
|
|
6ea4d4891b | ||
|
|
e8588cc63e | ||
|
|
9d971e000a | ||
|
|
ab2498d798 | ||
|
|
c403155006 | ||
|
|
0fb4ace5ba | ||
|
|
9030e300b6 | ||
|
|
7203e0bb9a | ||
|
|
3d7e909bde | ||
|
|
7ca934da7e | ||
|
|
2539d2583c | ||
|
|
8c3e996cde | ||
|
|
8178da996c | ||
|
|
b697a301c0 | ||
|
|
5fc7cfc72c | ||
|
|
c5005f9c3f | ||
|
|
1582c2a7cf | ||
|
|
e913330caa | ||
|
|
1d8f37e927 | ||
|
|
d3b3d276d9 | ||
|
|
d9bfaeeb1e | ||
|
|
5c0fab2668 | ||
|
|
362defdc91 | ||
|
|
b7d6c1b842 | ||
|
|
073e834ce5 | ||
|
|
b3ef77e03e | ||
|
|
1b520d1515 | ||
|
|
026a1d7cf2 | ||
|
|
f2601b62c9 | ||
|
|
a3a97fc84e | ||
|
|
98d2f02062 | ||
|
|
61cad07acc | ||
|
|
3110c482c9 | ||
|
|
53a886f818 | ||
|
|
2d4162323a | ||
|
|
74d555b5e1 | ||
|
|
7df8ab3cc2 | ||
|
|
90d9a5cfa7 | ||
|
|
864ad07699 | ||
|
|
e595a4acf9 | ||
|
|
3db80a19b7 | ||
|
|
b7a794e52b | ||
|
|
ba9f41d3be | ||
|
|
93a034f2be | ||
|
|
151e5af043 | ||
|
|
f143240089 | ||
|
|
dfc299b321 | ||
|
|
8acae30989 | ||
|
|
58f09bd280 | ||
|
|
70d127a302 | ||
|
|
7765e080d2 | ||
|
|
33f6dd6ef0 | ||
|
|
1fa581be3d | ||
|
|
5fab934d8c | ||
|
|
92b9908bf3 | ||
|
|
be67e1e4ce | ||
|
|
c10d314581 | ||
|
|
fa05c40677 | ||
|
|
92b3930413 | ||
|
|
6bfb9489bd | ||
|
|
c868e5b49b | ||
|
|
cdd97b8da3 | ||
|
|
9c7be5ffc0 | ||
|
|
20f3061d12 | ||
|
|
945cc96ea2 | ||
|
|
fcd5a97210 | ||
|
|
8b17ca7052 | ||
|
|
935e97e795 | ||
|
|
a0b8dcdce1 | ||
|
|
affb3bde06 | ||
|
|
b6d09a132e | ||
|
|
579b6aaf87 | ||
|
|
7013acef59 | ||
|
|
f6f7887fe7 | ||
|
|
0b774546e8 | ||
|
|
1031f6a885 | ||
|
|
abf9c15c39 | ||
|
|
31af2c546c | ||
|
|
db23c365db | ||
|
|
eb202e2f49 | ||
|
|
abb9188642 | ||
|
|
276d8ef7a7 | ||
|
|
78ec13f9f9 | ||
|
|
1c04078b57 | ||
|
|
d776dcf4fe | ||
|
|
22e64b647c | ||
|
|
c6a800cdcd | ||
|
|
ba2a19e564 | ||
|
|
135d6b55a8 | ||
|
|
a465dbc89a | ||
|
|
efe7c1a93a | ||
|
|
13bb0baa69 | ||
|
|
62e83cb41b | ||
|
|
e9cad615fb | ||
|
|
4274a11ed5 | ||
|
|
a8b6f7c9e0 | ||
|
|
766a4bd693 | ||
|
|
8d782b1070 | ||
|
|
7640cca9f2 | ||
|
|
cc4a48f964 | ||
|
|
18ca3cf72a | ||
|
|
ad810b1a83 | ||
|
|
11a3440a56 | ||
|
|
6d4a4268e6 | ||
|
|
5bad8732e2 |
127
README-EN.md
127
README-EN.md
@@ -2,15 +2,15 @@
|
||||
For communication about this project [please join this telegram chat](https://t.me/aux_ac).
|
||||
|
||||
For issues or feature requests, please go to [the issue section](https://github.com/GrKoR/esphome_aux_ac_component/issues). It will be perfect if you attach log to your issue. Log you can collect with [this python script](https://github.com/GrKoR/ac_python_logger). It helps you to save all data frames from the UART bus to a csv-file. This log combined with the detailed situation description will significantly speed up bug correction.
|
||||
|
||||
There is also a [detailed instruction describing how to properly request a feature](docs/HOW_TO_FEATURE_REQUEST-EN.md).
|
||||
|
||||
## DISCLAIMER ##
|
||||
1. All data of this project (software, firmware, schemes, 3d-models etc.) are provided **'AS IS'**. Everything you do with your devices, you are doing at your own risk. If you don't strongly understand what you are doing, just buy wifi-module from your air conditioner manufacturer.
|
||||
1. All data of this project (software, firmware, schemes, 3d-models etc.) are provided **'AS IS'**. Everything you do with your devices, you are doing at your own risk. If you don't strongly understand what you are doing, just buy Wi-Fi module from your air conditioner manufacturer.
|
||||
2. I am not a programmer. So source code is certainly not optimal and badly decorated (but there are a lot of comments in it; sorry, a significant part of it is in Russian). Also, code may be written unsafe. I tried to test all parts of the code, but I'm sure I missed a lot of things. So treat it with suspicion, expect a trick from it, and if you discover something wrong write an issue here.
|
||||
3. Russian and English readme files are substantially identical in meaning. But in case of differences, the [Russian](https://github.com/GrKoR/esphome_aux_ac_component#readme) version is more significant.
|
||||
|
||||
## Short description ##
|
||||
This custom component allows you to control your air conditioner through wifi if it is made in the AUX factory.<br />
|
||||
This custom component allows you to control your air conditioner through Wi-Fi if it is made in the AUX factory.<br />
|
||||
Component tested with ESPHome 1.18.0 and Rovex ALS1 air conditioner. It looks like many other air conditioners can be controlled by `aux_ac`, but this possibility isn't tested. See list of tested ACs below for more details.
|
||||
|
||||
|
||||
@@ -21,26 +21,16 @@ There is the following list of AUX-based air conditioner on the internet: AUX, A
|
||||
### List of compatible ACs (tested) ###
|
||||
[The list of tested ACs](docs/AC_TESTED.md) is placed in a separate file and includes tested by the author or by users ACs. This list is permanently updated, mainly based on feedback from users in [Telegram chat](https://t.me/aux_ac).<br />
|
||||
|
||||
If your AC is not in the list
|
||||
If your AC is listed above you should take a closer look at aux_ac.
|
||||
If the User Manual of your HVAC describes connection to wifi with mobile app ACFreedom it seems you may go deeper with aux_ac. But try all soft and hardware for your own risk. You must clearly understand what you are doing.
|
||||
If you are unsure it is better to wait while other users will test your model of AC (but it may never). Or please go to telegram-chat with your questions. Maybe you will get help there.
|
||||
|
||||
If you have tested your air conditioner and aux_ac works with it please let me know about it. I'll add this info to the list of tested ACs above. The best way to report about your test results is write a message in the telegram or in the issue section.
|
||||
|
||||
How to use it
|
||||
For correct component operation you need hardware and firmware. The hardware description is located in separate file.
|
||||
|
||||
Firmware: Integration aux_ac to your configuration
|
||||
You need ESPHome v.1.18.0 or above. External_components have appeared in this version. But it is better to use ESPHome v.1.20.4 or above cause there was alot of external_components errors corrected before this version.
|
||||
### If your AC is not in the list ###
|
||||
If your AC is listed above, you should take a closer look at `aux_ac`.<br />
|
||||
If the User Manual of your HVAC describes connection to wifi with mobile app ACFreedom it seems you may go deeper with `aux_ac`. But try all soft and hardware for your own risk. You must clearly understand what you are doing.<br />
|
||||
If you are unsure, it is better to wait while other users will test your model of AC (but it may never). Or please [go to telegram-chat](https://t.me/aux_ac) with your questions. Maybe you will get help there.
|
||||
|
||||
If you have tested your air conditioner and `aux_ac` works with it, please let me know about it. I'll add this info to the list of tested ACs above.
|
||||
The best way to report about your test results is writing a message in the [telegram](https://t.me/aux_ac) or [in the issue section](https://github.com/GrKoR/esphome_aux_ac_component/issues).
|
||||
1. If your AC is listed above, you should take a closer look at `aux_ac`.<br />
|
||||
2. If something about AUX is written on the nameplate of the air conditioner in the manufacturer line.<br />
|
||||
3. If the User Manual of your HVAC describes connection to Wi-Fi with mobile app ACFreedom it seems you may go deeper with `aux_ac`. But try all soft and hardware for your own risk. You must clearly understand what you are doing.<br />
|
||||
4. If the manufacturer of your AC offers a CTTM-40X24-WIFI-AKS Wi-Fi module (left) or the one in the photo on the right for control. Moreover, the right module can be either with a USB connector or with a 5-pin connector.<br />
|
||||
<img src="https://user-images.githubusercontent.com/57137862/172053621-60fe39d8-066e-44fa-91c5-725fa1f5c3bc.png" height="300"> <img src="https://user-images.githubusercontent.com/57137862/172053744-8ce4a13d-28cb-4688-a998-11ca3a7129df.png" height="300">
|
||||
|
||||
If you are unsure, it is better to wait while other users will test your model of AC (maybe never). Or please [go to telegram-chat](https://t.me/aux_ac) with your questions. Maybe you will get help there.<br />
|
||||
If you have tested your air conditioner and `aux_ac` works with it, please let me know about it. I'll add this info to the list of tested ACs above.<br />
|
||||
The best way to report about your test results is writing a message in the [telegram](https://t.me/aux_ac) or [in the issue section](https://github.com/GrKoR/esphome_aux_ac_component/issues).<br />
|
||||
|
||||
## How to use it ##
|
||||
For correct component operation, you need hardware and firmware. The hardware description is located [in a separate file](docs/HARDWARE-EN.md).
|
||||
@@ -56,6 +46,14 @@ external_components:
|
||||
type: git
|
||||
url: https://github.com/GrKoR/esphome_aux_ac_component
|
||||
```
|
||||
In case you need a specific version of the component, you can use the component declaration from the example below. The example uses version 0.2.14 of the component. You can find a list of available versions [on the GitHub tags page](https://github.com/GrKoR/esphome_aux_ac_component/tags).
|
||||
```yaml
|
||||
external_components:
|
||||
- source:
|
||||
type: git
|
||||
url: https://github.com/GrKoR/esphome_aux_ac_component
|
||||
ref: v.0.2.14
|
||||
```
|
||||
2. Configure UART to communicate with air conditioner:
|
||||
```yaml
|
||||
uart:
|
||||
@@ -97,29 +95,37 @@ climate:
|
||||
id: aux_id
|
||||
uart_id: ac_uart_bus
|
||||
period: 7s
|
||||
show_action: true
|
||||
display_inverted: false
|
||||
timeout: 150
|
||||
indoor_temperature:
|
||||
name: AC Indoor Temperature
|
||||
id: ac_indoor_temp
|
||||
timeout: 300
|
||||
optimistic: true
|
||||
indoor_ambient_temperature:
|
||||
name: AC Indoor Ambient Temperature
|
||||
id: ac_indoor_ambient_temp
|
||||
accuracy_decimals: 1
|
||||
internal: false
|
||||
outdoor_temperature:
|
||||
name: AC Outdoor Temperature
|
||||
id: ac_outdoor_temp
|
||||
outdoor_ambient_temperature:
|
||||
name: AC Outdoor Ambient Temperature
|
||||
id: ac_outdoor_ambient_temp
|
||||
internal: false
|
||||
outbound_temperature:
|
||||
name: AC Colant Outbound Temperature
|
||||
id: ac_outbound_temp
|
||||
outdoor_condenser_temperature:
|
||||
name: AC Outdoor Condenser Temperature
|
||||
id: ac_outdoor_condenser_temp
|
||||
internal: false
|
||||
inbound_temperature:
|
||||
name: AC Colant Inbound Temperature
|
||||
id: ac_inbound_temp
|
||||
compressor_suction_temperature:
|
||||
name: AC Compressor Suction Temperature
|
||||
id: ac_compressor_suction_temp
|
||||
internal: false
|
||||
compressor_temperature:
|
||||
name: AC Compressor Temperature
|
||||
id: ac_strange_temp
|
||||
indoor_coil_temperature:
|
||||
name: AC Indoor Coil Temperature
|
||||
id: ac_indoor_coil_temp
|
||||
internal: false
|
||||
compressor_discharge_temperature:
|
||||
name: AC Compressor Discharge Temperature
|
||||
id: ac_compressor_discharge_temp
|
||||
internal: false
|
||||
defrost_temperature:
|
||||
name: AC Defrost Temperature
|
||||
id: ac_defrost_temp
|
||||
internal: false
|
||||
display_state:
|
||||
name: AC Display State
|
||||
@@ -184,33 +190,38 @@ climate:
|
||||
|
||||
- **period** (*Optional*, [time](https://esphome.io/guides/configuration-types.html#config-time), default ``7s``): Period between status requests to the AC. `Aux_ac` will receive the new air conditioner status only after a regular request, even if you change the settings of AC using IR-remote.
|
||||
|
||||
- **show_action** (*Optional*, boolean, default ``true``): Whether to show current action of the device (experimental). For example, in the HEAT-COOL mode, AC hardware may be in one of the following actions:
|
||||
- HEATING: AC is heating the air in the room;
|
||||
- IDLE: AC is working in the FAN mode, cause the target temperature is reached;
|
||||
- COOLING: AC is cooling the air.
|
||||
The same thing will be in HEAT or COOL modes, with the only difference of the list of actions (IDLE + HEATING or IDLE + COOLING).
|
||||
|
||||
- **display_inverted** (*Optional*, boolean, default ``false``): It configures display driver logic level. As it turned out in the issue [#31](https://github.com/GrKoR/esphome_aux_ac_component/issues/31), different models of conditioners manage display different way. Rovex ACs powers off display by bit `1` in command packet and power it on by bit `0`. Many other conditioners do this vice versa.
|
||||
|
||||
- **timeout** (*Optional*, unsigned integer, default ``150``): Packet timeout for `aux_ac` data receiver.
|
||||
- **timeout** (*Optional*, unsigned integer, default ``300``): Packet timeout for `aux_ac` data receiver.
|
||||
In the most common use of `aux_ac`, it isn't necessary to change this value. This keyword is optional, so you may omit it.
|
||||
The only situation when you can play with timeout is heavily loaded ESP. When you are using your ESP for many hard tasks, it is possible that `aux_ac` does not have enough time to receive AC responses. In this case, you can slightly raise the timeout value. But the best solution would be to remove some of the tasks from the ESP.
|
||||
The timeout is limited to a range from `150` to `600` milliseconds. Other values are possible only with source code modification. But I don't recommend that.
|
||||
The timeout is limited to a range from `300` to `800` milliseconds. Other values are possible only with source code modification. But I don't recommend that.
|
||||
|
||||
- **indoor_temperature** (*Optional*): Parameters of the room air temperature sensor.
|
||||
- **optimistic** (*Optional*, boolean, default ``true``): Whether entity states should be updated immediately after receiving a command from Home Assistant/ESPHome.
|
||||
|
||||
- **indoor_ambient_temperature** (*Optional*): Parameters of the room air temperature sensor.
|
||||
- **name** (**Required**, string): The name for the temperature sensor.
|
||||
- **id** (*Optional*, [ID](https://esphome.io/guides/configuration-types.html#config-id)): Set the ID of this sensor for use in lambdas.
|
||||
- **internal** (*Optional*, boolean): Mark this component as internal. Internal components will not be exposed to the frontend (like Home Assistant). As opposed to default [Sensor](https://esphome.io/components/sensor/index.html#base-sensor-configuration) behaviour, this variable is **always true** except in cases where the user has set it directly.
|
||||
- All other options from [Sensor](https://esphome.io/components/sensor/index.html#base-sensor-configuration).
|
||||
> **ATTENTION!** The sensor's name was changed in v.1.0.0 to synchronize with AUX service manuals.
|
||||
|
||||
- **outdoor_temperature** (*Optional*): Parameters of the outdoor temperature sensor. They are the same as the **indoor_temperature** (see description above).
|
||||
> **Attention!** When the air conditioner is turned off, the outdoor temperature is updated rarely (every 6-7 hours). This isn't a bug of the component, but a feature of the air conditioner hardware. The only way to get changes more often is to create a template sensor, the temperature of which can be changed manually. When the air conditioner is working, the value of this sensor can be copied from the **outdoor_temperature**. When the air conditioner is turned off, the temperature value should be recalculated according to the dynamics of the **outbound_temperature** sensor (it changes frequently and shows values close to the air temperature when the air conditioner is turned off). You can't copy the value of **outbound_temperature** without changes to the template sensor in AC off mode, because these temperatures are not identical.
|
||||
- **outdoor_ambient_temperature** (*Optional*): Parameters of the outdoor temperature sensor. They are the same as the **indoor_ambient_temperature** (see description above).
|
||||
> **Attention!** When the air conditioner is turned off, the outdoor temperature is updated rarely (every 6-7 hours). This isn't a bug of the component, but a feature of the air conditioner hardware. The only way to get changes more often is to create a template sensor, the temperature of which can be changed manually. When the air conditioner is working, the value of this sensor can be copied from the **outdoor_ambient_temperature**. When the air conditioner is turned off, the temperature value should be recalculated according to the dynamics of the **compressor_suction_temperature** sensor (it changes frequently and shows values close to the air temperature when the air conditioner is turned off). You can't copy the value of **compressor_suction_temperature** without changes to the template sensor in AC off mode, because these temperatures are not identical.
|
||||
> **ATTENTION!** The sensor's name was changed in v.1.0.0 to synchronize with AUX service manuals.
|
||||
|
||||
- **inbound_temperature** (*Optional*): Parameters of the coolant inbound temperature sensor. They are the same as the **indoor_temperature** (see description above).
|
||||
- **outdoor_condenser_temperature** (*Optional*): Parameters of the temperature sensor for condenser in outdoor unit. All the parameters are the same as the **indoor_ambient_temperature** (see description above).
|
||||
|
||||
- **outbound_temperature** (*Optional*): Parameters of the coolant outbound temperature sensor. They are the same as the **indoor_temperature** (see description above).
|
||||
- **indoor_coil_temperature** (*Optional*): Parameters of the temperature sensor for indoor unit coil. All the parameters are the same as the **indoor_ambient_temperature** (see description above).
|
||||
> **ATTENTION!** The sensor's name was changed in v.1.0.0 to synchronize with AUX service manuals.
|
||||
|
||||
- **compressor_temperature** (*Optional*): Parameters of the compressor temperature sensor. They are the same as the **indoor_temperature** (see description above).
|
||||
- **compressor_suction_temperature** (*Optional*): Parameters of the temperature sensor for compressor suction pipe. All the parameters are the same as the **indoor_ambient_temperature** (see description above).
|
||||
> **ATTENTION!** The sensor's name was changed in v.1.0.0 to synchronize with AUX service manuals.
|
||||
|
||||
- **compressor_discharge_temperature** (*Optional*): Parameters of the temperature sensor for compressor discharge pipe. All the parameters are the same as the **indoor_ambient_temperature** (see description above).
|
||||
> **ATTENTION!** The sensor's name was changed in v.1.0.0 to synchronize with AUX service manuals.
|
||||
|
||||
- **defrost_temperature** (*Optional*): Parameters of the defrost temperature sensor in the outdoor unit. All the parameters are the same as the **indoor_ambient_temperature** (see description above).
|
||||
|
||||
- **display_state** (*Optional*): The information for the HVAC display state sensor (is display ON or OFF)
|
||||
- **name** (**Required**, string): The name for the display state sensor.
|
||||
@@ -220,13 +231,13 @@ climate:
|
||||
|
||||
- **defrost_state** (*Optional*): The information for the HVAC defrost function state sensor (is it ON or OFF). All settings are the same as for the **display_state** (see description above).
|
||||
|
||||
- **inverter_power** (*Optional*): The information for the inverter power sensor. All settings are the same as for the **indoor_temperature** (see description above).
|
||||
- **inverter_power** (*Optional*): The information for the inverter power sensor. All settings are the same as for the **indoor_ambient_temperature** (see description above).
|
||||
> **ATTENTION!** The parameter name was changed in v.0.2.9 due to incorrect spelling.
|
||||
|
||||
- **inverter_power_limit_state** (*Optional*): Configuration of the power limit state sensor. It displays the state of the power limitation function for the inverter HVAC (is it ON or OFF). All settings are the same as for the **display_state** (see description above).
|
||||
|
||||
- **inverter_power_limit_value** (*Optional*): Configuration of the power limit value sensor. All settings are the same as for the **indoor_temperature** (see description above).
|
||||
It reports the current value of the power limitation function for the inverter HVAC. This sensor represents the value only after the HVAC confirms the power limitation. The value is always in the range from 30 to 100%. This is the hardware limitation.
|
||||
- **inverter_power_limit_value** (*Optional*): Configuration of the power limit value sensor. All settings are the same as for the **indoor_ambient_temperature** (see description above).
|
||||
It reports the current value of the power limitation function for the inverter HVAC. This sensor represents the value only after the HVAC confirms the power limitation. The value is always in the range from 30% to 100%. This is the hardware limitation.
|
||||
|
||||
- **preset_reporter** (*Optional*): Parameters of text sensor with current preset. All settings are the same as for the **display_state** (see description above).
|
||||
ESPHome Climate devices are not reporting their active presets (from **supported_presets** and **custom_presets** lists) to MQTT. This behavior has been noticed at least in version 1.20.0. In case you are using MQTT and want to receive information about active preset, you should declare this sensor in your yaml.
|
||||
@@ -390,16 +401,16 @@ on_...:
|
||||
## Simple example ##
|
||||
The source code of this example is located in the [aux_ac_simple.yaml](https://github.com/GrKoR/esphome_aux_ac_component/blob/master/examples/simple/aux_ac_simple.yaml) file.
|
||||
|
||||
All settings in it is trivial. Just copy the file to your local folder, specify your wifi settings and compile YAML with ESPHome.
|
||||
All settings in it is trivial. Just copy the file to your local folder, specify your Wi-Fi settings and compile YAML with ESPHome.
|
||||
|
||||
|
||||
## Advanced example ##
|
||||
All sources are located [in advanced example folder](https://github.com/GrKoR/esphome_aux_ac_component/tree/master/examples/advanced).
|
||||
|
||||
This time we'll configure two relative identical air conditioners with `aux_ac` custom component.<br />
|
||||
Let's imagine we have ACs in a kitchen and in a living room. All ACs are the same brand and can be controlled by `aux_ac`.<br />
|
||||
Let's imagine we have ACs in a kitchen and in a living room. Air conditioners can be of the same brand or different brands - the main thing is that they are compatible with `aux_ac` and can be controlled using `aux_ac`.<br />
|
||||
Because we are lazy, we'll define all common configuration parts for two air conditioners in one `ac_common.yaml` file.<br />
|
||||
All specific parts of configuration are located in the `ac_kitchen.yaml` and `ac_livingroom.yaml`. Here we set `devicename` and `upper_devicename` for correct sensors and component naming. And here we specify the correct IP-address of the device from `secrets.yaml`.<br />
|
||||
All specific parts of configuration are located in the `ac_kitchen.yaml` and `ac_livingroom.yaml`. Here we set `devicename` and `upper_devicename` for correct sensors and component naming. And here we specify the correct IP address of the device from `secrets.yaml`.<br />
|
||||
**Don't forget** to specify `wifi_ip_kitchen`, `wifi_ota_ip_kitchen`, `wifi_ip_livingroom` and `wifi_ota_ip_livingroom` in the `secrets.yaml` along with the other sensitive information, such as passwords, tokens etc.
|
||||
|
||||
If you try to compile `ac_common.yaml` it will raise errors. You need to compile `ac_kitchen.yaml` or `ac_livingroom.yaml` instead.
|
||||
|
||||
112
README.md
112
README.md
@@ -1,17 +1,18 @@
|
||||
# Кастомный компонент для ESPHome для управления кондиционером по wifi <!--[](https://github.com/GrKoR/esphome_aux_ac_component/releases/) [](https://t.me/aux_ac) -->
|
||||
# Кастомный компонент для ESPHome для управления кондиционером по Wi-Fi <!--[](https://github.com/GrKoR/esphome_aux_ac_component/releases/) [](https://t.me/aux_ac) -->
|
||||
|
||||
English readme [is here](README-EN.md#esphome-aux-air-conditioner-custom-component-aux_ac).
|
||||
|
||||
Управляет кондиционерами на базе AUX по wifi.<br />
|
||||
Управляет кондиционерами на базе AUX по Wi-Fi.<br />
|
||||
По тексту ниже для компонента используется сокращение `aux_ac`.
|
||||
|
||||
Обсудить проект можно [в чате Телеграм](https://t.me/aux_ac).<br />
|
||||
Отзывы о багах и ошибках, а так же запросы на дополнительный функционал оставляйте [в соответствующем разделе](https://github.com/GrKoR/esphome_aux_ac_component/issues).
|
||||
Будет просто отлично, если к своему сообщению вы добавите лог и подробное описание. Для сбора логов есть [специальный скрипт на Python](https://github.com/GrKoR/ac_python_logger). С его помощью вы сможете сохранить в csv-файл все пакеты, которыми обменивается wifi-модуль и сплит-система. Если такой лог дополнить описанием, в какое время и что именно вы пытались включить, то это сильно ускорит исправление багов.
|
||||
Будет просто отлично, если к своему сообщению вы добавите лог и подробное описание. Для сбора логов есть [специальный скрипт на Python](https://github.com/GrKoR/ac_python_logger). С его помощью вы сможете сохранить в csv-файл все пакеты, которыми обменивается Wi-Fi модуль и сплит-система. Если такой лог дополнить описанием, в какое время и что именно вы пытались включить, то это сильно ускорит исправление багов.
|
||||
Также есть [подробная инструкция, описывающая как правильно запросить фичу](docs/HOW_TO_FEATURE_REQUEST.md).
|
||||
|
||||
|
||||
## ДИСКЛЭЙМЕР (ОТМАЗКИ) ##
|
||||
1. Все материалы этого проекта (программы, прошивки, схемы, 3D модели и т.п.) предоставляются "КАК ЕСТЬ". Всё, что вы делаете с вашим оборудованием, вы делаете на свой страх и риск. Автор не несет ответственности за результат и ничего не гарантирует. Если вы с абсолютной четкостью не понимаете, что именно вы делаете и для чего, лучше просто купите wifi-модуль у производителя вашего кондиционера.
|
||||
1. Все материалы этого проекта (программы, прошивки, схемы, 3D модели и т.п.) предоставляются "КАК ЕСТЬ". Всё, что вы делаете с вашим оборудованием, вы делаете на свой страх и риск. Автор не несет ответственности за результат и ничего не гарантирует. Если вы с абсолютной четкостью не понимаете, что именно вы делаете и для чего, лучше просто купите Wi-Fi модуль у производителя вашего кондиционера.
|
||||
2. Я ~~не настоящий сварщик~~ не программер. Поэтому код наверняка не оптимален и плохо оформлен (зато комментариев по коду я разместил от души), местами может быть написан небезопасно. И хоть я и старался протестировать всё, но уверен, что какие-то моменты упустил. Так что отнеситесь к коду с подозрением, ожидайте от него подвоха и если что-то увидели - [пишите в багрепорт](https://github.com/GrKoR/esphome_aux_ac_component/issues).
|
||||
|
||||
|
||||
@@ -24,11 +25,11 @@ AUX - это один из нескольких OEM-производителей
|
||||
[Список протестированных кондиционеров](docs/AC_TESTED.md) размещен в отдельном файле и включает те модели, на которых `aux_ac` был запущен автором компонента или пользователями. Этот список постоянно пополняется, преимущественно по обратной связи от пользователей [в чате Телеграм](https://t.me/aux_ac).<br />
|
||||
|
||||
### Если кондиционер в списке отсутствует ###
|
||||
Если ваш кондиционер отсутствует в списке протестированных, то это еще не значит, что его не получится подключить к wifi. Вот основные "звоночки", которые могут говорить о высоких шансах на успех:
|
||||
Если ваш кондиционер отсутствует в списке протестированных, то это еще не значит, что его не получится подключить к Wi-Fi. Вот основные "звоночки", которые могут говорить о высоких шансах на успех:
|
||||
1. Если производитель вашего кондиционера есть в списке протестированных выше, но модели нет.
|
||||
2. Если на шильдике кондиционера в строке производитель написано что-то про AUX или Аукс.
|
||||
3. Если в инструкции пользователя вашего кондиционера что-то написано про возможность управления по wifi с помощью мобильного приложения ACFreedom.
|
||||
4. Если производитель вашего кондиционера предлагает для управления wifi-модуль CTTM-40X24-WIFI-AKS (слева) или такой, как на фото справа. Причем правый модуль может быть как с USB-разъемом, так и с 5-контактным разъемом.
|
||||
3. Если в инструкции пользователя вашего кондиционера что-то написано про возможность управления по Wi-Fi с помощью мобильного приложения ACFreedom.
|
||||
4. Если производитель вашего кондиционера предлагает для управления Wi-Fi модуль CTTM-40X24-WIFI-AKS (слева) или такой, как на фото справа. Причем правый модуль может быть как с USB-разъемом, так и с 5-контактным разъемом.
|
||||
<img src="https://user-images.githubusercontent.com/57137862/172053621-60fe39d8-066e-44fa-91c5-725fa1f5c3bc.png" height="300"> <img src="https://user-images.githubusercontent.com/57137862/172053744-8ce4a13d-28cb-4688-a998-11ca3a7129df.png" height="300">
|
||||
|
||||
|
||||
@@ -53,6 +54,14 @@ external_components:
|
||||
type: git
|
||||
url: https://github.com/GrKoR/esphome_aux_ac_component
|
||||
```
|
||||
Если требуется прошить определенную версию компонента, используйте синтаксис из примера ниже. Здесь прошивается версия 0.2.14. Список версий смотрите [в тегах на гитхаб](https://github.com/GrKoR/esphome_aux_ac_component/tags).
|
||||
```yaml
|
||||
external_components:
|
||||
- source:
|
||||
type: git
|
||||
url: https://github.com/GrKoR/esphome_aux_ac_component
|
||||
ref: v.0.2.14
|
||||
```
|
||||
2. Настройте UART для коммуникации с вашим кондиционером:
|
||||
```yaml
|
||||
uart:
|
||||
@@ -95,29 +104,37 @@ climate:
|
||||
id: aux_id
|
||||
uart_id: ac_uart_bus
|
||||
period: 7s
|
||||
show_action: true
|
||||
display_inverted: false
|
||||
timeout: 150
|
||||
indoor_temperature:
|
||||
name: AC Indoor Temperature
|
||||
id: ac_indoor_temp
|
||||
timeout: 300
|
||||
optimistic: true
|
||||
indoor_ambient_temperature:
|
||||
name: AC Indoor Ambient Temperature
|
||||
id: ac_indoor_ambient_temp
|
||||
accuracy_decimals: 1
|
||||
internal: false
|
||||
outdoor_temperature:
|
||||
name: AC Outdoor Temperature
|
||||
id: ac_outdoor_temp
|
||||
outdoor_ambient_temperature:
|
||||
name: AC Outdoor Ambient Temperature
|
||||
id: ac_outdoor_ambient_temp
|
||||
internal: false
|
||||
outbound_temperature:
|
||||
name: AC Colant Outbound Temperature
|
||||
id: ac_outbound_temp
|
||||
outdoor_condenser_temperature:
|
||||
name: AC Outdoor Condenser Temperature
|
||||
id: ac_outdoor_condenser_temp
|
||||
internal: false
|
||||
inbound_temperature:
|
||||
name: AC Colant Inbound Temperature
|
||||
id: ac_inbound_temp
|
||||
compressor_suction_temperature:
|
||||
name: AC Compressor Suction Temperature
|
||||
id: ac_compressor_suction_temp
|
||||
internal: false
|
||||
compressor_temperature:
|
||||
name: AC Compressor Temperature
|
||||
id: ac_strange_temp
|
||||
indoor_coil_temperature:
|
||||
name: AC Indoor Coil Temperature
|
||||
id: ac_indoor_coil_temp
|
||||
internal: false
|
||||
compressor_discharge_temperature:
|
||||
name: AC Compressor Discharge Temperature
|
||||
id: ac_compressor_discharge_temp
|
||||
internal: false
|
||||
defrost_temperature:
|
||||
name: AC Defrost Temperature
|
||||
id: ac_defrost_temp
|
||||
internal: false
|
||||
display_state:
|
||||
name: AC Display State
|
||||
@@ -180,35 +197,42 @@ climate:
|
||||
|
||||
- **uart_id** (*Опциональный*, [ID](https://esphome.io/guides/configuration-types.html#config-id)): Укажите ID [шины UART](https://esphome.io/components/uart.html), к которой подключен кондиционер. Если сконфигурирована одна шина, то компонент подключит её автоматически. Если шин несколько, то лучше указать вручную.
|
||||
|
||||
- **period** (*Опциональный*, [время](https://esphome.io/guides/configuration-types.html#config-time), по умолчанию ``7s``): Период между запросами статуса кондиционера. `Aux_ac` получает новое состояние кондиционера только после регулярного запроса, потому что сам кондиционер об изменении параметров своеё работы не уведомляет. Поэтому нужно запрашивать его, вдруг пользователь установил иной режим работы с помощью ИК-пульта.
|
||||
|
||||
- **show_action** (*Опциональный*, логическое, по умолчанию ``true``): Показывать ли текущую задачу кондиционера (экспериментальная функция). Например, в режиме HEAT-COOL кондиционер может выполнять одну из следующих задач:
|
||||
- НАГРЕВ: нагревает воздух в комнате;
|
||||
- ПРОСТОЙ: кондиционер работает в режиме вентилятора для перемешивания воздуха в комнате, поскольку целевая температура уже достигнута;
|
||||
- ОХЛАЖДЕНИЕ: кондиционер охлаждает воздух в комнате.
|
||||
Аналогично будут отображаться действия кондиционера и для режимов ОТОПЛЕНИЕ и ОХЛАЖДЕНИЕ. Единственная разница будет в количестве действий: ПРОСТОЙ+НАГРЕВ для режима отопления и ПРОСТОЙ+ОХЛАЖДЕНИЕ для режима охлаждения комнаты.
|
||||
- **period** (*Опциональный*, [время](https://esphome.io/guides/configuration-types.html#config-time), по умолчанию ``7s``): Период между запросами статуса кондиционера. `Aux_ac` получает новое состояние кондиционера только после регулярного запроса, потому что сам кондиционер об изменении параметров своей работы не уведомляет. Поэтому нужно запрашивать его, вдруг пользователь установил иной режим работы с помощью ИК-пульта.
|
||||
|
||||
- **display_inverted** (*Опциональный*, логическое, по умолчанию ``false``): Настраивает способ управления дисплеем. Как выяснилось (issue [#31](https://github.com/GrKoR/esphome_aux_ac_component/issues/31)), включение-выключение дисплея обрабатывается кондиционерами по разному. Кондиционеры Rovex включают дисплей по `0` в соответствующем бите команды и выключают по биту `1`. Многие другие модели кондиционеров поступают наоборот.
|
||||
|
||||
- **timeout** (*Опциональный*, неотрицательное целое, по умолчанию ``150``): Таймаут получения пакета для ресивера данных `aux_ac`.
|
||||
- **timeout** (*Опциональный*, неотрицательное целое, по умолчанию ``300``): Таймаут получения пакета для ресивера данных `aux_ac`.
|
||||
Чаще всего вам это значение никогда не понадобится. Поскольку этот параметр опционален, то его можно смело пропустить, если нет необходимости менять таймауты.
|
||||
Единственная ситуация, когда вам может пригодиться этот параметр, - это сильно загруженная ESP. Если по какой-то неподдающейся логике причине вы кроме `aux_ac` нагрузили свою ESP кучей дополнительных ресурсоемких задач, то у компонента может просто не хватать времени для оперативного приёма ответов от кондиционера. В этом в логе будут сообщения о том, что последовательность команд была прервана по таймауту. Чтобы это исправить, лучше, конечно, немного разгрузить ESP. Если это вам не подходит, тогда можно увеличить таймаут.
|
||||
Значение таймаута в прошивке ограничено диапазоном от `150` до `600` миллисекунд. Устанавливать значения выше можно только отредактировав исходные коды компонента. Но сильно задирать таймаут не стоит. Кондиционер периодически рассылает пакеты без запроса со стороны `aux_ac` и это приводит к сбою в отправке команды.
|
||||
Единственная ситуация, когда вам может пригодиться этот параметр, - это сильно загруженная ESP. Если по какой-то неподдающейся логике причине вы кроме `aux_ac` нагрузили свою ESP кучей дополнительных ресурсоемких задач, то у компонента может просто не хватать времени для оперативного приёма ответов от кондиционера. В этом случае в логе будут сообщения о том, что последовательность команд была прервана по таймауту. Чтобы это исправить, лучше, конечно, немного разгрузить ESP. Если это вам не подходит, тогда можно увеличить таймаут.
|
||||
Значение таймаута в прошивке ограничено диапазоном от `300` до `800` миллисекунд. Устанавливать значения выше можно только отредактировав исходные коды компонента. Но сильно задирать таймаут не стоит. Кондиционер периодически рассылает пакеты без запроса со стороны `aux_ac` и это приводит к сбою в отправке команды.
|
||||
|
||||
- **indoor_temperature** (*Опциональный*): Параметры создаваемого датчика температуры воздуха, если такой датчик нужен
|
||||
- **optimistic** (*Опциональный*, логическое, по умолчанию ``true``) В «оптимистичном» режиме компонент не ждёт от кондиционера изменения параметров работы, а сразу после отправки команды в кондиционер сообщает в Home Assistant о новом состоянии. Если кондиционер команду не принял, то спустя несколько секунд eps получит текущее состояние всех систем и отправит в умный дом реальное состояние кондиционера. В итоге, если подавать в кондиционер неподдерживаемые команды, они будут записываться в историю Home Assistant и спустя время сбрасываться сбрасываться.
|
||||
В «пессимистичном» режиме esp отправляет команду в кондиционер, но об изменении состояний не сообщает до тех пор, пока не получит информацию о фактическом режиме работы кондиционера.
|
||||
В большинстве случаев разница между этими режимами будет практически незаметна.
|
||||
|
||||
- **indoor_ambient_temperature** (*Опциональный*): Параметры создаваемого датчика температуры воздуха, если такой датчик нужен
|
||||
- **name** (**Обязательный**, строка): Имя датчика температуры.
|
||||
- **id** (*Опциональный*, [ID](https://esphome.io/guides/configuration-types.html#config-id)): Можно указать свой ID для датчика для использования в лямбдах.
|
||||
- **internal** (*Опциональный*, логическое): Пометить данный датчик как внутренний. Внутренний датчик не будет передаваться во фронтэнд (такой как Home Assistant). В противоположность стандартному поведению [сенсоров](https://esphome.io/components/sensor/index.html#base-sensor-configuration) этот параметр для датчика в кондиционере **всегда выставлен в true** за исключением случаев, когда пользователь не установил его в `false`. То есть по умолчанию значение сенсора не будет передаваться во фронтенд даже если указано `name` для сенсора.
|
||||
- Все остальные параметры [сенсора](https://esphome.io/components/sensor/index.html#base-sensor-configuration) ESPHome.
|
||||
> **ВНИМАНИЕ!** Название сенсора было изменено в версии v.1.0.0 для синхронизации с сервисными схемами производителя кондиционеров.
|
||||
|
||||
- **outdoor_temperature** (*Опциональный*): Параметры создаваемого датчика уличной температуры воздуха, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_temperature** (см. выше).
|
||||
> **ВНИМАНИЕ!** Когда кондиционер выключен, температура наружного воздуха обновляется редко (раз в 6-7 часов). Это не баг компонента, а особенность работы железа кондиционера. Единственный способ получать изменения чаще - создать шаблонный сенсор, температуру которого изменять в ручную. Когда кондиционер работает, значение такого сенсора можно копировать из **outdoor_temperature**. Когда кондиционер выключен, значение температуры пересчитывать по динамике сенсора **outbound_temperature** (он изменяется часто и при выключенном кондее показывает значения близкие к температуре воздуха). Заморочки с пересчетом нужны потому, что показания сенсоров не идентичны и на графике значений шаблонного сенсора могут быть ступеньки при переходе с **outdoor_temperature** на **outbound_temperature** и обратно.
|
||||
- **outdoor_ambient_temperature** (*Опциональный*): Параметры создаваемого датчика уличной температуры воздуха, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_ambient_temperature** (см. выше).
|
||||
> **ВНИМАНИЕ!** Когда кондиционер выключен, температура наружного воздуха обновляется редко (раз в 6-7 часов). Это не баг компонента, а особенность работы железа кондиционера. Единственный способ получать изменения чаще - создать шаблонный сенсор, температуру которого изменять вручную. Когда кондиционер работает, значение такого сенсора можно копировать из **outdoor_ambient_temperature**. Когда кондиционер выключен, значение температуры пересчитывать по динамике сенсора **compressor_suction_temperature** (он изменяется часто и при выключенном кондее показывает значения близкие к температуре воздуха). Заморочки с пересчетом нужны потому, что показания сенсоров не идентичны и на графике значений шаблонного сенсора могут быть ступеньки при переходе с **outdoor_ambient_temperature** на **compressor_suction_temperature** и обратно.
|
||||
> **ВНИМАНИЕ!** Название сенсора было изменено в версии v.1.0.0 для синхронизации с сервисными схемами производителя кондиционеров.
|
||||
|
||||
- **inbound_temperature** (*Опциональный*): Параметры создаваемого датчика температуры на подаче теплоносителя, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_temperature** (см. выше).
|
||||
- **outdoor_condenser_temperature** (*Опциональный*): Параметры создаваемого датчика температуры конденсатора в наружном блоке кондиционера, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_ambient_temperature** (см. выше).
|
||||
|
||||
- **outbound_temperature** (*Опциональный*): Параметры создаваемого датчика температуры на обратке теплоносителя, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_temperature** (см. выше).
|
||||
- **indoor_coil_temperature** (*Опциональный*): Параметры создаваемого датчика температуры на теплообменнике во внутреннем блоке кондиционера, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_ambient_temperature** (см. выше).
|
||||
> **ВНИМАНИЕ!** Название сенсора было изменено в версии v.1.0.0 для синхронизации с сервисными схемами производителя кондиционеров.
|
||||
|
||||
- **compressor_temperature** (*Опциональный*): Параметры создаваемого датчика температуры компрессора, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_temperature** (см. выше).
|
||||
- **compressor_suction_temperature** (*Опциональный*): Параметры создаваемого датчика температуры на входе в компрессор, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_ambient_temperature** (см. выше).
|
||||
> **ВНИМАНИЕ!** Название сенсора было изменено в версии v.1.0.0 для синхронизации с сервисными схемами производителя кондиционеров.
|
||||
|
||||
- **compressor_discharge_temperature** (*Опциональный*): Параметры создаваемого датчика температуры на выходе компрессора, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_ambient_temperature** (см. выше).
|
||||
> **ВНИМАНИЕ!** Название сенсора было изменено в версии v.1.0.0 для синхронизации с сервисными схемами производителя кондиционеров.
|
||||
|
||||
- **defrost_temperature** (*Опциональный*): Параметры создаваемого датчика температуры разморозки в наружном блоке кондиционера, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_ambient_temperature** (см. выше).
|
||||
|
||||
- **display_state** (*Опциональный*): Параметры создаваемого датчика дисплея (включен или выключен), если такой датчик нужен.
|
||||
- **name** (**Обязательный**, строка): Имя датчика дисплея.
|
||||
@@ -218,19 +242,19 @@ climate:
|
||||
|
||||
- **defrost_state** (*Опциональный*): Параметры создаваемого датчика состояния разморозки (включена или выключена), если такой датчик нужен. Параметры аналогичны датчику дисплея **display_state**.
|
||||
|
||||
- **inverter_power** (*Опциональный*): Параметры создаваемого датчика мощности инвертора, если такой датчик нужен. Параметры аналогичны датчику дисплея **indoor_temperature**.
|
||||
- **inverter_power** (*Опциональный*): Параметры создаваемого датчика мощности инвертора, если такой датчик нужен. Параметры аналогичны датчику дисплея **display_state**.
|
||||
> **ВНИМАНИЕ!** Название параметра было изменено в версии v.0.2.9 в рамках борьбы с безграмотностью.
|
||||
|
||||
- **inverter_power_limit_state** (*Опциональный*): Параметры создаваемого датчика состояния функции ограничения мощности. Показывает, включена данная функция в настоящий момент или нет. По очевидным причинам актуально только для инверторных кондиционеров, для "старт-стоп" кондиционеров всегда будет "выключен". Параметры аналогичны датчику дисплея **display_state**.
|
||||
|
||||
- **inverter_power_limit_value** (*Опциональный*): Параметры создаваемого датчика текущего ограничения мощности, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_temperature** (см. выше).
|
||||
- **inverter_power_limit_value** (*Опциональный*): Параметры создаваемого датчика текущего ограничения мощности, если такой датчик нужен. Параметры аналогичны датчику внутренней температуры **indoor_ambient_temperature** (см. выше).
|
||||
Сенсор отображает текущее значение ограничения максимальной мощности для инверторного кондиционера. Значение в процентах. С кондиционерами "старт-стоп" по очевидным причинам не работает, всегда показывая значение `0%`. Заданное пользователем значения лимита будет отображено только после того, как кондиционер подтвердит полученное значение и начнет с ним работать.
|
||||
В силу ограничений на уровне железа лимит мощности может быть задан только в пределах от `30%` до `100%`.
|
||||
|
||||
- **preset_reporter** (*Опциональный*): Параметры создаваемого текстового датчика текущего активного пресета. Параметры аналогичны датчику дисплея **display_state**.
|
||||
Климатические устройства ESPHome не отправляют по MQTT активный пресет (см. **supported_presets** и **custom_presets**), в котором работает устройство. Если вы используете MQTT и хотите получать информацию о пресетах, то пропишите этот датчик в конфигурации.
|
||||
|
||||
- **vlouver_state** (*Опциональный*): Параметры создаваемого сенсора состояния вертикальных жалюзи. Параметры аналогичны датчику дисплея **display_state**. Состояние желюзи кодируется целочисленными значениями (подробнее смотри [aux_ac.vlouver_set action](#aux_ac_._vlouver_set) ниже).
|
||||
- **vlouver_state** (*Опциональный*): Параметры создаваемого сенсора состояния вертикальных жалюзи. Параметры аналогичны датчику дисплея **display_state**. Состояние жалюзи кодируется целочисленными значениями (подробнее смотри [aux_ac.vlouver_set action](#aux_ac_._vlouver_set) ниже).
|
||||
|
||||
- **supported_modes** (*Опциональный*, список): Список поддерживаемых режимов работы. Возможные значения: ``HEAT_COOL``, ``COOL``, ``HEAT``, ``DRY``, ``FAN_ONLY``. Обратите внимание: некоторые производители кондиционеров указывают на пульте режим AUTO, хотя по факту этот режим не работает по расписанию и только лишь поддерживает целевую температуру. Такой режим в ESPHome называется HEAT_COOL. По умолчанию список содержит только значение ``FAN_ONLY``.
|
||||
|
||||
@@ -390,7 +414,7 @@ on_...:
|
||||
Исходный код простейшего примера можно найти в файле [aux_ac_simple.yaml](https://github.com/GrKoR/esphome_aux_ac_component/blob/master/examples/simple/aux_ac_simple.yaml).
|
||||
|
||||
Все настройки в нем тривиальны и подробно описаны [в официальной документации на ESPHome](https://esphome.io/index.html) и дополнены в разделе о настройке компонента выше.<br />
|
||||
Просто скопируйте yaml-файл примера в локальную папку у себя на компьютере, пропишите настройки вашей сети WiFi и откомпилируйте YAML с использованием ESPHome.
|
||||
Просто скопируйте yaml-файл примера в локальную папку у себя на компьютере, пропишите настройки вашей сети Wi-Fi и откомпилируйте YAML с использованием ESPHome.
|
||||
|
||||
|
||||
## Продвинутый пример ##
|
||||
|
||||
753
components/aux_ac/aircon.cpp
Normal file
753
components/aux_ac/aircon.cpp
Normal file
@@ -0,0 +1,753 @@
|
||||
#include "aircon.h"
|
||||
#include "helpers.h"
|
||||
#include "command_builder.h"
|
||||
#include "frame_processor_manager.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_airconditioner
|
||||
{
|
||||
/*************************************************************************************************\
|
||||
\*************************************************************************************************/
|
||||
uint32_t Capabilities::normilize_packet_timeout(uint32_t timeout)
|
||||
{
|
||||
uint32_t result = timeout;
|
||||
if (result > Capabilities::AC_PACKET_TIMEOUT_MAX)
|
||||
result = Capabilities::AC_PACKET_TIMEOUT_MAX;
|
||||
else if (result < Capabilities::AC_PACKET_TIMEOUT_MIN)
|
||||
result = Capabilities::AC_PACKET_TIMEOUT_MIN;
|
||||
return result;
|
||||
}
|
||||
|
||||
float Capabilities::normilize_target_temperature(const float target_temperature)
|
||||
{
|
||||
float result = target_temperature;
|
||||
if (result > Capabilities::AC_MAX_TEMPERATURE)
|
||||
result = Capabilities::AC_MAX_TEMPERATURE;
|
||||
else if (result < Capabilities::AC_MIN_TEMPERATURE)
|
||||
result = Capabilities::AC_MIN_TEMPERATURE;
|
||||
return result;
|
||||
}
|
||||
|
||||
uint8_t Capabilities::normilize_inverter_power_limit(const uint8_t power_limit_value)
|
||||
{
|
||||
uint8_t result = power_limit_value;
|
||||
if (result > Capabilities::AC_MAX_INVERTER_POWER_LIMIT)
|
||||
result = Capabilities::AC_MAX_INVERTER_POWER_LIMIT;
|
||||
else if (result < Capabilities::AC_MIN_INVERTER_POWER_LIMIT)
|
||||
result = Capabilities::AC_MIN_INVERTER_POWER_LIMIT;
|
||||
return result;
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
const std::string Capabilities::AC_FIRMWARE_VERSION = "1.0.0";
|
||||
|
||||
// **************************************************************************************************
|
||||
// custom fan modes
|
||||
const std::string Capabilities::CUSTOM_FAN_MODE_MUTE = "MUTE";
|
||||
const std::string Capabilities::CUSTOM_FAN_MODE_TURBO = "TURBO";
|
||||
// **************************************************************************************************
|
||||
// custom presets
|
||||
const std::string Capabilities::CUSTOM_PRESET_CLEAN = "CLEAN";
|
||||
const std::string Capabilities::CUSTOM_PRESET_HEALTH = "HEALTH";
|
||||
const std::string Capabilities::CUSTOM_PRESET_ANTIFUNGUS = "ANTIFUNGUS";
|
||||
// **************************************************************************************************
|
||||
// predefined default params
|
||||
const float Capabilities::AC_MIN_TEMPERATURE = 16.0;
|
||||
const float Capabilities::AC_MAX_TEMPERATURE = 32.0;
|
||||
const float Capabilities::AC_TEMPERATURE_STEP_TARGET = 0.5;
|
||||
const float Capabilities::AC_TEMPERATURE_STEP_CURRENT = 0.1;
|
||||
const uint8_t Capabilities::AC_MIN_INVERTER_POWER_LIMIT = 30; // 30%
|
||||
const uint8_t Capabilities::AC_MAX_INVERTER_POWER_LIMIT = 100; // 100%
|
||||
const uint32_t Capabilities::AC_STATE_REQUEST_INTERVAL = 7000;
|
||||
const uint32_t Capabilities::AC_CONNECTION_LOST_TIMEOUT = 4000;
|
||||
const uint32_t Capabilities::AC_PACKET_TIMEOUT_MIN = 300;
|
||||
const uint32_t Capabilities::AC_PACKET_TIMEOUT_MAX = 800;
|
||||
|
||||
// **************************************************************************************************
|
||||
using esphome::helpers::update_property;
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::_send_frame_from_tx_queue()
|
||||
{
|
||||
if (!this->is_hardware_connected() || !this->has_ping())
|
||||
return;
|
||||
|
||||
if (this->_tx_frames.empty())
|
||||
return;
|
||||
|
||||
Frame *frame = this->_tx_frames.front();
|
||||
this->_tx_frames.pop();
|
||||
|
||||
frame->send(*this->_uart);
|
||||
|
||||
if (frame->get_frame_type() == FrameType::FRAME_TYPE_COMMAND)
|
||||
{
|
||||
_waiting_for_response_timer.reset();
|
||||
_waiting_for_response_timer.set_callback([this](TimerInterface *timer)
|
||||
{ ESP_LOGW(TAG, "Command response timeout!");
|
||||
timer->stop();
|
||||
this->set_receiver_callback(nullptr); });
|
||||
switch (frame->get_value(8))
|
||||
{
|
||||
case 0x01: // command: set AC mode
|
||||
{
|
||||
uint8_t crc16_1 = 0, crc16_2 = 0;
|
||||
frame->get_crc(crc16_1, crc16_2);
|
||||
this->set_receiver_callback([this, crc16_1, crc16_2](Frame &frame)
|
||||
{ if (this->_waiting_for_response_timer.is_enabled() &&
|
||||
frame.get_frame_type() == FrameType::FRAME_TYPE_RESPONSE &&
|
||||
frame.get_value(9) == 0x01)
|
||||
{
|
||||
// check the acknowledgement: should be equal to the command CRC
|
||||
if (frame.get_value(10) != crc16_1 ||
|
||||
frame.get_value(11) != crc16_2)
|
||||
{
|
||||
ESP_LOGW(TAG, "Command response acknowledgement error!");
|
||||
}
|
||||
this->_waiting_for_response_timer.stop();
|
||||
this->_waiting_for_response_timer.set_callback(helpers::dummy_stopper);
|
||||
this->set_receiver_callback(nullptr);
|
||||
if (!this->_command_queue.empty() &&
|
||||
this->_cmd_processor_state == command_processor_state_t::CMD_PROCESSOR_STATE_CMD_WAS_SENT)
|
||||
{
|
||||
this->_cmd_processor_state = command_processor_state_t::CMD_PROCESSOR_STATE_POSTCHECK_DONE;
|
||||
}
|
||||
} });
|
||||
_waiting_for_response_timer.start(this->get_packet_timeout());
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x11: // command: request frame 11
|
||||
{
|
||||
this->set_receiver_callback([this](Frame &frame)
|
||||
{ if (this->_waiting_for_response_timer.is_enabled() &&
|
||||
frame.get_frame_type() == FrameType::FRAME_TYPE_RESPONSE &&
|
||||
frame.get_value(9) == 0x11)
|
||||
{
|
||||
this->_waiting_for_response_timer.stop();
|
||||
this->_waiting_for_response_timer.set_callback(helpers::dummy_stopper);
|
||||
this->set_receiver_callback(nullptr);
|
||||
if (!this->_command_queue.empty() &&
|
||||
this->_cmd_processor_state == command_processor_state_t::CMD_PROCESSOR_STATE_WAITING_FOR_F11)
|
||||
{
|
||||
this->_cmd_processor_state = command_processor_state_t::CMD_PROCESSOR_STATE_PRECHECK_DONE;
|
||||
}
|
||||
} });
|
||||
_waiting_for_response_timer.start(this->get_packet_timeout());
|
||||
break;
|
||||
}
|
||||
|
||||
case 0x21: // command: request frame 21
|
||||
{
|
||||
this->set_receiver_callback([this](Frame &frame)
|
||||
{ if (this->_waiting_for_response_timer.is_enabled() &&
|
||||
frame.get_frame_type() == FrameType::FRAME_TYPE_RESPONSE &&
|
||||
frame.get_value(9) == 0x21)
|
||||
{
|
||||
this->_waiting_for_response_timer.stop();
|
||||
this->_waiting_for_response_timer.set_callback(helpers::dummy_stopper);
|
||||
this->set_receiver_callback(nullptr);
|
||||
} });
|
||||
_waiting_for_response_timer.start(this->get_packet_timeout());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown command: 0x%02X", frame->get_value(8));
|
||||
this->_waiting_for_response_timer.set_callback(helpers::dummy_stopper);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
delete frame;
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::_process_command_queue()
|
||||
{
|
||||
if (_command_queue.size() == 0)
|
||||
return;
|
||||
|
||||
Frame frame;
|
||||
ClimateCall &cmd = _command_queue.front();
|
||||
switch (this->_cmd_processor_state)
|
||||
{
|
||||
case CMD_PROCESSOR_STATE_NOT_STARTED:
|
||||
this->_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_REQUEST_11).fill_frame_with_command(frame);
|
||||
this->schedule_frame_to_send(frame);
|
||||
this->_cmd_processor_state = command_processor_state_t::CMD_PROCESSOR_STATE_WAITING_FOR_F11;
|
||||
break;
|
||||
|
||||
case CMD_PROCESSOR_STATE_WAITING_FOR_F11:
|
||||
ESP_LOGW(TAG, "It should never have happened: processing the command with state 'CMD_PROCESSOR_STATE_WAITING_FOR_F11'");
|
||||
break;
|
||||
|
||||
case CMD_PROCESSOR_STATE_PRECHECK_DONE:
|
||||
this->_cmd_builder->init_new_command(cmd).fill_frame_with_command(frame);
|
||||
this->schedule_frame_to_send(frame);
|
||||
this->_cmd_processor_state = command_processor_state_t::CMD_PROCESSOR_STATE_CMD_WAS_SENT;
|
||||
break;
|
||||
|
||||
case CMD_PROCESSOR_STATE_CMD_WAS_SENT:
|
||||
ESP_LOGW(TAG, "It should never have happened: processing the command with state 'CMD_PROCESSOR_STATE_CMD_WAS_SENT'");
|
||||
break;
|
||||
|
||||
case CMD_PROCESSOR_STATE_POSTCHECK_DONE:
|
||||
this->schedule_frame_to_send(*_frame_11_request);
|
||||
this->schedule_frame_to_send(*_frame_2x_request);
|
||||
this->_cmd_processor_state = command_processor_state_t::CMD_PROCESSOR_STATE_NOT_STARTED;
|
||||
_command_queue.pop();
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGW(TAG, "unknown command state '0x%02X'!", this->_cmd_processor_state);
|
||||
this->_cmd_processor_state = command_processor_state_t::CMD_PROCESSOR_STATE_NOT_STARTED;
|
||||
_command_queue.pop();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
AirCon::AirCon()
|
||||
{
|
||||
_incoming_frame = new Frame;
|
||||
_last_frame_11 = new Frame;
|
||||
_last_frame_2x = new Frame;
|
||||
|
||||
_frame_processor_manager = new FrameProcessorManager;
|
||||
_frame_processor_manager->set_aircon(*this);
|
||||
|
||||
_cmd_builder = new CommandBuilder(*this);
|
||||
|
||||
this->set_millis(&millis);
|
||||
_timer_manager.set_millis_func(&millis);
|
||||
|
||||
_frame_11_request = new Frame;
|
||||
this->_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_REQUEST_11).fill_frame_with_command(*_frame_11_request);
|
||||
|
||||
_frame_2x_request = new Frame;
|
||||
this->_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_REQUEST_21).fill_frame_with_command(*_frame_2x_request);
|
||||
|
||||
_frame_ping_response = new Frame;
|
||||
_frame_ping_response->append_data({Frame::get_start_byte(), 0x00, FrameType::FRAME_TYPE_PING, FrameDirection::FRAME_DIR_TO_AC, 0x01, 0x00, 0x08, 0x00});
|
||||
_frame_ping_response->append_data({0x1C, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||
_frame_ping_response->update_crc(true);
|
||||
}
|
||||
|
||||
AirCon::~AirCon()
|
||||
{
|
||||
delete _cmd_builder;
|
||||
delete _frame_processor_manager;
|
||||
|
||||
delete _incoming_frame;
|
||||
delete _last_frame_11;
|
||||
delete _last_frame_2x;
|
||||
delete _frame_11_request;
|
||||
delete _frame_2x_request;
|
||||
delete _frame_ping_response;
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::setup()
|
||||
{
|
||||
_traits.set_supports_current_temperature(true);
|
||||
_traits.set_supports_two_point_target_temperature(false);
|
||||
|
||||
_traits.set_visual_min_temperature(Capabilities::AC_MIN_TEMPERATURE);
|
||||
_traits.set_visual_max_temperature(Capabilities::AC_MAX_TEMPERATURE);
|
||||
_traits.set_visual_current_temperature_step(Capabilities::AC_TEMPERATURE_STEP_CURRENT);
|
||||
_traits.set_visual_target_temperature_step(Capabilities::AC_TEMPERATURE_STEP_TARGET);
|
||||
|
||||
/* + MINIMAL SET */
|
||||
_traits.add_supported_mode(ClimateMode::CLIMATE_MODE_OFF);
|
||||
_traits.add_supported_mode(ClimateMode::CLIMATE_MODE_FAN_ONLY);
|
||||
_traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_AUTO);
|
||||
_traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_LOW);
|
||||
_traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_MEDIUM);
|
||||
_traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_HIGH);
|
||||
_traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_OFF);
|
||||
_traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE);
|
||||
|
||||
// if the climate device supports reporting the active current action of the device with the action property.
|
||||
//_traits.set_supports_action(this->_show_action);
|
||||
|
||||
_frame11_request_timer.set_callback([this](TimerInterface *timer)
|
||||
{ this->schedule_frame_to_send(*this->_frame_11_request); });
|
||||
_timer_manager.register_timer(_frame11_request_timer);
|
||||
_frame11_request_timer.start(this->get_period());
|
||||
|
||||
_frame2x_request_timer.set_callback([this](TimerInterface *timer)
|
||||
{ this->schedule_frame_to_send(*this->_frame_2x_request); });
|
||||
_timer_manager.register_timer(_frame2x_request_timer);
|
||||
_frame2x_request_timer.start(this->get_period());
|
||||
|
||||
_ping_timeout_timer.set_callback([this](TimerInterface *timer)
|
||||
{ this->_has_ping = false;
|
||||
ESP_LOGW(TAG, "Air conditioner connection lost!"); });
|
||||
_timer_manager.register_timer(_ping_timeout_timer);
|
||||
_ping_timeout_timer.start(Capabilities::AC_CONNECTION_LOST_TIMEOUT);
|
||||
|
||||
_timer_manager.register_timer(_waiting_for_response_timer);
|
||||
_waiting_for_response_timer.stop();
|
||||
|
||||
// schedule initial requests
|
||||
this->schedule_frame_to_send(*this->_frame_11_request);
|
||||
this->schedule_frame_to_send(*this->_frame_2x_request);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::loop()
|
||||
{
|
||||
if (!this->is_hardware_connected())
|
||||
return;
|
||||
|
||||
_timer_manager.task();
|
||||
|
||||
FrameState frame_state = _incoming_frame->get_frame_state();
|
||||
switch (frame_state)
|
||||
{
|
||||
case FRAME_STATE_BLANK:
|
||||
case FRAME_STATE_PARTIALLY_LOADED:
|
||||
if (_uart->available() > 0)
|
||||
{
|
||||
_incoming_frame->load(*_uart);
|
||||
}
|
||||
else if (!this->_waiting_for_response_timer.is_enabled())
|
||||
{
|
||||
if (!this->_tx_frames.empty())
|
||||
{
|
||||
this->_send_frame_from_tx_queue();
|
||||
}
|
||||
else if (!this->_command_queue.empty())
|
||||
{
|
||||
this->_process_command_queue();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FRAME_STATE_ERROR:
|
||||
_incoming_frame->set_frame_time(this->ms());
|
||||
ESP_LOGW(TAG, "Incorrect frame! Frame state: %s, data: %s", _incoming_frame->state_to_string().c_str(), _incoming_frame->to_string(true).c_str());
|
||||
_incoming_frame->clear();
|
||||
break;
|
||||
|
||||
case FRAME_STATE_OK:
|
||||
_incoming_frame->set_frame_time(this->ms());
|
||||
ESP_LOGD(TAG, "%s", _incoming_frame->to_string(true).c_str());
|
||||
|
||||
this->_frame_processor_manager->process_frame(*_incoming_frame);
|
||||
|
||||
if (this->_receiver_callback != nullptr)
|
||||
this->_receiver_callback(*_incoming_frame);
|
||||
|
||||
_incoming_frame->clear();
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown frame state: %d (0x%02X)", frame_state, frame_state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::dump_config()
|
||||
{
|
||||
ESP_LOGCONFIG(TAG, "AUX HVAC:");
|
||||
ESP_LOGCONFIG(TAG, "firmware version: %s", Capabilities::AC_FIRMWARE_VERSION.c_str());
|
||||
this->dump_traits_(TAG);
|
||||
|
||||
LOG_SENSOR(" ", "Vertical louver state", this->_sensor_vlouver_state);
|
||||
LOG_BINARY_SENSOR(" ", "Display", this->_sensor_display_state);
|
||||
LOG_BINARY_SENSOR(" ", "Defrost status", this->_sensor_defrost_state);
|
||||
LOG_TEXT_SENSOR(" ", "Preset Reporter", this->_sensor_preset_reporter);
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Temperatures:");
|
||||
LOG_SENSOR(" ", "Indoor Ambient", this->_sensor_temperature_indoor_ambient);
|
||||
LOG_SENSOR(" ", "Indoor Coil", this->_sensor_temperature_indoor_coil);
|
||||
LOG_SENSOR(" ", "Outdoor Ambient", this->_sensor_temperature_outdoor_ambient);
|
||||
LOG_SENSOR(" ", "Outdoor Condenser", this->_sensor_temperature_outdoor_condenser_middle);
|
||||
LOG_SENSOR(" ", "Outdoor Defrost", this->_sensor_temperature_outdoor_defrost);
|
||||
LOG_SENSOR(" ", "Outdoor Discharge", this->_sensor_temperature_outdoor_discharge);
|
||||
LOG_SENSOR(" ", "Outdoor Suction", this->_sensor_temperature_outdoor_suction);
|
||||
|
||||
ESP_LOGCONFIG(TAG, " Inverter Power:");
|
||||
LOG_SENSOR(" ", "Actual Value", this->_sensor_inverter_power_actual);
|
||||
LOG_SENSOR(" ", "Limit Value", this->_sensor_inverter_power_limit_value);
|
||||
LOG_BINARY_SENSOR(" ", "Limitation State", this->_sensor_inverter_power_limit_state);
|
||||
};
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::control(const esphome::climate::ClimateCall &call)
|
||||
{
|
||||
bool has_command = false;
|
||||
|
||||
// User requested mode change
|
||||
if (call.get_mode().has_value())
|
||||
{
|
||||
ClimateMode mode = *call.get_mode();
|
||||
update_property(this->mode, mode, has_command);
|
||||
}
|
||||
|
||||
// User requested fan_mode change
|
||||
if (call.get_fan_mode().has_value())
|
||||
{
|
||||
ClimateFanMode fanmode = *call.get_fan_mode();
|
||||
update_property(this->fan_mode, fanmode, has_command);
|
||||
}
|
||||
else if (call.get_custom_fan_mode().has_value())
|
||||
{
|
||||
std::string customfanmode = *call.get_custom_fan_mode();
|
||||
if ((customfanmode == Capabilities::CUSTOM_FAN_MODE_TURBO) ||
|
||||
(customfanmode == Capabilities::CUSTOM_FAN_MODE_MUTE) ||
|
||||
(customfanmode == ""))
|
||||
{
|
||||
update_property(this->custom_fan_mode, customfanmode, has_command);
|
||||
}
|
||||
}
|
||||
|
||||
// User selected preset
|
||||
if (call.get_preset().has_value())
|
||||
{
|
||||
ClimatePreset preset = *call.get_preset();
|
||||
update_property(this->preset, preset, has_command);
|
||||
}
|
||||
else if (call.get_custom_preset().has_value())
|
||||
{
|
||||
std::string custom_preset = *call.get_custom_preset();
|
||||
if ((custom_preset == Capabilities::CUSTOM_PRESET_CLEAN) ||
|
||||
(custom_preset == Capabilities::CUSTOM_PRESET_ANTIFUNGUS) ||
|
||||
(custom_preset == Capabilities::CUSTOM_PRESET_HEALTH) ||
|
||||
(custom_preset == ""))
|
||||
{
|
||||
update_property(this->custom_preset, custom_preset, has_command);
|
||||
}
|
||||
}
|
||||
|
||||
// User requested swing_mode change
|
||||
if (call.get_swing_mode().has_value())
|
||||
{
|
||||
ClimateSwingMode swingmode = *call.get_swing_mode();
|
||||
update_property(this->swing_mode, swingmode, has_command);
|
||||
}
|
||||
|
||||
// User requested target temperature change
|
||||
if (call.get_target_temperature().has_value())
|
||||
{
|
||||
// it isn't allowed in FAN mode
|
||||
if (this->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)
|
||||
update_property(this->target_temperature, *call.get_target_temperature(), has_command);
|
||||
}
|
||||
|
||||
if (has_command)
|
||||
{
|
||||
this->schedule_command(call);
|
||||
|
||||
if (this->get_optimistic())
|
||||
this->publish_all_states();
|
||||
}
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::action_display_off()
|
||||
{
|
||||
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_display_state(false);
|
||||
Frame frame;
|
||||
_cmd_builder->fill_frame_with_command(frame);
|
||||
this->schedule_frame_to_send(frame);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::action_display_on()
|
||||
{
|
||||
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_display_state(true);
|
||||
Frame frame;
|
||||
_cmd_builder->fill_frame_with_command(frame);
|
||||
this->schedule_frame_to_send(frame);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::action_set_vlouver_swing()
|
||||
{
|
||||
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(ac_louver_V::AC_LOUVERV_SWING_UPDOWN);
|
||||
Frame frame;
|
||||
_cmd_builder->fill_frame_with_command(frame);
|
||||
this->schedule_frame_to_send(frame);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::action_set_vlouver_stop()
|
||||
{
|
||||
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(ac_louver_V::AC_LOUVERV_OFF);
|
||||
Frame frame;
|
||||
_cmd_builder->fill_frame_with_command(frame);
|
||||
this->schedule_frame_to_send(frame);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::action_set_vlouver_top_position()
|
||||
{
|
||||
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(ac_louver_V::AC_LOUVERV_TOP);
|
||||
Frame frame;
|
||||
_cmd_builder->fill_frame_with_command(frame);
|
||||
this->schedule_frame_to_send(frame);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::action_set_vlouver_middle_above_position()
|
||||
{
|
||||
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(ac_louver_V::AC_LOUVERV_MIDDLE_ABOVE);
|
||||
Frame frame;
|
||||
_cmd_builder->fill_frame_with_command(frame);
|
||||
this->schedule_frame_to_send(frame);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::action_set_vlouver_middle_position()
|
||||
{
|
||||
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(ac_louver_V::AC_LOUVERV_MIDDLE);
|
||||
Frame frame;
|
||||
_cmd_builder->fill_frame_with_command(frame);
|
||||
this->schedule_frame_to_send(frame);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::action_set_vlouver_middle_below_position()
|
||||
{
|
||||
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(ac_louver_V::AC_LOUVERV_MIDDLE_BELOW);
|
||||
Frame frame;
|
||||
_cmd_builder->fill_frame_with_command(frame);
|
||||
this->schedule_frame_to_send(frame);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::action_set_vlouver_bottom()
|
||||
{
|
||||
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(ac_louver_V::AC_LOUVERV_BOTTOM);
|
||||
Frame frame;
|
||||
_cmd_builder->fill_frame_with_command(frame);
|
||||
this->schedule_frame_to_send(frame);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::action_set_vlouver_position(vlouver_esphome_position_t position)
|
||||
{
|
||||
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_vertical_louver(vlouver_frontend_to_ac_louver_V(position));
|
||||
Frame frame;
|
||||
_cmd_builder->fill_frame_with_command(frame);
|
||||
this->schedule_frame_to_send(frame);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::action_power_limitation_off()
|
||||
{
|
||||
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE).set_inverter_power_limitation_state(false);
|
||||
Frame frame;
|
||||
_cmd_builder->fill_frame_with_command(frame);
|
||||
this->schedule_frame_to_send(frame);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::action_power_limitation_on(uint8_t limit)
|
||||
{
|
||||
_cmd_builder->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE)
|
||||
.set_inverter_power_limitation_state(true)
|
||||
.set_inverter_power_limitation_value(Capabilities::normilize_inverter_power_limit(limit));
|
||||
Frame frame;
|
||||
_cmd_builder->fill_frame_with_command(frame);
|
||||
this->schedule_frame_to_send(frame);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::schedule_frame_to_send(const Frame &frame)
|
||||
{
|
||||
Frame *tx_frame = new Frame(frame);
|
||||
_tx_frames.push(tx_frame);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::schedule_ping_response()
|
||||
{
|
||||
this->schedule_frame_to_send(*_frame_ping_response);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::schedule_command(const ClimateCall &cmd)
|
||||
{
|
||||
_command_queue.push(cmd);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
Frame &AirCon::get_last_frame_11()
|
||||
{
|
||||
return *(this->_last_frame_11);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
Frame &AirCon::get_last_frame_2x()
|
||||
{
|
||||
return *(this->_last_frame_2x);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::set_last_frame(const Frame &frame)
|
||||
{
|
||||
if (frame.get_frame_type() != FrameType::FRAME_TYPE_RESPONSE)
|
||||
return;
|
||||
|
||||
if (frame.get_body_length() < 2) // filter out frames without CMD byte
|
||||
return;
|
||||
|
||||
Frame *target_frame = nullptr;
|
||||
if (frame.get_value(9) == 0x11)
|
||||
{
|
||||
target_frame = _last_frame_11;
|
||||
}
|
||||
else if (frame.get_value(9, 0b11110000) == 0x20)
|
||||
{
|
||||
target_frame = _last_frame_2x;
|
||||
}
|
||||
|
||||
if (target_frame != nullptr)
|
||||
{
|
||||
target_frame->clear();
|
||||
target_frame->append_data(frame.data(), frame.size(), true);
|
||||
target_frame->set_frame_time(this->ms());
|
||||
}
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::_update_sensor_unit_of_measurement(Sensor *sensor)
|
||||
{
|
||||
if (sensor == nullptr)
|
||||
return;
|
||||
|
||||
if (this->temperature_in_fahrenheit && sensor->get_unit_of_measurement() != "°F")
|
||||
sensor->set_unit_of_measurement("°F");
|
||||
else if (!this->temperature_in_fahrenheit && sensor->get_unit_of_measurement() != "°C")
|
||||
sensor->set_unit_of_measurement("°C");
|
||||
}
|
||||
|
||||
void AirCon::update_all_sensors_unit_of_measurement()
|
||||
{
|
||||
this->_update_sensor_unit_of_measurement(_sensor_temperature_indoor_ambient);
|
||||
this->_update_sensor_unit_of_measurement(_sensor_temperature_indoor_coil);
|
||||
this->_update_sensor_unit_of_measurement(_sensor_temperature_outdoor_condenser_middle);
|
||||
this->_update_sensor_unit_of_measurement(_sensor_temperature_outdoor_ambient);
|
||||
this->_update_sensor_unit_of_measurement(_sensor_temperature_outdoor_defrost);
|
||||
this->_update_sensor_unit_of_measurement(_sensor_temperature_outdoor_discharge);
|
||||
this->_update_sensor_unit_of_measurement(_sensor_temperature_outdoor_suction);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
template <typename T>
|
||||
void publish_sensor_state(Sensor *sensor, optional<T> new_state)
|
||||
{
|
||||
if (sensor == nullptr)
|
||||
return;
|
||||
|
||||
if (new_state.has_value() && !std::isnan((float)(new_state.value())))
|
||||
{
|
||||
if (sensor->get_raw_state() == (float)(new_state.value()))
|
||||
return;
|
||||
|
||||
sensor->publish_state((float)(new_state.value()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::isnan(sensor->get_raw_state()))
|
||||
return;
|
||||
|
||||
sensor->publish_state(NAN);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void publish_sensor_state(BinarySensor *sensor, optional<bool> new_state)
|
||||
{
|
||||
if (sensor == nullptr)
|
||||
return;
|
||||
|
||||
if (!new_state.has_value())
|
||||
return;
|
||||
|
||||
if (sensor->state == new_state.value())
|
||||
return;
|
||||
|
||||
sensor->publish_state(new_state.value());
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void publish_sensor_state(TextSensor *sensor, optional<std::string> new_state)
|
||||
{
|
||||
if (sensor == nullptr)
|
||||
return;
|
||||
|
||||
if (!new_state.has_value())
|
||||
return;
|
||||
|
||||
if (sensor->get_raw_state() == new_state.value())
|
||||
return;
|
||||
|
||||
sensor->publish_state(new_state.value());
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
void AirCon::publish_all_states()
|
||||
{
|
||||
this->publish_state();
|
||||
|
||||
publish_sensor_state(_sensor_temperature_indoor_ambient, optional<float>(this->current_temperature));
|
||||
publish_sensor_state(_sensor_temperature_indoor_coil, this->temperature_indoor_coil);
|
||||
publish_sensor_state(_sensor_temperature_outdoor_condenser_middle, this->temperature_condenser_middle);
|
||||
publish_sensor_state(_sensor_temperature_outdoor_ambient, this->temperature_outdoor_ambient);
|
||||
publish_sensor_state(_sensor_temperature_outdoor_defrost, this->temperature_outdoor_defrost);
|
||||
publish_sensor_state(_sensor_temperature_outdoor_discharge, this->temperature_outdoor_discharge);
|
||||
publish_sensor_state(_sensor_temperature_outdoor_suction, this->temperature_outdoor_suction);
|
||||
|
||||
publish_sensor_state(_sensor_vlouver_state, optional<uint8_t>(this->get_current_vlouver_frontend_state()));
|
||||
publish_sensor_state(_sensor_display_state, optional<bool>(this->display_enabled));
|
||||
publish_sensor_state(_sensor_defrost_state, optional<bool>(this->defrost_enabled));
|
||||
|
||||
publish_sensor_state(_sensor_inverter_power_actual, this->inverter_power);
|
||||
publish_sensor_state(_sensor_inverter_power_limit_value, this->inverter_power_limitation_value);
|
||||
publish_sensor_state(_sensor_inverter_power_limit_state, this->inverter_power_limitation_on);
|
||||
|
||||
std::string state_str = "";
|
||||
if (this->preset == ClimatePreset::CLIMATE_PRESET_SLEEP)
|
||||
{
|
||||
state_str += "SLEEP";
|
||||
}
|
||||
else if (this->custom_preset.has_value())
|
||||
{
|
||||
state_str += this->custom_preset.value().c_str();
|
||||
}
|
||||
else
|
||||
{
|
||||
state_str += "NONE";
|
||||
}
|
||||
publish_sensor_state(_sensor_preset_reporter, optional<std::string>(state_str));
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
// converts vertical louver state from hardware codes to frontend code
|
||||
vlouver_esphome_position_t AirCon::aux_vlouver_to_frontend(const ac_louver_V vLouver)
|
||||
{
|
||||
return ac_louver_V_to_vlouver_frontend(vLouver);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
// current vertical louver position in esphome codes
|
||||
vlouver_esphome_position_t AirCon::get_current_vlouver_frontend_state()
|
||||
{
|
||||
return aux_vlouver_to_frontend(this->louver_vertical);
|
||||
}
|
||||
|
||||
// **************************************************************************************************
|
||||
// converts vertical louver position from frontend codes to hardware code
|
||||
ac_louver_V AirCon::frontend_vlouver_to_aux(const vlouver_esphome_position_t vLouver)
|
||||
{
|
||||
return vlouver_frontend_to_ac_louver_V(vLouver);
|
||||
}
|
||||
} // namespace aux_airconditioner
|
||||
} // namespace esphome
|
||||
276
components/aux_ac/aircon.h
Normal file
276
components/aux_ac/aircon.h
Normal file
@@ -0,0 +1,276 @@
|
||||
#pragma once
|
||||
|
||||
#include <math.h> // for NAN
|
||||
#include <queue>
|
||||
|
||||
#include "esphome.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#include "esphome/components/uart/uart_component.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/optional.h"
|
||||
|
||||
#include "frame.h"
|
||||
#include "aircon_common.h"
|
||||
#include "helpers.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_airconditioner
|
||||
{
|
||||
|
||||
using esphome::Component;
|
||||
using esphome::binary_sensor::BinarySensor;
|
||||
using esphome::climate::Climate;
|
||||
using esphome::climate::ClimateCall;
|
||||
using esphome::climate::ClimatePreset;
|
||||
using esphome::climate::ClimateSwingMode;
|
||||
using esphome::climate::ClimateTraits;
|
||||
using esphome::sensor::Sensor;
|
||||
using esphome::text_sensor::TextSensor;
|
||||
using esphome::uart::UARTComponent;
|
||||
|
||||
using esphome::helpers::Timer;
|
||||
using esphome::helpers::TimerInterface;
|
||||
using esphome::helpers::TimerManager;
|
||||
|
||||
using millis_function_t = uint32_t (*)();
|
||||
|
||||
static const char *const TAG = "AirCon";
|
||||
|
||||
/*************************************************************************************************\
|
||||
\*************************************************************************************************/
|
||||
class Capabilities
|
||||
{
|
||||
public:
|
||||
// **************************************************************************************************
|
||||
static const std::string AC_FIRMWARE_VERSION;
|
||||
// **************************************************************************************************
|
||||
// custom fan modes
|
||||
static const std::string CUSTOM_FAN_MODE_MUTE;
|
||||
static const std::string CUSTOM_FAN_MODE_TURBO;
|
||||
// **************************************************************************************************
|
||||
// custom presets
|
||||
static const std::string CUSTOM_PRESET_CLEAN;
|
||||
static const std::string CUSTOM_PRESET_HEALTH;
|
||||
static const std::string CUSTOM_PRESET_ANTIFUNGUS;
|
||||
// **************************************************************************************************
|
||||
// predefined default params
|
||||
static const uint32_t AC_STATE_REQUEST_INTERVAL;
|
||||
static const uint32_t AC_CONNECTION_LOST_TIMEOUT;
|
||||
|
||||
static const uint32_t AC_PACKET_TIMEOUT_MIN;
|
||||
static const uint32_t AC_PACKET_TIMEOUT_MAX;
|
||||
static uint32_t normilize_packet_timeout(uint32_t timeout);
|
||||
|
||||
static const float AC_TEMPERATURE_STEP_TARGET;
|
||||
static const float AC_TEMPERATURE_STEP_CURRENT;
|
||||
static const float AC_MIN_TEMPERATURE;
|
||||
static const float AC_MAX_TEMPERATURE;
|
||||
static float normilize_target_temperature(const float target_temperature);
|
||||
|
||||
static const uint8_t AC_MIN_INVERTER_POWER_LIMIT;
|
||||
static const uint8_t AC_MAX_INVERTER_POWER_LIMIT;
|
||||
static uint8_t normilize_inverter_power_limit(const uint8_t power_limit_value);
|
||||
};
|
||||
|
||||
/*************************************************************************************************\
|
||||
\*************************************************************************************************/
|
||||
class FrameProcessorManager;
|
||||
class CommandBuilder;
|
||||
class Frame;
|
||||
|
||||
class AirCon : public Component,
|
||||
public Climate
|
||||
{
|
||||
private:
|
||||
void _update_sensor_unit_of_measurement(Sensor *sensor);
|
||||
|
||||
protected:
|
||||
// esphome sensors that display the parameters of the air conditioner
|
||||
Sensor *_sensor_temperature_indoor_ambient{nullptr};
|
||||
Sensor *_sensor_temperature_indoor_coil{nullptr};
|
||||
Sensor *_sensor_temperature_outdoor_ambient{nullptr};
|
||||
Sensor *_sensor_temperature_outdoor_condenser_middle{nullptr};
|
||||
Sensor *_sensor_temperature_outdoor_defrost{nullptr};
|
||||
Sensor *_sensor_temperature_outdoor_discharge{nullptr};
|
||||
Sensor *_sensor_temperature_outdoor_suction{nullptr};
|
||||
|
||||
Sensor *_sensor_vlouver_state{nullptr};
|
||||
BinarySensor *_sensor_display_state{nullptr};
|
||||
BinarySensor *_sensor_defrost_state{nullptr};
|
||||
TextSensor *_sensor_preset_reporter{nullptr};
|
||||
Sensor *_sensor_inverter_power_actual{nullptr};
|
||||
Sensor *_sensor_inverter_power_limit_value{nullptr};
|
||||
BinarySensor *_sensor_inverter_power_limit_state{nullptr};
|
||||
|
||||
ClimateTraits _traits;
|
||||
UARTComponent *_uart{nullptr};
|
||||
bool _display_inverted{false};
|
||||
bool _optimistic{true}; // in optimistic mode, the entity states are updated immediately after receiving a command from Home Assistant/ESPHome
|
||||
uint32_t _update_period{Capabilities::AC_STATE_REQUEST_INTERVAL};
|
||||
uint32_t _packet_timeout{Capabilities::AC_PACKET_TIMEOUT_MIN};
|
||||
|
||||
bool _has_ping{false};
|
||||
millis_function_t _millis_func{nullptr};
|
||||
|
||||
std::queue<Frame *> _tx_frames;
|
||||
Frame *_incoming_frame{nullptr};
|
||||
Frame *_last_frame_11{nullptr};
|
||||
Frame *_last_frame_2x{nullptr};
|
||||
Frame *_frame_ping_response{nullptr};
|
||||
Frame *_frame_11_request{nullptr};
|
||||
Frame *_frame_2x_request{nullptr};
|
||||
FrameProcessorManager *_frame_processor_manager{nullptr};
|
||||
|
||||
std::queue<ClimateCall> _command_queue;
|
||||
command_processor_state_t _cmd_processor_state{CMD_PROCESSOR_STATE_NOT_STARTED};
|
||||
|
||||
CommandBuilder *_cmd_builder{nullptr};
|
||||
|
||||
void _send_frame_from_tx_queue();
|
||||
void _process_command_queue();
|
||||
|
||||
TimerManager _timer_manager;
|
||||
Timer _frame11_request_timer;
|
||||
Timer _frame2x_request_timer;
|
||||
Timer _waiting_for_response_timer;
|
||||
Timer _ping_timeout_timer;
|
||||
|
||||
std::function<void(Frame &)> _receiver_callback = nullptr;
|
||||
|
||||
public:
|
||||
AirCon();
|
||||
~AirCon();
|
||||
|
||||
// **************************************************************************************************
|
||||
// derived methods
|
||||
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
|
||||
virtual ClimateTraits traits() override { return _traits; }
|
||||
virtual void setup() override;
|
||||
virtual void loop() override;
|
||||
virtual void dump_config() override;
|
||||
virtual void control(const esphome::climate::ClimateCall &call) override;
|
||||
|
||||
// **************************************************************************************************
|
||||
// current state
|
||||
// ------- derived from Climate parameters -------
|
||||
// ClimateMode mode{CLIMATE_MODE_OFF}; /// The active mode of the climate device.
|
||||
// ClimateAction action{CLIMATE_ACTION_OFF}; /// The active state of the climate device.
|
||||
// float current_temperature{NAN}; /// The current temperature of the climate device, as reported from the integration.
|
||||
// float target_temperature; /// The target temperature of the climate device.
|
||||
// ClimateFanMode fan_mode{CLIMATE_FAN_OFF}; /// The active fan mode of the climate device.
|
||||
// std::string custom_fan_mode{}; /// The active custom fan mode of the climate device.
|
||||
// ClimateSwingMode swing_mode{CLIMATE_SWING_OFF}; /// The active swing mode of the climate device.
|
||||
// ClimatePreset preset{CLIMATE_PRESET_NONE}; /// The active preset of the climate device.
|
||||
// std::string custom_preset{}; /// The active custom preset mode of the climate device.
|
||||
// ------- own parameters -------
|
||||
ac_louver_V louver_vertical{AC_LOUVERV_OFF};
|
||||
ac_louver_H louver_horizontal{AC_LOUVERH_OFF};
|
||||
bool temperature_in_fahrenheit{false};
|
||||
bool display_enabled{true};
|
||||
uint8_t last_IR_passed{0}; // time since last IR-remote command passed
|
||||
|
||||
optional<bool> inverter_power_limitation_on{false};
|
||||
optional<uint8_t> inverter_power_limitation_value{100};
|
||||
bool ac_type_inverter{false};
|
||||
|
||||
optional<uint8_t> temperature_indoor_coil{}; // byte 17, cmd=0x21
|
||||
optional<uint8_t> temperature_condenser_middle{}; // byte 20, cmd=0x21
|
||||
optional<uint8_t> temperature_outdoor_ambient{}; // byte 18, cmd=0x21
|
||||
optional<uint8_t> temperature_outdoor_suction{}; // byte 21, cmd=0x21
|
||||
optional<uint8_t> temperature_outdoor_discharge{}; // byte 22, cmd=0x21
|
||||
optional<uint8_t> temperature_outdoor_defrost{}; // byte 23, cmd=0x21
|
||||
ac_fanspeed_real real_fan_speed{AC_REAL_FAN_OFF};
|
||||
optional<uint8_t> inverter_power{0};
|
||||
bool defrost_enabled{false};
|
||||
|
||||
// **************************************************************************************************
|
||||
// settings & config
|
||||
void set_uart(UARTComponent &uart) { _uart = &uart; }
|
||||
void set_uart(UARTComponent *uart) { _uart = uart; }
|
||||
UARTComponent &get_uart() { return *_uart; }
|
||||
bool is_hardware_connected() { return _uart != nullptr; }
|
||||
bool has_ping() { return this->_has_ping; }
|
||||
void reset_ping_timeout()
|
||||
{
|
||||
this->_has_ping = true;
|
||||
this->_ping_timeout_timer.reset();
|
||||
}
|
||||
void set_millis(millis_function_t millis) { _millis_func = millis; }
|
||||
uint32_t ms() { return (_millis_func != nullptr) ? _millis_func() : 0; }
|
||||
void set_display_inversion(bool inversion) { _display_inverted = inversion; }
|
||||
bool get_display_inversion() { return _display_inverted; }
|
||||
void set_optimistic(bool optimistic) { this->_optimistic = optimistic; }
|
||||
bool get_optimistic() { return this->_optimistic; }
|
||||
void set_period(uint32_t ms) { this->_update_period = ms; }
|
||||
uint32_t get_period() { return this->_update_period; }
|
||||
void set_packet_timeout(uint32_t ms) { this->_packet_timeout = Capabilities::normilize_packet_timeout(ms); }
|
||||
uint32_t get_packet_timeout() { return this->_packet_timeout; }
|
||||
void set_supported_modes(const std::set<ClimateMode> &modes) { _traits.set_supported_modes(modes); }
|
||||
void set_supported_swing_modes(const std::set<ClimateSwingMode> &modes) { _traits.set_supported_swing_modes(modes); }
|
||||
void set_supported_presets(const std::set<ClimatePreset> &presets) { _traits.set_supported_presets(presets); }
|
||||
void set_custom_presets(const std::set<std::string> &presets) { _traits.set_supported_custom_presets(presets); }
|
||||
void set_custom_fan_modes(const std::set<std::string> &modes) { _traits.set_supported_custom_fan_modes(modes); }
|
||||
|
||||
// **************************************************************************************************
|
||||
// setters for sensors
|
||||
void set_sensor_temperature_indoor_ambient(Sensor *temperature_sensor) { _sensor_temperature_indoor_ambient = temperature_sensor; }
|
||||
void set_sensor_temperature_indoor_coil(Sensor *temperature_sensor) { _sensor_temperature_indoor_coil = temperature_sensor; }
|
||||
void set_sensor_temperature_outdoor_ambient(Sensor *temperature_sensor) { _sensor_temperature_outdoor_ambient = temperature_sensor; }
|
||||
void set_sensor_temperature_outdoor_condenser_middle(Sensor *temperature_sensor) { _sensor_temperature_outdoor_condenser_middle = temperature_sensor; }
|
||||
void set_sensor_temperature_outdoor_defrost(Sensor *temperature_sensor) { _sensor_temperature_outdoor_defrost = temperature_sensor; }
|
||||
void set_sensor_temperature_outdoor_discharge(Sensor *temperature_sensor) { _sensor_temperature_outdoor_discharge = temperature_sensor; }
|
||||
void set_sensor_temperature_outdoor_suction(Sensor *temperature_sensor) { _sensor_temperature_outdoor_suction = temperature_sensor; }
|
||||
|
||||
void set_sensor_vlouver_state(Sensor *sensor) { _sensor_vlouver_state = sensor; }
|
||||
void set_sensor_display(BinarySensor *sensor) { _sensor_display_state = sensor; }
|
||||
void set_sensor_defrost_state(BinarySensor *sensor) { _sensor_defrost_state = sensor; }
|
||||
void set_sensor_preset_reporter(TextSensor *sensor) { _sensor_preset_reporter = sensor; }
|
||||
|
||||
void set_sensor_inverter_power(Sensor *sensor) { _sensor_inverter_power_actual = sensor; }
|
||||
void set_sensor_inverter_power_limit_value(Sensor *sensor) { _sensor_inverter_power_limit_value = sensor; }
|
||||
void set_sensor_inverter_power_limit_state(BinarySensor *sensor) { _sensor_inverter_power_limit_state = sensor; }
|
||||
|
||||
// **************************************************************************************************
|
||||
// actions
|
||||
void action_display_off();
|
||||
void action_display_on();
|
||||
void action_set_vlouver_swing();
|
||||
void action_set_vlouver_stop();
|
||||
void action_set_vlouver_top_position();
|
||||
void action_set_vlouver_middle_above_position();
|
||||
void action_set_vlouver_middle_position();
|
||||
void action_set_vlouver_middle_below_position();
|
||||
void action_set_vlouver_bottom();
|
||||
void action_set_vlouver_position(vlouver_esphome_position_t position);
|
||||
void action_power_limitation_off();
|
||||
void action_power_limitation_on(uint8_t limit);
|
||||
|
||||
// **************************************************************************************************
|
||||
// other methods
|
||||
void schedule_frame_to_send(const Frame &frame);
|
||||
void schedule_ping_response();
|
||||
void schedule_command(const ClimateCall &cmd);
|
||||
Frame &get_last_frame_11();
|
||||
Frame &get_last_frame_2x();
|
||||
void set_last_frame(const Frame &frame);
|
||||
void update_all_sensors_unit_of_measurement();
|
||||
void publish_all_states();
|
||||
void set_receiver_callback(std::function<void(Frame &)> callback) { this->_receiver_callback = callback; }
|
||||
|
||||
// converts vertical louver state from hardware codes to frontend codes
|
||||
vlouver_esphome_position_t aux_vlouver_to_frontend(const ac_louver_V vLouver);
|
||||
|
||||
// current vertical louver position in esphome codes
|
||||
vlouver_esphome_position_t get_current_vlouver_frontend_state();
|
||||
|
||||
// converts vertical louver position from frontend codes to hardware code
|
||||
ac_louver_V frontend_vlouver_to_aux(const vlouver_esphome_position_t vLouver);
|
||||
};
|
||||
|
||||
} // namespace aux_airconditioner
|
||||
} // namespace esphome
|
||||
275
components/aux_ac/aircon_common.cpp
Normal file
275
components/aux_ac/aircon_common.cpp
Normal file
@@ -0,0 +1,275 @@
|
||||
#include "aircon_common.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_airconditioner
|
||||
{
|
||||
std::string ac_mode_to_string(ac_mode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case AC_MODE_AUTO:
|
||||
return "AC_MODE_AUTO";
|
||||
|
||||
case AC_MODE_COOL:
|
||||
return "AC_MODE_COOL";
|
||||
|
||||
case AC_MODE_DRY:
|
||||
return "AC_MODE_DRY";
|
||||
|
||||
case AC_MODE_HEAT:
|
||||
return "AC_MODE_HEAT";
|
||||
|
||||
case AC_MODE_FAN:
|
||||
return "AC_MODE_FAN";
|
||||
|
||||
default:
|
||||
return "mode unknown";
|
||||
}
|
||||
}
|
||||
|
||||
ClimateMode ac_mode_to_climate_mode(ac_mode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case AC_MODE_AUTO:
|
||||
return ClimateMode::CLIMATE_MODE_HEAT_COOL;
|
||||
|
||||
case AC_MODE_COOL:
|
||||
return ClimateMode::CLIMATE_MODE_COOL;
|
||||
|
||||
case AC_MODE_DRY:
|
||||
return ClimateMode::CLIMATE_MODE_DRY;
|
||||
|
||||
case AC_MODE_HEAT:
|
||||
return ClimateMode::CLIMATE_MODE_HEAT;
|
||||
|
||||
case AC_MODE_FAN:
|
||||
return ClimateMode::CLIMATE_MODE_FAN_ONLY;
|
||||
|
||||
default:
|
||||
return ClimateMode::CLIMATE_MODE_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
ac_mode climate_mode_to_ac_mode(ClimateMode mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case ClimateMode::CLIMATE_MODE_HEAT_COOL:
|
||||
return AC_MODE_AUTO;
|
||||
|
||||
case ClimateMode::CLIMATE_MODE_COOL:
|
||||
return AC_MODE_COOL;
|
||||
|
||||
case ClimateMode::CLIMATE_MODE_DRY:
|
||||
return AC_MODE_DRY;
|
||||
|
||||
case ClimateMode::CLIMATE_MODE_HEAT:
|
||||
return AC_MODE_HEAT;
|
||||
|
||||
case ClimateMode::CLIMATE_MODE_FAN_ONLY:
|
||||
return AC_MODE_FAN;
|
||||
|
||||
default:
|
||||
return AC_MODE_FAN;
|
||||
}
|
||||
}
|
||||
|
||||
ac_louver_V vlouver_frontend_to_ac_louver_V(const vlouver_esphome_position_t vlouver_frontend)
|
||||
{
|
||||
switch (vlouver_frontend)
|
||||
{
|
||||
case AC_VLOUVER_FRONTEND_SWING:
|
||||
return ac_louver_V::AC_LOUVERV_SWING_UPDOWN;
|
||||
|
||||
case AC_VLOUVER_FRONTEND_STOP:
|
||||
return ac_louver_V::AC_LOUVERV_OFF;
|
||||
|
||||
case AC_VLOUVER_FRONTEND_TOP:
|
||||
return ac_louver_V::AC_LOUVERV_SWING_UPDOWN;
|
||||
|
||||
case AC_VLOUVER_FRONTEND_MIDDLE_ABOVE:
|
||||
return ac_louver_V::AC_LOUVERV_MIDDLE_ABOVE;
|
||||
|
||||
case AC_VLOUVER_FRONTEND_MIDDLE:
|
||||
return ac_louver_V::AC_LOUVERV_MIDDLE;
|
||||
|
||||
case AC_VLOUVER_FRONTEND_MIDDLE_BELOW:
|
||||
return ac_louver_V::AC_LOUVERV_MIDDLE_BELOW;
|
||||
|
||||
case AC_VLOUVER_FRONTEND_BOTTOM:
|
||||
return ac_louver_V::AC_LOUVERV_BOTTOM;
|
||||
|
||||
default:
|
||||
return ac_louver_V::AC_LOUVERV_OFF;
|
||||
}
|
||||
}
|
||||
|
||||
vlouver_esphome_position_t ac_louver_V_to_vlouver_frontend(const ac_louver_V aux_vlouver)
|
||||
{
|
||||
switch (aux_vlouver)
|
||||
{
|
||||
case AC_LOUVERV_SWING_UPDOWN:
|
||||
return AC_VLOUVER_FRONTEND_SWING;
|
||||
|
||||
case AC_LOUVERV_OFF:
|
||||
return AC_VLOUVER_FRONTEND_STOP;
|
||||
|
||||
case AC_LOUVERV_TOP:
|
||||
return AC_VLOUVER_FRONTEND_TOP;
|
||||
|
||||
case AC_LOUVERV_MIDDLE_ABOVE:
|
||||
return AC_VLOUVER_FRONTEND_MIDDLE_ABOVE;
|
||||
|
||||
case AC_LOUVERV_MIDDLE:
|
||||
return AC_VLOUVER_FRONTEND_MIDDLE;
|
||||
|
||||
case AC_LOUVERV_MIDDLE_BELOW:
|
||||
return AC_VLOUVER_FRONTEND_MIDDLE_BELOW;
|
||||
|
||||
case AC_LOUVERV_BOTTOM:
|
||||
return AC_VLOUVER_FRONTEND_BOTTOM;
|
||||
|
||||
default:
|
||||
return AC_VLOUVER_FRONTEND_STOP;
|
||||
}
|
||||
}
|
||||
|
||||
std::string ac_louver_V_to_string(ac_louver_V louver)
|
||||
{
|
||||
switch (louver)
|
||||
{
|
||||
case AC_LOUVERV_SWING_UPDOWN:
|
||||
return "AC_LOUVERV_SWING_UPDOWN";
|
||||
|
||||
case AC_LOUVERV_TOP:
|
||||
return "AC_LOUVERV_TOP";
|
||||
|
||||
case AC_LOUVERV_MIDDLE_ABOVE:
|
||||
return "AC_LOUVERV_MIDDLE_ABOVE";
|
||||
|
||||
case AC_LOUVERV_MIDDLE:
|
||||
return "AC_LOUVERV_MIDDLE";
|
||||
|
||||
case AC_LOUVERV_MIDDLE_BELOW:
|
||||
return "AC_LOUVERV_MIDDLE_BELOW";
|
||||
|
||||
case AC_LOUVERV_BOTTOM:
|
||||
return "AC_LOUVERV_BOTTOM";
|
||||
|
||||
case AC_LOUVERV_OFF:
|
||||
return "AC_LOUVERV_OFF";
|
||||
|
||||
default:
|
||||
return "unknown vertical louver position";
|
||||
}
|
||||
}
|
||||
|
||||
std::string ac_louver_H_to_string(ac_louver_H louver)
|
||||
{
|
||||
switch (louver)
|
||||
{
|
||||
case AC_LOUVERH_SWING_LEFTRIGHT:
|
||||
return "AC_LOUVERH_SWING_LEFTRIGHT";
|
||||
|
||||
case AC_LOUVERH_OFF:
|
||||
return "AC_LOUVERH_OFF";
|
||||
|
||||
default:
|
||||
return "unknown horizontal louver position";
|
||||
}
|
||||
}
|
||||
|
||||
std::string ac_fanspeed_to_string(ac_fanspeed fanspeed)
|
||||
{
|
||||
switch (fanspeed)
|
||||
{
|
||||
case AC_FANSPEED_HIGH:
|
||||
return "AC_FANSPEED_HIGH";
|
||||
|
||||
case AC_FANSPEED_MEDIUM:
|
||||
return "AC_FANSPEED_MEDIUM";
|
||||
|
||||
case AC_FANSPEED_LOW:
|
||||
return "AC_FANSPEED_LOW";
|
||||
|
||||
case AC_FANSPEED_AUTO:
|
||||
return "AC_FANSPEED_AUTO";
|
||||
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
ClimateFanMode ac_fanspeed_to_climate_fan_mode(ac_fanspeed fanspeed)
|
||||
{
|
||||
switch (fanspeed)
|
||||
{
|
||||
case AC_FANSPEED_HIGH:
|
||||
return ClimateFanMode::CLIMATE_FAN_HIGH;
|
||||
|
||||
case AC_FANSPEED_MEDIUM:
|
||||
return ClimateFanMode::CLIMATE_FAN_MEDIUM;
|
||||
|
||||
case AC_FANSPEED_LOW:
|
||||
return ClimateFanMode::CLIMATE_FAN_LOW;
|
||||
|
||||
case AC_FANSPEED_AUTO:
|
||||
return ClimateFanMode::CLIMATE_FAN_AUTO;
|
||||
|
||||
default:
|
||||
return ClimateFanMode::CLIMATE_FAN_LOW;
|
||||
}
|
||||
}
|
||||
|
||||
ac_fanspeed climate_fan_mode_to_ac_fanspeed(ClimateFanMode fanmode)
|
||||
{
|
||||
switch (fanmode)
|
||||
{
|
||||
case ClimateFanMode::CLIMATE_FAN_AUTO:
|
||||
return AC_FANSPEED_AUTO;
|
||||
|
||||
case ClimateFanMode::CLIMATE_FAN_LOW:
|
||||
return AC_FANSPEED_LOW;
|
||||
|
||||
case ClimateFanMode::CLIMATE_FAN_MEDIUM:
|
||||
return AC_FANSPEED_MEDIUM;
|
||||
|
||||
case ClimateFanMode::CLIMATE_FAN_HIGH:
|
||||
return AC_FANSPEED_HIGH;
|
||||
|
||||
default:
|
||||
return ac_fanspeed::AC_FANSPEED_LOW;
|
||||
}
|
||||
}
|
||||
|
||||
std::string ac_fanspeed_real_to_string(ac_fanspeed_real real_fanspeed)
|
||||
{
|
||||
switch (real_fanspeed)
|
||||
{
|
||||
case AC_REAL_FAN_OFF:
|
||||
return "AC_REAL_FAN_OFF";
|
||||
|
||||
case AC_REAL_FAN_MUTE:
|
||||
return "AC_REAL_FAN_MUTE";
|
||||
|
||||
case AC_REAL_FAN_LOW:
|
||||
return "AC_REAL_FAN_LOW";
|
||||
|
||||
case AC_REAL_FAN_MID:
|
||||
return "AC_REAL_FAN_MID";
|
||||
|
||||
case AC_REAL_FAN_HIGH:
|
||||
return "AC_REAL_FAN_HIGH";
|
||||
|
||||
case AC_REAL_FAN_TURBO:
|
||||
return "AC_REAL_FAN_TURBO";
|
||||
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aux_airconditioner
|
||||
} // namespace esphome
|
||||
107
components/aux_ac/aircon_common.h
Normal file
107
components/aux_ac/aircon_common.h
Normal file
@@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/core/optional.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_airconditioner
|
||||
{
|
||||
enum ac_mode : uint8_t
|
||||
{
|
||||
AC_MODE_AUTO = 0x00,
|
||||
AC_MODE_COOL = 0x20,
|
||||
AC_MODE_DRY = 0x40,
|
||||
AC_MODE_HEAT = 0x80,
|
||||
AC_MODE_FAN = 0xC0
|
||||
};
|
||||
|
||||
std::string ac_mode_to_string(ac_mode mode);
|
||||
|
||||
using esphome::climate::ClimateMode;
|
||||
ClimateMode ac_mode_to_climate_mode(ac_mode mode);
|
||||
ac_mode climate_mode_to_ac_mode(ClimateMode mode);
|
||||
|
||||
// vertical louvers position in esphome / HA frontend
|
||||
enum vlouver_esphome_position_t : uint8_t
|
||||
{
|
||||
AC_VLOUVER_FRONTEND_SWING = 0x00,
|
||||
AC_VLOUVER_FRONTEND_STOP = 0x01,
|
||||
AC_VLOUVER_FRONTEND_TOP = 0x02,
|
||||
AC_VLOUVER_FRONTEND_MIDDLE_ABOVE = 0x03,
|
||||
AC_VLOUVER_FRONTEND_MIDDLE = 0x04,
|
||||
AC_VLOUVER_FRONTEND_MIDDLE_BELOW = 0x05,
|
||||
AC_VLOUVER_FRONTEND_BOTTOM = 0x06,
|
||||
};
|
||||
|
||||
enum ac_louver_V : uint8_t
|
||||
{
|
||||
AC_LOUVERV_SWING_UPDOWN = 0x00,
|
||||
AC_LOUVERV_TOP = 0x01,
|
||||
AC_LOUVERV_MIDDLE_ABOVE = 0x02,
|
||||
AC_LOUVERV_MIDDLE = 0x03,
|
||||
AC_LOUVERV_MIDDLE_BELOW = 0x04,
|
||||
AC_LOUVERV_BOTTOM = 0x05,
|
||||
// 0x06 tested and doing nothing
|
||||
AC_LOUVERV_OFF = 0x07
|
||||
};
|
||||
|
||||
ac_louver_V vlouver_frontend_to_ac_louver_V(const vlouver_esphome_position_t vlouver_frontend);
|
||||
vlouver_esphome_position_t ac_louver_V_to_vlouver_frontend(const ac_louver_V aux_vlouver);
|
||||
|
||||
std::string ac_louver_V_to_string(ac_louver_V louver);
|
||||
|
||||
enum ac_louver_H : uint8_t
|
||||
{
|
||||
AC_LOUVERH_SWING_LEFTRIGHT = 0x00,
|
||||
// AC_LOUVERH_OFF_AUX = 0x20, // 0b00100000
|
||||
AC_LOUVERH_OFF = 0xE0 // 0b11100000
|
||||
};
|
||||
|
||||
std::string ac_louver_H_to_string(ac_louver_H louver);
|
||||
|
||||
enum ac_fanspeed : uint8_t
|
||||
{
|
||||
AC_FANSPEED_HIGH = 0x20,
|
||||
AC_FANSPEED_MEDIUM = 0x40,
|
||||
AC_FANSPEED_LOW = 0x60,
|
||||
AC_FANSPEED_AUTO = 0xA0
|
||||
};
|
||||
|
||||
std::string ac_fanspeed_to_string(ac_fanspeed fanspeed);
|
||||
|
||||
using esphome::climate::ClimateFanMode;
|
||||
ClimateFanMode ac_fanspeed_to_climate_fan_mode(ac_fanspeed fanspeed);
|
||||
ac_fanspeed climate_fan_mode_to_ac_fanspeed(ClimateFanMode fanmode);
|
||||
|
||||
enum ac_fanspeed_real : uint8_t
|
||||
{
|
||||
AC_REAL_FAN_OFF = 0x00,
|
||||
AC_REAL_FAN_MUTE = 0x01,
|
||||
AC_REAL_FAN_LOW = 0x02,
|
||||
AC_REAL_FAN_MID = 0x04,
|
||||
AC_REAL_FAN_HIGH = 0x06,
|
||||
AC_REAL_FAN_TURBO = 0x07
|
||||
};
|
||||
|
||||
std::string ac_fanspeed_real_to_string(ac_fanspeed_real real_fanspeed);
|
||||
|
||||
enum command_type_t : uint8_t
|
||||
{
|
||||
COMMAND_TYPE_NONE = 0x00,
|
||||
COMMAND_TYPE_REQUEST_11 = 0x01,
|
||||
COMMAND_TYPE_REQUEST_21 = 0x02,
|
||||
COMMAND_TYPE_SET_STATE = 0x03,
|
||||
};
|
||||
|
||||
enum command_processor_state_t : uint8_t
|
||||
{
|
||||
CMD_PROCESSOR_STATE_NOT_STARTED = 0x00,
|
||||
CMD_PROCESSOR_STATE_WAITING_FOR_F11 = 0x01,
|
||||
CMD_PROCESSOR_STATE_PRECHECK_DONE = 0x02,
|
||||
CMD_PROCESSOR_STATE_CMD_WAS_SENT = 0x03,
|
||||
CMD_PROCESSOR_STATE_POSTCHECK_DONE = 0x04,
|
||||
};
|
||||
|
||||
} // namespace aux_airconditioner
|
||||
} // namespace GrKoR
|
||||
@@ -1,186 +1,169 @@
|
||||
#pragma once
|
||||
|
||||
#include "aux_ac.h"
|
||||
#include "aircon.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace aux_ac {
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_airconditioner
|
||||
{
|
||||
class AirCon;
|
||||
|
||||
// **************************************** DISPLAY ACTIONS ****************************************
|
||||
template <typename... Ts>
|
||||
class AirConDisplayOffAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConDisplayOffAction(AirCon *ac) : ac_(ac) {}
|
||||
// **************************************** DISPLAY ACTIONS ****************************************
|
||||
template <typename... Ts>
|
||||
class AirConDisplayOffAction : public Action<Ts...>
|
||||
{
|
||||
public:
|
||||
explicit AirConDisplayOffAction(AirCon *ac) : ac_(ac) {}
|
||||
|
||||
void play(Ts... x) override { this->ac_->displayOffSequence(); }
|
||||
void play(Ts... x) override { this->ac_->action_display_off(); }
|
||||
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConDisplayOnAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConDisplayOnAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConDisplayOnAction : public Action<Ts...>
|
||||
{
|
||||
public:
|
||||
explicit AirConDisplayOnAction(AirCon *ac) : ac_(ac) {}
|
||||
|
||||
void play(Ts... x) override { this->ac_->displayOnSequence(); }
|
||||
void play(Ts... x) override { this->ac_->action_display_on(); }
|
||||
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
// **************************************** VERTICAL LOUVER ACTIONS ****************************************
|
||||
template <typename... Ts>
|
||||
class AirConVLouverSwingAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConVLouverSwingAction(AirCon *ac) : ac_(ac) {}
|
||||
// **************************************** VERTICAL LOUVER ACTIONS ****************************************
|
||||
template <typename... Ts>
|
||||
class AirConVLouverSwingAction : public Action<Ts...>
|
||||
{
|
||||
public:
|
||||
explicit AirConVLouverSwingAction(AirCon *ac) : ac_(ac) {}
|
||||
|
||||
void play(Ts... x) override { this->ac_->setVLouverSwingSequence(); }
|
||||
void play(Ts... x) override { this->ac_->action_set_vlouver_swing(); }
|
||||
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConVLouverStopAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConVLouverStopAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConVLouverStopAction : public Action<Ts...>
|
||||
{
|
||||
public:
|
||||
explicit AirConVLouverStopAction(AirCon *ac) : ac_(ac) {}
|
||||
|
||||
void play(Ts... x) override { this->ac_->setVLouverStopSequence(); }
|
||||
void play(Ts... x) override { this->ac_->action_set_vlouver_stop(); }
|
||||
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConVLouverTopAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConVLouverTopAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConVLouverTopAction : public Action<Ts...>
|
||||
{
|
||||
public:
|
||||
explicit AirConVLouverTopAction(AirCon *ac) : ac_(ac) {}
|
||||
|
||||
void play(Ts... x) override { this->ac_->setVLouverTopSequence(); }
|
||||
void play(Ts... x) override { this->ac_->action_set_vlouver_top_position(); }
|
||||
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConVLouverMiddleAboveAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConVLouverMiddleAboveAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConVLouverMiddleAboveAction : public Action<Ts...>
|
||||
{
|
||||
public:
|
||||
explicit AirConVLouverMiddleAboveAction(AirCon *ac) : ac_(ac) {}
|
||||
|
||||
void play(Ts... x) override { this->ac_->setVLouverMiddleAboveSequence(); }
|
||||
void play(Ts... x) override { this->ac_->action_set_vlouver_middle_above_position(); }
|
||||
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConVLouverMiddleAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConVLouverMiddleAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConVLouverMiddleAction : public Action<Ts...>
|
||||
{
|
||||
public:
|
||||
explicit AirConVLouverMiddleAction(AirCon *ac) : ac_(ac) {}
|
||||
|
||||
void play(Ts... x) override { this->ac_->setVLouverMiddleSequence(); }
|
||||
void play(Ts... x) override { this->ac_->action_set_vlouver_middle_position(); }
|
||||
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConVLouverMiddleBelowAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConVLouverMiddleBelowAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConVLouverMiddleBelowAction : public Action<Ts...>
|
||||
{
|
||||
public:
|
||||
explicit AirConVLouverMiddleBelowAction(AirCon *ac) : ac_(ac) {}
|
||||
|
||||
void play(Ts... x) override { this->ac_->setVLouverMiddleBelowSequence(); }
|
||||
void play(Ts... x) override { this->ac_->action_set_vlouver_middle_below_position(); }
|
||||
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConVLouverBottomAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConVLouverBottomAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConVLouverBottomAction : public Action<Ts...>
|
||||
{
|
||||
public:
|
||||
explicit AirConVLouverBottomAction(AirCon *ac) : ac_(ac) {}
|
||||
|
||||
void play(Ts... x) override { this->ac_->setVLouverBottomSequence(); }
|
||||
void play(Ts... x) override { this->ac_->action_set_vlouver_bottom(); }
|
||||
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConVLouverSetAction : public Action<Ts...> {
|
||||
public:
|
||||
AirConVLouverSetAction(AirCon *ac) : ac_(ac) {}
|
||||
TEMPLATABLE_VALUE(uint8_t, value);
|
||||
template <typename... Ts>
|
||||
class AirConVLouverSetAction : public Action<Ts...>
|
||||
{
|
||||
public:
|
||||
AirConVLouverSetAction(AirCon *ac) : ac_(ac) {}
|
||||
TEMPLATABLE_VALUE(uint8_t, value);
|
||||
|
||||
void play(Ts... x) {
|
||||
vlpos_ = this->value_.value(x...);
|
||||
this->ac_->setVLouverFrontendSequence((ac_vlouver_frontend)vlpos_);
|
||||
}
|
||||
void play(Ts... x)
|
||||
{
|
||||
this->ac_->action_set_vlouver_position((vlouver_esphome_position_t)this->value_.value(x...));
|
||||
}
|
||||
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
uint8_t vlpos_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
// **************************************** SEND TEST PACKET ACTION ****************************************
|
||||
template <typename... Ts>
|
||||
class AirConSendTestPacketAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConSendTestPacketAction(AirCon *ac) : ac_(ac) {}
|
||||
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) {
|
||||
this->data_func_ = func;
|
||||
this->static_ = false;
|
||||
}
|
||||
void set_data_static(const std::vector<uint8_t> &data) {
|
||||
this->data_static_ = data;
|
||||
this->static_ = true;
|
||||
}
|
||||
// **************************************** POWER LIMITATION ACTIONS ****************************************
|
||||
template <typename... Ts>
|
||||
class AirConPowerLimitationOffAction : public Action<Ts...>
|
||||
{
|
||||
public:
|
||||
explicit AirConPowerLimitationOffAction(AirCon *ac) : ac_(ac) {}
|
||||
|
||||
void play(Ts... x) override {
|
||||
if (this->static_) {
|
||||
this->ac_->sendTestPacket(this->data_static_);
|
||||
} else {
|
||||
auto val = this->data_func_(x...);
|
||||
this->ac_->sendTestPacket(val);
|
||||
}
|
||||
}
|
||||
void play(Ts... x) override { this->ac_->action_power_limitation_off(); }
|
||||
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
bool static_{false};
|
||||
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
|
||||
std::vector<uint8_t> data_static_{};
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
// **************************************** POWER LIMITATION ACTIONS ****************************************
|
||||
template <typename... Ts>
|
||||
class AirConPowerLimitationOffAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit AirConPowerLimitationOffAction(AirCon *ac) : ac_(ac) {}
|
||||
template <typename... Ts>
|
||||
class AirConPowerLimitationOnAction : public Action<Ts...>
|
||||
{
|
||||
public:
|
||||
AirConPowerLimitationOnAction(AirCon *ac) : ac_(ac) {}
|
||||
TEMPLATABLE_VALUE(uint8_t, value);
|
||||
|
||||
void play(Ts... x) override { this->ac_->powerLimitationOffSequence(); }
|
||||
void play(Ts... x)
|
||||
{
|
||||
this->ac_->action_power_limitation_on(this->value_.value(x...));
|
||||
}
|
||||
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
class AirConPowerLimitationOnAction : public Action<Ts...> {
|
||||
public:
|
||||
AirConPowerLimitationOnAction(AirCon *ac) : ac_(ac) {}
|
||||
TEMPLATABLE_VALUE(uint8_t, value);
|
||||
|
||||
void play(Ts... x) {
|
||||
this->pwr_lim_ = this->value_.value(x...);
|
||||
this->ac_->powerLimitationOnSequence(this->pwr_lim_);
|
||||
}
|
||||
|
||||
protected:
|
||||
AirCon *ac_;
|
||||
uint8_t pwr_lim_;
|
||||
};
|
||||
|
||||
} // namespace aux_ac
|
||||
} // namespace esphome
|
||||
} // namespace aux_airconditioner
|
||||
} // namespace esphome
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@ from esphome.const import (
|
||||
CONF_DATA,
|
||||
CONF_ID,
|
||||
CONF_INTERNAL,
|
||||
CONF_OPTIMISTIC,
|
||||
CONF_PERIOD,
|
||||
CONF_POSITION,
|
||||
CONF_SUPPORTED_MODES,
|
||||
@@ -37,30 +38,43 @@ CODEOWNERS = ["@GrKoR"]
|
||||
DEPENDENCIES = ["climate", "uart"]
|
||||
AUTO_LOAD = ["sensor", "binary_sensor", "text_sensor"]
|
||||
|
||||
CONF_SHOW_ACTION = "show_action"
|
||||
CONF_SHOW_ACTION_DEPRICATED = "show_action"
|
||||
CONF_INDOOR_TEMPERATURE_DEPRICATED = "indoor_temperature"
|
||||
CONF_INBOUND_TEMPERATURE_DEPRICATED = "inbound_temperature"
|
||||
CONF_OUTDOOR_TEMPERATURE_DEPRICATED = "outdoor_temperature"
|
||||
CONF_OUTBOUND_TEMPERATURE_DEPRICATED = "outbound_temperature"
|
||||
CONF_COMPRESSOR_TEMPERATURE_DEPRICATED = "compressor_temperature"
|
||||
|
||||
CONF_INDOOR_TEMPERATURE = "indoor_temperature"
|
||||
CONF_OUTDOOR_TEMPERATURE = "outdoor_temperature"
|
||||
ICON_OUTDOOR_TEMPERATURE = "mdi:home-thermometer-outline"
|
||||
CONF_INDOOR_AMBIENT_TEMPERATURE = "indoor_ambient_temperature"
|
||||
|
||||
CONF_INBOUND_TEMPERATURE = "inbound_temperature"
|
||||
ICON_INBOUND_TEMPERATURE = "mdi:thermometer-plus"
|
||||
CONF_INDOOR_COIL_TEMPERATURE = "indoor_coil_temperature"
|
||||
ICON_INDOOR_COIL_TEMPERATURE = "mdi:thermometer-plus"
|
||||
|
||||
CONF_OUTBOUND_TEMPERATURE = "outbound_temperature"
|
||||
ICON_OUTBOUND_TEMPERATURE = "mdi:thermometer-minus"
|
||||
CONF_OUTDOOR_AMBIENT_TEMPERATURE = "outdoor_ambient_temperature"
|
||||
ICON_OUTDOOR_AMBIENT_TEMPERATURE = "mdi:home-thermometer-outline"
|
||||
|
||||
CONF_COMPRESSOR_TEMPERATURE = "compressor_temperature"
|
||||
ICON_COMPRESSOR_TEMPERATURE = "mdi:thermometer-lines"
|
||||
CONF_OUTDOOR_CONDENSER_TEMPERATURE = "outdoor_condenser_temperature"
|
||||
ICON_OUTDOOR_CONDENSER_TEMPERATURE = "mdi:thermometer-minus"
|
||||
|
||||
CONF_DEFROST_TEMPERATURE = "defrost_temperature"
|
||||
ICON_DEFROST_TEMPERATURE = "mdi:thermometer-lines"
|
||||
|
||||
CONF_COMPRESSOR_DISCHARGE_TEMPERATURE = "compressor_discharge_temperature"
|
||||
ICON_COMPRESSOR_DISCHARGE_TEMPERATURE = "mdi:thermometer-lines"
|
||||
|
||||
CONF_COMPRESSOR_SUCTION_TEMPERATURE = "compressor_suction_temperature"
|
||||
ICON_COMPRESSOR_SUCTION_TEMPERATURE = "mdi:thermometer-lines"
|
||||
|
||||
CONF_DISPLAY_STATE = "display_state"
|
||||
ICON_DISPLAY_STATE = "mdi:clock-digital"
|
||||
|
||||
CONF_INVERTER_POWER = "inverter_power"
|
||||
CONF_INVERTER_POWER_DEPRICATED = "invertor_power"
|
||||
|
||||
CONF_DEFROST_STATE = "defrost_state"
|
||||
ICON_DEFROST = "mdi:snowflake-melt"
|
||||
ICON_DEFROST_STATE = "mdi:snowflake-melt"
|
||||
|
||||
CONF_DISPLAY_INVERTED = "display_inverted"
|
||||
ICON_DISPLAY = "mdi:clock-digital"
|
||||
|
||||
CONF_PRESET_REPORTER = "preset_reporter"
|
||||
ICON_PRESET_REPORTER = "mdi:format-list-group"
|
||||
@@ -75,52 +89,45 @@ CONF_INVERTER_POWER_LIMIT_STATE = "inverter_power_limit_state"
|
||||
ICON_INVERTER_POWER_LIMIT_STATE = "mdi:meter-electric-outline"
|
||||
|
||||
|
||||
aux_ac_ns = cg.esphome_ns.namespace("aux_ac")
|
||||
aux_ac_ns = cg.esphome_ns.namespace("aux_airconditioner")
|
||||
AirCon = aux_ac_ns.class_("AirCon", climate.Climate, cg.Component)
|
||||
Capabilities = aux_ac_ns.namespace("Constants")
|
||||
Capabilities = aux_ac_ns.namespace("Capabilities")
|
||||
|
||||
# Display actions
|
||||
AirConDisplayOffAction = aux_ac_ns.class_("AirConDisplayOffAction", automation.Action)
|
||||
AirConDisplayOnAction = aux_ac_ns.class_("AirConDisplayOnAction", automation.Action)
|
||||
|
||||
# test packet action
|
||||
AirConSendTestPacketAction = aux_ac_ns.class_(
|
||||
"AirConSendTestPacketAction", automation.Action
|
||||
)
|
||||
AirConDisplayOffAction = aux_ac_ns.class_(
|
||||
"AirConDisplayOffAction", automation.Action)
|
||||
AirConDisplayOnAction = aux_ac_ns.class_(
|
||||
"AirConDisplayOnAction", automation.Action)
|
||||
|
||||
# vertical louvers actions
|
||||
AirConVLouverSwingAction = aux_ac_ns.class_(
|
||||
"AirConVLouverSwingAction", automation.Action
|
||||
)
|
||||
AirConVLouverStopAction = aux_ac_ns.class_("AirConVLouverStopAction", automation.Action)
|
||||
AirConVLouverTopAction = aux_ac_ns.class_("AirConVLouverTopAction", automation.Action)
|
||||
"AirConVLouverSwingAction", automation.Action)
|
||||
AirConVLouverStopAction = aux_ac_ns.class_(
|
||||
"AirConVLouverStopAction", automation.Action)
|
||||
AirConVLouverTopAction = aux_ac_ns.class_(
|
||||
"AirConVLouverTopAction", automation.Action)
|
||||
AirConVLouverMiddleAboveAction = aux_ac_ns.class_(
|
||||
"AirConVLouverMiddleAboveAction", automation.Action
|
||||
)
|
||||
"AirConVLouverMiddleAboveAction", automation.Action)
|
||||
AirConVLouverMiddleAction = aux_ac_ns.class_(
|
||||
"AirConVLouverMiddleAction", automation.Action
|
||||
)
|
||||
"AirConVLouverMiddleAction", automation.Action)
|
||||
AirConVLouverMiddleBelowAction = aux_ac_ns.class_(
|
||||
"AirConVLouverMiddleBelowAction", automation.Action
|
||||
)
|
||||
"AirConVLouverMiddleBelowAction", automation.Action)
|
||||
AirConVLouverBottomAction = aux_ac_ns.class_(
|
||||
"AirConVLouverBottomAction", automation.Action
|
||||
)
|
||||
"AirConVLouverBottomAction", automation.Action)
|
||||
AirConVLouverSetAction = aux_ac_ns.class_(
|
||||
"AirConVLouverSetAction", automation.Action
|
||||
)
|
||||
"AirConVLouverSetAction", automation.Action)
|
||||
|
||||
# power limitation actions
|
||||
AirConPowerLimitationOffAction = aux_ac_ns.class_(
|
||||
"AirConPowerLimitationOffAction", automation.Action
|
||||
)
|
||||
"AirConPowerLimitationOffAction", automation.Action)
|
||||
AirConPowerLimitationOnAction = aux_ac_ns.class_(
|
||||
"AirConPowerLimitationOnAction", automation.Action
|
||||
)
|
||||
"AirConPowerLimitationOnAction", automation.Action)
|
||||
|
||||
|
||||
AC_PACKET_TIMEOUT_MIN = 300
|
||||
AC_PACKET_TIMEOUT_MAX = 800
|
||||
|
||||
|
||||
AC_PACKET_TIMEOUT_MIN = 150
|
||||
AC_PACKET_TIMEOUT_MAX = 600
|
||||
def validate_packet_timeout(value):
|
||||
minV = AC_PACKET_TIMEOUT_MIN
|
||||
maxV = AC_PACKET_TIMEOUT_MAX
|
||||
@@ -131,6 +138,8 @@ def validate_packet_timeout(value):
|
||||
|
||||
AC_POWER_LIMIT_MIN = 30
|
||||
AC_POWER_LIMIT_MAX = 100
|
||||
|
||||
|
||||
def validate_power_limit_range(value):
|
||||
minV = AC_POWER_LIMIT_MIN
|
||||
maxV = AC_POWER_LIMIT_MAX
|
||||
@@ -161,27 +170,21 @@ ALLOWED_CLIMATE_SWING_MODES = {
|
||||
validate_swing_modes = cv.enum(ALLOWED_CLIMATE_SWING_MODES, upper=True)
|
||||
|
||||
CUSTOM_FAN_MODES = {
|
||||
"MUTE": Capabilities.MUTE,
|
||||
"TURBO": Capabilities.TURBO,
|
||||
"MUTE": Capabilities.CUSTOM_FAN_MODE_MUTE,
|
||||
"TURBO": Capabilities.CUSTOM_FAN_MODE_TURBO,
|
||||
}
|
||||
validate_custom_fan_modes = cv.enum(CUSTOM_FAN_MODES, upper=True)
|
||||
|
||||
CUSTOM_PRESETS = {
|
||||
"CLEAN": Capabilities.CLEAN,
|
||||
"HEALTH": Capabilities.HEALTH,
|
||||
"ANTIFUNGUS": Capabilities.ANTIFUNGUS,
|
||||
"CLEAN": Capabilities.CUSTOM_PRESET_CLEAN,
|
||||
"HEALTH": Capabilities.CUSTOM_PRESET_HEALTH,
|
||||
"ANTIFUNGUS": Capabilities.CUSTOM_PRESET_ANTIFUNGUS,
|
||||
}
|
||||
validate_custom_presets = cv.enum(CUSTOM_PRESETS, upper=True)
|
||||
|
||||
|
||||
def validate_raw_data(value):
|
||||
if isinstance(value, list):
|
||||
return cv.Schema([cv.hex_uint8_t])(value)
|
||||
raise cv.Invalid("data must be a list of bytes")
|
||||
|
||||
|
||||
def output_info(config):
|
||||
"""_LOGGER.info(config.items())"""
|
||||
# _LOGGER.info(config.items())
|
||||
return config
|
||||
|
||||
|
||||
@@ -190,12 +193,15 @@ CONFIG_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(AirCon),
|
||||
cv.Optional(CONF_PERIOD, default="7s"): cv.time_period,
|
||||
cv.Optional(CONF_SHOW_ACTION, default="true"): cv.boolean,
|
||||
# cv.Optional(CONF_SHOW_ACTION, default="true"): cv.boolean,
|
||||
cv.Optional(CONF_SHOW_ACTION_DEPRICATED): cv.invalid(
|
||||
f"Parameter '{CONF_SHOW_ACTION_DEPRICATED}' was deleted in v.1.0.0. Update your config please."
|
||||
),
|
||||
cv.Optional(CONF_DISPLAY_INVERTED, default="false"): cv.boolean,
|
||||
cv.Optional(CONF_TIMEOUT, default=AC_PACKET_TIMEOUT_MIN): validate_packet_timeout,
|
||||
|
||||
cv.Optional(CONF_OPTIMISTIC, default="true"): cv.boolean,
|
||||
cv.Optional(CONF_INVERTER_POWER_DEPRICATED): cv.invalid(
|
||||
"The name of sensor was changed in v.0.2.9 from 'invertor_power' to 'inverter_power'. Update your config please."
|
||||
f"The name of sensor was changed in v.0.2.9 from '{CONF_INVERTER_POWER_DEPRICATED}' to '{CONF_INVERTER_POWER}'. Update your config please."
|
||||
),
|
||||
cv.Optional(CONF_INVERTER_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
@@ -209,7 +215,10 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
),
|
||||
|
||||
cv.Optional(CONF_INDOOR_TEMPERATURE): sensor.sensor_schema(
|
||||
cv.Optional(CONF_INDOOR_TEMPERATURE_DEPRICATED): cv.invalid(
|
||||
f"Parameter '{CONF_INDOOR_TEMPERATURE_DEPRICATED}' was deleted in v.1.0.0, use '{CONF_INDOOR_AMBIENT_TEMPERATURE}' instead."
|
||||
),
|
||||
cv.Optional(CONF_INDOOR_AMBIENT_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
@@ -220,9 +229,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_OUTDOOR_TEMPERATURE): sensor.sensor_schema(
|
||||
|
||||
cv.Optional(CONF_INBOUND_TEMPERATURE_DEPRICATED): cv.invalid(
|
||||
f"Parameter '{CONF_INBOUND_TEMPERATURE_DEPRICATED}' was deleted in v.1.0.0, use '{CONF_INDOOR_COIL_TEMPERATURE}' instead."
|
||||
),
|
||||
cv.Optional(CONF_INDOOR_COIL_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_OUTDOOR_TEMPERATURE,
|
||||
icon=ICON_INDOOR_COIL_TEMPERATURE,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
@@ -231,9 +244,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_INBOUND_TEMPERATURE): sensor.sensor_schema(
|
||||
|
||||
cv.Optional(CONF_OUTDOOR_TEMPERATURE_DEPRICATED): cv.invalid(
|
||||
f"Parameter '{CONF_OUTDOOR_TEMPERATURE_DEPRICATED}' was deleted in v.1.0.0, use '{CONF_OUTDOOR_AMBIENT_TEMPERATURE}' instead."
|
||||
),
|
||||
cv.Optional(CONF_OUTDOOR_AMBIENT_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_INBOUND_TEMPERATURE,
|
||||
icon=ICON_OUTDOOR_AMBIENT_TEMPERATURE,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
@@ -242,9 +259,10 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_OUTBOUND_TEMPERATURE): sensor.sensor_schema(
|
||||
|
||||
cv.Optional(CONF_OUTDOOR_CONDENSER_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_OUTBOUND_TEMPERATURE,
|
||||
icon=ICON_OUTDOOR_CONDENSER_TEMPERATURE,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
@@ -253,9 +271,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_COMPRESSOR_TEMPERATURE): sensor.sensor_schema(
|
||||
|
||||
cv.Optional(CONF_COMPRESSOR_TEMPERATURE_DEPRICATED): cv.invalid(
|
||||
f"Parameter '{CONF_COMPRESSOR_TEMPERATURE_DEPRICATED}' was deleted in v.1.0.0, use '{CONF_COMPRESSOR_DISCHARGE_TEMPERATURE}' instead."
|
||||
),
|
||||
cv.Optional(CONF_COMPRESSOR_DISCHARGE_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_COMPRESSOR_TEMPERATURE,
|
||||
icon=ICON_COMPRESSOR_DISCHARGE_TEMPERATURE,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
@@ -264,6 +286,34 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
|
||||
}
|
||||
),
|
||||
|
||||
cv.Optional(CONF_OUTBOUND_TEMPERATURE_DEPRICATED): cv.invalid(
|
||||
f"Parameter '{CONF_OUTBOUND_TEMPERATURE_DEPRICATED}' was deleted in v.1.0.0, use '{CONF_COMPRESSOR_SUCTION_TEMPERATURE}' instead."
|
||||
),
|
||||
cv.Optional(CONF_COMPRESSOR_SUCTION_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_COMPRESSOR_SUCTION_TEMPERATURE,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
|
||||
}
|
||||
),
|
||||
|
||||
cv.Optional(CONF_DEFROST_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_DEFROST_TEMPERATURE,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
|
||||
}
|
||||
),
|
||||
|
||||
cv.Optional(CONF_VLOUVER_STATE): sensor.sensor_schema(
|
||||
icon=ICON_VLOUVER_STATE,
|
||||
accuracy_decimals=0,
|
||||
@@ -273,14 +323,14 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_DISPLAY_STATE): binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_DISPLAY,
|
||||
icon=ICON_DISPLAY_STATE,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_DEFROST_STATE): binary_sensor.binary_sensor_schema(
|
||||
icon=ICON_DEFROST,
|
||||
icon=ICON_DEFROST_STATE,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_INTERNAL, default="true"): cv.boolean,
|
||||
@@ -334,76 +384,88 @@ async def to_code(config):
|
||||
await climate.register_climate(var, config)
|
||||
|
||||
parent = await cg.get_variable(config[CONF_UART_ID])
|
||||
cg.add(var.initAC(parent))
|
||||
cg.add(var.set_uart(parent))
|
||||
|
||||
if CONF_INDOOR_TEMPERATURE in config:
|
||||
conf = config[CONF_INDOOR_TEMPERATURE]
|
||||
if CONF_INDOOR_AMBIENT_TEMPERATURE in config:
|
||||
conf = config[CONF_INDOOR_AMBIENT_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_indoor_temperature_sensor(sens))
|
||||
cg.add(var.set_sensor_temperature_indoor_ambient(sens))
|
||||
|
||||
if CONF_OUTDOOR_TEMPERATURE in config:
|
||||
conf = config[CONF_OUTDOOR_TEMPERATURE]
|
||||
if CONF_INDOOR_COIL_TEMPERATURE in config:
|
||||
conf = config[CONF_INDOOR_COIL_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_outdoor_temperature_sensor(sens))
|
||||
cg.add(var.set_sensor_temperature_indoor_coil(sens))
|
||||
|
||||
if CONF_OUTBOUND_TEMPERATURE in config:
|
||||
conf = config[CONF_OUTBOUND_TEMPERATURE]
|
||||
if CONF_OUTDOOR_AMBIENT_TEMPERATURE in config:
|
||||
conf = config[CONF_OUTDOOR_AMBIENT_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_outbound_temperature_sensor(sens))
|
||||
cg.add(var.set_sensor_temperature_outdoor_ambient(sens))
|
||||
|
||||
if CONF_INBOUND_TEMPERATURE in config:
|
||||
conf = config[CONF_INBOUND_TEMPERATURE]
|
||||
if CONF_OUTDOOR_CONDENSER_TEMPERATURE in config:
|
||||
conf = config[CONF_OUTDOOR_CONDENSER_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_inbound_temperature_sensor(sens))
|
||||
cg.add(var.set_sensor_temperature_outdoor_condenser_middle(sens))
|
||||
|
||||
if CONF_COMPRESSOR_TEMPERATURE in config:
|
||||
conf = config[CONF_COMPRESSOR_TEMPERATURE]
|
||||
if CONF_DEFROST_TEMPERATURE in config:
|
||||
conf = config[CONF_DEFROST_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_compressor_temperature_sensor(sens))
|
||||
cg.add(var.set_sensor_temperature_outdoor_defrost(sens))
|
||||
|
||||
if CONF_COMPRESSOR_DISCHARGE_TEMPERATURE in config:
|
||||
conf = config[CONF_COMPRESSOR_DISCHARGE_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_sensor_temperature_outdoor_discharge(sens))
|
||||
|
||||
if CONF_COMPRESSOR_SUCTION_TEMPERATURE in config:
|
||||
conf = config[CONF_COMPRESSOR_SUCTION_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_sensor_temperature_outdoor_suction(sens))
|
||||
|
||||
if CONF_VLOUVER_STATE in config:
|
||||
conf = config[CONF_VLOUVER_STATE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_vlouver_state_sensor(sens))
|
||||
cg.add(var.set_sensor_vlouver_state(sens))
|
||||
|
||||
if CONF_DISPLAY_STATE in config:
|
||||
conf = config[CONF_DISPLAY_STATE]
|
||||
sens = await binary_sensor.new_binary_sensor(conf)
|
||||
cg.add(var.set_display_sensor(sens))
|
||||
cg.add(var.set_sensor_display(sens))
|
||||
|
||||
if CONF_DEFROST_STATE in config:
|
||||
conf = config[CONF_DEFROST_STATE]
|
||||
sens = await binary_sensor.new_binary_sensor(conf)
|
||||
cg.add(var.set_defrost_state(sens))
|
||||
|
||||
if CONF_INVERTER_POWER in config:
|
||||
conf = config[CONF_INVERTER_POWER]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_inverter_power_sensor(sens))
|
||||
cg.add(var.set_sensor_defrost_state(sens))
|
||||
|
||||
if CONF_PRESET_REPORTER in config:
|
||||
conf = config[CONF_PRESET_REPORTER]
|
||||
sens = await text_sensor.new_text_sensor(conf)
|
||||
cg.add(var.set_preset_reporter_sensor(sens))
|
||||
|
||||
cg.add(var.set_sensor_preset_reporter(sens))
|
||||
|
||||
if CONF_INVERTER_POWER in config:
|
||||
conf = config[CONF_INVERTER_POWER]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_sensor_inverter_power(sens))
|
||||
|
||||
if CONF_INVERTER_POWER_LIMIT_VALUE in config:
|
||||
conf = config[CONF_INVERTER_POWER_LIMIT_VALUE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_inverter_power_limit_value_sensor(sens))
|
||||
cg.add(var.set_sensor_inverter_power_limit_value(sens))
|
||||
|
||||
if CONF_INVERTER_POWER_LIMIT_STATE in config:
|
||||
conf = config[CONF_INVERTER_POWER_LIMIT_STATE]
|
||||
sens = await binary_sensor.new_binary_sensor(conf)
|
||||
cg.add(var.set_inverter_power_limit_state_sensor(sens))
|
||||
cg.add(var.set_sensor_inverter_power_limit_state(sens))
|
||||
|
||||
cg.add(var.set_period(config[CONF_PERIOD].total_milliseconds))
|
||||
cg.add(var.set_show_action(config[CONF_SHOW_ACTION]))
|
||||
cg.add(var.set_display_inverted(config[CONF_DISPLAY_INVERTED]))
|
||||
# cg.add(var.set_show_action(config[CONF_SHOW_ACTION]))
|
||||
cg.add(var.set_display_inversion(config[CONF_DISPLAY_INVERTED]))
|
||||
cg.add(var.set_packet_timeout(config[CONF_TIMEOUT]))
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
if CONF_SUPPORTED_MODES in config:
|
||||
cg.add(var.set_supported_modes(config[CONF_SUPPORTED_MODES]))
|
||||
if CONF_SUPPORTED_SWING_MODES in config:
|
||||
cg.add(var.set_supported_swing_modes(config[CONF_SUPPORTED_SWING_MODES]))
|
||||
cg.add(var.set_supported_swing_modes(
|
||||
config[CONF_SUPPORTED_SWING_MODES]))
|
||||
if CONF_SUPPORTED_PRESETS in config:
|
||||
cg.add(var.set_supported_presets(config[CONF_SUPPORTED_PRESETS]))
|
||||
if CONF_CUSTOM_PRESETS in config:
|
||||
@@ -412,27 +474,27 @@ async def to_code(config):
|
||||
cg.add(var.set_custom_fan_modes(config[CONF_CUSTOM_FAN_MODES]))
|
||||
|
||||
|
||||
|
||||
DISPLAY_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(AirCon),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.display_off", AirConDisplayOffAction, DISPLAY_ACTION_SCHEMA
|
||||
)
|
||||
async def display_off_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.display_on", AirConDisplayOnAction, DISPLAY_ACTION_SCHEMA
|
||||
)
|
||||
async def display_on_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
VLOUVER_ACTION_SCHEMA = maybe_simple_id(
|
||||
@@ -441,55 +503,61 @@ VLOUVER_ACTION_SCHEMA = maybe_simple_id(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_stop", AirConVLouverStopAction, VLOUVER_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_stop_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_swing", AirConVLouverSwingAction, VLOUVER_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_swing_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_top", AirConVLouverTopAction, VLOUVER_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_top_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_middle_above", AirConVLouverMiddleAboveAction, VLOUVER_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_middle_above_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_middle", AirConVLouverMiddleAction, VLOUVER_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_middle_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_middle_below", AirConVLouverMiddleBelowAction, VLOUVER_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_middle_below_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_bottom", AirConVLouverBottomAction, VLOUVER_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_bottom_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
VLOUVER_SET_ACTION_SCHEMA = cv.Schema(
|
||||
@@ -499,81 +567,47 @@ VLOUVER_SET_ACTION_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.vlouver_set", AirConVLouverSetAction, VLOUVER_SET_ACTION_SCHEMA
|
||||
)
|
||||
async def vlouver_set_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||
template_ = await cg.templatable(config[CONF_POSITION], args, int)
|
||||
cg.add(var.set_value(template_))
|
||||
return var
|
||||
|
||||
|
||||
|
||||
POWER_LIMITATION_OFF_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(AirCon),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.power_limit_off", AirConPowerLimitationOffAction, POWER_LIMITATION_OFF_ACTION_SCHEMA
|
||||
)
|
||||
async def power_limit_off_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
|
||||
|
||||
POWER_LIMITATION_ON_ACTION_SCHEMA = cv.Schema(
|
||||
POWER_LIMITATION_ON_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(AirCon),
|
||||
cv.Optional(CONF_LIMIT, default=AC_POWER_LIMIT_MIN): validate_power_limit_range,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.power_limit_on", AirConPowerLimitationOnAction, POWER_LIMITATION_ON_ACTION_SCHEMA
|
||||
)
|
||||
async def power_limit_on_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||
template_ = await cg.templatable(config[CONF_LIMIT], args, int)
|
||||
cg.add(var.set_value(template_))
|
||||
return var
|
||||
|
||||
|
||||
|
||||
# *********************************************************************************************************
|
||||
# ВАЖНО! Только для инженеров!
|
||||
# Вызывайте метод aux_ac.send_packet только если понимаете, что делаете! Он не проверяет данные, а передаёт
|
||||
# кондиционеру всё как есть. Какой эффект получится от передачи кондиционеру рандомных байт, никто не знает.
|
||||
# Вы действуете на свой страх и риск.
|
||||
# *********************************************************************************************************
|
||||
SEND_TEST_PACKET_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(AirCon),
|
||||
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"aux_ac.send_packet", AirConSendTestPacketAction, SEND_TEST_PACKET_ACTION_SCHEMA
|
||||
)
|
||||
async def send_packet_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
data = config[CONF_DATA]
|
||||
if isinstance(data, bytes):
|
||||
data = list(data)
|
||||
|
||||
if cg.is_template(data):
|
||||
templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8))
|
||||
cg.add(var.set_data_template(templ))
|
||||
else:
|
||||
cg.add(var.set_data_static(data))
|
||||
|
||||
return var
|
||||
|
||||
454
components/aux_ac/command_builder.cpp
Normal file
454
components/aux_ac/command_builder.cpp
Normal file
@@ -0,0 +1,454 @@
|
||||
#include "command_builder.h"
|
||||
#include "aircon.h"
|
||||
#include "frame.h"
|
||||
#include "esphome/core/optional.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_airconditioner
|
||||
{
|
||||
|
||||
CommandBuilder::CommandBuilder(AirCon &aircon)
|
||||
{
|
||||
_aircon = &aircon;
|
||||
_command_frame = new Frame;
|
||||
}
|
||||
|
||||
CommandBuilder::~CommandBuilder()
|
||||
{
|
||||
delete _command_frame;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::init_new_command(command_type_t command_type)
|
||||
{
|
||||
_command_frame->clear();
|
||||
|
||||
switch (command_type)
|
||||
{
|
||||
case COMMAND_TYPE_SET_STATE:
|
||||
_command_frame->append_data(_aircon->get_last_frame_11().data(), _aircon->get_last_frame_11().size());
|
||||
_command_frame->set_frame_dir(FrameDirection::FRAME_DIR_TO_AC).set_frame_type(FrameType::FRAME_TYPE_COMMAND);
|
||||
_command_frame->set_value(8, COMMAND).set_value(9, FLAG);
|
||||
break;
|
||||
|
||||
case COMMAND_TYPE_REQUEST_11:
|
||||
_command_frame->append_data({_command_frame->get_start_byte(), 0x00, FrameType::FRAME_TYPE_COMMAND, FrameDirection::FRAME_DIR_TO_AC, 0x00, 0x00, COMMAND_REQUEST_BODY_LENGTH, 0x00});
|
||||
_command_frame->append_data({0x11, FLAG});
|
||||
break;
|
||||
|
||||
case COMMAND_TYPE_REQUEST_21:
|
||||
_command_frame->append_data({_command_frame->get_start_byte(), 0x00, FrameType::FRAME_TYPE_COMMAND, FrameDirection::FRAME_DIR_TO_AC, 0x00, 0x00, COMMAND_REQUEST_BODY_LENGTH, 0x00});
|
||||
_command_frame->append_data({0x21, FLAG});
|
||||
break;
|
||||
|
||||
case COMMAND_TYPE_NONE:
|
||||
default:
|
||||
ESP_LOGW(TAG, "Command type 0x%02X is unsupported", command_type);
|
||||
break;
|
||||
}
|
||||
|
||||
_command_frame->update_crc(true);
|
||||
if (_command_frame->get_frame_state() == FRAME_STATE_OK)
|
||||
_command_frame->set_frame_time(_aircon->ms());
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::init_new_command(ClimateCall &cmd)
|
||||
{
|
||||
this->init_new_command(command_type_t::COMMAND_TYPE_SET_STATE);
|
||||
|
||||
if (cmd.get_mode().has_value())
|
||||
this->set_climate_mode(*cmd.get_mode());
|
||||
|
||||
if (cmd.get_fan_mode().has_value())
|
||||
this->set_climate_fan_mode(*cmd.get_fan_mode());
|
||||
else if (cmd.get_custom_fan_mode().has_value())
|
||||
this->set_climate_custom_fan_mode(*cmd.get_custom_fan_mode());
|
||||
|
||||
if (cmd.get_preset().has_value())
|
||||
this->set_climate_preset(*cmd.get_preset());
|
||||
else if (cmd.get_custom_preset().has_value())
|
||||
this->set_climate_custom_preset(*cmd.get_custom_preset());
|
||||
|
||||
if (cmd.get_swing_mode().has_value())
|
||||
this->set_climate_swing_mode(*cmd.get_swing_mode());
|
||||
|
||||
if (cmd.get_target_temperature().has_value())
|
||||
if (this->_aircon->mode != ClimateMode::CLIMATE_MODE_FAN_ONLY)
|
||||
this->set_target_temperature(*cmd.get_target_temperature());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::fill_frame_with_command(Frame &frame)
|
||||
{
|
||||
_command_frame->update_crc(true);
|
||||
if (_command_frame->get_frame_state() != FRAME_STATE_OK)
|
||||
return *this;
|
||||
|
||||
frame.clear();
|
||||
frame.append_data(_command_frame->data(), 8 + _command_frame->get_body_length() + 2, true);
|
||||
frame.set_frame_time(_command_frame->get_frame_time());
|
||||
return *this;
|
||||
}
|
||||
|
||||
Frame CommandBuilder::get_builder_result()
|
||||
{
|
||||
return *_command_frame;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_climate_mode(ClimateMode value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
this->set_power(value != ClimateMode::CLIMATE_MODE_OFF);
|
||||
|
||||
if (value == ClimateMode::CLIMATE_MODE_OFF)
|
||||
return *this;
|
||||
|
||||
this->set_mode(climate_mode_to_ac_mode(value));
|
||||
if (value == ClimateMode::CLIMATE_MODE_FAN_ONLY)
|
||||
{
|
||||
this->set_sleep_mode(false);
|
||||
}
|
||||
else if (value == ClimateMode::CLIMATE_MODE_DRY)
|
||||
{
|
||||
this->set_sleep_mode(false);
|
||||
this->set_fan_turbo(false);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_climate_fan_mode(ClimateFanMode value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case ClimateFanMode::CLIMATE_FAN_AUTO:
|
||||
this->set_fan_speed(ac_fanspeed::AC_FANSPEED_AUTO);
|
||||
this->set_fan_turbo(false);
|
||||
this->set_fan_mute(false);
|
||||
break;
|
||||
|
||||
case ClimateFanMode::CLIMATE_FAN_LOW:
|
||||
this->set_fan_speed(ac_fanspeed::AC_FANSPEED_LOW);
|
||||
this->set_fan_turbo(false);
|
||||
this->set_fan_mute(false);
|
||||
break;
|
||||
|
||||
case ClimateFanMode::CLIMATE_FAN_MEDIUM:
|
||||
this->set_fan_speed(ac_fanspeed::AC_FANSPEED_MEDIUM);
|
||||
this->set_fan_turbo(false);
|
||||
this->set_fan_mute(false);
|
||||
break;
|
||||
|
||||
case ClimateFanMode::CLIMATE_FAN_HIGH:
|
||||
this->set_fan_speed(ac_fanspeed::AC_FANSPEED_HIGH);
|
||||
this->set_fan_turbo(false);
|
||||
this->set_fan_mute(false);
|
||||
break;
|
||||
|
||||
// Other possible values should be ignored
|
||||
case ClimateFanMode::CLIMATE_FAN_ON:
|
||||
case ClimateFanMode::CLIMATE_FAN_OFF:
|
||||
case ClimateFanMode::CLIMATE_FAN_MIDDLE:
|
||||
case ClimateFanMode::CLIMATE_FAN_FOCUS:
|
||||
case ClimateFanMode::CLIMATE_FAN_DIFFUSE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_climate_custom_fan_mode(std::string value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
if (value == Capabilities::CUSTOM_FAN_MODE_TURBO)
|
||||
{
|
||||
this->set_fan_turbo(true);
|
||||
this->set_fan_mute(false);
|
||||
}
|
||||
else if (value == Capabilities::CUSTOM_FAN_MODE_MUTE)
|
||||
{
|
||||
this->set_fan_turbo(false);
|
||||
this->set_fan_mute(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->set_fan_turbo(false);
|
||||
this->set_fan_mute(false);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_climate_preset(ClimatePreset value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case ClimatePreset::CLIMATE_PRESET_SLEEP:
|
||||
// SLEEP function works in COOL and HEAT modes. Some air conditioners allow it in AUTO and DRY mode also.
|
||||
// We ignore this. Trying to enable it in any mode.
|
||||
this->set_sleep_mode(true);
|
||||
this->set_health_mode(false);
|
||||
break;
|
||||
|
||||
case ClimatePreset::CLIMATE_PRESET_NONE:
|
||||
this->set_health_mode(false);
|
||||
this->set_sleep_mode(false);
|
||||
this->set_antifungus_mode(false);
|
||||
this->set_iClean_mode(false);
|
||||
break;
|
||||
|
||||
// all other presets are ignored
|
||||
case ClimatePreset::CLIMATE_PRESET_HOME:
|
||||
case ClimatePreset::CLIMATE_PRESET_AWAY:
|
||||
case ClimatePreset::CLIMATE_PRESET_BOOST:
|
||||
case ClimatePreset::CLIMATE_PRESET_COMFORT:
|
||||
case ClimatePreset::CLIMATE_PRESET_ECO:
|
||||
case ClimatePreset::CLIMATE_PRESET_ACTIVITY:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_climate_custom_preset(std::string value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
if (value == Capabilities::CUSTOM_PRESET_CLEAN)
|
||||
{
|
||||
this->set_iClean_mode(true);
|
||||
this->set_antifungus_mode(false);
|
||||
}
|
||||
else if (value == Capabilities::CUSTOM_PRESET_HEALTH)
|
||||
{
|
||||
this->set_health_mode(true);
|
||||
this->set_fan_turbo(false);
|
||||
this->set_fan_mute(false);
|
||||
this->set_sleep_mode(false);
|
||||
}
|
||||
else if (value == Capabilities::CUSTOM_PRESET_ANTIFUNGUS)
|
||||
{
|
||||
this->set_antifungus_mode(true);
|
||||
this->set_iClean_mode(false);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_climate_swing_mode(ClimateSwingMode value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case ClimateSwingMode::CLIMATE_SWING_OFF:
|
||||
this->set_vertical_louver(ac_louver_V::AC_LOUVERV_OFF);
|
||||
this->set_horizontal_louver(ac_louver_H::AC_LOUVERH_OFF);
|
||||
break;
|
||||
|
||||
case ClimateSwingMode::CLIMATE_SWING_BOTH:
|
||||
this->set_vertical_louver(ac_louver_V::AC_LOUVERV_SWING_UPDOWN);
|
||||
this->set_horizontal_louver(ac_louver_H::AC_LOUVERH_SWING_LEFTRIGHT);
|
||||
break;
|
||||
|
||||
case ClimateSwingMode::CLIMATE_SWING_VERTICAL:
|
||||
this->set_vertical_louver(ac_louver_V::AC_LOUVERV_SWING_UPDOWN);
|
||||
this->set_horizontal_louver(ac_louver_H::AC_LOUVERH_OFF);
|
||||
break;
|
||||
|
||||
case ClimateSwingMode::CLIMATE_SWING_HORIZONTAL:
|
||||
this->set_vertical_louver(ac_louver_V::AC_LOUVERV_OFF);
|
||||
this->set_horizontal_louver(ac_louver_H::AC_LOUVERH_SWING_LEFTRIGHT);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_target_temperature(float value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
value = Capabilities::normilize_target_temperature(value);
|
||||
_command_frame->set_value(10, (uint8_t)(value - 8), 0b11111000, 3);
|
||||
_command_frame->set_bit(12, 7, (value - (uint8_t)(value) >= 0.5));
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_vertical_louver(ac_louver_V value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
_command_frame->set_value(10, (uint8_t)value, 0b00000111);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_horizontal_louver(ac_louver_H value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
_command_frame->set_value(11, (uint8_t)value, 0b11100000);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_fan_speed(ac_fanspeed value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
_command_frame->set_value(13, (uint8_t)value, 0b11100000);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_fan_turbo(bool value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
_command_frame->set_bit(14, 6, value);
|
||||
if (value)
|
||||
_command_frame->set_bit(14, 7, false); // MUTE off
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_fan_mute(bool value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
_command_frame->set_bit(14, 7, value);
|
||||
if (value)
|
||||
_command_frame->set_bit(14, 6, false); // TURBO off
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_mode(ac_mode value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
_command_frame->set_value(15, (uint8_t)value, 0b11100000);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_fahrenheit_temperature(bool value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
_command_frame->set_bit(15, 1, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_sleep_mode(bool value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
_command_frame->set_bit(15, 2, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_power(bool value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
_command_frame->set_bit(18, 5, value); // power
|
||||
if (value) // iClean should be off in power on mode
|
||||
_command_frame->set_bit(18, 2, false); //
|
||||
else // Health function should be off in power down mode
|
||||
_command_frame->set_bit(18, 1, false); //
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_iClean_mode(bool value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
_command_frame->set_bit(18, 2, value);
|
||||
if (value) // iClean works in power off mode only
|
||||
_command_frame->set_bit(18, 5, false);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_health_mode(bool value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
_command_frame->set_bit(18, 1, value);
|
||||
if (value) // Health function works in power on mode only
|
||||
_command_frame->set_bit(18, 5, true);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_antifungus_mode(bool value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
_command_frame->set_bit(20, 3, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_display_state(bool value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
_command_frame->set_bit(20, 4, value ^ _aircon->get_display_inversion());
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_inverter_power_limitation_state(bool enabled)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
if (!_aircon->ac_type_inverter)
|
||||
return *this;
|
||||
|
||||
_command_frame->set_bit(21, 7, enabled);
|
||||
return *this;
|
||||
}
|
||||
|
||||
CommandBuilder &CommandBuilder::set_inverter_power_limitation_value(uint8_t value)
|
||||
{
|
||||
if (_command_frame->get_body_length() != COMMAND_SET_BODY_LENGTH)
|
||||
return *this;
|
||||
|
||||
if (!_aircon->ac_type_inverter)
|
||||
return *this;
|
||||
|
||||
_command_frame->set_value(21, Capabilities::normilize_inverter_power_limit(value), 0b01111111);
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace aux_airconditioner
|
||||
} // namespace esphome
|
||||
71
components/aux_ac/command_builder.h
Normal file
71
components/aux_ac/command_builder.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "aircon_common.h"
|
||||
#include "helpers.h"
|
||||
#include "esphome.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_airconditioner
|
||||
{
|
||||
|
||||
using esphome::climate::ClimateCall;
|
||||
using esphome::climate::ClimateFanMode;
|
||||
using esphome::climate::ClimateMode;
|
||||
using esphome::climate::ClimatePreset;
|
||||
using esphome::climate::ClimateSwingMode;
|
||||
|
||||
class AirCon;
|
||||
class Frame;
|
||||
|
||||
class CommandBuilder
|
||||
{
|
||||
private:
|
||||
static const uint8_t COMMAND = 0x01;
|
||||
static const uint8_t FLAG = 0x01;
|
||||
static const uint8_t COMMAND_SET_BODY_LENGTH = 0x0F;
|
||||
static const uint8_t COMMAND_REQUEST_BODY_LENGTH = 0x02;
|
||||
|
||||
AirCon *_aircon{nullptr};
|
||||
Frame *_command_frame{nullptr};
|
||||
|
||||
public:
|
||||
CommandBuilder() = delete;
|
||||
CommandBuilder(AirCon &aircon);
|
||||
~CommandBuilder();
|
||||
|
||||
CommandBuilder &init_new_command(command_type_t command_type = COMMAND_TYPE_SET_STATE);
|
||||
CommandBuilder &init_new_command(ClimateCall &cmd);
|
||||
CommandBuilder &fill_frame_with_command(Frame &frame);
|
||||
Frame get_builder_result();
|
||||
|
||||
// ESPHome climate setters (high level)
|
||||
CommandBuilder &set_climate_mode(ClimateMode value);
|
||||
CommandBuilder &set_climate_fan_mode(ClimateFanMode value);
|
||||
CommandBuilder &set_climate_custom_fan_mode(std::string value);
|
||||
CommandBuilder &set_climate_preset(ClimatePreset value);
|
||||
CommandBuilder &set_climate_custom_preset(std::string value);
|
||||
CommandBuilder &set_climate_swing_mode(ClimateSwingMode value);
|
||||
|
||||
// basic setters (low level)
|
||||
CommandBuilder &set_target_temperature(float value);
|
||||
CommandBuilder &set_vertical_louver(ac_louver_V value);
|
||||
CommandBuilder &set_horizontal_louver(ac_louver_H value);
|
||||
CommandBuilder &set_fan_speed(ac_fanspeed value);
|
||||
CommandBuilder &set_fan_turbo(bool value);
|
||||
CommandBuilder &set_fan_mute(bool value);
|
||||
CommandBuilder &set_mode(ac_mode value);
|
||||
CommandBuilder &set_fahrenheit_temperature(bool value);
|
||||
CommandBuilder &set_sleep_mode(bool value);
|
||||
CommandBuilder &set_power(bool value);
|
||||
CommandBuilder &set_iClean_mode(bool value);
|
||||
CommandBuilder &set_health_mode(bool value);
|
||||
CommandBuilder &set_antifungus_mode(bool value);
|
||||
CommandBuilder &set_display_state(bool value);
|
||||
CommandBuilder &set_inverter_power_limitation_state(bool enabled);
|
||||
CommandBuilder &set_inverter_power_limitation_value(uint8_t value);
|
||||
};
|
||||
|
||||
} // namespace aux_airconditioner
|
||||
} // namespace esphome
|
||||
417
components/aux_ac/frame.cpp
Normal file
417
components/aux_ac/frame.cpp
Normal file
@@ -0,0 +1,417 @@
|
||||
#include "frame.h"
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_airconditioner
|
||||
{
|
||||
|
||||
bool Frame::_is_header_loaded() const
|
||||
{
|
||||
return this->size() >= Frame::FRAME_HEADER_SIZE;
|
||||
}
|
||||
|
||||
Frame::crc16_t Frame::_calc_crc(uint8_t data_size) const
|
||||
{
|
||||
Frame::crc16_t crc16;
|
||||
|
||||
uint8_t data_length = data_size;
|
||||
uint8_t corrected_data_length = data_length + (data_length % 2); // data length should be even for crc16
|
||||
|
||||
uint8_t crc_buffer[corrected_data_length];
|
||||
memset(crc_buffer, 0, corrected_data_length);
|
||||
memcpy(crc_buffer, this->data(), data_length);
|
||||
|
||||
data_length = corrected_data_length;
|
||||
|
||||
uint32_t crc_tmp = 0;
|
||||
uint16_t *p_u16 = (uint16_t *)crc_buffer;
|
||||
while (data_length > 0)
|
||||
{
|
||||
crc_tmp += *p_u16;
|
||||
p_u16++;
|
||||
data_length -= 2;
|
||||
}
|
||||
crc_tmp = (crc_tmp >> 16) + (crc_tmp & 0xFFFF);
|
||||
crc_tmp = ~crc_tmp;
|
||||
|
||||
crc16.crc16 = crc_tmp & 0xFFFF;
|
||||
return crc16;
|
||||
}
|
||||
|
||||
FrameState Frame::_set_frame_state(FrameState state)
|
||||
{
|
||||
_state = state;
|
||||
return this->_state;
|
||||
}
|
||||
|
||||
std::string Frame::_dump_data(const uint8_t *data, uint8_t data_length)
|
||||
{
|
||||
if (data == nullptr || data_length == 0)
|
||||
return "";
|
||||
|
||||
uint8_t counter = 0;
|
||||
std::stringstream ss;
|
||||
ss << std::hex << std::uppercase;
|
||||
while (counter < data_length)
|
||||
{
|
||||
ss << std::setfill('0') << std::setw(2) << (int)*data;
|
||||
counter++;
|
||||
data++;
|
||||
if (counter < data_length)
|
||||
ss << " ";
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
FrameType Frame::get_frame_type() const
|
||||
{
|
||||
return (this->_is_header_loaded()) ? (FrameType)this->get_value(Frame::OFFSET_FRAME_TYPE) : (FrameType)0;
|
||||
}
|
||||
|
||||
Frame &Frame::set_frame_type(FrameType frame_type)
|
||||
{
|
||||
if (this->_is_header_loaded())
|
||||
this->set_value(Frame::OFFSET_FRAME_TYPE, frame_type);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint8_t Frame::get_body_length() const
|
||||
{
|
||||
return (this->_is_header_loaded()) ? this->get_value(Frame::OFFSET_BODY_LENGTH) : 0;
|
||||
}
|
||||
|
||||
Frame &Frame::set_body_length(uint8_t body_length)
|
||||
{
|
||||
if (this->_is_header_loaded())
|
||||
this->set_value(Frame::OFFSET_BODY_LENGTH, body_length);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
FrameDirection Frame::get_frame_dir() const
|
||||
{
|
||||
return (this->_is_header_loaded()) ? (FrameDirection)this->get_value(Frame::OFFSET_FRAME_DIRECTION) : (FrameDirection)0;
|
||||
}
|
||||
|
||||
Frame &Frame::set_frame_dir(FrameDirection frame_direction)
|
||||
{
|
||||
if (this->_is_header_loaded())
|
||||
this->set_value(Frame::OFFSET_FRAME_DIRECTION, frame_direction);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Frame &Frame::set_frame_time(uint32_t time)
|
||||
{
|
||||
this->_frame_time = time;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Frame &Frame::clear()
|
||||
{
|
||||
this->_data.clear();
|
||||
this->_frame_time = 0;
|
||||
this->update_frame_state();
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Frame::send(UARTComponent &uart)
|
||||
{
|
||||
uart.write_array(this->data(), this->size());
|
||||
ESP_LOGD(TAG, "%s", this->to_string(true).c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FrameState Frame::load(UARTComponent &uart)
|
||||
{
|
||||
if (!this->has_frame_state(FRAME_STATE_PARTIALLY_LOADED))
|
||||
this->clear();
|
||||
|
||||
if (uart.available() == 0)
|
||||
return this->get_frame_state();
|
||||
|
||||
uint8_t data_byte = 0;
|
||||
if (this->has_frame_state(FRAME_STATE_BLANK))
|
||||
{
|
||||
while (uart.available() &&
|
||||
this->has_frame_state(FRAME_STATE_BLANK))
|
||||
{
|
||||
if (!uart.read_byte(&data_byte))
|
||||
{
|
||||
ESP_LOGW(TAG, "uart read error");
|
||||
break;
|
||||
}
|
||||
|
||||
if (data_byte == this->get_start_byte())
|
||||
{
|
||||
this->append_data(data_byte);
|
||||
this->update_frame_state();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (uart.available() &&
|
||||
this->has_frame_state(FRAME_STATE_PARTIALLY_LOADED))
|
||||
{
|
||||
this->update_frame_state();
|
||||
if (this->has_frame_state(FRAME_STATE_OK))
|
||||
break;
|
||||
|
||||
if (this->has_frame_state(FRAME_STATE_ERROR))
|
||||
{
|
||||
ESP_LOGW(TAG, "Broken frame received: %s", this->to_string(true).c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
if (!uart.read_byte(&data_byte))
|
||||
{
|
||||
ESP_LOGW(TAG, "UART read error");
|
||||
break;
|
||||
}
|
||||
|
||||
this->append_data(data_byte);
|
||||
}
|
||||
|
||||
return this->update_frame_state();
|
||||
}
|
||||
|
||||
Frame &Frame::append_data(uint8_t data, bool update_state)
|
||||
{
|
||||
this->_data.insert(this->_data.end(), data);
|
||||
if (update_state)
|
||||
this->update_frame_state();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Frame &Frame::append_data(const uint8_t data, const uint8_t count, bool update_state)
|
||||
{
|
||||
this->_data.insert(this->_data.end(), count, data);
|
||||
if (update_state)
|
||||
this->update_frame_state();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Frame &Frame::append_data(std::vector<uint8_t> data, bool update_state)
|
||||
{
|
||||
this->_data.insert(this->_data.end(), data.begin(), data.end());
|
||||
if (update_state)
|
||||
this->update_frame_state();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Frame &Frame::append_data(const uint8_t *data, uint8_t data_length, bool update_state)
|
||||
{
|
||||
if (data != nullptr && data_length != 0)
|
||||
std::copy(data, data + data_length, std::back_inserter(this->_data));
|
||||
|
||||
if (update_state)
|
||||
this->update_frame_state();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Frame &Frame::trim_data(uint8_t first_element_index)
|
||||
{
|
||||
if (first_element_index < this->size())
|
||||
{
|
||||
this->_data.erase(this->_data.begin() + first_element_index, this->_data.end());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Frame &Frame::update_crc(bool update_state)
|
||||
{
|
||||
if (!this->_is_header_loaded())
|
||||
return *this;
|
||||
|
||||
uint8_t expected_frame_size = Frame::FRAME_HEADER_SIZE + this->get_body_length() + sizeof(crc16_t);
|
||||
if (this->size() < expected_frame_size - 2 ||
|
||||
this->size() > expected_frame_size)
|
||||
return *this;
|
||||
|
||||
if (this->size() > expected_frame_size)
|
||||
{
|
||||
this->_data.erase(this->_data.begin() + expected_frame_size, this->_data.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
this->_data.insert(this->_data.end(), expected_frame_size - this->size(), 0x00);
|
||||
}
|
||||
|
||||
crc16_t crc = this->_calc_crc(this->size() - sizeof(crc16_t));
|
||||
this->_data.erase(this->_data.end() - 2, this->_data.end());
|
||||
this->_data.insert(this->_data.end(), {crc.crc[0], crc.crc[1]});
|
||||
|
||||
if (update_state)
|
||||
this->update_frame_state();
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Frame::is_valid_crc() const
|
||||
{
|
||||
if (this->size() < 2)
|
||||
return false;
|
||||
|
||||
crc16_t crc;
|
||||
memcpy(&crc, &(this->_data.rbegin()[1]), 2);
|
||||
return this->_calc_crc(this->size() - 2).crc16 == crc.crc16;
|
||||
}
|
||||
|
||||
bool Frame::get_bit(uint8_t data_index, uint8_t bit_index) const
|
||||
{
|
||||
if (bit_index > 7)
|
||||
return false;
|
||||
|
||||
return get_value(data_index, (1 << bit_index)) >> bit_index == 1;
|
||||
}
|
||||
|
||||
Frame &Frame::set_bit(uint8_t data_index, uint8_t bit_index, bool value)
|
||||
{
|
||||
if (bit_index > 7)
|
||||
return *this;
|
||||
|
||||
this->set_value(data_index, (value << bit_index), (1 << bit_index));
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint8_t Frame::get_value(uint8_t index, uint8_t mask, uint8_t shift) const
|
||||
{
|
||||
if (index >= this->size())
|
||||
return 0;
|
||||
|
||||
return (this->_data[index] & mask) >> shift;
|
||||
}
|
||||
|
||||
Frame &Frame::set_value(uint8_t index, uint8_t value, uint8_t mask, uint8_t shift)
|
||||
{
|
||||
if (index >= this->size())
|
||||
return *this;
|
||||
|
||||
this->_data[index] &= ~mask;
|
||||
this->_data[index] |= (value << shift) & mask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Frame::get_crc(uint16_t &crc16) const
|
||||
{
|
||||
if (this->size() < 2)
|
||||
return false;
|
||||
|
||||
memcpy(&crc16, &(this->_data.rbegin()[1]), 2);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Frame::get_crc(uint8_t &crc16_1, uint8_t &crc16_2) const
|
||||
{
|
||||
if (this->size() < 2)
|
||||
return false;
|
||||
|
||||
crc16_1 = this->_data.rbegin()[1];
|
||||
crc16_2 = this->_data.rbegin()[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
FrameState Frame::update_frame_state()
|
||||
{
|
||||
this->_state = FRAME_STATE_ERROR;
|
||||
if (this->size() == 0)
|
||||
return this->_set_frame_state(FRAME_STATE_BLANK);
|
||||
|
||||
if (this->_data[0] != this->get_start_byte())
|
||||
return this->_set_frame_state(FRAME_STATE_ERROR);
|
||||
|
||||
if (this->size() < Frame::FRAME_HEADER_SIZE)
|
||||
return this->_set_frame_state(FRAME_STATE_PARTIALLY_LOADED);
|
||||
|
||||
if (this->size() >= Frame::FRAME_HEADER_SIZE)
|
||||
{
|
||||
if (this->size() < Frame::FRAME_HEADER_SIZE + this->get_body_length() + sizeof(crc16_t))
|
||||
return this->_set_frame_state(FRAME_STATE_PARTIALLY_LOADED);
|
||||
|
||||
if (this->size() > Frame::FRAME_HEADER_SIZE + this->get_body_length() + sizeof(crc16_t))
|
||||
return this->_set_frame_state(FRAME_STATE_ERROR);
|
||||
|
||||
if (this->size() == Frame::FRAME_HEADER_SIZE + this->get_body_length() + sizeof(crc16_t))
|
||||
{
|
||||
return this->_set_frame_state(this->is_valid_crc() ? FRAME_STATE_OK : FRAME_STATE_ERROR);
|
||||
}
|
||||
}
|
||||
return this->_state;
|
||||
}
|
||||
|
||||
std::string Frame::to_string(bool show_time) const
|
||||
{
|
||||
std::stringstream ss;
|
||||
if (show_time)
|
||||
ss << std::setfill('0') << std::setw(10) << _frame_time << ": ";
|
||||
|
||||
if (this->has_frame_state(FRAME_STATE_OK))
|
||||
{
|
||||
ss << this->direction_to_string()
|
||||
<< "[" << _dump_data(this->data(), Frame::FRAME_HEADER_SIZE) << "] "
|
||||
<< _dump_data(this->data() + Frame::FRAME_HEADER_SIZE, this->get_body_length()) << ((this->get_body_length() != 0) ? " " : "")
|
||||
<< "[" << _dump_data(this->data() + Frame::FRAME_HEADER_SIZE + this->get_body_length(), sizeof(crc16_t)) << "]";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "[--] " << _dump_data(this->data(), this->size());
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string Frame::state_to_string() const
|
||||
{
|
||||
switch (this->get_frame_state())
|
||||
{
|
||||
case FRAME_STATE_BLANK:
|
||||
return "blank";
|
||||
|
||||
case FRAME_STATE_ERROR:
|
||||
return "error";
|
||||
|
||||
case FRAME_STATE_PARTIALLY_LOADED:
|
||||
return "partially loaded";
|
||||
|
||||
case FRAME_STATE_OK:
|
||||
return "ok";
|
||||
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::string Frame::type_to_string() const
|
||||
{
|
||||
switch (this->get_frame_type())
|
||||
{
|
||||
case FRAME_TYPE_COMMAND:
|
||||
return "command";
|
||||
|
||||
case FRAME_TYPE_INIT:
|
||||
return "init";
|
||||
|
||||
case FRAME_TYPE_PING:
|
||||
return "ping";
|
||||
|
||||
case FRAME_TYPE_RESPONSE:
|
||||
return "response";
|
||||
|
||||
case FRAME_TYPE_STRANGE:
|
||||
return "strange";
|
||||
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
std::string Frame::direction_to_string() const
|
||||
{
|
||||
return (this->get_frame_dir() == FRAME_DIR_TO_AC) ? "[=>] " : "[<=] ";
|
||||
}
|
||||
|
||||
} // namespace aux_ac
|
||||
} // namespace esphome
|
||||
110
components/aux_ac/frame.h
Normal file
110
components/aux_ac/frame.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <vector>
|
||||
#include "esphome.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "esphome/components/uart/uart_component.h"
|
||||
|
||||
#include "frame_constants.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_airconditioner
|
||||
{
|
||||
|
||||
using esphome::uart::UARTComponent;
|
||||
|
||||
// **************************************************************************************************
|
||||
class Frame
|
||||
{
|
||||
protected:
|
||||
// Frame header params
|
||||
static const uint8_t FRAME_HEADER_SIZE = 8;
|
||||
static const uint8_t OFFSET_START_BYTE = 0;
|
||||
static const uint8_t OFFSET_FRAME_TYPE = 2;
|
||||
static const uint8_t OFFSET_FRAME_DIRECTION = 3;
|
||||
static const uint8_t OFFSET_BODY_LENGTH = 6;
|
||||
|
||||
union crc16_t
|
||||
{
|
||||
uint16_t crc16;
|
||||
uint8_t crc[2];
|
||||
} __attribute__((packed));
|
||||
static_assert(sizeof(crc16_t) == 2);
|
||||
|
||||
uint32_t _frame_time = 0;
|
||||
std::vector<uint8_t> _data = {};
|
||||
FrameState _state = FRAME_STATE_BLANK;
|
||||
static const uint8_t START_BYTE = 0xBB;
|
||||
|
||||
bool _is_header_loaded() const;
|
||||
crc16_t _calc_crc(uint8_t data_size) const;
|
||||
FrameState _set_frame_state(FrameState state);
|
||||
static std::string _dump_data(const uint8_t *data, uint8_t data_length);
|
||||
|
||||
public:
|
||||
Frame() : _frame_time(0){};
|
||||
Frame(uint32_t time) : _frame_time(time){};
|
||||
Frame(uint32_t time, FrameType frame_type, FrameDirection frame_direction)
|
||||
: _frame_time(time),
|
||||
_data({START_BYTE, 0x00, frame_type, frame_direction, 0x00, 0x00, 0x00, 0x00}) { this->update_frame_state(); }
|
||||
Frame(uint32_t time, std::vector<uint8_t> data)
|
||||
: _frame_time(time),
|
||||
_data(data) { this->update_frame_state(); }
|
||||
~Frame() = default;
|
||||
|
||||
static uint8_t get_start_byte() { return Frame::START_BYTE; };
|
||||
|
||||
bool has_type(FrameType frame_type) const { return get_frame_type() == frame_type; };
|
||||
FrameType get_frame_type() const;
|
||||
Frame &set_frame_type(FrameType frame_type);
|
||||
|
||||
uint8_t get_body_length() const;
|
||||
Frame &set_body_length(uint8_t body_length);
|
||||
|
||||
FrameDirection get_frame_dir() const;
|
||||
Frame &set_frame_dir(FrameDirection frame_direction);
|
||||
|
||||
uint32_t get_frame_time() { return this->_frame_time; };
|
||||
Frame &set_frame_time(uint32_t time);
|
||||
|
||||
Frame &clear();
|
||||
|
||||
bool send(UARTComponent &uart);
|
||||
FrameState load(UARTComponent &uart);
|
||||
|
||||
Frame &append_data(uint8_t data, bool update_state = false);
|
||||
Frame &append_data(const uint8_t data, const uint8_t count, bool update_state = false);
|
||||
Frame &append_data(std::vector<uint8_t> data, bool update_state = false);
|
||||
Frame &append_data(const uint8_t *data, uint8_t data_length, bool update_state = false);
|
||||
Frame &trim_data(uint8_t first_element_index);
|
||||
Frame &update_crc(bool update_state = false);
|
||||
bool is_valid_crc() const;
|
||||
bool is_valid_frame() const { return this->has_frame_state(FRAME_STATE_OK); };
|
||||
|
||||
bool get_bit(uint8_t data_index, uint8_t bit_index) const;
|
||||
Frame &set_bit(uint8_t data_index, uint8_t bit_index, bool value);
|
||||
uint8_t get_value(uint8_t index, uint8_t mask = 255, uint8_t shift = 0) const;
|
||||
Frame &set_value(uint8_t index, uint8_t value, uint8_t mask = 255, uint8_t shift = 0);
|
||||
|
||||
bool get_crc(uint16_t &crc16) const;
|
||||
bool get_crc(uint8_t &crc16_1, uint8_t &crc16_2) const;
|
||||
|
||||
FrameState get_frame_state() const { return this->_state; };
|
||||
bool has_frame_state(FrameState frame_state) const { return this->get_frame_state() == frame_state; };
|
||||
FrameState update_frame_state();
|
||||
|
||||
const uint8_t *data() const { return this->_data.data(); };
|
||||
uint8_t size() const { return this->_data.size(); };
|
||||
|
||||
std::string to_string(bool show_time = false) const;
|
||||
std::string state_to_string() const;
|
||||
std::string type_to_string() const;
|
||||
std::string direction_to_string() const;
|
||||
};
|
||||
|
||||
} // namespace aux_ac
|
||||
} // namespace esphome
|
||||
34
components/aux_ac/frame_constants.h
Normal file
34
components/aux_ac/frame_constants.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_airconditioner
|
||||
{
|
||||
|
||||
enum FrameType : uint8_t
|
||||
{
|
||||
FRAME_TYPE_PING = 0x01,
|
||||
FRAME_TYPE_COMMAND = 0x06,
|
||||
FRAME_TYPE_RESPONSE = 0x07,
|
||||
FRAME_TYPE_INIT = 0x09,
|
||||
FRAME_TYPE_STRANGE = 0x0b,
|
||||
};
|
||||
|
||||
enum FrameDirection : uint8_t
|
||||
{
|
||||
FRAME_DIR_TO_DONGLE = 0x00,
|
||||
FRAME_DIR_TO_AC = 0x80,
|
||||
};
|
||||
|
||||
enum FrameState : uint8_t
|
||||
{
|
||||
FRAME_STATE_BLANK = 0x00,
|
||||
FRAME_STATE_PARTIALLY_LOADED = 0x01,
|
||||
FRAME_STATE_OK = 0x0F,
|
||||
FRAME_STATE_ERROR = 0xFF,
|
||||
};
|
||||
|
||||
} // namespace aux_airconditioner
|
||||
} // namespace esphome
|
||||
371
components/aux_ac/frame_processor.cpp
Normal file
371
components/aux_ac/frame_processor.cpp
Normal file
@@ -0,0 +1,371 @@
|
||||
#include "frame_processor.h"
|
||||
|
||||
#include "frame.h"
|
||||
#include "helpers.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_airconditioner
|
||||
{
|
||||
|
||||
using esphome::helpers::update_property;
|
||||
|
||||
/*********************************************************************************************\
|
||||
\*********************************************************************************************/
|
||||
void FrameProcessorInterface::process(const Frame &frame, AirCon &aircon) const
|
||||
{
|
||||
if (!this->applicable(frame))
|
||||
return;
|
||||
|
||||
if (!aircon.is_hardware_connected())
|
||||
return;
|
||||
|
||||
aircon.reset_ping_timeout();
|
||||
this->_specific_process(frame, aircon);
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
\*********************************************************************************************/
|
||||
bool FrameProcessorPing::applicable(const Frame &frame) const
|
||||
{
|
||||
return frame.has_type(FrameType::FRAME_TYPE_PING);
|
||||
}
|
||||
|
||||
FrameType FrameProcessorPing::get_applicable_frame_type() const
|
||||
{
|
||||
return FrameType::FRAME_TYPE_PING;
|
||||
}
|
||||
|
||||
void FrameProcessorPing::_specific_process(const Frame &frame, AirCon &aircon) const
|
||||
{
|
||||
aircon.schedule_ping_response();
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
\*********************************************************************************************/
|
||||
bool FrameProcessorResponse01::applicable(const Frame &frame) const
|
||||
{
|
||||
return frame.has_type(FrameType::FRAME_TYPE_RESPONSE) &&
|
||||
frame.get_body_length() == 0x04 &&
|
||||
frame.get_value(9) == 0x01;
|
||||
}
|
||||
|
||||
FrameType FrameProcessorResponse01::get_applicable_frame_type() const
|
||||
{
|
||||
return FrameType::FRAME_TYPE_RESPONSE;
|
||||
}
|
||||
|
||||
void FrameProcessorResponse01::_specific_process(const Frame &frame, AirCon &aircon) const
|
||||
{
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
\*********************************************************************************************/
|
||||
ClimateMode FrameProcessorResponse11::_power_and_mode_to_climate_mode(bool power_on, ac_mode mode) const
|
||||
{
|
||||
ClimateMode result = ClimateMode::CLIMATE_MODE_OFF;
|
||||
if (power_on)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case AC_MODE_AUTO:
|
||||
result = ClimateMode::CLIMATE_MODE_HEAT_COOL;
|
||||
break;
|
||||
|
||||
case AC_MODE_COOL:
|
||||
result = ClimateMode::CLIMATE_MODE_COOL;
|
||||
break;
|
||||
|
||||
case AC_MODE_DRY:
|
||||
result = ClimateMode::CLIMATE_MODE_DRY;
|
||||
break;
|
||||
|
||||
case AC_MODE_HEAT:
|
||||
result = ClimateMode::CLIMATE_MODE_HEAT;
|
||||
break;
|
||||
|
||||
case AC_MODE_FAN:
|
||||
result = ClimateMode::CLIMATE_MODE_FAN_ONLY;
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGW(TAG, "Warning: unknown air conditioner mode: 0x%02X", mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool FrameProcessorResponse11::applicable(const Frame &frame) const
|
||||
{
|
||||
return frame.has_type(FrameType::FRAME_TYPE_RESPONSE) &&
|
||||
frame.get_body_length() == 0x0F &&
|
||||
frame.get_value(9) == 0x11;
|
||||
}
|
||||
|
||||
FrameType FrameProcessorResponse11::get_applicable_frame_type() const
|
||||
{
|
||||
return FrameType::FRAME_TYPE_RESPONSE;
|
||||
}
|
||||
|
||||
void FrameProcessorResponse11::_specific_process(const Frame &frame, AirCon &aircon) const
|
||||
{
|
||||
aircon.set_last_frame(frame);
|
||||
|
||||
bool state_changed = false;
|
||||
|
||||
// target temperature:
|
||||
// byte 10: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b10
|
||||
// byte 12: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b12
|
||||
update_property(aircon.target_temperature, (float)(8.0 + (float)frame.get_value(10, 0b11111000, 3) + (frame.get_bit(12, 7) ? 0.5 : 0.0)), state_changed);
|
||||
|
||||
// vertical louver state:
|
||||
// byte 10: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b10
|
||||
// horizontal louver state:
|
||||
// byte 11: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b11
|
||||
update_property(aircon.louver_vertical, (ac_louver_V)frame.get_value(10, 0b00000111), state_changed);
|
||||
update_property(aircon.louver_horizontal, (ac_louver_H)frame.get_value(11, 0b11100000), state_changed);
|
||||
if (aircon.louver_vertical == AC_LOUVERV_SWING_UPDOWN && aircon.louver_horizontal != AC_LOUVERH_SWING_LEFTRIGHT)
|
||||
update_property(aircon.swing_mode, ClimateSwingMode::CLIMATE_SWING_VERTICAL, state_changed);
|
||||
else if (aircon.louver_vertical != AC_LOUVERV_SWING_UPDOWN && aircon.louver_horizontal == AC_LOUVERH_SWING_LEFTRIGHT)
|
||||
update_property(aircon.swing_mode, ClimateSwingMode::CLIMATE_SWING_HORIZONTAL, state_changed);
|
||||
else if (aircon.louver_vertical == AC_LOUVERV_SWING_UPDOWN && aircon.louver_horizontal == AC_LOUVERH_SWING_LEFTRIGHT)
|
||||
update_property(aircon.swing_mode, ClimateSwingMode::CLIMATE_SWING_BOTH, state_changed);
|
||||
else if (aircon.louver_vertical != AC_LOUVERV_SWING_UPDOWN && aircon.louver_horizontal != AC_LOUVERH_SWING_LEFTRIGHT)
|
||||
update_property(aircon.swing_mode, ClimateSwingMode::CLIMATE_SWING_OFF, state_changed);
|
||||
|
||||
// last IR-command was this time ago (minutes)
|
||||
// byte 12: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b12
|
||||
update_property(aircon.last_IR_passed, frame.get_value(12, 0b00111111), state_changed);
|
||||
|
||||
// fan speed:
|
||||
// byte 13: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b13
|
||||
update_property(aircon.fan_mode, ac_fanspeed_to_climate_fan_mode((ac_fanspeed)frame.get_value(13, 0b11100000)), state_changed);
|
||||
|
||||
// timer activation & delay:
|
||||
// byte 13: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b13
|
||||
// byte 14: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b14
|
||||
// byte 18: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b18
|
||||
// timer.delay_minutes_uint16 = frame.get_value(13, 0b00011111) * 60 + frame.get_value(14, 0b00011111);
|
||||
// timer.enabled_bool = frame.get_bit(18, 6);
|
||||
// update_property(aircon.???, ???, state_changed);
|
||||
|
||||
// fan TURBO mode:
|
||||
// byte 14: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b14
|
||||
if (frame.get_bit(14, 6))
|
||||
{
|
||||
update_property(aircon.custom_fan_mode, Capabilities::CUSTOM_FAN_MODE_TURBO, state_changed);
|
||||
}
|
||||
else if (aircon.custom_fan_mode == Capabilities::CUSTOM_FAN_MODE_TURBO)
|
||||
{
|
||||
update_property(aircon.custom_fan_mode, (std::string) "", state_changed);
|
||||
}
|
||||
|
||||
// fan MUTE mode:
|
||||
// byte 14: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b14
|
||||
if (frame.get_bit(14, 7))
|
||||
{
|
||||
update_property(aircon.custom_fan_mode, Capabilities::CUSTOM_FAN_MODE_MUTE, state_changed);
|
||||
}
|
||||
else if (aircon.custom_fan_mode == Capabilities::CUSTOM_FAN_MODE_MUTE)
|
||||
{
|
||||
update_property(aircon.custom_fan_mode, (std::string) "", state_changed);
|
||||
}
|
||||
|
||||
// power & mode:
|
||||
// byte 15: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b15
|
||||
// byte 18: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b18
|
||||
update_property(aircon.mode, _power_and_mode_to_climate_mode(frame.get_bit(18, 5), (ac_mode)frame.get_value(15, 0b11100000)), state_changed);
|
||||
|
||||
// temperature: Celsius or Fahrenheit
|
||||
// byte 15: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b15
|
||||
if (update_property(aircon.temperature_in_fahrenheit, frame.get_bit(15, 1), state_changed))
|
||||
aircon.update_all_sensors_unit_of_measurement();
|
||||
|
||||
// SLEEP preset:
|
||||
// byte 15: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b15
|
||||
if (frame.get_bit(15, 2))
|
||||
{
|
||||
update_property(aircon.preset, ClimatePreset::CLIMATE_PRESET_SLEEP, state_changed);
|
||||
}
|
||||
else if (aircon.preset == ClimatePreset::CLIMATE_PRESET_SLEEP)
|
||||
{
|
||||
update_property(aircon.preset, ClimatePreset::CLIMATE_PRESET_NONE, state_changed);
|
||||
}
|
||||
|
||||
// iFeel function: disabled due to uselessness (and doesn't work with wi-fi)
|
||||
// byte 15: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b15
|
||||
// update_property(aircon.iFeel, frame.get_bit(15, 3), state_changed);
|
||||
|
||||
// iClean function:
|
||||
// byte 18: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b18
|
||||
if (frame.get_bit(18, 2) && !frame.get_bit(18, 5)) // iClean + Power_Off
|
||||
{
|
||||
update_property(aircon.custom_preset, Capabilities::CUSTOM_PRESET_CLEAN, state_changed);
|
||||
}
|
||||
else if (aircon.custom_preset == Capabilities::CUSTOM_PRESET_CLEAN)
|
||||
{
|
||||
update_property(aircon.custom_preset, (std::string) "", state_changed);
|
||||
}
|
||||
|
||||
// Health function:
|
||||
// byte 18: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b18
|
||||
if (frame.get_bit(18, 1) && frame.get_bit(18, 5)) // Health + Power_On
|
||||
{
|
||||
update_property(aircon.custom_preset, Capabilities::CUSTOM_PRESET_HEALTH, state_changed);
|
||||
}
|
||||
else if (aircon.custom_preset == Capabilities::CUSTOM_PRESET_HEALTH)
|
||||
{
|
||||
update_property(aircon.custom_preset, (std::string) "", state_changed);
|
||||
}
|
||||
|
||||
// Antifungus function:
|
||||
// byte 20: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b20
|
||||
if (frame.get_bit(20, 3))
|
||||
{
|
||||
update_property(aircon.custom_preset, Capabilities::CUSTOM_PRESET_ANTIFUNGUS, state_changed);
|
||||
}
|
||||
else if (aircon.custom_preset == Capabilities::CUSTOM_PRESET_ANTIFUNGUS)
|
||||
{
|
||||
update_property(aircon.custom_preset, (std::string) "", state_changed);
|
||||
}
|
||||
|
||||
// Display:
|
||||
// byte 20: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b20
|
||||
update_property(aircon.display_enabled, (bool)(frame.get_bit(20, 4) ^ aircon.get_display_inversion()), state_changed);
|
||||
|
||||
// Power limitation for inverters:
|
||||
// byte 21: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_11_b21
|
||||
if (aircon.ac_type_inverter)
|
||||
{
|
||||
update_property(aircon.inverter_power_limitation_value, frame.get_value(21, 0b01111111), state_changed);
|
||||
update_property(aircon.inverter_power_limitation_on, frame.get_bit(21, 7), state_changed);
|
||||
}
|
||||
else
|
||||
{
|
||||
aircon.inverter_power_limitation_value.reset();
|
||||
aircon.inverter_power_limitation_on.reset();
|
||||
}
|
||||
|
||||
if (state_changed)
|
||||
{
|
||||
aircon.publish_all_states();
|
||||
}
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
\*********************************************************************************************/
|
||||
bool FrameProcessorResponse2x::applicable(const Frame &frame) const
|
||||
{
|
||||
return frame.has_type(FrameType::FRAME_TYPE_RESPONSE) &&
|
||||
frame.get_body_length() == 0x18 &&
|
||||
frame.get_value(9, 0b11110000) == 0x20;
|
||||
}
|
||||
|
||||
FrameType FrameProcessorResponse2x::get_applicable_frame_type() const
|
||||
{
|
||||
return FrameType::FRAME_TYPE_RESPONSE;
|
||||
}
|
||||
|
||||
void FrameProcessorResponse2x::_specific_process(const Frame &frame, AirCon &aircon) const
|
||||
{
|
||||
aircon.set_last_frame(frame);
|
||||
|
||||
bool state_changed = false;
|
||||
|
||||
// TODO: doublecheck the temperature bytes. Probably here is a mess...
|
||||
|
||||
// air conditioner type:
|
||||
// byte 10: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b10
|
||||
update_property(aircon.ac_type_inverter, frame.get_bit(10, 5), state_changed);
|
||||
|
||||
// byte 11: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b11
|
||||
|
||||
// iClean + defrost
|
||||
// byte 12: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b12
|
||||
update_property(aircon.defrost_enabled, frame.get_bit(12, 5), state_changed);
|
||||
|
||||
// real FAN speed
|
||||
// byte 13: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b13
|
||||
update_property(aircon.real_fan_speed, (ac_fanspeed_real)frame.get_value(13, 0b00000111), state_changed);
|
||||
|
||||
// byte 14: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b14
|
||||
|
||||
// ambient indoor temperature:
|
||||
// byte 15: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b15
|
||||
// byte 31: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b31
|
||||
update_property(aircon.current_temperature, (float)(frame.get_value(31, 0b00001111) / 10.0 + frame.get_value(15) - 0x20), state_changed);
|
||||
|
||||
// byte 16: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b16
|
||||
|
||||
// indoor coil temperature:
|
||||
// byte 17: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b17
|
||||
if (frame.get_value(17) >= 0x20)
|
||||
update_property(aircon.temperature_indoor_coil, (uint8_t)(frame.get_value(17) - 0x20), state_changed);
|
||||
else
|
||||
aircon.temperature_indoor_coil.reset();
|
||||
|
||||
// byte 18: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b18
|
||||
// TODO: maybe this is a mess
|
||||
if (frame.get_value(18) >= 0x20)
|
||||
update_property(aircon.temperature_outdoor_ambient, (uint8_t)(frame.get_value(18) - 0x20), state_changed);
|
||||
else
|
||||
aircon.temperature_outdoor_ambient.reset();
|
||||
|
||||
// byte 19: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b19
|
||||
|
||||
// condenser middle temperature sensor:
|
||||
// byte 20: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b20
|
||||
if (frame.get_value(20) >= 0x20)
|
||||
update_property(aircon.temperature_condenser_middle, (uint8_t)(frame.get_value(20) - 0x20), state_changed);
|
||||
else
|
||||
aircon.temperature_condenser_middle.reset();
|
||||
|
||||
// temperature sensor #2 "PIPE"?:
|
||||
// byte 21: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b21
|
||||
// This byte is equal to 0x20 for inverters without this sensor.
|
||||
// This byte is equal to 0x00 for on-off air conditioners.
|
||||
if (frame.get_value(21) >= 0x20)
|
||||
update_property(aircon.temperature_outdoor_suction, (uint8_t)(frame.get_value(21) - 0x20), state_changed);
|
||||
else
|
||||
aircon.temperature_outdoor_suction.reset();
|
||||
|
||||
// compressor temperature:
|
||||
// byte 22: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b22
|
||||
if (frame.get_value(22, 0b01111111) >= 0x20)
|
||||
update_property(aircon.temperature_outdoor_discharge, (uint8_t)(frame.get_value(22, 0b01111111) - 0x20), state_changed);
|
||||
else
|
||||
aircon.temperature_outdoor_discharge.reset();
|
||||
|
||||
// byte 23: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b23
|
||||
// TODO: maybe this is a mess
|
||||
if (frame.get_value(23) >= 0x20)
|
||||
update_property(aircon.temperature_outdoor_defrost, (uint8_t)(frame.get_value(23) - 0x20), state_changed);
|
||||
else
|
||||
aircon.temperature_outdoor_defrost.reset();
|
||||
|
||||
// inverter power (0..100 %)
|
||||
// byte 24: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b24
|
||||
if (aircon.ac_type_inverter)
|
||||
update_property(aircon.inverter_power, frame.get_value(24, 0b01111111), state_changed);
|
||||
else
|
||||
aircon.inverter_power.reset();
|
||||
|
||||
// byte 25: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b25
|
||||
// byte 26: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b26
|
||||
// byte 27: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b27
|
||||
// byte 28: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b28
|
||||
// byte 29: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b29
|
||||
// byte 30: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b30
|
||||
|
||||
// ambient temperature fractional part (see byte 15)
|
||||
// byte 31: https://github.com/GrKoR/AUX_HVAC_Protocol#packet_cmd_21_b31
|
||||
|
||||
if (state_changed)
|
||||
aircon.publish_all_states();
|
||||
}
|
||||
|
||||
} // namespace aux_airconditioner
|
||||
} // namespace esphome
|
||||
87
components/aux_ac/frame_processor.h
Normal file
87
components/aux_ac/frame_processor.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <list>
|
||||
#include "esphome.h"
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#include "aircon_common.h"
|
||||
#include "frame_constants.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_airconditioner
|
||||
{
|
||||
|
||||
using esphome::climate::ClimateAction;
|
||||
using esphome::climate::ClimateFanMode;
|
||||
using esphome::climate::ClimateMode;
|
||||
using esphome::climate::ClimatePreset;
|
||||
using esphome::climate::ClimateSwingMode;
|
||||
|
||||
class AirCon;
|
||||
class Frame;
|
||||
|
||||
//*********************************************************************************************
|
||||
class FrameProcessorInterface
|
||||
{
|
||||
protected:
|
||||
virtual void _specific_process(const Frame &frame, AirCon &aircon) const = 0;
|
||||
|
||||
public:
|
||||
virtual ~FrameProcessorInterface() = default;
|
||||
virtual bool applicable(const Frame &frame) const = 0;
|
||||
virtual FrameType get_applicable_frame_type() const = 0;
|
||||
void process(const Frame &frame, AirCon &aircon) const;
|
||||
};
|
||||
|
||||
//*********************************************************************************************
|
||||
class FrameProcessorPing : public FrameProcessorInterface
|
||||
{
|
||||
protected:
|
||||
void _specific_process(const Frame &frame, AirCon &aircon) const override;
|
||||
|
||||
public:
|
||||
FrameProcessorPing() = default;
|
||||
bool applicable(const Frame &frame) const override;
|
||||
FrameType get_applicable_frame_type() const override;
|
||||
};
|
||||
|
||||
//*********************************************************************************************
|
||||
class FrameProcessorResponse01 : public FrameProcessorInterface
|
||||
{
|
||||
protected:
|
||||
void _specific_process(const Frame &frame, AirCon &aircon) const override;
|
||||
|
||||
public:
|
||||
FrameProcessorResponse01() = default;
|
||||
bool applicable(const Frame &frame) const override;
|
||||
FrameType get_applicable_frame_type() const override;
|
||||
};
|
||||
|
||||
//*********************************************************************************************
|
||||
class FrameProcessorResponse11 : public FrameProcessorInterface
|
||||
{
|
||||
protected:
|
||||
ClimateMode _power_and_mode_to_climate_mode(bool power_on, ac_mode mode) const;
|
||||
void _specific_process(const Frame &frame, AirCon &aircon) const override;
|
||||
|
||||
public:
|
||||
FrameProcessorResponse11() = default;
|
||||
bool applicable(const Frame &frame) const override;
|
||||
FrameType get_applicable_frame_type() const override;
|
||||
};
|
||||
|
||||
//*********************************************************************************************
|
||||
class FrameProcessorResponse2x : public FrameProcessorInterface
|
||||
{
|
||||
protected:
|
||||
void _specific_process(const Frame &frame, AirCon &aircon) const override;
|
||||
|
||||
public:
|
||||
FrameProcessorResponse2x() = default;
|
||||
bool applicable(const Frame &frame) const override;
|
||||
FrameType get_applicable_frame_type() const override;
|
||||
};
|
||||
|
||||
} // namespace aux_airconditioner
|
||||
} // namespace esphome
|
||||
74
components/aux_ac/frame_processor_manager.cpp
Normal file
74
components/aux_ac/frame_processor_manager.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "frame_processor_manager.h"
|
||||
#include "frame.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_airconditioner
|
||||
{
|
||||
|
||||
void FrameProcessorManager::_update_map()
|
||||
{
|
||||
_processor_map.clear();
|
||||
for (FrameProcessorInterface *processor : _processors)
|
||||
{
|
||||
auto it = std::find(_processor_map[processor->get_applicable_frame_type()].begin(), _processor_map[processor->get_applicable_frame_type()].end(), processor);
|
||||
if (it == _processor_map[processor->get_applicable_frame_type()].end())
|
||||
_processor_map[processor->get_applicable_frame_type()].push_back(processor);
|
||||
}
|
||||
}
|
||||
|
||||
FrameProcessorManager::FrameProcessorManager()
|
||||
{
|
||||
_processors.clear();
|
||||
_processors.push_back(new FrameProcessorPing);
|
||||
_processors.push_back(new FrameProcessorResponse01);
|
||||
_processors.push_back(new FrameProcessorResponse11);
|
||||
_processors.push_back(new FrameProcessorResponse2x);
|
||||
|
||||
this->_update_map();
|
||||
}
|
||||
|
||||
void FrameProcessorManager::add_frame_processor(FrameProcessorInterface *frame_processor)
|
||||
{
|
||||
if (frame_processor == nullptr)
|
||||
return;
|
||||
|
||||
_processors.push_back(frame_processor);
|
||||
this->_update_map();
|
||||
}
|
||||
|
||||
void FrameProcessorManager::delete_all_processors()
|
||||
{
|
||||
while (!_processors.empty())
|
||||
{
|
||||
delete _processors.front();
|
||||
_processors.pop_front();
|
||||
}
|
||||
_processor_map.clear();
|
||||
}
|
||||
|
||||
void FrameProcessorManager::process_frame(Frame &frame)
|
||||
{
|
||||
auto processor_it = _processor_map.find(frame.get_frame_type());
|
||||
if (processor_it == _processor_map.end())
|
||||
{
|
||||
ESP_LOGW(TAG, "No processor for frame type 0x%02X (%s). Frame: %s", frame.get_frame_type(), frame.type_to_string().c_str(), frame.to_string().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// check if list of processors is empty
|
||||
if (processor_it->second.size() == 0)
|
||||
return;
|
||||
|
||||
for (FrameProcessorInterface *processor : processor_it->second)
|
||||
{
|
||||
if (processor->applicable(frame))
|
||||
{
|
||||
processor->process(frame, *_aircon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aux_airconditioner
|
||||
} // namespace esphome
|
||||
34
components/aux_ac/frame_processor_manager.h
Normal file
34
components/aux_ac/frame_processor_manager.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "frame_processor.h"
|
||||
#include <map>
|
||||
#include <list>
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace aux_airconditioner
|
||||
{
|
||||
class AirCon;
|
||||
class FrameProcessorInterface;
|
||||
class Frame;
|
||||
|
||||
class FrameProcessorManager
|
||||
{
|
||||
protected:
|
||||
AirCon *_aircon = nullptr;
|
||||
std::map<uint8_t, std::list<FrameProcessorInterface *>> _processor_map;
|
||||
std::list<FrameProcessorInterface *> _processors;
|
||||
void _update_map();
|
||||
|
||||
public:
|
||||
FrameProcessorManager();
|
||||
~FrameProcessorManager() { this->delete_all_processors(); }
|
||||
|
||||
void set_aircon(AirCon &aircon) { _aircon = &aircon; }
|
||||
void add_frame_processor(FrameProcessorInterface *frame_processor);
|
||||
void delete_all_processors();
|
||||
void process_frame(Frame &frame);
|
||||
};
|
||||
|
||||
} // namespace aux_airconditioner
|
||||
} // namespace esphome
|
||||
12
components/aux_ac/helpers.cpp
Normal file
12
components/aux_ac/helpers.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include "helpers.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace helpers
|
||||
{
|
||||
|
||||
uint32_t TimerManager::_millis = 0;
|
||||
|
||||
} // namespace helpers
|
||||
|
||||
} // namespace GrKoR
|
||||
117
components/aux_ac/helpers.h
Normal file
117
components/aux_ac/helpers.h
Normal file
@@ -0,0 +1,117 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <functional>
|
||||
#include <list>
|
||||
#include "esphome/core/optional.h"
|
||||
|
||||
namespace esphome
|
||||
{
|
||||
namespace helpers
|
||||
{
|
||||
|
||||
/*************************************************************************************************\
|
||||
\*************************************************************************************************/
|
||||
class TimerInterface
|
||||
{
|
||||
public:
|
||||
virtual bool is_expired() const = 0;
|
||||
virtual bool is_enabled() const = 0;
|
||||
virtual void start(uint32_t period_ms) = 0;
|
||||
virtual void stop() = 0;
|
||||
virtual void reset() = 0;
|
||||
virtual void set_callback(std::function<void(TimerInterface *)> callback) = 0;
|
||||
virtual void trigger_callback() = 0;
|
||||
};
|
||||
|
||||
/*************************************************************************************************\
|
||||
\*************************************************************************************************/
|
||||
using millis_function_t = uint32_t (*)();
|
||||
|
||||
class TimerManager
|
||||
{
|
||||
public:
|
||||
static void set_millis(uint32_t current_time) { TimerManager::_millis = current_time; }
|
||||
static uint32_t get_millis() { return TimerManager::_millis; }
|
||||
|
||||
void set_millis_func(millis_function_t millis) { _millis_func = millis; }
|
||||
|
||||
void register_timer(TimerInterface &timer) { _timers.push_back(&timer); }
|
||||
|
||||
void task()
|
||||
{
|
||||
if (_millis_func != nullptr)
|
||||
_millis = _millis_func();
|
||||
|
||||
for (auto timer : _timers)
|
||||
if (timer->is_enabled() && timer->is_expired())
|
||||
timer->trigger_callback();
|
||||
}
|
||||
|
||||
private:
|
||||
millis_function_t _millis_func{nullptr};
|
||||
static uint32_t _millis;
|
||||
std::list<TimerInterface *> _timers;
|
||||
};
|
||||
|
||||
/*************************************************************************************************\
|
||||
\*************************************************************************************************/
|
||||
static void dummy_stopper(TimerInterface *timer) { timer->stop(); }
|
||||
|
||||
class Timer : public TimerInterface
|
||||
{
|
||||
public:
|
||||
Timer() : _callback(dummy_stopper), _period_ms(0) {}
|
||||
|
||||
virtual bool is_expired() const override { return TimerManager::get_millis() - this->_last_trigger_time >= this->_period_ms; }
|
||||
virtual bool is_enabled() const override { return this->_period_ms > 0; }
|
||||
|
||||
virtual void start(uint32_t period_ms) override
|
||||
{
|
||||
this->_period_ms = period_ms;
|
||||
this->reset();
|
||||
}
|
||||
virtual void stop() override { this->_period_ms = 0; }
|
||||
virtual void reset() override { this->_last_trigger_time = TimerManager::get_millis(); }
|
||||
|
||||
virtual void set_callback(std::function<void(TimerInterface *)> callback) override { this->_callback = callback; }
|
||||
virtual void trigger_callback() override
|
||||
{
|
||||
this->_callback((TimerInterface *)this);
|
||||
this->reset();
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(TimerInterface *)> _callback = nullptr;
|
||||
uint32_t _period_ms;
|
||||
uint32_t _last_trigger_time;
|
||||
};
|
||||
|
||||
/*********************************************************************************************\
|
||||
\*********************************************************************************************/
|
||||
template <typename T>
|
||||
bool update_property(T &property, const T &value, bool &flag)
|
||||
{
|
||||
if (property != value)
|
||||
{
|
||||
property = value;
|
||||
flag = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool update_property(optional<T> &property, const T &value, bool &flag)
|
||||
{
|
||||
if (property != value)
|
||||
{
|
||||
property = value;
|
||||
flag = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace helpers
|
||||
} // namespace esphome
|
||||
@@ -2,24 +2,31 @@
|
||||
`Aux_ac` has been tested and works successfully with the air conditioners from the list below.<br/>
|
||||
Кондиционеры из списка ниже протестированы и точно совместимы с `aux_ac`.
|
||||
|
||||
+ AUX (models: ASW-H09A4/LK-700R1, ASW-H09B4/LK-700R1, ASW-H09A4/LK-700R1DI, AMWM-xxx multisplit, AL-H48/5DR2(U)/ALMD-H48/5DR2, ASW-H07A4/FP-R1DI, ASW-H09A4/FP-R1DI, ASW-H07A4/DE-R1DI, KFR-26GW/BpHRB+3)
|
||||
+ Ballu (models: BSUI-12HN8, BLC_CF/in-60HN1)
|
||||
+ Centek (models: CT-65Q09, CT-65Q12, CT-65Z10, CT-65Z18, CT-65A12)
|
||||
+ Argo (models: Greenstyle 9000, Greenstyle 12000, Greenstyle 18000)
|
||||
+ AUX (models: ALMD-H48/5DR2 / AL-H48/5DR2(U), AMWM-H07/4R1 multisplit, ASM-H12LL, ASM-H24LD, ASW-H07A4/DE-R1DI, ASW-H07A4/FP-R1DI, ASW-H09A4/FP-R1DI, ASW-H12A4/HA-R2DI, ASW-H09A4/LK-700R1, ASW-H09A4/LK-700R1DI, ASW-H12B4/JD-R2DI, ASW-H12A4/FAR1, ASW-H09B4/LK-700R1, ASW-H12C5C4/JOR3DI-B8, ASW-H12U3/JIR1DI-US, AUX-18QC/I / AUX-18QC/O, AWM-09G1V4-X, KFR-26GW/BpHRB+3, KFR-26GW/BpQYA2+2R3, KFR-26GW/BpQYD2+2R3, KFR-26GW/BpR3QYA1+1, KFR-26GW/BpR3QYD1+1, KFR-26GW/BpR3QYQ1+1, KFR-26GW/BpR3QYQ2+2, KFR-35GW/BpQYA1+1R3, KFR-35GW/BpQYA2+2R3, KFR-35GW/BpQYD1+1R3, KFR-35GW/BpQYD2+2R3, KFR-35GW/BpR3QYQ1+1, KFR-35GW/BpR3QYQ2+2 (see [issue #71](https://github.com/GrKoR/esphome_aux_ac_component/issues/71) for detais of `Aegean Sea`[爱琴海] AUX family AC connection)
|
||||
+ Ballu (models: BLC_CF-60HN1, BSUI-09HN8, BSUI-12HN8, BSUI-18HN8, BSW-09HN1, BSW-12HN1)
|
||||
+ Centek (models: CT-65A12, CT-65F09, CT-65F12, CT-65Q09, CT-65Q12, CT-65U13, CT-65Z10, CT-65Z18, CT-65V24)
|
||||
+ Dimstal (model: SMND-QC-12-J-Smart ECO)
|
||||
+ Elgin (models: HWFI09B2IA/ HWFE09B2NA)
|
||||
+ Energolux (models: SAS07L4-A, SAS07M2-AI, SAS09Z4-AI, SASxxBN1-AI)
|
||||
+ Energolux (models: SAS07L2-A, SAS07L4-A, SAS07M2-AI, SAS09B3-A, SAS09L4-A, SAS09Z4-AI, SAS12BN1-AI, SAS12CH1-AI, SAS12Z3-AI)
|
||||
+ Hyundai (models: H-AR16-07H, H-AR21-07H, H-AR21-09H)
|
||||
+ iClima (models: ICI-09A/IUI-09A)
|
||||
+ Idea (models: ISR-12HR-SA7-DN1 ION)
|
||||
+ IGC (models: RAK-07NH multysplit)
|
||||
+ IGC (models: RAK-07NH multysplit, RAS-07AX/RAC-07AX)
|
||||
+ Ishimatsu (models: AVK-09I)
|
||||
+ Loriot (models: LAC-09AS)
|
||||
+ RCool (models: GRA12B0-KSZKLM641)
|
||||
+ Roda (models: RS-AL09F)
|
||||
+ Roda (models: RS-AL09F, RS-AL24F)
|
||||
+ Rovex (models: RS-07ALS1, RS-09ALS1, RS-12ALS1)
|
||||
+ Royal Clima (models: CO-D 18HNI/CO-E 18HNI, RCI-SA30HN)
|
||||
+ Samurai (models: SMA-07HRN1 ION, SMA-09HRN1 ION)
|
||||
+ Subtropic (models: SUB-07HN1, SUB-12HN1)
|
||||
+ Subtropic (models: SUB-07HN1, SUB-09HN1, SUB-12HN1)
|
||||
+ Tesla (models: TA35FFML-12410M)
|
||||
+ TOYOTOMI (models: SONZAI THN/THG-A35SZ)
|
||||
+ VOX (models: IVA5-12JR1, IJO09-SC4D)
|
||||
+ Xigma (models: XG-SJ56RHA-IDU)
|
||||
+ Zephir (models: ZEL 12000BTU)
|
||||
+ Бирюса (models: B-09FIR/B-09FIQ)
|
||||
+ Бирюса (models: B-07DPR/B-07DPQ, B-09FIR/B-09FIQ)
|
||||
|
||||
## Tested and INCOMPATIBLE air conditioners ##
|
||||
ACs from the list below are **INCOMPATIBLE** with `aux_ac`.<br/>
|
||||
|
||||
@@ -8,17 +8,44 @@ I leave GPIO0 in air cause I don't see any reason to solder additional component
|
||||
ESP-12E before DC-DC and air conditioner connected:
|
||||

|
||||
|
||||
Air conditioner internal block has a 5-wire connection to the wifi-module. Connector is [JST SM](https://www.jst-mfg.com/product/pdf/eng/eSM.pdf).
|
||||
|
||||
## Wires ##
|
||||
1. Yellow: +14V DC. Measured +14.70V max and +13.70V min. Service manual declares up to +16V.
|
||||
Air conditioner internal block has a 5-wire or a 4-wire (pseudo-USB) connection to the wifi-module. There are another types of connection too. For example AUX Aegean Sea ( 爱琴海 ), check [issue #71](https://github.com/GrKoR/esphome_aux_ac_component/issues/71) for details.
|
||||
|
||||
## 5-wire connection
|
||||
It use [JST SM](https://www.jst-mfg.com/product/pdf/eng/eSM.pdf) connector for 5-wire connection.
|
||||
|
||||
### Pinout ###
|
||||
1. Yellow: +12V..+14V DC. Measured +14.70V max and +13.70V min. Service manual declares up to +16V.
|
||||
2. Black: ground.
|
||||
3. White: +5V DC (max: +5.63V; min: +4.43V) I have no idea what this is for. It goes directly to the air conditioner microcontroller through resistor 1kOhm and it does not affect the operation of the module.
|
||||
3. White: +5V DC (max: +5.63V; min: +4.43V) Enable signal for the 3V3 buck regulator on the OEM module. It goes directly to the air conditioner microcontroller through resistor 1kOhm. It's non used with the EPS module.
|
||||
4. Blue: TX of air conditioner. High is +5V.
|
||||
5. Red: RX of air conditioner. High is +5V.
|
||||
|
||||
|
||||
You should feed your ESP **from +12V..+14V line only**! It is prohibited to use +5V line for this purpose.
|
||||
+5V line is digital signal line and directly goes to conditioner's controller. It can't provide enough power. In worst scenario you probably can burn down your air conditioner controller.
|
||||
|
||||
## 4-wire connection (pseudo-USB)
|
||||
For 4-wire connection it is used USB-like connector. It is only physical USB but its pinout is UART with +12V..+14V power line.
|
||||
|
||||
**ATTENTION!** It is incompatible with normal USB devices! Ordinary USB device like USB flash drive will be damaged if it will be plugged in air conditioner USB connector.
|
||||
|
||||
**ATTENTION #2!** Manufacturer was changed power circuit and connector pinout in 2022-2023: power rail has +8.5V DC and TX/RX pins are swapped.
|
||||
|
||||
### Pinout ###
|
||||
<img src="https://github.com/GrKoR/esphome_aux_ac_component/blob/master/images/USB-pinout.png?raw=true" width="400">
|
||||
|
||||
1. +12V..+14V DC before 2022-2023, +8.5V DC after 2022-2023. Service manual declares up to +16V.
|
||||
2. RX of air conditioner before 2022-2023, TX for later modifications. High level is +5V.
|
||||
3. TX of air conditioner before 2022-2023, RX for later modifications. High level is +5V.
|
||||
4. GND - ground.
|
||||
|
||||
Big thanks to [@diabl0](https://github.com/diabl0) for this pinout in [issue #70](https://github.com/GrKoR/esphome_aux_ac_component/issues/70).
|
||||
|
||||
If you are not sure, on which USB-pins do you have RX and TX lines, than don't afraid to connect it randomly. Neither air conditioner nor ESP will be damaged in this situation, just `aux_ac` can't receive data from the air conditioner. Swap TX and RX and your device will probably work.
|
||||
|
||||
## Power supply
|
||||
|
||||
For power supply it is possible to use any kind of suitable modules. I use this:
|
||||
.
|
||||
.
|
||||
|
||||
## Connections ##
|
||||
Black wire of AC's connector goes to the middle pin of the power module and to the GND pin of esp-12e.
|
||||
@@ -26,7 +53,7 @@ Yellow wire is connected to the Vin pin of the power module.
|
||||
Blue wire is connected to the RXD pin of esp-12e.
|
||||
Red wire is connected to the TXD pin of esp-12e.
|
||||
|
||||
**ATTENTION!** In case you are using board like NodeMCU instead of clean esp8266/esp32 module, you shouldn't connect RX & TX wires of air conditioneer to TX & RX pins of board. Use any other digital pins for UART connection. It doesn't matter if your board will use hardware or software UART. All UART types are working well.
|
||||
**ATTENTION!** In case you are using board like NodeMCU instead of clean esp8266/esp32 module, you shouldn't connect RX & TX wires of air conditioner to TX & RX pins of board! *(TXD1/RXD1, TXD2/RXD2 are also most likely not suitable.)* Use any other digital pins for UART connection. It doesn't matter if your board will use hardware or software UART. All UART types are working well.
|
||||
The usage of alternate pins for NodeMCU-like boards is necessary cause RX & TX lines of this boards are often have additional components like resistors or USB-TTL converters connected. This components are violate esp-to-ac UART connection.
|
||||
|
||||
Here is it:
|
||||
@@ -35,7 +62,7 @@ Here is it:
|
||||
All connections in custom 3d-printed case looks like this:
|
||||

|
||||
|
||||
Cause I haven't JST SM connector I made own:
|
||||
Since I didn't have JST SM connectors, I made my own:
|
||||
.
|
||||
|
||||
It is made of standard 2.54mm pins and 3D-printed case.
|
||||
@@ -44,4 +71,4 @@ All models for 3D-printing are available too: [STL-files for connector](https://
|
||||
## The result ##
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@@ -1,23 +1,49 @@
|
||||
## Электроника, необходимая для управления кондиционером по wifi ##
|
||||
Я тестировал проект на esp8266 (esp-12e). Минимальная обвязка традиционная и выглядит так:
|
||||
Я тестировал проект на esp8266 (esp-12e). Минимальная обвязка традиционна и выглядит так:
|
||||

|
||||
|
||||
Для прошивки esp8266 в первый раз нужно в дополнение к обвязке, показанной на схеме выше, притянуть к Земле пин IO0 (GPIO0). После этого ESPHome может быть загружена в esp8266 по UART0. Если при этом вы указали OTA в конфигурации ESPHome, то в дальнейшем пин IO0 можно подтянуть к питанию или оставить висеть в воздухе. Он никак не будет влиять на загрузку новых прошивок, потому что все апдейты можно будет делать "по воздуху" (то есть по wifi). Я никуда IO0 не подтягивал и ничего к нему не паял, потому что не вижу смысла это делать ради одного раза. Первую прошивку делал в самодельном переходнике на макетке.
|
||||
Для прошивки esp8266 в первый раз нужно, в дополнение к обвязке, показанной на схеме выше, притянуть к земле пин IO0 (GPIO0). После этого ESPHome может быть загружена в esp8266 по UART0. Если при этом вы указали OTA в конфигурации ESPHome, то в дальнейшем пин IO0 можно подтянуть к питанию или оставить висеть в воздухе. Он никак не будет влиять на загрузку новых прошивок, потому что все апдейты можно будет делать "по воздуху" (то есть по Wi-Fi). Я никуда IO0 не подтягивал и ничего к нему не паял, потому что не вижу смысла это делать ради одного раза. Первую прошивку делал в самодельном переходнике на макетке.
|
||||
|
||||
Плата esp-12e перед подключением кондиционера и модуля питания:
|
||||

|
||||
|
||||
Внутренний блок сплит-системы имеет 5-проводное подключение к модулю wifi. Коннектор [JST SM](https://www.jst-mfg.com/product/pdf/eng/eSM.pdf).
|
||||
Внутренний блок сплит-системы может иметь 5-проводное или 4-проводное подключение (псевдо-USB) к модулю Wi-Fi. Встречаются и другие виды подключения. Например, AUX Aegean Sea ( 爱琴海 ), за подробностями подключения сюда: [issue #71](https://github.com/GrKoR/esphome_aux_ac_component/issues/71).
|
||||
|
||||
## 5-проводное подключение
|
||||
Для 5-проводного подключения используется коннектор [JST SM](https://www.jst-mfg.com/product/pdf/eng/eSM.pdf).
|
||||
|
||||
## Перечень проводников ##
|
||||
1. Желтый: +14В постоянного тока. Осциллограф показал от +13.70В до +14.70В. В сервисном мануале встречалось, что питание возможно до +16В.
|
||||
### Распиновка ###
|
||||
1. Желтый: +12В..+14В постоянного тока. Осциллограф показал от +13.70В до +14.70В. В сервисном мануале встречалось, что питание возможно до +16В.
|
||||
2. Черный: земля.
|
||||
3. Белый: +5В постоянного тока (измерено от +4.43В до +5.63В). Для чего нужна эта линия - не понятно. У меня нет версий. Эксперименты с родным wifi-модулем сплит-системы показали, что эта линия в работе wifi не участвует. Линия идет напрямую на ножку контроллера в сплите через резистор 1 кОм.
|
||||
3. Белый: +5В постоянного тока (измерено от +4.43В до +5.63В). По информации от пользователей, это сигнальная линия, включающая по DC-DC конвертор на wifi-модуле по команде с кондиционера. Линия идет напрямую на ножку контроллера в сплите через резистор 1 кОм. Эксперименты с родным Wi-Fi модулем сплит-системы показали, что эта линия в работе Wi-Fi не участвует и имеет всегда высокий уровень. В работе компонента и самодельного wifi-модуля эта линия не используется.
|
||||
4. Синий: TX кондиционера. Высокий уровень +5В.
|
||||
5. Red: RX кондиционера. Высокий уровень +5В.
|
||||
|
||||
Питание ESP подключать **ТОЛЬКО** к линии +12В..+14В! Имеющийся в 5-проводном подключении контакт с +5В для питания ESP использоваться не должен. Он является сигнальным, подключен через резистор непосредственно на ногу контроллера на материнской плате кондиционера и способен выдавать лишь мизерный ток. При неудачном стечении обстоятельств, повесив питание ESP на эту линию, можно сжечь мозги кондиционеру.
|
||||
|
||||
## 4-проводное подключение (псевдо-USB)
|
||||
Для 4-проводного подключения используется USB-коннектор. От настоящего USB здесь только коннектор. По пинам в этом разъеме UART и +12В..+14В питание.
|
||||
|
||||
**ВНИМАНИЕ!** С устройствами c настоящим USB этот интерфейс не совместим! Если вставить в разъём обычную USB-флешку или другое устройство, скорее всего оно просто сгорит.
|
||||
|
||||
**ВНИМАНИЕ №2!** В 2022-2023 производитель поменял распиновку и схему питания. Теперь кондиционеры на USB-коннектор выдают +8.5В. А пины RX и TX поменялись местами.
|
||||
|
||||
### Распиновка ###
|
||||
<img src="https://github.com/GrKoR/esphome_aux_ac_component/blob/master/images/USB-pinout.png?raw=true" width="400">
|
||||
|
||||
1. +12В..+14В постоянного тока для кондиционеров до 2023 года, +8.5В для кондиционеров 2022-2023 годов и моложе. В сервисном мануале на кондиционеры до 2022 года встречалось, что питание возможно до +16В.
|
||||
2. RX кондиционера до 2022-2023 года, TX для более поздних модификаций. Высокий уровень +5В.
|
||||
3. TX кондиционера до 2022-2023 года, RX для более поздних модификаций. Высокий уровень +5В.
|
||||
4. земля.
|
||||
|
||||
Большое спасибо [@diabl0](https://github.com/diabl0) за эту распиновку ([issue #70](https://github.com/GrKoR/esphome_aux_ac_component/issues/70)).
|
||||
|
||||
Если вы не знаете, на каких именно пинах USB-разъема в вашем случае расположены TX и RX, то не бойтесь подключить наугад. Ни кондиционер, ни ESP не пострадают, если вы перепутаете линии TX и RX. Просто компонент не увидит кондиционер, о чем будут сообщения в логе. В таком случае просто попробуйте поменять TX и RX местами.
|
||||
|
||||
## Питание
|
||||
|
||||
Для питания ESP8266 можно использовать любой подходящий DC-DC преобразователь. Я использовал такой:
|
||||
.
|
||||
.
|
||||
|
||||
## Подключение ##
|
||||
Черный провод (земля) подключается к земле DC-DC преобразователя и к пину GND модуля ESP8266.
|
||||
@@ -25,7 +51,7 @@
|
||||
Синий провод подключается к пину RXD модуля esp-12e.
|
||||
Красный провод подключается к пину TXD модуля esp-12e.
|
||||
|
||||
**ВНИМАНИЕ!** Если вы используете не голый модуль esp32/esp8266, а плату типа NodeMCU, то не подключайте провода TX и RX кондиционера к пинам TX и RX платы! Используйте любые другие свободные пины для UART. При этом для ESP8266 UART будет программный, но это не страшно. Ресурсов ESP8266 хватит для работы.
|
||||
**ВНИМАНИЕ!** Если вы используете не голый модуль esp32/esp8266, а плату типа NodeMCU, то не подключайте провода TX и RX кондиционера к пинам TX и RX платы! *(TXD1/RXD1, TXD2/RXD2 также скорее всего не подойдут.)* Используйте любые другие свободные пины для UART. При этом для ESP8266 UART будет программный, но это не страшно. Ресурсов ESP8266 хватит для работы.
|
||||
Использовать альтернативные пины для плат типа NodeMCU необходимо потому, что часто на линиях RX и TX этих плат установлены резисторы, а также на этих пинах висит USB-TTL конвертер (если он есть на плате). Эти компоненты мешают ESP наладить соединение с кондиционером.
|
||||
|
||||
|
||||
|
||||
18
docs/HOW_TO_FEATURE_REQUEST-EN.md
Normal file
18
docs/HOW_TO_FEATURE_REQUEST-EN.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# How to request a feature for a component #
|
||||
|
||||
With any functions, the alignment is such that they simply cannot be written down by name.
|
||||
We need a log from someone who has air conditioner with such functions. If you are such a person, then you can help yourself and the community.
|
||||
|
||||
To capture the log you need to do the following steps:
|
||||
1. Run a specially written [tool](https://github.com/GrKoR/ac_python_logger) to collect logs.
|
||||
2. Turn on the AC.
|
||||
3. Wait 10+ seconds. (During this time, the ESP will receive all packets from AC).
|
||||
4. Turn on the desired function using AC's IR remote.
|
||||
5. Wait 10+ seconds ones more. While you are waiting, you can write down what you have done.
|
||||
6. Turn off the desired function.
|
||||
7. Wait 10+ seconds again and write down what you've done.
|
||||
8. Repeat steps 4..7 for all other functions you interested in.
|
||||
9. Stop the log recording with a script.
|
||||
10. Send collected log and your notes (explanations to the log) to [issues](https://github.com/GrKoR/esphome_aux_ac_component/issues) or to [telegram chat](https://t.me/aux_ac).
|
||||
|
||||
Instead of a Python script from the step #1, you can simply save the logs from the esphome web-interface with copy-paste or from the command line, but there is a lot of extra stuff there. And it's easy to miss something. But in principle, it is also quite a working option.
|
||||
17
docs/HOW_TO_FEATURE_REQUEST.md
Normal file
17
docs/HOW_TO_FEATURE_REQUEST.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Как запросить расширение функционала компонента #
|
||||
|
||||
С любыми новыми функциями кондиционера ситуация такая, что просто по названию из инструкции или с пульта их не сделать. Нужен лог от владельца кондиционера, у кого такие функции есть. Если вы такой владелей, то можете помочь себе и сообществу.
|
||||
|
||||
Последовательность действий такая:
|
||||
1. Запускате специально написанную [тулзу](https://github.com/GrKoR/ac_python_logger) для сбора логов.
|
||||
2. Включете кондиционер.
|
||||
3. Ждёте 10+ секунд, чтобы все возможные типы пакетов прошли по UART на ESP.
|
||||
4. Включаете нужную функцию с помощью ИК-пульта.
|
||||
5. Опять ждёте 10+ секунд. Во время ожидания можно записать в отдельный текстовый файл или на бумажку, что именно сделано.
|
||||
6. Выключаете нужную функцию.
|
||||
7. Опять ждёте 10+ секунд, не забывая записывать комментарии по сделанному.
|
||||
8. Повторяете шаги 4..7 для всех других функций, которые хочется иметь в компоненте `aux_ac`.
|
||||
9. Останавливаете запись лога.
|
||||
10. Отправляете собранный лог и свои заметки (пояснения к логу) в [issues](https://github.com/GrKoR/esphome_aux_ac_component/issues) или в [телеграм](https://t.me/aux_ac).
|
||||
|
||||
Вместо использования Python-скрипта можно логи из веб-интерфейса esphome сохранять копипастом или из командной строки. Но там много лишнего шлётся и легко что-то пропустить. Но в принципе это тоже вполне рабочий вариант.
|
||||
@@ -63,39 +63,47 @@ climate:
|
||||
id: aux_id
|
||||
uart_id: ac_uart_bus
|
||||
period: 7s
|
||||
show_action: true
|
||||
display_inverted: true
|
||||
indoor_temperature:
|
||||
name: ${upper_devicename} Indoor Temperature
|
||||
id: ${devicename}_indoor_temp
|
||||
internal: false
|
||||
optimistic: true
|
||||
display_state:
|
||||
name: ${upper_devicename} Display State
|
||||
id: ${devicename}_display_state
|
||||
internal: false
|
||||
outdoor_temperature:
|
||||
name: ${upper_devicename} Outdoor Temperature
|
||||
id: ${devicename}_outdoor_temp
|
||||
indoor_ambient_temperature:
|
||||
name: ${upper_devicename} Indoor Ambient Temperature
|
||||
id: ${devicename}_indoor_ambient_temp
|
||||
internal: false
|
||||
outbound_temperature:
|
||||
name: ${upper_devicename} Colant Outbound Temperature
|
||||
id: ${devicename}_outbound_temp
|
||||
indoor_coil_temperature:
|
||||
name: ${upper_devicename} Indoor Coil Temperature
|
||||
id: ${devicename}_indoor_coil_temp
|
||||
internal: false
|
||||
inbound_temperature:
|
||||
name: ${upper_devicename} Colant Inbound Temperature
|
||||
id: ${devicename}_inbound_temp
|
||||
outdoor_ambient_temperature:
|
||||
name: ${upper_devicename} Outdoor Ambient Temperature
|
||||
id: ${devicename}_outdoor_ambient_temp
|
||||
internal: false
|
||||
compressor_temperature:
|
||||
name: ${upper_devicename} Compressor Temperature
|
||||
id: ${devicename}_strange_temp
|
||||
outdoor_condenser_temperature:
|
||||
name: ${upper_devicename} Outdoor Condenser Temperature
|
||||
id: ${devicename}_outdoor_condenser_temp
|
||||
internal: false
|
||||
compressor_suction_temperature:
|
||||
name: ${upper_devicename} Compressor Suction Temperature
|
||||
id: ${devicename}_compressor_suction_temp
|
||||
internal: false
|
||||
compressor_discharge_temperature:
|
||||
name: ${upper_devicename} Compressor Discharge Temperature
|
||||
id: ${devicename}_compressor_discharge_temp
|
||||
internal: false
|
||||
defrost_temperature:
|
||||
name: ${upper_devicename} Defrost Temperature
|
||||
id: ${devicename}_defrost_temp
|
||||
internal: false
|
||||
defrost_state:
|
||||
name: ${upper_devicename} Defrost State
|
||||
id: ${devicename}_defrost_state
|
||||
internal: false
|
||||
invertor_power:
|
||||
name: ${upper_devicename} Invertor Power
|
||||
id: ${devicename}_invertor_power
|
||||
inverter_power:
|
||||
name: ${upper_devicename} Inverter Power
|
||||
id: ${devicename}_inverter_power
|
||||
internal: false
|
||||
preset_reporter:
|
||||
name: ${upper_devicename} Preset Reporter
|
||||
@@ -108,7 +116,9 @@ climate:
|
||||
visual:
|
||||
min_temperature: 16
|
||||
max_temperature: 32
|
||||
temperature_step: 0.5
|
||||
temperature_step:
|
||||
target_temperature: 0.5
|
||||
current_temperature: 0.1
|
||||
supported_modes:
|
||||
- HEAT_COOL
|
||||
- COOL
|
||||
@@ -137,6 +147,8 @@ sensor:
|
||||
update_interval: 30s
|
||||
unit_of_measurement: "dBa"
|
||||
accuracy_decimals: 0
|
||||
- platform: uptime
|
||||
name: ${upper_devicename} Uptime Sensor
|
||||
|
||||
|
||||
switch:
|
||||
@@ -153,43 +165,72 @@ switch:
|
||||
turn_off_action:
|
||||
- aux_ac.display_off: aux_id
|
||||
|
||||
|
||||
- platform: template
|
||||
name: ${upper_devicename} Power Limitation
|
||||
lambda: |-
|
||||
if (id(${devicename}_inverter_power).state) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
turn_on_action:
|
||||
#- aux_ac.power_limit_on: aux_id
|
||||
- aux_ac.power_limit_on:
|
||||
id: aux_id
|
||||
limit: 40
|
||||
turn_off_action:
|
||||
- aux_ac.power_limit_off: aux_id
|
||||
|
||||
|
||||
button:
|
||||
- platform: template
|
||||
name: ${upper_devicename} 26 deg Cool Low Fan
|
||||
on_press:
|
||||
- climate.control:
|
||||
id: aux_id
|
||||
mode: COOL
|
||||
target_temperature: 26°C
|
||||
fan_mode: LOW
|
||||
#custom_fan_mode: MUTE
|
||||
swing_mode: "OFF"
|
||||
|
||||
- platform: template
|
||||
name: ${upper_devicename} VLouver Stop
|
||||
icon: "mdi:circle-small"
|
||||
on_press:
|
||||
- aux_ac.vlouver_stop: aux_id
|
||||
|
||||
|
||||
- platform: template
|
||||
name: ${upper_devicename} VLouver Swing
|
||||
icon: "mdi:pan-vertical"
|
||||
on_press:
|
||||
- aux_ac.vlouver_swing: aux_id
|
||||
|
||||
|
||||
- platform: template
|
||||
name: ${upper_devicename} VLouver Top
|
||||
icon: "mdi:pan-up"
|
||||
on_press:
|
||||
- aux_ac.vlouver_top: aux_id
|
||||
|
||||
|
||||
- platform: template
|
||||
name: ${upper_devicename} VLouver Middle Above
|
||||
icon: "mdi:pan-top-left"
|
||||
on_press:
|
||||
- aux_ac.vlouver_middle_above: aux_id
|
||||
|
||||
|
||||
- platform: template
|
||||
name: ${upper_devicename} VLouver Middle
|
||||
icon: "mdi:pan-left"
|
||||
on_press:
|
||||
- aux_ac.vlouver_middle: aux_id
|
||||
|
||||
|
||||
- platform: template
|
||||
name: ${upper_devicename} VLouver Middle Below
|
||||
icon: "mdi:pan-bottom-left"
|
||||
on_press:
|
||||
- aux_ac.vlouver_middle_below: aux_id
|
||||
|
||||
|
||||
- platform: template
|
||||
name: ${upper_devicename} VLouver Bottom
|
||||
icon: "mdi:pan-down"
|
||||
@@ -213,4 +254,17 @@ number:
|
||||
then:
|
||||
- aux_ac.vlouver_set:
|
||||
id: aux_id
|
||||
position: !lambda "return x;"
|
||||
position: !lambda "return x;"
|
||||
|
||||
- platform: template
|
||||
name: ${upper_devicename} Power Limit
|
||||
id: ${devicename}_power_limit
|
||||
icon: "mdi:battery-unknown"
|
||||
mode: "slider"
|
||||
min_value: 30
|
||||
max_value: 100
|
||||
step: 1
|
||||
set_action:
|
||||
then:
|
||||
- lambda: !lambda "id(aux_id).action_power_limitation_on( x );"
|
||||
|
||||
|
||||
BIN
images/USB-pinout.png
Normal file
BIN
images/USB-pinout.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
@@ -1,191 +0,0 @@
|
||||
import time
|
||||
import aioesphomeapi
|
||||
import asyncio
|
||||
import re
|
||||
import sys
|
||||
import argparse
|
||||
from aioesphomeapi.api_pb2 import (LOG_LEVEL_NONE,
|
||||
LOG_LEVEL_ERROR,
|
||||
LOG_LEVEL_WARN,
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_DEBUG,
|
||||
LOG_LEVEL_VERBOSE,
|
||||
LOG_LEVEL_VERY_VERBOSE)
|
||||
|
||||
def createParser ():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='''This script is used for collecting logs from ac_aux ESPHome component.
|
||||
For more info, see https://github.com/GrKoR/ac_python_logger''',
|
||||
add_help = False)
|
||||
parent_group = parser.add_argument_group (title='Params')
|
||||
parent_group.add_argument ('--help', '-h', action='help', help='show this help message and exit')
|
||||
parent_group.add_argument ('-i', '--ip', nargs=1, required=True, help='IP address of the esphome device')
|
||||
parent_group.add_argument ('-p', '--pwd', nargs=1, required=True, help='native API password for the esphome device')
|
||||
return parser
|
||||
|
||||
async def main():
|
||||
"""Connect to an ESPHome device and wait for state changes."""
|
||||
api = aioesphomeapi.APIClient(namespace.ip[0], 6053, namespace.pwd[0])
|
||||
|
||||
try:
|
||||
await api.connect(login=True)
|
||||
except aioesphomeapi.InvalidAuthAPIError as e:
|
||||
return print(e)
|
||||
|
||||
print(api.api_version)
|
||||
|
||||
async def display_off():
|
||||
await api.execute_service(
|
||||
service,
|
||||
data={
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
||||
"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x01, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, 0xE0, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0x00],
|
||||
}
|
||||
)
|
||||
|
||||
async def display_on():
|
||||
await api.execute_service(
|
||||
service,
|
||||
data={
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
||||
"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x01, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, 0xE0, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00],
|
||||
}
|
||||
)
|
||||
|
||||
async def ac_enable():
|
||||
await api.execute_service(
|
||||
service,
|
||||
data={
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
||||
"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x00, 0x87, 0xE0, 0x2F, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00],
|
||||
}
|
||||
)
|
||||
|
||||
async def ac_disable():
|
||||
await api.execute_service(
|
||||
service,
|
||||
data={
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
||||
"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x00, 0x87, 0xE0, 0x2F, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
||||
}
|
||||
)
|
||||
|
||||
async def ac_get11_01():
|
||||
await api.execute_service(
|
||||
service,
|
||||
data={
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
||||
"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x02, 0x00, 0x11, 0x01],
|
||||
}
|
||||
)
|
||||
|
||||
async def ac_get11_00():
|
||||
await api.execute_service(
|
||||
service,
|
||||
data={
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
||||
"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x02, 0x00, 0x11, 0x00],
|
||||
}
|
||||
)
|
||||
|
||||
async def ac_set_vlouver(lvr):
|
||||
await api.execute_service(
|
||||
service,
|
||||
data={
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
||||
"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, lvr, 0xE0, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00],
|
||||
}
|
||||
)
|
||||
|
||||
async def ac_set_hlouver(lvr):
|
||||
await api.execute_service(
|
||||
service,
|
||||
data={
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
||||
"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, lvr, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00],
|
||||
}
|
||||
)
|
||||
|
||||
# key надо искать в выводе list_entities_services
|
||||
service = aioesphomeapi.UserService(
|
||||
name="send_data",
|
||||
key=311254518,
|
||||
args=[
|
||||
aioesphomeapi.UserServiceArg(name="data_buf", type=aioesphomeapi.UserServiceArgType.INT_ARRAY),
|
||||
],
|
||||
)
|
||||
|
||||
time.sleep(7)
|
||||
await ac_get11_00()
|
||||
time.sleep(7)
|
||||
await ac_get11_01()
|
||||
|
||||
#await ac_set_vlouver( 0b10010000 ) # swing on
|
||||
#await ac_set_vlouver( 0b10010111 ) # swing off
|
||||
#await ac_set_vlouver( 0b10010001 ) # 1
|
||||
#await ac_set_vlouver( 0b10010010 ) # 2
|
||||
#await ac_set_vlouver( 0b10010011 ) # 3
|
||||
#await ac_set_vlouver( 0b10010100 ) # 4
|
||||
#await ac_set_vlouver( 0b10010101 ) # 5
|
||||
#await ac_set_vlouver( 0b10010110 ) # не работает, сбрасывает на swing on
|
||||
#time.sleep(5)
|
||||
|
||||
#await ac_set_hlouver( 0b00000000 ) # swing on
|
||||
#await ac_set_hlouver( 0b11100000 ) # swing off
|
||||
#await ac_set_hlouver( 0b00100000 ) # не работает, сбрасывает в swing off
|
||||
#await ac_set_hlouver( 0b01000000 ) # не работает, сбрасывает в swing off
|
||||
#await ac_set_hlouver( 0b01100000 ) # не работает, сбрасывает в swing off
|
||||
#await ac_set_hlouver( 0b10000000 ) # не работает, сбрасывает в swing off
|
||||
#await ac_set_hlouver( 0b10100000 ) # не работает, сбрасывает в swing off
|
||||
#await ac_set_hlouver( 0b11000000 ) # не работает, сбрасывает в swing off
|
||||
#time.sleep(5)
|
||||
|
||||
async def test_byte(bt):
|
||||
await api.execute_service(
|
||||
service,
|
||||
data={
|
||||
#display on
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
||||
#"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, 0xE0, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00],
|
||||
#display off
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
||||
"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, 0xE0, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0x00],
|
||||
# swing on
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
||||
#"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x90, 0xE0, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00],
|
||||
# swing off
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
||||
#"data_buf": [0xBB, 0x00, 0x06, 0x80, 0x00, 0x00, 0x0F, 0x00, 0x01, 0x01, 0x97, 0xE0, 0x00, 0x20, 0x00, 0xC0, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00],
|
||||
}
|
||||
)
|
||||
'''
|
||||
не проходит команда, если байт 1 или 7 не 0x00
|
||||
не проходит команда, если байт 3 не 0x80
|
||||
|
||||
проходит и не меняется, если меняю байт 4 или 5
|
||||
'''
|
||||
|
||||
#await test_byte(0b10000110)
|
||||
#await test_byte(0b01000110)
|
||||
#await test_byte(0b00100110)
|
||||
#await test_byte(0b00010110)
|
||||
time.sleep(2)
|
||||
|
||||
|
||||
parser = createParser()
|
||||
namespace = parser.parse_args()
|
||||
print("IP: ", namespace.ip[0])
|
||||
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
#asyncio.ensure_future(main())
|
||||
#loop.run_forever()
|
||||
loop.run_until_complete(main())
|
||||
except aioesphomeapi.InvalidAuthAPIError as e:
|
||||
print(e)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
loop.close()
|
||||
pass
|
||||
@@ -1,5 +1,5 @@
|
||||
external_components:
|
||||
- source: github://GrKoR/esphome_aux_ac_component@dev
|
||||
- source: github://GrKoR/esphome_aux_ac_component
|
||||
components: [ aux_ac ]
|
||||
refresh: 0s
|
||||
|
||||
@@ -58,37 +58,44 @@ climate:
|
||||
id: aux_id
|
||||
uart_id: ac_uart_bus
|
||||
period: 7s
|
||||
show_action: true
|
||||
display_inverted: true
|
||||
indoor_temperature:
|
||||
name: $upper_devicename Indoor Temperature
|
||||
id: ${devicename}_indoor_temp
|
||||
internal: false
|
||||
display_state:
|
||||
name: $upper_devicename Display State
|
||||
id: ${devicename}_display_state
|
||||
internal: false
|
||||
outdoor_temperature:
|
||||
name: $upper_devicename Outdoor Temperature
|
||||
id: ${devicename}_outdoor_temp
|
||||
indoor_ambient_temperature:
|
||||
name: $upper_devicename Indoor Ambient Temperature
|
||||
id: ${devicename}_indoor_ambient_temp
|
||||
internal: false
|
||||
outbound_temperature:
|
||||
name: $upper_devicename Colant Outbound Temperature
|
||||
id: ${devicename}_outbound_temp
|
||||
outdoor_ambient_temperature:
|
||||
name: $upper_devicename Outdoor Ambient Temperature
|
||||
id: ${devicename}_outdoor_ambient_temp
|
||||
internal: false
|
||||
inbound_temperature:
|
||||
name: $upper_devicename Colant Inbound Temperature
|
||||
id: ${devicename}_inbound_temp
|
||||
outdoor_condenser_temperature:
|
||||
name: $upper_devicename Outdoor Condenser Temperature
|
||||
id: ${devicename}_outdoor_condenser_temp
|
||||
internal: false
|
||||
compressor_temperature:
|
||||
name: $upper_devicename Compressor Temperature
|
||||
id: ${devicename}_strange_temp
|
||||
compressor_suction_temperature:
|
||||
name: $upper_devicename Compressor Suction Temperature
|
||||
id: ${devicename}_compressor_suction_temp
|
||||
internal: false
|
||||
indoor_coil_temperature:
|
||||
name: $upper_devicename Indoor Coil Temperature
|
||||
id: ${devicename}_indoor_coil_temp
|
||||
internal: false
|
||||
compressor_discharge_temperature:
|
||||
name: $upper_devicename Compressor Discharge Temperature
|
||||
id: ${devicename}_compressor_discharge_temp
|
||||
internal: false
|
||||
defrost_temperature:
|
||||
name: $upper_devicename Defrost Temperature
|
||||
id: ${devicename}_defrost_temp
|
||||
internal: false
|
||||
defrost_state:
|
||||
name: $upper_devicename Defrost State
|
||||
id: ${devicename}_defrost_state
|
||||
internal: false
|
||||
invertor_power:
|
||||
inverter_power:
|
||||
name: $upper_devicename Invertor Power
|
||||
id: ${devicename}_invertor_power
|
||||
internal: false
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
external_components:
|
||||
- source: github://GrKoR/esphome_aux_ac_component@dev
|
||||
components: [ aux_ac ]
|
||||
refresh: 0s
|
||||
|
||||
substitutions:
|
||||
devicename: test_aux_ac_ext_engeneer
|
||||
upper_devicename: Test AUX
|
||||
|
||||
esphome:
|
||||
name: $devicename
|
||||
platform: ESP8266
|
||||
board: esp12e
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_ssid
|
||||
password: !secret wifi_pass
|
||||
manual_ip:
|
||||
static_ip: !secret wifi_ip
|
||||
gateway: !secret wifi_gateway
|
||||
subnet: !secret wifi_subnet
|
||||
dns1: 8.8.8.8
|
||||
dns2: 1.1.1.1
|
||||
reboot_timeout: 0s
|
||||
ap:
|
||||
ssid: Test AUX Fallback Hotspot
|
||||
password: !secret wifi_ap_pass
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
baud_rate: 0
|
||||
|
||||
api:
|
||||
password: !secret api_pass
|
||||
reboot_timeout: 0s
|
||||
services:
|
||||
# этот сервис можно вызвать из Home Assistant или Python. Он отправляет полученные байты в кондиционер
|
||||
- service: send_data
|
||||
variables:
|
||||
data_buf: int[]
|
||||
then:
|
||||
# ВАЖНО! Только для инженеров!
|
||||
# Вызывайте метод aux_ac.send_packet только если понимаете, что делаете! Он не проверяет данные, а передаёт
|
||||
# кондиционеру всё как есть. Какой эффект получится от передачи кондиционеру рандомных байт, никто не знает.
|
||||
# Вы действуете на свой страх и риск.
|
||||
- aux_ac.send_packet:
|
||||
id: aux_id
|
||||
data: !lambda |-
|
||||
std::vector<uint8_t> data{};
|
||||
for (int n : data_buf) {
|
||||
data.push_back( (uint8_t) n );
|
||||
}
|
||||
return data;
|
||||
|
||||
ota:
|
||||
password: !secret ota_pass
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
uart:
|
||||
id: ac_uart_bus
|
||||
tx_pin: GPIO1
|
||||
rx_pin: GPIO3
|
||||
baud_rate: 4800
|
||||
data_bits: 8
|
||||
parity: EVEN
|
||||
stop_bits: 1
|
||||
|
||||
sensor:
|
||||
- platform: uptime
|
||||
name: Uptime Sensor
|
||||
|
||||
climate:
|
||||
- platform: aux_ac
|
||||
name: $upper_devicename
|
||||
id: aux_id
|
||||
uart_id: ac_uart_bus
|
||||
period: 7s
|
||||
show_action: true
|
||||
display_inverted: true
|
||||
indoor_temperature:
|
||||
name: $upper_devicename Indoor Temperature
|
||||
id: ${devicename}_indoor_temp
|
||||
internal: false
|
||||
display_state:
|
||||
name: $upper_devicename Display State
|
||||
id: ${devicename}_display_state
|
||||
internal: false
|
||||
outdoor_temperature:
|
||||
name: $upper_devicename Outdoor Temperature
|
||||
id: ${devicename}_outdoor_temp
|
||||
internal: false
|
||||
outbound_temperature:
|
||||
name: $upper_devicename Colant Outbound Temperature
|
||||
id: ${devicename}_outbound_temp
|
||||
internal: false
|
||||
inbound_temperature:
|
||||
name: $upper_devicename Colant Inbound Temperature
|
||||
id: ${devicename}_inbound_temp
|
||||
internal: false
|
||||
compressor_temperature:
|
||||
name: $upper_devicename Compressor Temperature
|
||||
id: ${devicename}_strange_temp
|
||||
internal: false
|
||||
defrost_state:
|
||||
name: $upper_devicename Defrost State
|
||||
id: ${devicename}_defrost_state
|
||||
internal: false
|
||||
invertor_power:
|
||||
name: $upper_devicename Invertor Power
|
||||
id: ${devicename}_invertor_power
|
||||
internal: false
|
||||
preset_reporter:
|
||||
name: $upper_devicename Preset Reporter
|
||||
id: ${devicename}_preset_reporter
|
||||
internal: false
|
||||
visual:
|
||||
min_temperature: 16
|
||||
max_temperature: 32
|
||||
temperature_step: 0.5
|
||||
supported_modes:
|
||||
- HEAT_COOL
|
||||
- COOL
|
||||
- HEAT
|
||||
- DRY
|
||||
- FAN_ONLY
|
||||
custom_fan_modes:
|
||||
- MUTE
|
||||
- TURBO
|
||||
supported_presets:
|
||||
- SLEEP
|
||||
custom_presets:
|
||||
- CLEAN
|
||||
- HEALTH
|
||||
- ANTIFUNGUS
|
||||
supported_swing_modes:
|
||||
- VERTICAL
|
||||
- HORIZONTAL
|
||||
- BOTH
|
||||
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
name: AC Display
|
||||
lambda: |-
|
||||
if (id(${devicename}_display_state).state) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
turn_on_action:
|
||||
- aux_ac.display_on: aux_id
|
||||
turn_off_action:
|
||||
- aux_ac.display_off: aux_id
|
||||
@@ -1,5 +1,5 @@
|
||||
external_components:
|
||||
- source: github://GrKoR/esphome_aux_ac_component@dev
|
||||
- source: github://GrKoR/esphome_aux_ac_component
|
||||
components: [ aux_ac ]
|
||||
refresh: 0s
|
||||
|
||||
@@ -59,32 +59,39 @@ climate:
|
||||
id: aux_id
|
||||
uart_id: ac_uart_bus
|
||||
period: 7s
|
||||
show_action: true
|
||||
display_inverted: true
|
||||
timeout: 150
|
||||
indoor_temperature:
|
||||
name: $upper_devicename Indoor Temperature
|
||||
id: ${devicename}_indoor_temp
|
||||
internal: false
|
||||
timeout: 300
|
||||
display_state:
|
||||
name: $upper_devicename Display State
|
||||
id: ${devicename}_display_state
|
||||
internal: false
|
||||
outdoor_temperature:
|
||||
name: $upper_devicename Outdoor Temperature
|
||||
id: ${devicename}_outdoor_temp
|
||||
indoor_ambient_temperature:
|
||||
name: $upper_devicename Indoor Ambient Temperature
|
||||
id: ${devicename}_indoor_ambient_temp
|
||||
internal: false
|
||||
outbound_temperature:
|
||||
name: $upper_devicename Colant Outbound Temperature
|
||||
id: ${devicename}_outbound_temp
|
||||
outdoor_ambient_temperature:
|
||||
name: $upper_devicename Outdoor Ambient Temperature
|
||||
id: ${devicename}_outdoor_ambient_temp
|
||||
internal: false
|
||||
inbound_temperature:
|
||||
name: $upper_devicename Colant Inbound Temperature
|
||||
id: ${devicename}_inbound_temp
|
||||
outdoor_condenser_temperature:
|
||||
name: $upper_devicename Outdoor Condenser Temperature
|
||||
id: ${devicename}_outdoor_condenser_temp
|
||||
internal: false
|
||||
compressor_temperature:
|
||||
name: $upper_devicename Compressor Temperature
|
||||
id: ${devicename}_strange_temp
|
||||
compressor_suction_temperature:
|
||||
name: $upper_devicename Compressor Suction Temperature
|
||||
id: ${devicename}_compressor_suction_temp
|
||||
internal: false
|
||||
indoor_coil_temperature:
|
||||
name: $upper_devicename Indoor Coil Temperature
|
||||
id: ${devicename}_indoor_coil_temp
|
||||
internal: false
|
||||
compressor_discharge_temperature:
|
||||
name: $upper_devicename Compressor Discharge Temperature
|
||||
id: ${devicename}_compressor_discharge_temp
|
||||
internal: false
|
||||
defrost_temperature:
|
||||
name: $upper_devicename Defrost Temperature
|
||||
id: ${devicename}_defrost_temp
|
||||
internal: false
|
||||
defrost_state:
|
||||
name: $upper_devicename Defrost State
|
||||
@@ -158,5 +165,5 @@ number:
|
||||
set_action:
|
||||
then:
|
||||
- lambda: !lambda |-
|
||||
id(aux_id).powerLimitationOnSequence( x );
|
||||
id(aux_id).action_power_limitation_on( x );
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: ..\components
|
||||
path: ../components
|
||||
|
||||
substitutions:
|
||||
devicename: test_local_airflow_dir
|
||||
@@ -61,7 +61,6 @@ climate:
|
||||
id: aux_id
|
||||
uart_id: ac_uart_bus
|
||||
period: 7s
|
||||
show_action: true
|
||||
display_inverted: true
|
||||
|
||||
|
||||
@@ -121,5 +120,4 @@ number:
|
||||
set_action:
|
||||
then:
|
||||
- lambda: !lambda |-
|
||||
if (x == 6) x = 7; // делаем так, чтобы выключение отрабатывать корректно
|
||||
id(aux_id).setVLouverSequence( static_cast<esphome::aux_ac::ac_louver_V>(x) );
|
||||
id(aux_id).action_set_vlouver_position( static_cast<esphome::aux_airconditioner::vlouver_esphome_position_t>(x));
|
||||
|
||||
@@ -59,32 +59,39 @@ climate:
|
||||
id: aux_id
|
||||
uart_id: ac_uart_bus
|
||||
period: 7s
|
||||
show_action: true
|
||||
display_inverted: true
|
||||
timeout: 150
|
||||
indoor_temperature:
|
||||
name: $upper_devicename Indoor Temperature
|
||||
id: ${devicename}_indoor_temp
|
||||
internal: false
|
||||
timeout: 300
|
||||
display_state:
|
||||
name: $upper_devicename Display State
|
||||
id: ${devicename}_display_state
|
||||
internal: false
|
||||
outdoor_temperature:
|
||||
name: $upper_devicename Outdoor Temperature
|
||||
id: ${devicename}_outdoor_temp
|
||||
indoor_ambient_temperature:
|
||||
name: $upper_devicename Indoor Ambient Temperature
|
||||
id: ${devicename}_indoor_ambient_temp
|
||||
internal: false
|
||||
outbound_temperature:
|
||||
name: $upper_devicename Colant Outbound Temperature
|
||||
id: ${devicename}_outbound_temp
|
||||
outdoor_ambient_temperature:
|
||||
name: $upper_devicename Outdoor Ambient Temperature
|
||||
id: ${devicename}_outdoor_ambient_temp
|
||||
internal: false
|
||||
inbound_temperature:
|
||||
name: $upper_devicename Colant Inbound Temperature
|
||||
id: ${devicename}_inbound_temp
|
||||
outdoor_condenser_temperature:
|
||||
name: $upper_devicename Outdoor Condenser Temperature
|
||||
id: ${devicename}_outdoor_condenser_temp
|
||||
internal: false
|
||||
compressor_temperature:
|
||||
name: $upper_devicename Compressor Temperature
|
||||
id: ${devicename}_strange_temp
|
||||
compressor_suction_temperature:
|
||||
name: $upper_devicename Compressor Suction Temperature
|
||||
id: ${devicename}_compressor_suction_temp
|
||||
internal: false
|
||||
indoor_coil_temperature:
|
||||
name: $upper_devicename Indoor Coil Temperature
|
||||
id: ${devicename}_indoor_coil_temp
|
||||
internal: false
|
||||
compressor_discharge_temperature:
|
||||
name: $upper_devicename Compressor Discharge Temperature
|
||||
id: ${devicename}_compressor_discharge_temp
|
||||
internal: false
|
||||
defrost_temperature:
|
||||
name: $upper_devicename Defrost Temperature
|
||||
id: ${devicename}_defrost_temp
|
||||
internal: false
|
||||
defrost_state:
|
||||
name: $upper_devicename Defrost State
|
||||
@@ -158,5 +165,5 @@ number:
|
||||
set_action:
|
||||
then:
|
||||
- lambda: !lambda |-
|
||||
id(aux_id).powerLimitationOnSequence( x );
|
||||
id(aux_id).action_power_limitation_on( x );
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: ..\components
|
||||
path: ../components
|
||||
|
||||
substitutions:
|
||||
devicename: test_local_airflow_dir
|
||||
@@ -59,37 +59,45 @@ climate:
|
||||
id: aux_id
|
||||
uart_id: ac_uart_bus
|
||||
period: 7s
|
||||
show_action: true
|
||||
display_inverted: true
|
||||
indoor_temperature:
|
||||
name: $upper_devicename Indoor Temperature
|
||||
id: ${devicename}_indoor_temp
|
||||
optimistic: true
|
||||
indoor_ambient_temperature:
|
||||
name: $upper_devicename Indoor Ambient Temperature
|
||||
id: ${devicename}_indoor_ambient_temp
|
||||
internal: false
|
||||
outdoor_ambient_temperature:
|
||||
name: $upper_devicename Outdoor Ambient Temperature
|
||||
id: ${devicename}_outdoor_ambient_temp
|
||||
internal: false
|
||||
outdoor_condenser_temperature:
|
||||
name: $upper_devicename Outdoor Condenser Temperature
|
||||
id: ${devicename}_outdoor_condenser_temp
|
||||
internal: false
|
||||
compressor_suction_temperature:
|
||||
name: $upper_devicename Compressor Suction Temperature
|
||||
id: ${devicename}_compressor_suction_temp
|
||||
internal: false
|
||||
indoor_coil_temperature:
|
||||
name: $upper_devicename Indoor Coil Temperature
|
||||
id: ${devicename}_indoor_coil_temp
|
||||
internal: false
|
||||
compressor_discharge_temperature:
|
||||
name: $upper_devicename Compressor Discharge Temperature
|
||||
id: ${devicename}_compressor_discharge_temp
|
||||
internal: false
|
||||
defrost_temperature:
|
||||
name: $upper_devicename Defrost Temperature
|
||||
id: ${devicename}_defrost_temp
|
||||
internal: false
|
||||
display_state:
|
||||
name: $upper_devicename Display State
|
||||
id: ${devicename}_display_state
|
||||
internal: false
|
||||
outdoor_temperature:
|
||||
name: $upper_devicename Outdoor Temperature
|
||||
id: ${devicename}_outdoor_temp
|
||||
internal: false
|
||||
outbound_temperature:
|
||||
name: $upper_devicename Colant Outbound Temperature
|
||||
id: ${devicename}_outbound_temp
|
||||
internal: false
|
||||
inbound_temperature:
|
||||
name: $upper_devicename Colant Inbound Temperature
|
||||
id: ${devicename}_inbound_temp
|
||||
internal: false
|
||||
compressor_temperature:
|
||||
name: $upper_devicename Compressor Temperature
|
||||
id: ${devicename}_strange_temp
|
||||
internal: false
|
||||
defrost_state:
|
||||
name: $upper_devicename Defrost State
|
||||
id: ${devicename}_defrost_state
|
||||
internal: false
|
||||
invertor_power:
|
||||
inverter_power:
|
||||
name: $upper_devicename Invertor Power
|
||||
id: ${devicename}_invertor_power
|
||||
internal: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
external_components:
|
||||
- source:
|
||||
type: local
|
||||
path: ..\components
|
||||
path: ../components
|
||||
#- source: github://GrKoR/esphome_aux_ac_component@dev
|
||||
#components: [ aux_ac ]
|
||||
#refresh: 0s
|
||||
|
||||
Reference in New Issue
Block a user