├── .github └── workflows │ ├── dev.yml │ └── release.yml ├── .gitignore ├── README.org ├── components └── uart_vrf │ ├── __init__.py │ ├── climate.py │ ├── uart_vrf_climate.cpp │ ├── uart_vrf_climate.h │ ├── uart_vrf_component.cpp │ ├── uart_vrf_component.h │ ├── vrf.cpp │ ├── vrf.h │ ├── vrf_const.h │ ├── vrf_demry.cpp │ ├── vrf_demry.h │ ├── vrf_demry_const.h │ ├── vrf_zhonghong.cpp │ ├── vrf_zhonghong.h │ └── vrf_zhonghong_const.h ├── example ├── esp01s.yaml └── esp32wroom32d.yaml └── images ├── demry_3th_main.jpg ├── demry_4th_main.jpg └── zhonghong_b17_main.jpg /.github/workflows/dev.yml: -------------------------------------------------------------------------------- 1 | name: Dev 2 | 3 | on: 4 | pull_request: 5 | branches: [ "main" ] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-24.04 11 | strategy: 12 | matrix: 13 | config_name: [esp01s, esp32wroom32d] 14 | steps: 15 | - name: Checkout repo 16 | uses: actions/checkout@v4 17 | with: 18 | submodules: 'recursive' 19 | - name: Build 20 | id: esphome-build 21 | uses: esphome/build-action@v7.0.0 22 | with: 23 | yaml-file: "example/${{ matrix.config_name }}.yaml" 24 | version: 2024.11.3 25 | platform: linux/amd64 26 | - name: Upload bin 27 | uses: actions/upload-artifact@v4.4.3 28 | with: 29 | # Artifact name 30 | name: "${{ steps.esphome-build.outputs.name }}-bin" 31 | # A file, directory or wildcard pattern that describes what to upload 32 | path: ${{ steps.esphome-build.outputs.name }}/*.bin 33 | # The desired behavior if no files are found using the provided path. 34 | retention-days: 3 35 | 36 | notify: 37 | runs-on: ubuntu-24.04 38 | needs: build 39 | steps: 40 | - name: Notify artifact link 41 | uses: marocchino/sticky-pull-request-comment@v2 42 | with: 43 | hide_and_recreate: true 44 | hide_classify: "OUTDATED" 45 | message: | 46 | You can download the binary from the [Artifacts section](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for debugging. 47 | 48 | - Commit Sha: ${{ github.event.pull_request.head.sha }} 49 | - Action run number: **${{ github.run_number }}** 50 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-24.04 11 | strategy: 12 | matrix: 13 | config_name: [esp01s, esp32wroom32d] 14 | steps: 15 | - name: Checkout repo 16 | uses: actions/checkout@v4 17 | with: 18 | submodules: 'recursive' 19 | - name: Build 20 | id: esphome-build 21 | uses: esphome/build-action@v7.0.0 22 | with: 23 | yaml-file: "example/${{ matrix.config_name }}.yaml" 24 | version: 2024.12.4 25 | platform: linux/amd64 26 | - name: Upload file to release 27 | uses: svenstaro/upload-release-action@v2 28 | with: 29 | repo_token: ${{ secrets.GITHUB_TOKEN }} 30 | file: ./${{ steps.esphome-build.outputs.name }}/*.bin 31 | tag: ${{ github.ref }} 32 | file_glob: true 33 | overwrite: true 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.~undo-tree~ 3 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | * ESPHome Uart VRF 2 | 中央空调 VRF 网关串口通信 ESPHome 固件,支持德姆瑞 DEMRY、平头熊 PTXZN、中弘系列。 3 | 4 | * 功能 5 | 1. 实现了与常用的 vrf 进行 UART 或 RS485 通信 6 | 2. 自动探测 vrf 的通信协议,当前支持 demry 和 zhonghong 两种协议 7 | 3. 自动生成 ha climate 实体 8 | 9 | * 固件说明 10 | [[https://github.com/idreamshen/esphome-uart-vrf/releases][Release]] 已生成可直接烧录的固件 11 | - uart-vrf-esp01s-esp8266.factory.bin: 完整固件,适用于 esp8266 芯片。 12 | - uart-vrf-esp01s-esp8266.ota.bin: OTA 固件,适用于 esp8266 芯片。 13 | - uart-vrf-esp32wroom32d-esp32.factory.bin: 完整固件,适用于 ESP32-WROOM-32D 芯片(德姆瑞款上板芯片) 14 | - uart-vrf-esp32wroom32d-esp32.ota.bin: OTA 固件,适用于 ESP32-WROOM-32D 芯片(德姆瑞款上板芯片) 15 | 16 | * 测试机型 17 | 以下机型经过测试 18 | |--------+--------------+--------------------------------+-------------------------------------------------| 19 | | 品牌 | 型号 | 图片 | 备注 | 20 | |--------+--------------+--------------------------------+-------------------------------------------------| 21 | | 中弘 | B17 | [[/images/zhonghong_b17_main.jpg]] | 需要在中弘 web 后台将 RS485 校验改为无 | 22 | | 德姆瑞 | 第三代 | [[/images/demry_3th_main.jpg]] | | 23 | | 德姆瑞 | 第四代 | [[/images/demry_4th_main.jpg]] | 部分款 RS485 无法正常通信,需改为与上板串口通信 | 24 | | 平头熊 | 与德姆瑞一致 | | 部分款 RS485 无法正常通信,需改为与上板串口通信 | 25 | |--------+--------------+--------------------------------+-------------------------------------------------| 26 | 27 | * 核心配置 28 | #+begin_src yaml 29 | external_components: 30 | - source: 31 | type: git 32 | url: https://github.com/idreamshen/esphome-uart-vrf 33 | ref: v1.1.1 34 | 35 | uart: 36 | - id: myuart1 37 | tx_pin: 1 38 | rx_pin: 3 39 | baud_rate: 9600 40 | 41 | uart_vrf: 42 | 43 | climate: 44 | #+end_src 45 | 46 | * 完整配置 47 | - example/esp01s.yaml: 可用于 esp01s 与 esp8266 48 | - example/esp32wroom32d.yaml: 可用于德姆瑞 vrf 上板的 ESP32-WROOM-32D 芯片 49 | 50 | * 改装成品 51 | 如果希望直接使用成品改装套件,可在闲鱼搜索“VRF 改装”,也可访问 https://p.goofish.com/p/J08WFFKk 进行购买 52 | -------------------------------------------------------------------------------- /components/uart_vrf/__init__.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | import esphome.config_validation as cv 3 | from esphome.const import CONF_ID 4 | from esphome.components import uart 5 | 6 | 7 | DEPENDENCIES = ['uart'] 8 | AUTO_LOAD = ['climate'] 9 | 10 | VRF_ID = 'uart_vrf_id' 11 | 12 | uart_vrf_ns = cg.esphome_ns.namespace('uart_vrf') 13 | UartVrfComponent = uart_vrf_ns.class_('UartVrfComponent', cg.Component, uart.UARTDevice) 14 | 15 | CONFIG_SCHEMA = cv.Schema({ 16 | cv.GenerateID(): cv.declare_id(UartVrfComponent), 17 | }).extend(cv.COMPONENT_SCHEMA).extend(uart.UART_DEVICE_SCHEMA) 18 | 19 | def to_code(config): 20 | u = yield cg.get_variable(config["uart_id"]) 21 | var = cg.new_Pvariable(config[CONF_ID], u) 22 | yield cg.register_component(var, config) 23 | yield uart.register_uart_device(var, config) 24 | -------------------------------------------------------------------------------- /components/uart_vrf/climate.py: -------------------------------------------------------------------------------- 1 | import esphome.codegen as cg 2 | import esphome.config_validation as cv 3 | from esphome.components import climate 4 | from esphome.const import CONF_ID 5 | from .. import UartVrfComponent 6 | 7 | DEPENDENCIES = ['uart_vrf'] 8 | 9 | uart_vrf_ns = cg.esphome_ns.namespace('uart_vrf') 10 | uartVrfClimate = uart_vrf_ns.class_('UartVrfClimate', 11 | climate.Climate, 12 | cg.Component, 13 | cg.Parented.template(UartVrfComponent), 14 | ) 15 | 16 | CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend({ 17 | cv.GenerateID(): cv.declare_id(uartVrfClimate), 18 | cv.GenerateID("uart_vrf_id"): cv.use_id(UartVrfComponent), 19 | cv.Optional("bind_climate_id"): cv.use_id(climate) 20 | }).extend(cv.COMPONENT_SCHEMA) 21 | 22 | def to_code(config): 23 | paren = yield cg.get_variable(config["uart_vrf_id"]) 24 | var = cg.new_Pvariable(config[CONF_ID]) 25 | yield cg.register_parented(var, paren) 26 | yield cg.register_component(var, config) 27 | yield climate.register_climate(var, config) 28 | 29 | # cg.add(paren.register_homekit_entities(var)) 30 | # if "bind_climate_id" in config: 31 | # bind_climate = yield cg.get_variable(config["bind_climate_id"]) 32 | # cg.add(var.set_bind_climate(bind_climate)) -------------------------------------------------------------------------------- /components/uart_vrf/uart_vrf_climate.cpp: -------------------------------------------------------------------------------- 1 | #include "esphome/core/application.h" 2 | #include "uart_vrf_climate.h" 3 | 4 | namespace esphome { 5 | namespace uart_vrf { 6 | 7 | static const char *const TAG = "uart_vrf.climate"; 8 | 9 | void UartVrfClimate::setup() { 10 | ESP_LOGD(TAG, "UartVrfClimate::setup"); 11 | } 12 | 13 | void UartVrfClimate::dump_config() { 14 | } 15 | 16 | void UartVrfClimate::control(const climate::ClimateCall &call) { 17 | if (call.get_mode().has_value()) { 18 | this->mode = *call.get_mode(); 19 | 20 | vrf_protocol::VrfCmd cmd; 21 | switch (this->mode) { 22 | case climate::ClimateMode::CLIMATE_MODE_OFF: 23 | cmd = this->core_climate_->cmd_control_mode(vrf_protocol::VrfClimateMode::CLIMATE_MODE_OFF); 24 | this->get_parent()->send_cmd(cmd); 25 | break; 26 | case climate::ClimateMode::CLIMATE_MODE_COOL: 27 | cmd = this->core_climate_->cmd_control_mode(vrf_protocol::VrfClimateMode::CLIMATE_MODE_COOL); 28 | this->get_parent()->send_cmd(cmd); 29 | break; 30 | case climate::ClimateMode::CLIMATE_MODE_HEAT: 31 | cmd = this->core_climate_->cmd_control_mode(vrf_protocol::VrfClimateMode::CLIMATE_MODE_HEAT); 32 | this->get_parent()->send_cmd(cmd); 33 | break; 34 | case climate::ClimateMode::CLIMATE_MODE_FAN_ONLY: 35 | cmd = this->core_climate_->cmd_control_mode(vrf_protocol::VrfClimateMode::CLIMATE_MODE_FAN_ONLY); 36 | this->get_parent()->send_cmd(cmd); 37 | break; 38 | case climate::ClimateMode::CLIMATE_MODE_DRY: 39 | cmd = this->core_climate_->cmd_control_mode(vrf_protocol::VrfClimateMode::CLIMATE_MODE_DRY); 40 | this->get_parent()->send_cmd(cmd); 41 | break; 42 | default: 43 | break; 44 | } 45 | } 46 | 47 | if (call.get_target_temperature().has_value()) { 48 | uint8_t temperature = uint8_t(*call.get_target_temperature()); 49 | this->target_temperature = temperature; 50 | vrf_protocol::VrfCmd cmd = this->core_climate_->cmd_control_target_temperature(this->target_temperature); 51 | this->get_parent()->send_cmd(cmd); 52 | } 53 | 54 | if (call.get_fan_mode().has_value()) { 55 | this->fan_mode = *call.get_fan_mode(); 56 | vrf_protocol::VrfCmd cmd; 57 | switch (*this->fan_mode) { 58 | case climate::ClimateFanMode::CLIMATE_FAN_AUTO: 59 | cmd = this->core_climate_->cmd_control_fan_mode(vrf_protocol::VrfClimateFanMode::CLIMATE_FAN_MODE_AUTO); 60 | this->get_parent()->send_cmd(cmd); 61 | break; 62 | case climate::ClimateFanMode::CLIMATE_FAN_LOW: 63 | cmd = this->core_climate_->cmd_control_fan_mode(vrf_protocol::VrfClimateFanMode::CLIMATE_FAN_MODE_LOW); 64 | this->get_parent()->send_cmd(cmd); 65 | break; 66 | case climate::ClimateFanMode::CLIMATE_FAN_MEDIUM: 67 | cmd = this->core_climate_->cmd_control_fan_mode(vrf_protocol::VrfClimateFanMode::CLIMATE_FAN_MODE_MEDIUM); 68 | this->get_parent()->send_cmd(cmd); 69 | break; 70 | case climate::ClimateFanMode::CLIMATE_FAN_HIGH: 71 | cmd = this->core_climate_->cmd_control_fan_mode(vrf_protocol::VrfClimateFanMode::CLIMATE_FAN_MODE_HIGH); 72 | this->get_parent()->send_cmd(cmd); 73 | break; 74 | default: 75 | break; 76 | } 77 | } 78 | 79 | this->publish_state(); 80 | return; 81 | } 82 | 83 | climate::ClimateTraits UartVrfClimate::traits() { 84 | auto traits = climate::ClimateTraits(); 85 | traits.set_supports_current_temperature(true); 86 | traits.set_visual_target_temperature_step(1); 87 | traits.set_visual_temperature_step(1); 88 | traits.set_visual_min_temperature(16); 89 | traits.set_visual_max_temperature(30); 90 | traits.set_supported_fan_modes({ 91 | climate::CLIMATE_FAN_AUTO, 92 | climate::CLIMATE_FAN_LOW, 93 | climate::CLIMATE_FAN_MEDIUM, 94 | climate::CLIMATE_FAN_HIGH 95 | }); 96 | traits.set_supported_modes({ 97 | climate::CLIMATE_MODE_OFF, 98 | climate::CLIMATE_MODE_HEAT, 99 | climate::CLIMATE_MODE_COOL, 100 | climate::CLIMATE_MODE_FAN_ONLY, 101 | climate::CLIMATE_MODE_DRY 102 | }); 103 | return traits; 104 | }; 105 | 106 | } 107 | } -------------------------------------------------------------------------------- /components/uart_vrf/uart_vrf_climate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome/core/component.h" 4 | #include "esphome/components/climate/climate.h" 5 | #include "./uart_vrf_component.h" 6 | 7 | namespace esphome { 8 | namespace uart_vrf { 9 | 10 | class UartVrfComponent; 11 | 12 | class UartVrfClimate : public Component, public climate::Climate, public Parented { 13 | public: 14 | UartVrfClimate(vrf_protocol::VrfClimate* core_climate) { 15 | this->core_climate_ = core_climate; 16 | } 17 | void setup() override; 18 | void dump_config() override; 19 | void control(const climate::ClimateCall &call) override; 20 | climate::ClimateTraits traits() override; 21 | vrf_protocol::VrfClimate* get_core_climate() { return this->core_climate_; }; 22 | 23 | protected: 24 | vrf_protocol::VrfClimate* core_climate_; 25 | 26 | private: 27 | 28 | }; 29 | 30 | } // namespace uart_vrf 31 | } // namespace esphome -------------------------------------------------------------------------------- /components/uart_vrf/uart_vrf_component.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "vrf_zhonghong.h" 4 | #include "vrf_demry.h" 5 | #include "uart_vrf_component.h" 6 | #include "uart_vrf_climate.h" 7 | 8 | namespace esphome { 9 | namespace uart_vrf { 10 | 11 | static const char *const TAG = "uart_vrf"; 12 | 13 | void VrfGatewayWrapper::add_gateway(vrf_protocol::VrfGateway* gateway) { 14 | this->gateways_.push_back(gateway); 15 | } 16 | 17 | void VrfGatewayWrapper::consume_data(uint8_t data) { 18 | if (this->vrf_gateway_ != nullptr) { 19 | this->vrf_gateway_->consume_data(data); 20 | return; 21 | } 22 | 23 | for (auto& gateway : this->gateways_) { 24 | if (gateway->get_climates().size() > 0) { 25 | this->vrf_gateway_ = gateway; 26 | this->vrf_gateway_->consume_data(data); 27 | return; 28 | } 29 | } 30 | 31 | for (auto& gateway : this->gateways_) { 32 | gateway->consume_data(data); 33 | } 34 | } 35 | 36 | uint8_t VrfGatewayWrapper::get_next_idx() { 37 | if (this->next_idx_ >= this->gateways_.size()) { 38 | this->next_idx_ = 0; 39 | } 40 | 41 | return this->next_idx_; 42 | } 43 | 44 | void VrfGatewayWrapper::incr_next_idx() { 45 | this->next_idx_ = this->next_idx_ + 1; 46 | 47 | if (this->next_idx_ >= this->gateways_.size()) { 48 | this->next_idx_ = 0; 49 | } 50 | } 51 | 52 | vrf_protocol::VrfCmd VrfGatewayWrapper::cmd_find_climates() { 53 | if (this->vrf_gateway_ != nullptr) { 54 | return this->vrf_gateway_->cmd_find_climates(); 55 | } 56 | 57 | if (this->gateways_.size() == 0) { 58 | return {}; 59 | } 60 | 61 | vrf_protocol::VrfCmd cmd = this->gateways_[this->get_next_idx()]->cmd_find_climates(); 62 | this->incr_next_idx(); 63 | return cmd; 64 | } 65 | 66 | vrf_protocol::VrfCmd VrfGatewayWrapper::cmd_query_next_climate() { 67 | if (this->vrf_gateway_ == nullptr) { 68 | return {}; 69 | } 70 | 71 | return this->vrf_gateway_->cmd_query_next_climate(); 72 | } 73 | 74 | std::vector VrfGatewayWrapper::get_climates() { 75 | if (this->vrf_gateway_ == nullptr) { 76 | return {}; 77 | } 78 | 79 | return this->vrf_gateway_->get_climates(); 80 | } 81 | 82 | void UartVrfComponent::setup() { 83 | ESP_LOGD(TAG, "setup"); 84 | 85 | vrf_protocol::VrfGateway* demryGateway = new vrf_protocol::VrfDemryGateway(1); 86 | vrf_protocol::VrfGateway* zhonghongGateway = new vrf_protocol::VrfZhonghongGateway(1); 87 | 88 | demryGateway->add_on_climate_create_callback([this](vrf_protocol::VrfClimate* climate) { 89 | this->on_climate_create_callback(climate); 90 | }); 91 | 92 | zhonghongGateway->add_on_climate_create_callback([this](vrf_protocol::VrfClimate* climate) { 93 | this->on_climate_create_callback(climate); 94 | }); 95 | 96 | this->vrf_gateway_wrapper_ = new VrfGatewayWrapper(); 97 | this->vrf_gateway_wrapper_->add_gateway(demryGateway); 98 | this->vrf_gateway_wrapper_->add_gateway(zhonghongGateway); 99 | 100 | this->set_interval("fire_cmd", 300, [this] { this->fire_cmd(); }); 101 | this->set_interval("find_climates", 5000, [this] { this->find_climates(); }); 102 | this->set_interval("query_next_climate", 1000, [this] { this->query_next_climate(); }); 103 | } 104 | 105 | void UartVrfComponent::on_climate_create_callback(vrf_protocol::VrfClimate* climate) { 106 | climate->add_on_state_callback([this](vrf_protocol::VrfClimate* climate) { 107 | this->on_climate_state_callback(climate); 108 | }); 109 | 110 | auto *uart_climate = new UartVrfClimate(climate); 111 | uart_climate->set_parent(this); 112 | uart_climate->set_name(climate->get_name().c_str()); 113 | uart_climate->set_object_id(climate->get_name().c_str()); 114 | App.register_component(uart_climate); 115 | App.register_climate(uart_climate); 116 | this->climates_.push_back(uart_climate); 117 | } 118 | 119 | void UartVrfComponent::on_climate_state_callback(vrf_protocol::VrfClimate* vrf_climate) { 120 | UartVrfClimate* target_climate = nullptr; 121 | for (auto& climate : this->climates_) { 122 | if (climate->get_core_climate() == vrf_climate) { 123 | target_climate = climate; 124 | } 125 | } 126 | 127 | if (target_climate == nullptr) { 128 | return; 129 | } 130 | 131 | target_climate->current_temperature = vrf_climate->get_current_temperature(); 132 | target_climate->target_temperature = vrf_climate->get_target_temperature(); 133 | 134 | switch (vrf_climate->get_mode()) { 135 | case vrf_protocol::VrfClimateMode::CLIMATE_MODE_OFF: 136 | target_climate->mode = climate::ClimateMode::CLIMATE_MODE_OFF; 137 | break; 138 | case vrf_protocol::VrfClimateMode::CLIMATE_MODE_COOL: 139 | target_climate->mode = climate::ClimateMode::CLIMATE_MODE_COOL; 140 | break; 141 | case vrf_protocol::VrfClimateMode::CLIMATE_MODE_HEAT: 142 | target_climate->mode = climate::ClimateMode::CLIMATE_MODE_HEAT; 143 | break; 144 | case vrf_protocol::VrfClimateMode::CLIMATE_MODE_FAN_ONLY: 145 | target_climate->mode = climate::ClimateMode::CLIMATE_MODE_FAN_ONLY; 146 | break; 147 | case vrf_protocol::VrfClimateMode::CLIMATE_MODE_DRY: 148 | target_climate->mode = climate::ClimateMode::CLIMATE_MODE_DRY; 149 | break; 150 | default: 151 | break; 152 | } 153 | 154 | switch (vrf_climate->get_fan_mode()) { 155 | case vrf_protocol::VrfClimateFanMode::CLIMATE_FAN_MODE_AUTO: 156 | target_climate->fan_mode = climate::ClimateFanMode::CLIMATE_FAN_AUTO; 157 | break; 158 | case vrf_protocol::VrfClimateFanMode::CLIMATE_FAN_MODE_LOW: 159 | target_climate->fan_mode = climate::ClimateFanMode::CLIMATE_FAN_LOW; 160 | break; 161 | case vrf_protocol::VrfClimateFanMode::CLIMATE_FAN_MODE_MEDIUM: 162 | target_climate->fan_mode = climate::ClimateFanMode::CLIMATE_FAN_MEDIUM; 163 | break; 164 | case vrf_protocol::VrfClimateFanMode::CLIMATE_FAN_MODE_HIGH: 165 | target_climate->fan_mode = climate::ClimateFanMode::CLIMATE_FAN_HIGH; 166 | break; 167 | default: 168 | break; 169 | } 170 | 171 | target_climate->publish_state(); 172 | } 173 | 174 | void UartVrfComponent::loop() { 175 | if (this->vrf_gateway_wrapper_ == nullptr) { 176 | return; 177 | } 178 | 179 | while(available() > 0) { 180 | uint8_t c = read(); 181 | this->vrf_gateway_wrapper_->consume_data(c); 182 | } 183 | } 184 | 185 | void UartVrfComponent::send_cmd(vrf_protocol::VrfCmd cmd) { 186 | while (cmd.has_next_cmd_val()) { 187 | std::vector cmd_val = cmd.next_cmd_val(); 188 | if (cmd_val.size() == 0) { 189 | continue; 190 | } 191 | 192 | this->pending_cmds_.push_back(cmd_val); 193 | } 194 | 195 | this->fire_cmd(); 196 | } 197 | 198 | void UartVrfComponent::find_climates() { 199 | if (this->vrf_gateway_wrapper_ == nullptr) { 200 | return; 201 | } 202 | 203 | if (this->vrf_gateway_wrapper_->get_climates().size() == 0) { 204 | vrf_protocol::VrfCmd cmd = this->vrf_gateway_wrapper_->cmd_find_climates(); 205 | this->send_cmd(cmd); 206 | } 207 | } 208 | 209 | void UartVrfComponent::fire_cmd() { 210 | unsigned long now = millis(); 211 | if (now - last_time_fire_cmd < 100) { 212 | return; 213 | } 214 | 215 | if (this->pending_cmds_.size() <= 0) { 216 | return; 217 | } 218 | 219 | last_time_fire_cmd = now; 220 | 221 | std::vector cmd_val = this->pending_cmds_[0]; 222 | this->pending_cmds_.erase(this->pending_cmds_.begin(), this->pending_cmds_.begin() + 1); 223 | ESP_LOGD(TAG, "uart send %s", format_hex_pretty(cmd_val).c_str()); 224 | write_array(cmd_val.data(), cmd_val.size()); 225 | } 226 | 227 | void UartVrfComponent::query_next_climate() { 228 | if (this->pending_cmds_.size() <= 2) { 229 | vrf_protocol::VrfCmd cmd = this->vrf_gateway_wrapper_->cmd_query_next_climate(); 230 | this->send_cmd(cmd); 231 | } 232 | } 233 | 234 | 235 | } // namespace uart_vrf 236 | } // namespace esphome 237 | -------------------------------------------------------------------------------- /components/uart_vrf/uart_vrf_component.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "esphome.h" 4 | #include "esphome/core/component.h" 5 | #include "esphome/components/uart/uart.h" 6 | 7 | namespace vrf_protocol { // Forward declaration 8 | class VrfCmd; 9 | class VrfGateway; 10 | } 11 | #include "vrf.h" 12 | 13 | namespace esphome { 14 | namespace uart_vrf { 15 | 16 | class UartVrfClimate; 17 | 18 | class VrfGatewayWrapper { 19 | 20 | public: 21 | void add_gateway(vrf_protocol::VrfGateway* gateway); 22 | void consume_data(uint8_t data); 23 | vrf_protocol::VrfCmd cmd_find_climates(); 24 | vrf_protocol::VrfCmd cmd_query_next_climate(); 25 | std::vector get_climates(); 26 | 27 | private: 28 | // 目标 29 | vrf_protocol::VrfGateway* vrf_gateway_{nullptr}; 30 | std::vector gateways_; 31 | uint8_t next_idx_{0}; 32 | 33 | uint8_t get_next_idx(); 34 | void incr_next_idx(); 35 | }; 36 | 37 | class UartVrfComponent : public Component, public uart::UARTDevice { 38 | public: 39 | UartVrfComponent(uart::UARTComponent *uartComponent) : Component(), UARTDevice(uartComponent) { 40 | this->uart_ = uartComponent; 41 | }; 42 | void setup() override; 43 | void loop() override; 44 | void send_cmd(vrf_protocol::VrfCmd cmd); 45 | uart::UARTComponent* get_uart() { return this->uart_; }; 46 | void on_climate_create_callback(vrf_protocol::VrfClimate* climate); 47 | void on_climate_state_callback(vrf_protocol::VrfClimate* climate); 48 | 49 | protected: 50 | uart::UARTComponent* uart_; 51 | VrfGatewayWrapper* vrf_gateway_wrapper_; 52 | std::vector climates_; 53 | std::vector> pending_cmds_; 54 | unsigned long last_time_heartbeat_cmds_{0}; 55 | unsigned long last_time_fire_cmd{0}; 56 | 57 | void fire_cmd(); 58 | void find_climates(); 59 | void query_next_climate(); 60 | 61 | }; 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /components/uart_vrf/vrf.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "vrf.h" 6 | 7 | #include "esphome/core/log.h" 8 | 9 | namespace vrf_protocol { 10 | 11 | uint8_t checksum(std::vector cmd) { 12 | uint8_t sum = 0; 13 | for (uint8_t i = 0; i < cmd.size(); i++) 14 | { 15 | sum = sum + cmd[i]; 16 | } 17 | return sum; 18 | } 19 | 20 | void VrfClimate::set_mode(VrfClimateMode mode) { 21 | if (this->mode_ != mode) { 22 | this->need_fire_data_updated=true; 23 | } 24 | this->mode_ = mode; 25 | }; 26 | 27 | void VrfClimate::set_fan_mode(VrfClimateFanMode fan_mode) { 28 | if (this->fan_mode_ != fan_mode) { 29 | this->need_fire_data_updated=true; 30 | } 31 | this->fan_mode_ = fan_mode; 32 | }; 33 | 34 | void VrfClimate::set_current_temperature(uint8_t temperature) { 35 | if (this->current_temperature_ != temperature) { 36 | this->need_fire_data_updated=true; 37 | } 38 | this->current_temperature_ = temperature; 39 | }; 40 | 41 | void VrfClimate::set_target_temperature(uint8_t temperature) { 42 | if (this->target_temperature_ != temperature) { 43 | this->need_fire_data_updated=true; 44 | } 45 | this->target_temperature_ = temperature; 46 | }; 47 | 48 | void VrfClimate::fire_data_updated() { 49 | if (this->need_fire_data_updated) { 50 | this->need_fire_data_updated = false; 51 | for (auto &callback : this-> on_state_callbacks_) { 52 | callback(this); 53 | } 54 | } 55 | } 56 | 57 | VrfCmd VrfGateway::cmd_query_next_climate() { 58 | VrfCmd empty_cmd = VrfCmd(); 59 | 60 | if (this->climates_.size() == 0) { 61 | return empty_cmd; 62 | } 63 | 64 | if (this->next_query_climate_idx_ >= this->climates_.size()) { 65 | this->next_query_climate_idx_ = 0; 66 | } 67 | 68 | VrfCmd cmd = this->climates_[this->next_query_climate_idx_]->cmd_query(); 69 | this->next_query_climate_idx_ = this->next_query_climate_idx_ + 1; 70 | return cmd; 71 | } 72 | 73 | void VrfGateway::add_on_climate_create_callback(std::function &&callback) { 74 | this->climate_create_callbacks_.push_back(callback); 75 | }; 76 | 77 | std::vector VrfGateway::get_climates() { 78 | return this->climates_; 79 | }; 80 | 81 | } -------------------------------------------------------------------------------- /components/uart_vrf/vrf.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "vrf_const.h" 8 | 9 | namespace vrf_protocol { 10 | 11 | uint8_t checksum(std::vector cmd); 12 | 13 | class VrfCmd { 14 | public: 15 | VrfCmd() {} 16 | VrfCmd(std::vector cmd) { this->cmds_.push_back(cmd); } 17 | VrfCmd(std::vector> cmds) { this->cmds_ = cmds; } 18 | 19 | bool has_next_cmd_val() { 20 | if (this->cmds_.size() == 0) { 21 | return false; 22 | } 23 | 24 | if (idx_ >= this->cmds_.size()) { 25 | return false; 26 | } 27 | 28 | return true; 29 | } 30 | 31 | std::vector next_cmd_val() { 32 | if (!has_next_cmd_val()) { 33 | return {}; 34 | } 35 | 36 | std::vector next_val = this->cmds_[idx_]; 37 | idx_ = idx_ + 1; 38 | return next_val; 39 | } 40 | 41 | protected: 42 | uint8_t idx_{0}; 43 | std::vector> cmds_; 44 | }; 45 | 46 | class VrfClimate { 47 | public: 48 | 49 | VrfClimateMode get_mode() { return this->mode_; }; 50 | uint8_t get_current_temperature() { return this->current_temperature_; }; 51 | uint8_t get_target_temperature() { return this->target_temperature_; }; 52 | VrfClimateFanMode get_fan_mode() { return this->fan_mode_; }; 53 | void set_mode(VrfClimateMode mode); 54 | void set_fan_mode(VrfClimateFanMode fan_mode); 55 | void set_current_temperature(uint8_t temperature); 56 | void set_target_temperature(uint8_t temperature); 57 | void fire_data_updated(); 58 | void add_on_state_callback(std::function &&callback) { this->on_state_callbacks_.push_back(callback); }; 59 | std::string& get_unique_id() { return this->unique_id_; }; 60 | std::string& get_name() { return this->name_; }; 61 | 62 | virtual VrfCmd cmd_query() = 0; 63 | virtual VrfCmd cmd_control_mode(VrfClimateMode mode) = 0; 64 | virtual VrfCmd cmd_control_target_temperature(uint8_t target_temperature) = 0; 65 | virtual VrfCmd cmd_control_fan_mode(VrfClimateFanMode mode) = 0; 66 | 67 | protected: 68 | uint8_t slave_addr_; 69 | std::string unique_id_; 70 | std::string name_; 71 | VrfClimateMode mode_{VrfClimateMode::CLIMATE_MODE_OFF}; 72 | uint8_t current_temperature_{21}; 73 | uint8_t target_temperature_; 74 | VrfClimateFanMode fan_mode_{VrfClimateFanMode::CLIMATE_FAN_MODE_LOW}; 75 | bool need_fire_data_updated{false}; 76 | std::vector> on_state_callbacks_; 77 | }; 78 | 79 | class VrfGateway { 80 | public: 81 | VrfGateway(uint8_t slave_addr) { this->slave_addr_=slave_addr;}; 82 | virtual void consume_data(uint8_t data) = 0; 83 | virtual VrfCmd cmd_find_climates() = 0; 84 | VrfCmd cmd_query_next_climate(); 85 | 86 | // 当 climate 被创建时触发 87 | void add_on_climate_create_callback(std::function &&callback); 88 | 89 | std::vector get_climates(); 90 | 91 | protected: 92 | uint8_t slave_addr_; 93 | uint8_t next_query_climate_idx_{0}; 94 | std::vector climates_; 95 | std::vector> climate_create_callbacks_; 96 | }; 97 | 98 | } -------------------------------------------------------------------------------- /components/uart_vrf/vrf_const.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace vrf_protocol { 4 | 5 | enum class VrfClimateMode : uint8_t { 6 | CLIMATE_MODE_OFF = 0, 7 | CLIMATE_MODE_COOL = 1, 8 | CLIMATE_MODE_HEAT = 2, 9 | CLIMATE_MODE_FAN_ONLY = 3, 10 | CLIMATE_MODE_DRY = 4, 11 | }; 12 | 13 | enum class VrfClimateFanMode : uint8_t { 14 | CLIMATE_FAN_MODE_AUTO = 0, 15 | CLIMATE_FAN_MODE_LOW = 1, 16 | CLIMATE_FAN_MODE_MEDIUM = 2, 17 | CLIMATE_FAN_MODE_HIGH = 3, 18 | }; 19 | 20 | } -------------------------------------------------------------------------------- /components/uart_vrf/vrf_demry.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "esphome.h" 4 | #include "vrf_demry.h" 5 | 6 | namespace vrf_protocol { 7 | 8 | static const char *const TAG = "vrf_protocol.demry"; 9 | 10 | void VrfDemryGateway::consume_data_handle_found_climates() { 11 | uint8_t tmp[4] = {this->data_[5], this->data_[6], this->data_[7], this->data_[8]}; 12 | for (int i=0; i < 4; i++) { 13 | std::bitset<8> bits(tmp[i]); 14 | for (int j=0; j < 8; j++) { 15 | if (bits.test(j)) { 16 | uint8_t id = (3-i) * 8 + j; 17 | VrfDemryClimate* target_climate = this->find_or_create_climate(id); 18 | } 19 | } 20 | } 21 | } 22 | 23 | void VrfDemryGateway::consume_data_handle_query_climate() { 24 | uint8_t id = this->data_[1]; 25 | uint8_t climate_switch = this->data_[2]; 26 | uint8_t climate_mode = this->data_[3]; 27 | uint8_t climate_target_temperature = this->data_[4]; 28 | uint8_t climate_fan_mode = this->data_[5]; 29 | uint8_t climate_current_temperature = this->data_[6]; 30 | 31 | VrfDemryClimate* target_climate = this->find_or_create_climate(id); 32 | 33 | if (climate_switch == false) { 34 | target_climate->set_mode(VrfClimateMode::CLIMATE_MODE_OFF); 35 | } else { 36 | switch (VrfDemryClimateMode(climate_mode)) 37 | { 38 | case VrfDemryClimateMode::DEMRY_CLIMATE_MODE_COOL: 39 | target_climate->set_mode(VrfClimateMode::CLIMATE_MODE_COOL); 40 | break; 41 | case VrfDemryClimateMode::DEMRY_CLIMATE_MODE_HEAT: 42 | target_climate->set_mode(VrfClimateMode::CLIMATE_MODE_HEAT); 43 | break; 44 | case VrfDemryClimateMode::DEMRY_CLIMATE_MODE_FAN: 45 | target_climate->set_mode(VrfClimateMode::CLIMATE_MODE_FAN_ONLY); 46 | break; 47 | case VrfDemryClimateMode::DEMRY_CLIMATE_MODE_DRY: 48 | target_climate->set_mode(VrfClimateMode::CLIMATE_MODE_DRY); 49 | break; 50 | default: 51 | break; 52 | } 53 | } 54 | 55 | target_climate->set_target_temperature(climate_target_temperature); 56 | target_climate->set_current_temperature(climate_current_temperature); 57 | 58 | switch (VrfDemryClimateFanSpeed(climate_fan_mode)) 59 | { 60 | case VrfDemryClimateFanSpeed::DEMRY_CLIMATE_FAN_SPEED_AUTO: 61 | target_climate->set_fan_mode(VrfClimateFanMode::CLIMATE_FAN_MODE_AUTO); 62 | break; 63 | case VrfDemryClimateFanSpeed::DEMRY_CLIMATE_FAN_SPEED_HIGH: 64 | target_climate->set_fan_mode(VrfClimateFanMode::CLIMATE_FAN_MODE_HIGH); 65 | break; 66 | case VrfDemryClimateFanSpeed::DEMRY_CLIMATE_FAN_SPEED_MEDIUM: 67 | target_climate->set_fan_mode(VrfClimateFanMode::CLIMATE_FAN_MODE_MEDIUM); 68 | break; 69 | case VrfDemryClimateFanSpeed::DEMRY_CLIMATE_FAN_SPEED_LOW: 70 | target_climate->set_fan_mode(VrfClimateFanMode::CLIMATE_FAN_MODE_LOW); 71 | break; 72 | default: 73 | break; 74 | } 75 | target_climate->fire_data_updated(); 76 | } 77 | 78 | void VrfDemryGateway::consume_data(uint8_t data) { 79 | this->data_.push_back(data); 80 | 81 | while (this->data_.size() > 0) { 82 | 83 | if (this->data_.size() < 10) { 84 | return; 85 | } 86 | 87 | uint8_t sum = checksum(std::vector(this->data_.begin(), this->data_.begin() + 9)); 88 | if (sum != this->data_[9]) { 89 | // checksum failed 90 | this->data_.erase(this->data_.begin(), this->data_.begin() + 1); 91 | continue; 92 | } 93 | 94 | if (this->data_[0] != this->slave_addr_) { 95 | // not my data 96 | this->data_.clear(); 97 | return; 98 | } 99 | 100 | if (this->data_[1] == 0xAA) { 101 | this->consume_data_handle_found_climates(); 102 | } else { 103 | this->consume_data_handle_query_climate(); 104 | } 105 | 106 | // erase 10 bytes 107 | esphome::ESP_LOGD(TAG, "consume succ, data=%s", esphome::format_hex_pretty(this->data_.data(), 10).c_str()); 108 | this->data_.erase(this->data_.begin(), this->data_.begin() + 10); 109 | } 110 | } 111 | 112 | VrfCmd VrfDemryGateway::cmd_find_climates() { 113 | std::vector cmd = { 114 | this->slave_addr_, 0xAA, 0xFF, 0xFF, 115 | 0xFF, 0xFF, 0xFF, 0xFF, 116 | 0xFF }; 117 | uint8_t sum = checksum(cmd); 118 | cmd.push_back(sum); 119 | return VrfCmd(cmd); 120 | } 121 | 122 | VrfDemryClimate* VrfDemryGateway::find_or_create_climate(uint8_t id) { 123 | VrfDemryClimate* target_climate = nullptr; 124 | for(auto &climate : this->climates_) { 125 | VrfDemryClimate* climate_cast = reinterpret_cast(climate); 126 | if (climate_cast->get_id() == id) { 127 | target_climate = climate_cast; 128 | break; 129 | } 130 | } 131 | 132 | if (target_climate == nullptr) { 133 | target_climate = new VrfDemryClimate(this->slave_addr_, id); 134 | this->climates_.push_back(target_climate); 135 | 136 | for(auto &callback : this->climate_create_callbacks_) { 137 | callback(target_climate); 138 | } 139 | } 140 | 141 | return target_climate; 142 | } 143 | 144 | VrfCmd VrfDemryClimate::cmd_query() { 145 | std::vector cmd = { 146 | this->slave_addr_, this->id_, 0xFF, 0xFF, 147 | 0xFF, 0xFF, 0xFF, 0xFF, 148 | 0xFF}; 149 | uint8_t sum = checksum(cmd); 150 | cmd.push_back(sum); 151 | return VrfCmd(cmd); 152 | } 153 | 154 | static VrfDemryClimateSwitch convert_vrf_mode_to_demry_switch(VrfClimateMode mode) { 155 | if (mode == VrfClimateMode::CLIMATE_MODE_OFF) { 156 | return VrfDemryClimateSwitch::DEMRY_CLIAMTE_SWITCH_DISABLE; 157 | } 158 | return VrfDemryClimateSwitch::DEMRY_CLIAMTE_SWITCH_ENABLE; 159 | } 160 | 161 | static VrfDemryClimateMode convert_vrf_mode_to_demry_mode(VrfClimateMode mode) { 162 | if (mode == VrfClimateMode::CLIMATE_MODE_OFF) { 163 | return VrfDemryClimateMode::DEMRY_CLIMATE_MODE_HOLD; 164 | } else if (mode == VrfClimateMode::CLIMATE_MODE_COOL) { 165 | return VrfDemryClimateMode::DEMRY_CLIMATE_MODE_COOL; 166 | } else if (mode == VrfClimateMode::CLIMATE_MODE_HEAT) { 167 | return VrfDemryClimateMode::DEMRY_CLIMATE_MODE_HEAT; 168 | } else if (mode == VrfClimateMode::CLIMATE_MODE_FAN_ONLY) { 169 | return VrfDemryClimateMode::DEMRY_CLIMATE_MODE_FAN; 170 | } else if (mode == VrfClimateMode::CLIMATE_MODE_DRY) { 171 | return VrfDemryClimateMode::DEMRY_CLIMATE_MODE_DRY; 172 | } 173 | 174 | return VrfDemryClimateMode::DEMRY_CLIMATE_MODE_HOLD; 175 | } 176 | 177 | static VrfDemryClimateFanSpeed convert_vrf_fan_mode_to_demry_fan_speed(VrfClimateFanMode fan_mode) { 178 | if (fan_mode == VrfClimateFanMode::CLIMATE_FAN_MODE_AUTO) { 179 | return VrfDemryClimateFanSpeed::DEMRY_CLIMATE_FAN_SPEED_AUTO; 180 | } else if (fan_mode == VrfClimateFanMode::CLIMATE_FAN_MODE_HIGH) { 181 | return VrfDemryClimateFanSpeed::DEMRY_CLIMATE_FAN_SPEED_HIGH; 182 | } else if (fan_mode == VrfClimateFanMode::CLIMATE_FAN_MODE_MEDIUM) { 183 | return VrfDemryClimateFanSpeed::DEMRY_CLIMATE_FAN_SPEED_MEDIUM; 184 | } else if (fan_mode == VrfClimateFanMode::CLIMATE_FAN_MODE_LOW) { 185 | return VrfDemryClimateFanSpeed::DEMRY_CLIMATE_FAN_SPEED_LOW; 186 | } 187 | 188 | return VrfDemryClimateFanSpeed::DEMRY_CLIMATE_FAN_SPEED_HOLD; 189 | } 190 | 191 | VrfCmd VrfDemryClimate::cmd_control_mode(VrfClimateMode mode) { 192 | this->need_fire_data_updated = true; 193 | 194 | std::vector cmd = { 195 | this->slave_addr_, 196 | this->id_, 197 | uint8_t(convert_vrf_mode_to_demry_switch(mode)), // 开关 198 | uint8_t(convert_vrf_mode_to_demry_mode(mode)), // 模式 199 | this->target_temperature_, // 目标温度 200 | uint8_t(convert_vrf_fan_mode_to_demry_fan_speed(this->fan_mode_)), // 风速 201 | 0xFF, 202 | 0xFF, 203 | 0xFF}; 204 | uint8_t sum = checksum(cmd); 205 | cmd.push_back(sum); 206 | return VrfCmd({cmd}); 207 | } 208 | 209 | VrfCmd VrfDemryClimate::cmd_control_target_temperature(uint8_t target_temperature) { 210 | this->need_fire_data_updated = true; 211 | 212 | std::vector cmd = { 213 | this->slave_addr_, 214 | this->id_, 215 | uint8_t(convert_vrf_mode_to_demry_switch(this->mode_)), // 开关 216 | uint8_t(convert_vrf_mode_to_demry_mode(this->mode_)), // 模式 217 | target_temperature, // 目标温度 218 | uint8_t(convert_vrf_fan_mode_to_demry_fan_speed(this->fan_mode_)), // 风速 219 | 0xFF, 220 | 0xFF, 221 | 0xFF}; 222 | uint8_t sum = checksum(cmd); 223 | cmd.push_back(sum); 224 | return VrfCmd(cmd); 225 | } 226 | 227 | VrfCmd VrfDemryClimate::cmd_control_fan_mode(VrfClimateFanMode mode) { 228 | this->need_fire_data_updated = true; 229 | 230 | std::vector cmd = { 231 | this->slave_addr_, 232 | this->id_, 233 | uint8_t(convert_vrf_mode_to_demry_switch(this->mode_)), // 开关 234 | uint8_t(convert_vrf_mode_to_demry_mode(this->mode_)), // 模式 235 | this->target_temperature_, // 目标温度 236 | uint8_t(convert_vrf_fan_mode_to_demry_fan_speed(mode)), // 风速 237 | 0xFF, 238 | 0xFF, 239 | 0xFF}; 240 | uint8_t sum = checksum(cmd); 241 | cmd.push_back(sum); 242 | return VrfCmd(cmd); 243 | } 244 | } 245 | 246 | -------------------------------------------------------------------------------- /components/uart_vrf/vrf_demry.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "esphome/core/helpers.h" 5 | #include "vrf.h" 6 | #include "vrf_demry_const.h" 7 | 8 | namespace vrf_protocol { 9 | 10 | class VrfDemryClimate : public VrfClimate { 11 | public: 12 | VrfDemryClimate(uint8_t slave_addr, uint8_t id) { 13 | this->slave_addr_ = slave_addr; 14 | this->id_ = id; 15 | this->unique_id_ = esphome::str_sprintf("%d_%d", this->slave_addr_, this->id_); 16 | this->name_ = esphome::str_sprintf("vrf_climate_%s", this->unique_id_.c_str()); 17 | }; 18 | 19 | uint8_t get_id() { return this->id_; }; 20 | VrfCmd cmd_query() override; 21 | VrfCmd cmd_control_mode(vrf_protocol::VrfClimateMode mode) override; 22 | VrfCmd cmd_control_target_temperature(uint8_t target_temperature) override; 23 | VrfCmd cmd_control_fan_mode(vrf_protocol::VrfClimateFanMode mode) override; 24 | 25 | protected: 26 | uint8_t id_; 27 | }; 28 | 29 | class VrfDemryGateway : public VrfGateway { 30 | public: 31 | VrfDemryGateway(uint8_t slave_addr): VrfGateway(slave_addr) {}; 32 | void consume_data(uint8_t data) override; 33 | VrfCmd cmd_find_climates() override; 34 | 35 | VrfDemryClimate* find_or_create_climate(uint8_t id); 36 | 37 | protected: 38 | std::vector data_; 39 | 40 | private: 41 | void consume_data_handle_found_climates(); 42 | void consume_data_handle_query_climate(); 43 | }; 44 | 45 | } -------------------------------------------------------------------------------- /components/uart_vrf/vrf_demry_const.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace vrf_protocol { 4 | 5 | enum class VrfDemryClimateSwitch : uint8_t { 6 | DEMRY_CLIAMTE_SWITCH_DISABLE = 0x00, 7 | DEMRY_CLIAMTE_SWITCH_ENABLE = 0x01, 8 | }; 9 | 10 | enum class VrfDemryClimateMode : uint8_t { 11 | DEMRY_CLIMATE_MODE_HEAT = 0x01, 12 | DEMRY_CLIMATE_MODE_COOL = 0x02, 13 | DEMRY_CLIMATE_MODE_FAN = 0x04, 14 | DEMRY_CLIMATE_MODE_DRY = 0x08, 15 | 16 | DEMRY_CLIMATE_MODE_HOLD = 0xFF, 17 | }; 18 | 19 | enum class VrfDemryClimateFanSpeed : uint8_t { 20 | DEMRY_CLIMATE_FAN_SPEED_AUTO = 0x00, 21 | DEMRY_CLIMATE_FAN_SPEED_LOW = 0x01, 22 | DEMRY_CLIMATE_FAN_SPEED_MEDIUM = 0x02, 23 | DEMRY_CLIMATE_FAN_SPEED_HIGH = 0x03, 24 | 25 | DEMRY_CLIMATE_FAN_SPEED_HOLD = 0xFF, 26 | }; 27 | 28 | } -------------------------------------------------------------------------------- /components/uart_vrf/vrf_zhonghong.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "esphome.h" 4 | #include "esphome/core/hal.h" 5 | #include "vrf_zhonghong.h" 6 | 7 | namespace vrf_protocol { 8 | 9 | static const char *const TAG = "vrf_protocol.zhonghong"; 10 | 11 | void VrfZhonghongGateway::consume_data_handle_found_climates() { 12 | uint8_t num = this->data_[3]; // 空调数量 13 | for (uint8_t i = 0; i < num; i++) 14 | { 15 | uint8_t outdoor_addr = this->data_[4 + i * 3]; 16 | uint8_t indoor_addr = this->data_[4 + i * 3 + 1]; 17 | uint8_t online = this->data_[4 + i * 3 + 2]; 18 | 19 | if (online == 1) { 20 | VrfZhonghongClimate* target_climate = this->find_or_create_climate(outdoor_addr, indoor_addr); 21 | } 22 | } 23 | } 24 | 25 | void VrfZhonghongGateway::consume_data_handle_query_climate() { 26 | uint8_t num = this->data_[3]; // 空调数量 27 | for (uint8_t i = 0; i < num; i++) 28 | { 29 | uint8_t outdoor_addr = this->data_[4 + i * 10]; 30 | uint8_t indoor_addr = this->data_[4 + i * 10 + 1]; 31 | uint8_t on_off = this->data_[4 + i * 10 + 2]; 32 | uint8_t target_temperature = this->data_[4 + i * 10 + 3]; 33 | uint8_t mode = this->data_[4 + i * 10 + 4]; 34 | uint8_t fan_mode = this->data_[4 + i * 10 + 5]; 35 | uint8_t current_temperature = this->data_[4 + i * 10 + 6]; 36 | 37 | VrfZhonghongClimate* target_climate = this->find_or_create_climate(outdoor_addr, indoor_addr); 38 | 39 | if (VrfZhonghongClimateSwitch(on_off) == VrfZhonghongClimateSwitch::ZHONG_HONG_CLIMATE_SWITCH_OFF) { 40 | target_climate->set_mode(VrfClimateMode::CLIMATE_MODE_OFF); 41 | } else { 42 | switch (VrfZhonghongClimateMode(mode)) 43 | { 44 | case VrfZhonghongClimateMode::ZHONG_HONG_CLIMATE_MODE_COOL: 45 | target_climate->set_mode(VrfClimateMode::CLIMATE_MODE_COOL); 46 | break; 47 | case VrfZhonghongClimateMode::ZHONG_HONG_CLIMATE_MODE_HEAT: 48 | target_climate->set_mode(VrfClimateMode::CLIMATE_MODE_HEAT); 49 | break; 50 | case VrfZhonghongClimateMode::ZHONG_HONG_CLIMATE_MODE_FAN: 51 | target_climate->set_mode(VrfClimateMode::CLIMATE_MODE_FAN_ONLY); 52 | break; 53 | case VrfZhonghongClimateMode::ZHONG_HONG_CLIMATE_MODE_DRY: 54 | target_climate->set_mode(VrfClimateMode::CLIMATE_MODE_DRY); 55 | break; 56 | default: 57 | break; 58 | } 59 | } 60 | 61 | switch (VrfZhonghongClimateFanSpeed(fan_mode)) 62 | { 63 | case VrfZhonghongClimateFanSpeed::ZHONG_HONG_CLIMATE_FAN_SPEED_HIGH: 64 | target_climate->set_fan_mode(VrfClimateFanMode::CLIMATE_FAN_MODE_HIGH); 65 | break; 66 | case VrfZhonghongClimateFanSpeed::ZHONG_HONG_CLIMATE_FAN_SPEED_MEDIUM: 67 | target_climate->set_fan_mode(VrfClimateFanMode::CLIMATE_FAN_MODE_MEDIUM); 68 | break; 69 | case VrfZhonghongClimateFanSpeed::ZHONG_HONG_CLIMATE_FAN_SPEED_LOW: 70 | target_climate->set_fan_mode(VrfClimateFanMode::CLIMATE_FAN_MODE_LOW); 71 | break; 72 | default: 73 | break; 74 | } 75 | 76 | if (current_temperature != 0) { 77 | target_climate->set_current_temperature(current_temperature); 78 | } else { 79 | esphome::ESP_LOGW(TAG, "consume data of current_temperature is zero"); 80 | } 81 | target_climate->set_target_temperature(target_temperature); 82 | target_climate->fire_data_updated(); 83 | } 84 | } 85 | 86 | 87 | void VrfZhonghongGateway::consume_data(uint8_t data) { 88 | this->data_.push_back(data); 89 | 90 | while (this->data_.size() >= 6) { 91 | 92 | uint8_t slave_addr = this->data_[0]; 93 | if (slave_addr != this->slave_addr_) { 94 | this->data_.erase(this->data_.begin(), this->data_.begin() + 1); 95 | continue; 96 | } 97 | 98 | uint8_t func = this->data_[1]; 99 | if (func < uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_CTRL_SWITCH) || func > uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_QUERY)) { 100 | this->data_.erase(this->data_.begin(), this->data_.begin() + 2); 101 | continue; 102 | } 103 | 104 | uint8_t length = 0; // 完整命令长度 105 | if (func >= uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_CTRL_SWITCH) && func <= uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_CTRL_FAN_MODE)) { 106 | length = 6; 107 | } 108 | 109 | if (func == uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_QUERY)) { 110 | uint8_t func_value = this->data_[2]; 111 | uint8_t num = this->data_[3]; 112 | 113 | if (func_value == uint8_t(VrfZhonghongFuncValue::ZHONG_HONG_FUNC_VALUE_ONLINE)) { 114 | length = 4 + num * 3 + 1; 115 | } else { 116 | length = 4 + num * 10 + 1; 117 | } 118 | } 119 | 120 | if (this->data_.size() < length) { 121 | break; 122 | } 123 | 124 | if (length == 0) { 125 | this->data_.clear(); 126 | continue; 127 | } 128 | 129 | uint8_t sum = checksum(std::vector(this->data_.begin(), this->data_.begin() + length - 1)); 130 | 131 | if (sum != this->data_[length - 1]) { 132 | // checksum failed 133 | this->data_.erase(this->data_.begin(), this->data_.begin() + 1); 134 | continue; 135 | } 136 | 137 | if (func == uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_QUERY)) { 138 | uint8_t func_value = this->data_[2]; 139 | uint8_t num = this->data_[3]; // 空调数量 140 | 141 | if (func_value == uint8_t(VrfZhonghongFuncValue::ZHONG_HONG_FUNC_VALUE_ONLINE)) { 142 | this->consume_data_handle_found_climates(); 143 | } else { 144 | this->consume_data_handle_query_climate(); 145 | } 146 | } 147 | 148 | esphome::ESP_LOGD(TAG, "consume succ, data=%s", esphome::format_hex_pretty(this->data_.data(), length).c_str()); 149 | this->data_.erase(this->data_.begin(), this->data_.begin() + length); 150 | } 151 | } 152 | 153 | VrfCmd VrfZhonghongGateway::cmd_find_climates() { 154 | std::vector cmd = { 155 | this->slave_addr_, 156 | uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_QUERY), 157 | 0x02, 158 | 0xFF, 159 | 0xFF, 160 | 0xFF}; 161 | uint8_t sum = checksum(cmd); 162 | cmd.push_back(sum); 163 | return VrfCmd(cmd); 164 | } 165 | 166 | VrfZhonghongClimate* VrfZhonghongGateway::find_or_create_climate(uint8_t outdoor_addr, uint8_t indoor_addr) { 167 | VrfZhonghongClimate* target_climate = nullptr; 168 | for(auto &climate : this->climates_) { 169 | VrfZhonghongClimate* climate_cast = reinterpret_cast(climate); 170 | if (climate_cast->get_outdoor_addr() == outdoor_addr 171 | && climate_cast->get_indoor_addr() == indoor_addr) { 172 | target_climate = climate_cast; 173 | break; 174 | } 175 | } 176 | 177 | if (target_climate == nullptr) { 178 | target_climate = new VrfZhonghongClimate(this->slave_addr_, outdoor_addr, indoor_addr); 179 | this->climates_.push_back(target_climate); 180 | 181 | for(auto &callback : this->climate_create_callbacks_) { 182 | callback(target_climate); 183 | } 184 | } 185 | 186 | return target_climate; 187 | } 188 | 189 | VrfCmd VrfZhonghongClimate::cmd_query() { 190 | unsigned long now = esphome::millis(); 191 | if (now - last_time_ctrl < 2000) { 192 | // 如果控制指令与查询指令间隔小于 2s 193 | // 则不进行查询 194 | return VrfCmd(); 195 | } 196 | 197 | std::vector cmd = { 198 | this->slave_addr_, 199 | uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_QUERY), 200 | 0x01, 201 | 0x01, 202 | this->outdoor_addr_, 203 | this->indoor_addr_}; 204 | uint8_t sum = checksum(cmd); 205 | cmd.push_back(sum); 206 | return VrfCmd(cmd); 207 | } 208 | 209 | VrfCmd VrfZhonghongClimate::cmd_control_mode(VrfClimateMode mode) { 210 | this->need_fire_data_updated = true; 211 | 212 | std::vector cmd_on_off = { 213 | this->slave_addr_, 214 | 0, // 功能码 215 | 0, // 控制值 216 | 0x01, // 空调数量 217 | this->outdoor_addr_, 218 | this->indoor_addr_, 219 | }; 220 | 221 | std::vector cmd_mode = { 222 | this->slave_addr_, 223 | 0, // 功能码 224 | 0, // 控制值 225 | 0x01, // 空调数量 226 | this->outdoor_addr_, 227 | this->indoor_addr_, 228 | }; 229 | 230 | switch (mode) 231 | { 232 | case VrfClimateMode::CLIMATE_MODE_OFF: 233 | cmd_on_off[1] = uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_CTRL_SWITCH); 234 | cmd_on_off[2] = uint8_t(VrfZhonghongClimateSwitch::ZHONG_HONG_CLIMATE_SWITCH_OFF); 235 | 236 | cmd_mode = {}; 237 | break; 238 | case VrfClimateMode::CLIMATE_MODE_COOL: 239 | cmd_on_off[1] = uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_CTRL_SWITCH); 240 | cmd_on_off[2] = uint8_t(VrfZhonghongClimateSwitch::ZHONG_HONG_CLIMATE_SWITCH_ON); 241 | 242 | cmd_mode[1] = uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_CTRL_MODE); 243 | cmd_mode[2] = uint8_t(VrfZhonghongClimateMode::ZHONG_HONG_CLIMATE_MODE_COOL); 244 | break; 245 | case VrfClimateMode::CLIMATE_MODE_HEAT: 246 | cmd_on_off[1] = uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_CTRL_SWITCH); 247 | cmd_on_off[2] = uint8_t(VrfZhonghongClimateSwitch::ZHONG_HONG_CLIMATE_SWITCH_ON); 248 | 249 | cmd_mode[1] = uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_CTRL_MODE); 250 | cmd_mode[2] = uint8_t(VrfZhonghongClimateMode::ZHONG_HONG_CLIMATE_MODE_HEAT); 251 | break; 252 | case VrfClimateMode::CLIMATE_MODE_FAN_ONLY: 253 | cmd_on_off[1] = uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_CTRL_SWITCH); 254 | cmd_on_off[2] = uint8_t(VrfZhonghongClimateSwitch::ZHONG_HONG_CLIMATE_SWITCH_ON); 255 | 256 | cmd_mode[1] = uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_CTRL_MODE); 257 | cmd_mode[2] = uint8_t(VrfZhonghongClimateMode::ZHONG_HONG_CLIMATE_MODE_FAN); 258 | break; 259 | case VrfClimateMode::CLIMATE_MODE_DRY: 260 | cmd_on_off[1] = uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_CTRL_SWITCH); 261 | cmd_on_off[2] = uint8_t(VrfZhonghongClimateSwitch::ZHONG_HONG_CLIMATE_SWITCH_ON); 262 | 263 | cmd_mode[1] = uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_CTRL_MODE); 264 | cmd_mode[2] = uint8_t(VrfZhonghongClimateMode::ZHONG_HONG_CLIMATE_MODE_DRY); 265 | break; 266 | default: 267 | break; 268 | } 269 | 270 | uint8_t sum_cmd_on_off = checksum(cmd_on_off); 271 | cmd_on_off.push_back(sum_cmd_on_off); 272 | 273 | uint8_t sum_cmd_mode = checksum(cmd_mode); 274 | cmd_mode.push_back(sum_cmd_mode); 275 | 276 | VrfCmd cmd; 277 | 278 | std::vector> cmds = {}; 279 | if (mode == VrfClimateMode::CLIMATE_MODE_OFF) { 280 | cmd = VrfCmd(cmd_on_off); 281 | } else { 282 | cmd = VrfCmd({cmd_mode, cmd_on_off}); 283 | } 284 | 285 | last_time_ctrl = esphome::millis(); 286 | 287 | return cmd; 288 | } 289 | 290 | VrfCmd VrfZhonghongClimate::cmd_control_target_temperature(uint8_t target_temperature) { 291 | this->need_fire_data_updated = true; 292 | 293 | uint8_t temperature = target_temperature; 294 | if (temperature < 16) { 295 | temperature = 16; 296 | } 297 | if (temperature > 30) { 298 | temperature = 30; 299 | } 300 | 301 | std::vector cmd = { 302 | this->slave_addr_, 303 | uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_CTRL_TEMPERATURE), // 功能码 304 | temperature, // 控制值 305 | 0x01, // 空调数量 306 | this->outdoor_addr_, 307 | this->indoor_addr_, 308 | }; 309 | 310 | uint8_t sum = checksum(cmd); 311 | cmd.push_back(sum); 312 | 313 | last_time_ctrl = esphome::millis(); 314 | 315 | return VrfCmd(cmd); 316 | } 317 | 318 | VrfCmd VrfZhonghongClimate::cmd_control_fan_mode(VrfClimateFanMode mode) { 319 | this->need_fire_data_updated = true; 320 | 321 | std::vector cmd = { 322 | this->slave_addr_, 323 | uint8_t(VrfZhonghongFunc::ZHONG_HONG_FUNC_CTRL_FAN_MODE), // 功能码 324 | 0, // 控制值 325 | 0x01, // 空调数量 326 | this->outdoor_addr_, 327 | this->indoor_addr_, 328 | }; 329 | 330 | switch (mode) 331 | { 332 | case VrfClimateFanMode::CLIMATE_FAN_MODE_AUTO: 333 | cmd[2] = uint8_t(VrfZhonghongClimateFanSpeed::ZHONG_HONG_CLIMATE_FAN_SPEED_LOW); // 强制低速 334 | break; 335 | case VrfClimateFanMode::CLIMATE_FAN_MODE_HIGH: 336 | cmd[2] = uint8_t(VrfZhonghongClimateFanSpeed::ZHONG_HONG_CLIMATE_FAN_SPEED_HIGH); 337 | break; 338 | case VrfClimateFanMode::CLIMATE_FAN_MODE_MEDIUM: 339 | cmd[2] = uint8_t(VrfZhonghongClimateFanSpeed::ZHONG_HONG_CLIMATE_FAN_SPEED_MEDIUM); 340 | break; 341 | case VrfClimateFanMode::CLIMATE_FAN_MODE_LOW: 342 | cmd[2] = uint8_t(VrfZhonghongClimateFanSpeed::ZHONG_HONG_CLIMATE_FAN_SPEED_LOW); 343 | break; 344 | default: 345 | break; 346 | } 347 | uint8_t sum = checksum(cmd); 348 | cmd.push_back(sum); 349 | 350 | last_time_ctrl = esphome::millis(); 351 | 352 | return VrfCmd(cmd); 353 | } 354 | } 355 | 356 | -------------------------------------------------------------------------------- /components/uart_vrf/vrf_zhonghong.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "esphome/core/helpers.h" 5 | #include "vrf.h" 6 | #include "vrf_zhonghong_const.h" 7 | 8 | namespace vrf_protocol { 9 | 10 | class VrfZhonghongClimate : public vrf_protocol::VrfClimate { 11 | public: 12 | VrfZhonghongClimate(uint8_t slave_addr, uint8_t outdoor_addr, uint8_t indoor_addr) { 13 | this->slave_addr_ = slave_addr; 14 | this->outdoor_addr_ = outdoor_addr; 15 | this->indoor_addr_ = indoor_addr; 16 | this->unique_id_ = esphome::str_sprintf("%d_%d_%d", 17 | this->slave_addr_, this->outdoor_addr_, this->indoor_addr_); 18 | this->name_ = esphome::str_sprintf("vrf_climate_%s", this->unique_id_.c_str()); 19 | }; 20 | 21 | uint8_t get_outdoor_addr() { return this->outdoor_addr_; }; 22 | uint8_t get_indoor_addr() { return this->indoor_addr_; }; 23 | 24 | VrfCmd cmd_query() override; 25 | VrfCmd cmd_control_mode(vrf_protocol::VrfClimateMode mode) override; 26 | VrfCmd cmd_control_target_temperature(uint8_t target_temperature) override; 27 | VrfCmd cmd_control_fan_mode(vrf_protocol::VrfClimateFanMode mode) override; 28 | 29 | protected: 30 | // 外机地址 31 | uint8_t outdoor_addr_; 32 | // 内机地址 33 | uint8_t indoor_addr_; 34 | // 上一次控制时间 35 | unsigned long last_time_ctrl{0}; 36 | 37 | }; 38 | 39 | class VrfZhonghongGateway : public vrf_protocol::VrfGateway { 40 | public: 41 | VrfZhonghongGateway(uint8_t slave_addr) : VrfGateway(slave_addr) {}; 42 | void consume_data(uint8_t data) override; 43 | VrfCmd cmd_find_climates() override; 44 | VrfZhonghongClimate* find_or_create_climate(uint8_t outdoor_addr, uint8_t indoor_addr); 45 | 46 | protected: 47 | std::vector data_; 48 | 49 | private: 50 | void consume_data_handle_found_climates(); 51 | void consume_data_handle_query_climate(); 52 | }; 53 | 54 | } -------------------------------------------------------------------------------- /components/uart_vrf/vrf_zhonghong_const.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace vrf_protocol { 4 | 5 | enum class VrfZhonghongClimateSwitch : uint8_t { 6 | ZHONG_HONG_CLIMATE_SWITCH_OFF = 0x00, 7 | ZHONG_HONG_CLIMATE_SWITCH_ON = 0x01, 8 | }; 9 | 10 | enum class VrfZhonghongClimateMode : uint8_t { 11 | ZHONG_HONG_CLIMATE_MODE_HEAT = 0x08, 12 | ZHONG_HONG_CLIMATE_MODE_COOL = 0x01, 13 | ZHONG_HONG_CLIMATE_MODE_FAN = 0x04, 14 | ZHONG_HONG_CLIMATE_MODE_DRY = 0x02, 15 | 16 | ZHONG_HONG_CLIMATE_MODE_HOLD = 0xFF, 17 | }; 18 | 19 | enum class VrfZhonghongClimateFanSpeed : uint8_t { 20 | ZHONG_HONG_CLIMATE_FAN_SPEED_LOW = 0x04, 21 | ZHONG_HONG_CLIMATE_FAN_SPEED_MEDIUM = 0x02, 22 | ZHONG_HONG_CLIMATE_FAN_SPEED_HIGH = 0x01, 23 | 24 | ZHONG_HONG_CLIMATE_FAN_SPEED_HOLD = 0xFF, 25 | }; 26 | 27 | enum class VrfZhonghongFuncValue : uint8_t { 28 | ZHONG_HONG_FUNC_VALUE_SINGLE = 0x01, 29 | ZHONG_HONG_FUNC_VALUE_MULTI = 0x0F, 30 | ZHONG_HONG_FUNC_VALUE_ALL = 0xFF, 31 | ZHONG_HONG_FUNC_VALUE_ONLINE = 0x02, 32 | }; 33 | 34 | enum class VrfZhonghongFunc : uint8_t { 35 | ZHONG_HONG_FUNC_CTRL_SWITCH = 0x31, 36 | ZHONG_HONG_FUNC_CTRL_TEMPERATURE = 0x32, 37 | ZHONG_HONG_FUNC_CTRL_MODE = 0x33, 38 | ZHONG_HONG_FUNC_CTRL_FAN_MODE = 0x34, 39 | 40 | ZHONG_HONG_FUNC_QUERY = 0x50, 41 | }; 42 | 43 | } -------------------------------------------------------------------------------- /example/esp01s.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: uart-vrf-esp01s 3 | friendly_name: uart-vrf-esp01s 4 | name_add_mac_suffix: true 5 | 6 | external_components: 7 | - source: 8 | type: git 9 | url: https://github.com/idreamshen/esphome-uart-vrf 10 | 11 | uart: 12 | - id: myuart1 13 | tx_pin: 1 14 | rx_pin: 3 15 | baud_rate: 9600 16 | 17 | uart_vrf: 18 | 19 | climate: 20 | 21 | esp8266: 22 | board: esp01_1m 23 | 24 | logger: 25 | baud_rate: 0 26 | 27 | api: 28 | encryption: 29 | key: "vUAZJVUjuhi2kEiYRjjAwsSxuXVvUd1PTPNJWGNa2rs=" 30 | 31 | ota: 32 | - platform: esphome 33 | 34 | wifi: 35 | ap: 36 | password: "12345678" 37 | 38 | captive_portal: 39 | 40 | web_server: 41 | port: 80 42 | local: true 43 | 44 | debug: 45 | update_interval: 60s 46 | 47 | switch: 48 | - platform: factory_reset 49 | name: Reset 50 | 51 | text_sensor: 52 | - platform: debug 53 | device: 54 | name: "Device Info" 55 | reset_reason: 56 | name: "Reset Reason" 57 | 58 | sensor: 59 | - platform: debug 60 | free: 61 | name: "Heap Free" 62 | fragmentation: 63 | name: "Heap Fragmentation" 64 | block: 65 | name: "Heap Max Block" 66 | loop_time: 67 | name: "Loop Time" 68 | -------------------------------------------------------------------------------- /example/esp32wroom32d.yaml: -------------------------------------------------------------------------------- 1 | esphome: 2 | name: uart-vrf-esp32wroom32d 3 | friendly_name: uart-vrf-esp32wroom32d 4 | name_add_mac_suffix: true 5 | 6 | esp32: 7 | board: esp32dev 8 | framework: 9 | type: esp-idf 10 | sdkconfig_options: 11 | CONFIG_FREERTOS_UNICORE: y 12 | advanced: 13 | ignore_efuse_custom_mac: true 14 | ignore_efuse_mac_crc: true 15 | 16 | external_components: 17 | - source: 18 | type: git 19 | url: https://github.com/idreamshen/esphome-uart-vrf 20 | 21 | # Enable logging 22 | logger: 23 | 24 | # Enable Home Assistant API 25 | api: 26 | encryption: 27 | key: "vUAZJVUjuhi2kEiYRjjAwsSxuXVvUd1PTPNJWGNa2rs=" 28 | 29 | ota: 30 | - platform: esphome 31 | password: "161fdd9fedb5649b758a44978e20caf8" 32 | 33 | wifi: 34 | ap: 35 | password: "12345678" 36 | 37 | captive_portal: 38 | 39 | uart: 40 | - id: myuart1 41 | tx_pin: 17 42 | rx_pin: 16 43 | baud_rate: 9600 44 | debug: 45 | direction: RX 46 | dummy_receiver: false 47 | sequence: 48 | - light.turn_on: receive_led 49 | - delay: 200ms 50 | - light.turn_off: receive_led 51 | 52 | output: 53 | - platform: gpio 54 | pin: 55 | number: 15 56 | inverted: true 57 | id: led_output 58 | 59 | light: 60 | - platform: binary 61 | name: "RX LED" 62 | output: led_output 63 | id: receive_led 64 | internal: true 65 | 66 | uart_vrf: 67 | 68 | climate: 69 | 70 | web_server: 71 | port: 80 72 | local: true 73 | 74 | debug: 75 | update_interval: 60s 76 | 77 | button: 78 | - platform: factory_reset 79 | name: Reset Button 80 | id: button_reset 81 | 82 | text_sensor: 83 | - platform: debug 84 | device: 85 | name: "Device Info" 86 | reset_reason: 87 | name: "Reset Reason" 88 | 89 | sensor: 90 | - platform: debug 91 | free: 92 | name: "Heap Free" 93 | block: 94 | name: "Heap Max Block" 95 | loop_time: 96 | name: "Loop Time" 97 | -------------------------------------------------------------------------------- /images/demry_3th_main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idreamshen/esphome-uart-vrf/312f928516d4e9995f2965cde2692b5d91794db5/images/demry_3th_main.jpg -------------------------------------------------------------------------------- /images/demry_4th_main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idreamshen/esphome-uart-vrf/312f928516d4e9995f2965cde2692b5d91794db5/images/demry_4th_main.jpg -------------------------------------------------------------------------------- /images/zhonghong_b17_main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idreamshen/esphome-uart-vrf/312f928516d4e9995f2965cde2692b5d91794db5/images/zhonghong_b17_main.jpg --------------------------------------------------------------------------------