mirror of
https://github.com/esphome/esphome.git
synced 2025-04-14 06:40:32 +01:00
Merge branch 'dev' into dev
This commit is contained in:
commit
bc2ed2e105
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@ -46,7 +46,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: "3.9"
|
python-version: "3.9"
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.7.1
|
uses: docker/setup-buildx-action@v3.8.0
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3.2.0
|
uses: docker/setup-qemu-action@v3.2.0
|
||||||
|
|
||||||
|
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@ -13,6 +13,7 @@ on:
|
|||||||
- ".github/workflows/ci.yml"
|
- ".github/workflows/ci.yml"
|
||||||
- "!.yamllint"
|
- "!.yamllint"
|
||||||
- "!.github/dependabot.yml"
|
- "!.github/dependabot.yml"
|
||||||
|
- "!docker/**"
|
||||||
merge_group:
|
merge_group:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -90,7 +90,7 @@ jobs:
|
|||||||
python-version: "3.9"
|
python-version: "3.9"
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.7.1
|
uses: docker/setup-buildx-action@v3.8.0
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
if: matrix.platform != 'linux/amd64'
|
if: matrix.platform != 'linux/amd64'
|
||||||
uses: docker/setup-qemu-action@v3.2.0
|
uses: docker/setup-qemu-action@v3.2.0
|
||||||
@ -184,7 +184,7 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.7.1
|
uses: docker/setup-buildx-action@v3.8.0
|
||||||
|
|
||||||
- name: Log in to docker hub
|
- name: Log in to docker hub
|
||||||
if: matrix.registry == 'dockerhub'
|
if: matrix.registry == 'dockerhub'
|
||||||
|
@ -1,11 +1,6 @@
|
|||||||
import esphome.codegen as cg
|
|
||||||
import esphome.config_validation as cv
|
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER
|
import esphome.codegen as cg
|
||||||
|
|
||||||
from esphome.core import CORE
|
|
||||||
from esphome.components.esp32 import get_esp32_variant
|
from esphome.components.esp32 import get_esp32_variant
|
||||||
from esphome.const import PLATFORM_ESP8266
|
|
||||||
from esphome.components.esp32.const import (
|
from esphome.components.esp32.const import (
|
||||||
VARIANT_ESP32,
|
VARIANT_ESP32,
|
||||||
VARIANT_ESP32C2,
|
VARIANT_ESP32C2,
|
||||||
@ -15,6 +10,9 @@ from esphome.components.esp32.const import (
|
|||||||
VARIANT_ESP32S2,
|
VARIANT_ESP32S2,
|
||||||
VARIANT_ESP32S3,
|
VARIANT_ESP32S3,
|
||||||
)
|
)
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import CONF_ANALOG, CONF_INPUT, CONF_NUMBER, PLATFORM_ESP8266
|
||||||
|
from esphome.core import CORE
|
||||||
|
|
||||||
CODEOWNERS = ["@esphome/core"]
|
CODEOWNERS = ["@esphome/core"]
|
||||||
|
|
||||||
@ -102,11 +100,11 @@ ESP32_VARIANT_ADC1_PIN_TO_CHANNEL = {
|
|||||||
6: adc1_channel_t.ADC1_CHANNEL_6,
|
6: adc1_channel_t.ADC1_CHANNEL_6,
|
||||||
},
|
},
|
||||||
VARIANT_ESP32H2: {
|
VARIANT_ESP32H2: {
|
||||||
0: adc1_channel_t.ADC1_CHANNEL_0,
|
1: adc1_channel_t.ADC1_CHANNEL_0,
|
||||||
1: adc1_channel_t.ADC1_CHANNEL_1,
|
2: adc1_channel_t.ADC1_CHANNEL_1,
|
||||||
2: adc1_channel_t.ADC1_CHANNEL_2,
|
3: adc1_channel_t.ADC1_CHANNEL_2,
|
||||||
3: adc1_channel_t.ADC1_CHANNEL_3,
|
4: adc1_channel_t.ADC1_CHANNEL_3,
|
||||||
4: adc1_channel_t.ADC1_CHANNEL_4,
|
5: adc1_channel_t.ADC1_CHANNEL_4,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,109 +30,162 @@ static const char *const TAG = "debug";
|
|||||||
|
|
||||||
std::string DebugComponent::get_reset_reason_() {
|
std::string DebugComponent::get_reset_reason_() {
|
||||||
std::string reset_reason;
|
std::string reset_reason;
|
||||||
switch (rtc_get_reset_reason(0)) {
|
switch (esp_reset_reason()) {
|
||||||
case POWERON_RESET:
|
case ESP_RST_POWERON:
|
||||||
reset_reason = "Power On Reset";
|
reset_reason = "Reset due to power-on event";
|
||||||
break;
|
break;
|
||||||
|
case ESP_RST_EXT:
|
||||||
|
reset_reason = "Reset by external pin";
|
||||||
|
break;
|
||||||
|
case ESP_RST_SW:
|
||||||
|
reset_reason = "Software reset via esp_restart";
|
||||||
|
break;
|
||||||
|
case ESP_RST_PANIC:
|
||||||
|
reset_reason = "Software reset due to exception/panic";
|
||||||
|
break;
|
||||||
|
case ESP_RST_INT_WDT:
|
||||||
|
reset_reason = "Reset (software or hardware) due to interrupt watchdog";
|
||||||
|
break;
|
||||||
|
case ESP_RST_TASK_WDT:
|
||||||
|
reset_reason = "Reset due to task watchdog";
|
||||||
|
break;
|
||||||
|
case ESP_RST_WDT:
|
||||||
|
reset_reason = "Reset due to other watchdogs";
|
||||||
|
break;
|
||||||
|
case ESP_RST_DEEPSLEEP:
|
||||||
|
reset_reason = "Reset after exiting deep sleep mode";
|
||||||
|
break;
|
||||||
|
case ESP_RST_BROWNOUT:
|
||||||
|
reset_reason = "Brownout reset (software or hardware)";
|
||||||
|
break;
|
||||||
|
case ESP_RST_SDIO:
|
||||||
|
reset_reason = "Reset over SDIO";
|
||||||
|
break;
|
||||||
|
#ifdef USE_ESP32_VARIANT_ESP32
|
||||||
|
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4))
|
||||||
|
case ESP_RST_USB:
|
||||||
|
reset_reason = "Reset by USB peripheral";
|
||||||
|
break;
|
||||||
|
case ESP_RST_JTAG:
|
||||||
|
reset_reason = "Reset by JTAG";
|
||||||
|
break;
|
||||||
|
case ESP_RST_EFUSE:
|
||||||
|
reset_reason = "Reset due to efuse error";
|
||||||
|
break;
|
||||||
|
case ESP_RST_PWR_GLITCH:
|
||||||
|
reset_reason = "Reset due to power glitch detected";
|
||||||
|
break;
|
||||||
|
case ESP_RST_CPU_LOCKUP:
|
||||||
|
reset_reason = "Reset due to CPU lock up (double exception)";
|
||||||
|
break;
|
||||||
|
#endif // ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 4)
|
||||||
|
#endif // USE_ESP32_VARIANT_ESP32
|
||||||
|
default: // Includes ESP_RST_UNKNOWN
|
||||||
|
switch (rtc_get_reset_reason(0)) {
|
||||||
|
case POWERON_RESET:
|
||||||
|
reset_reason = "Power On Reset";
|
||||||
|
break;
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||||
case SW_RESET:
|
case SW_RESET:
|
||||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
||||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||||
case RTC_SW_SYS_RESET:
|
case RTC_SW_SYS_RESET:
|
||||||
#endif
|
#endif
|
||||||
reset_reason = "Software Reset Digital Core";
|
reset_reason = "Software Reset Digital Core";
|
||||||
break;
|
break;
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||||
case OWDT_RESET:
|
case OWDT_RESET:
|
||||||
reset_reason = "Watch Dog Reset Digital Core";
|
reset_reason = "Watch Dog Reset Digital Core";
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
case DEEPSLEEP_RESET:
|
case DEEPSLEEP_RESET:
|
||||||
reset_reason = "Deep Sleep Reset Digital Core";
|
reset_reason = "Deep Sleep Reset Digital Core";
|
||||||
break;
|
break;
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||||
case SDIO_RESET:
|
case SDIO_RESET:
|
||||||
reset_reason = "SLC Module Reset Digital Core";
|
reset_reason = "SLC Module Reset Digital Core";
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
case TG0WDT_SYS_RESET:
|
case TG0WDT_SYS_RESET:
|
||||||
reset_reason = "Timer Group 0 Watch Dog Reset Digital Core";
|
reset_reason = "Timer Group 0 Watch Dog Reset Digital Core";
|
||||||
break;
|
break;
|
||||||
case TG1WDT_SYS_RESET:
|
case TG1WDT_SYS_RESET:
|
||||||
reset_reason = "Timer Group 1 Watch Dog Reset Digital Core";
|
reset_reason = "Timer Group 1 Watch Dog Reset Digital Core";
|
||||||
break;
|
break;
|
||||||
case RTCWDT_SYS_RESET:
|
case RTCWDT_SYS_RESET:
|
||||||
reset_reason = "RTC Watch Dog Reset Digital Core";
|
reset_reason = "RTC Watch Dog Reset Digital Core";
|
||||||
break;
|
break;
|
||||||
#if !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
#if !defined(USE_ESP32_VARIANT_ESP32C6) && !defined(USE_ESP32_VARIANT_ESP32H2)
|
||||||
case INTRUSION_RESET:
|
case INTRUSION_RESET:
|
||||||
reset_reason = "Intrusion Reset CPU";
|
reset_reason = "Intrusion Reset CPU";
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||||
case TGWDT_CPU_RESET:
|
case TGWDT_CPU_RESET:
|
||||||
reset_reason = "Timer Group Reset CPU";
|
reset_reason = "Timer Group Reset CPU";
|
||||||
break;
|
break;
|
||||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
||||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||||
case TG0WDT_CPU_RESET:
|
case TG0WDT_CPU_RESET:
|
||||||
reset_reason = "Timer Group 0 Reset CPU";
|
reset_reason = "Timer Group 0 Reset CPU";
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||||
case SW_CPU_RESET:
|
case SW_CPU_RESET:
|
||||||
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
#elif defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || \
|
||||||
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||||
case RTC_SW_CPU_RESET:
|
case RTC_SW_CPU_RESET:
|
||||||
#endif
|
#endif
|
||||||
reset_reason = "Software Reset CPU";
|
reset_reason = "Software Reset CPU";
|
||||||
break;
|
break;
|
||||||
case RTCWDT_CPU_RESET:
|
case RTCWDT_CPU_RESET:
|
||||||
reset_reason = "RTC Watch Dog Reset CPU";
|
reset_reason = "RTC Watch Dog Reset CPU";
|
||||||
break;
|
break;
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32)
|
#if defined(USE_ESP32_VARIANT_ESP32)
|
||||||
case EXT_CPU_RESET:
|
case EXT_CPU_RESET:
|
||||||
reset_reason = "External CPU Reset";
|
reset_reason = "External CPU Reset";
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
case RTCWDT_BROWN_OUT_RESET:
|
case RTCWDT_BROWN_OUT_RESET:
|
||||||
reset_reason = "Voltage Unstable Reset";
|
reset_reason = "Voltage Unstable Reset";
|
||||||
break;
|
break;
|
||||||
case RTCWDT_RTC_RESET:
|
case RTCWDT_RTC_RESET:
|
||||||
reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module";
|
reset_reason = "RTC Watch Dog Reset Digital Core And RTC Module";
|
||||||
break;
|
break;
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3) || \
|
||||||
defined(USE_ESP32_VARIANT_ESP32C6)
|
defined(USE_ESP32_VARIANT_ESP32C6)
|
||||||
case TG1WDT_CPU_RESET:
|
case TG1WDT_CPU_RESET:
|
||||||
reset_reason = "Timer Group 1 Reset CPU";
|
reset_reason = "Timer Group 1 Reset CPU";
|
||||||
break;
|
break;
|
||||||
case SUPER_WDT_RESET:
|
case SUPER_WDT_RESET:
|
||||||
reset_reason = "Super Watchdog Reset Digital Core And RTC Module";
|
reset_reason = "Super Watchdog Reset Digital Core And RTC Module";
|
||||||
break;
|
break;
|
||||||
case EFUSE_RESET:
|
case EFUSE_RESET:
|
||||||
reset_reason = "eFuse Reset Digital Core";
|
reset_reason = "eFuse Reset Digital Core";
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S2) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||||
case GLITCH_RTC_RESET:
|
case GLITCH_RTC_RESET:
|
||||||
reset_reason = "Glitch Reset Digital Core And RTC Module";
|
reset_reason = "Glitch Reset Digital Core And RTC Module";
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3) || defined(USE_ESP32_VARIANT_ESP32C6)
|
||||||
case USB_UART_CHIP_RESET:
|
case USB_UART_CHIP_RESET:
|
||||||
reset_reason = "USB UART Reset Digital Core";
|
reset_reason = "USB UART Reset Digital Core";
|
||||||
break;
|
break;
|
||||||
case USB_JTAG_CHIP_RESET:
|
case USB_JTAG_CHIP_RESET:
|
||||||
reset_reason = "USB JTAG Reset Digital Core";
|
reset_reason = "USB JTAG Reset Digital Core";
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
|
#if defined(USE_ESP32_VARIANT_ESP32C3) || defined(USE_ESP32_VARIANT_ESP32S3)
|
||||||
case POWER_GLITCH_RESET:
|
case POWER_GLITCH_RESET:
|
||||||
reset_reason = "Power Glitch Reset Digital Core And RTC Module";
|
reset_reason = "Power Glitch Reset Digital Core And RTC Module";
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
reset_reason = "Unknown Reset Reason";
|
reset_reason = "Unknown Reset Reason";
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
|
ESP_LOGD(TAG, "Reset Reason: %s", reset_reason.c_str());
|
||||||
return reset_reason;
|
return reset_reason;
|
||||||
@ -294,4 +347,4 @@ void DebugComponent::update_platform_() {
|
|||||||
|
|
||||||
} // namespace debug
|
} // namespace debug
|
||||||
} // namespace esphome
|
} // namespace esphome
|
||||||
#endif
|
#endif // USE_ESP32
|
||||||
|
@ -27,6 +27,9 @@ namespace esp32_ble {
|
|||||||
|
|
||||||
static const char *const TAG = "esp32_ble";
|
static const char *const TAG = "esp32_ble";
|
||||||
|
|
||||||
|
static RAMAllocator<BLEEvent> EVENT_ALLOCATOR( // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
|
RAMAllocator<BLEEvent>::ALLOW_FAILURE | RAMAllocator<BLEEvent>::ALLOC_INTERNAL);
|
||||||
|
|
||||||
void ESP32BLE::setup() {
|
void ESP32BLE::setup() {
|
||||||
global_ble = this;
|
global_ble = this;
|
||||||
ESP_LOGCONFIG(TAG, "Setting up BLE...");
|
ESP_LOGCONFIG(TAG, "Setting up BLE...");
|
||||||
@ -322,7 +325,8 @@ void ESP32BLE::loop() {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
delete ble_event; // NOLINT(cppcoreguidelines-owning-memory)
|
ble_event->~BLEEvent();
|
||||||
|
EVENT_ALLOCATOR.deallocate(ble_event, 1);
|
||||||
ble_event = this->ble_events_.pop();
|
ble_event = this->ble_events_.pop();
|
||||||
}
|
}
|
||||||
if (this->advertising_ != nullptr) {
|
if (this->advertising_ != nullptr) {
|
||||||
@ -331,9 +335,14 @@ void ESP32BLE::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
void ESP32BLE::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||||
BLEEvent *new_event = new BLEEvent(event, param); // NOLINT(cppcoreguidelines-owning-memory)
|
BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
|
||||||
|
if (new_event == nullptr) {
|
||||||
|
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new (new_event) BLEEvent(event, param);
|
||||||
global_ble->ble_events_.push(new_event);
|
global_ble->ble_events_.push(new_event);
|
||||||
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
|
} // NOLINT(clang-analyzer-unix.Malloc)
|
||||||
|
|
||||||
void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||||
ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event);
|
ESP_LOGV(TAG, "(BLE) gap_event_handler - %d", event);
|
||||||
@ -344,9 +353,14 @@ void ESP32BLE::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_ble_gap
|
|||||||
|
|
||||||
void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
void ESP32BLE::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||||
esp_ble_gatts_cb_param_t *param) {
|
esp_ble_gatts_cb_param_t *param) {
|
||||||
BLEEvent *new_event = new BLEEvent(event, gatts_if, param); // NOLINT(cppcoreguidelines-owning-memory)
|
BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
|
||||||
|
if (new_event == nullptr) {
|
||||||
|
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new (new_event) BLEEvent(event, gatts_if, param);
|
||||||
global_ble->ble_events_.push(new_event);
|
global_ble->ble_events_.push(new_event);
|
||||||
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
|
} // NOLINT(clang-analyzer-unix.Malloc)
|
||||||
|
|
||||||
void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if,
|
||||||
esp_ble_gatts_cb_param_t *param) {
|
esp_ble_gatts_cb_param_t *param) {
|
||||||
@ -358,9 +372,14 @@ void ESP32BLE::real_gatts_event_handler_(esp_gatts_cb_event_t event, esp_gatt_if
|
|||||||
|
|
||||||
void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
void ESP32BLE::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||||
esp_ble_gattc_cb_param_t *param) {
|
esp_ble_gattc_cb_param_t *param) {
|
||||||
BLEEvent *new_event = new BLEEvent(event, gattc_if, param); // NOLINT(cppcoreguidelines-owning-memory)
|
BLEEvent *new_event = EVENT_ALLOCATOR.allocate(1);
|
||||||
|
if (new_event == nullptr) {
|
||||||
|
// Memory too fragmented to allocate new event. Can only drop it until memory comes back
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new (new_event) BLEEvent(event, gattc_if, param);
|
||||||
global_ble->ble_events_.push(new_event);
|
global_ble->ble_events_.push(new_event);
|
||||||
} // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
|
} // NOLINT(clang-analyzer-unix.Malloc)
|
||||||
|
|
||||||
void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
void ESP32BLE::real_gattc_event_handler_(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||||
esp_ble_gattc_cb_param_t *param) {
|
esp_ble_gattc_cb_param_t *param) {
|
||||||
|
@ -51,8 +51,11 @@ CONF_IGNORE_MISSING_GLYPHS = "ignore_missing_glyphs"
|
|||||||
# Cache loaded freetype fonts
|
# Cache loaded freetype fonts
|
||||||
class FontCache(dict):
|
class FontCache(dict):
|
||||||
def __missing__(self, key):
|
def __missing__(self, key):
|
||||||
res = self[key] = freetype.Face(key)
|
try:
|
||||||
return res
|
res = self[key] = freetype.Face(key)
|
||||||
|
return res
|
||||||
|
except freetype.FT_Exception as e:
|
||||||
|
raise cv.Invalid(f"Could not load Font file {key}: {e}") from e
|
||||||
|
|
||||||
|
|
||||||
FONT_CACHE = FontCache()
|
FONT_CACHE = FontCache()
|
||||||
|
@ -19,10 +19,12 @@ template<typename... Ts> class MideaActionBase : public Action<Ts...> {
|
|||||||
|
|
||||||
template<typename... Ts> class FollowMeAction : public MideaActionBase<Ts...> {
|
template<typename... Ts> class FollowMeAction : public MideaActionBase<Ts...> {
|
||||||
TEMPLATABLE_VALUE(float, temperature)
|
TEMPLATABLE_VALUE(float, temperature)
|
||||||
|
TEMPLATABLE_VALUE(bool, use_fahrenheit)
|
||||||
TEMPLATABLE_VALUE(bool, beeper)
|
TEMPLATABLE_VALUE(bool, beeper)
|
||||||
|
|
||||||
void play(Ts... x) override {
|
void play(Ts... x) override {
|
||||||
this->parent_->do_follow_me(this->temperature_.value(x...), this->beeper_.value(x...));
|
this->parent_->do_follow_me(this->temperature_.value(x...), this->use_fahrenheit_.value(x...),
|
||||||
|
this->beeper_.value(x...));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#ifdef USE_ARDUINO
|
#ifdef USE_ARDUINO
|
||||||
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include "air_conditioner.h"
|
#include "air_conditioner.h"
|
||||||
#include "ac_adapter.h"
|
#include "ac_adapter.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
@ -121,7 +122,7 @@ void AirConditioner::dump_config() {
|
|||||||
|
|
||||||
/* ACTIONS */
|
/* ACTIONS */
|
||||||
|
|
||||||
void AirConditioner::do_follow_me(float temperature, bool beeper) {
|
void AirConditioner::do_follow_me(float temperature, bool use_fahrenheit, bool beeper) {
|
||||||
#ifdef USE_REMOTE_TRANSMITTER
|
#ifdef USE_REMOTE_TRANSMITTER
|
||||||
// Check if temperature is finite (not NaN or infinite)
|
// Check if temperature is finite (not NaN or infinite)
|
||||||
if (!std::isfinite(temperature)) {
|
if (!std::isfinite(temperature)) {
|
||||||
@ -131,13 +132,14 @@ void AirConditioner::do_follow_me(float temperature, bool beeper) {
|
|||||||
|
|
||||||
// Round and convert temperature to long, then clamp and convert it to uint8_t
|
// Round and convert temperature to long, then clamp and convert it to uint8_t
|
||||||
uint8_t temp_uint8 =
|
uint8_t temp_uint8 =
|
||||||
static_cast<uint8_t>(std::max(0L, std::min(static_cast<long>(UINT8_MAX), std::lroundf(temperature))));
|
static_cast<uint8_t>(esphome::clamp<long>(std::lroundf(temperature), 0L, static_cast<long>(UINT8_MAX)));
|
||||||
|
|
||||||
ESP_LOGD(Constants::TAG, "Follow me action called with temperature: %f °C, rounded to: %u °C", temperature,
|
char temp_symbol = use_fahrenheit ? 'F' : 'C';
|
||||||
temp_uint8);
|
ESP_LOGD(Constants::TAG, "Follow me action called with temperature: %.5f °%c, rounded to: %u °%c", temperature,
|
||||||
|
temp_symbol, temp_uint8, temp_symbol);
|
||||||
|
|
||||||
// Create and transmit the data
|
// Create and transmit the data
|
||||||
IrFollowMeData data(temp_uint8, beeper);
|
IrFollowMeData data(temp_uint8, use_fahrenheit, beeper);
|
||||||
this->transmitter_.transmit(data);
|
this->transmitter_.transmit(data);
|
||||||
#else
|
#else
|
||||||
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
|
ESP_LOGW(Constants::TAG, "Action needs remote_transmitter component");
|
||||||
|
@ -32,7 +32,7 @@ class AirConditioner : public ApplianceBase<dudanov::midea::ac::AirConditioner>,
|
|||||||
/* ### ACTIONS ### */
|
/* ### ACTIONS ### */
|
||||||
/* ############### */
|
/* ############### */
|
||||||
|
|
||||||
void do_follow_me(float temperature, bool beeper = false);
|
void do_follow_me(float temperature, bool use_fahrenheit, bool beeper = false);
|
||||||
void do_display_toggle();
|
void do_display_toggle();
|
||||||
void do_swing_step();
|
void do_swing_step();
|
||||||
void do_beeper_on() { this->set_beeper_feedback(true); }
|
void do_beeper_on() { this->set_beeper_feedback(true); }
|
||||||
|
@ -18,6 +18,7 @@ from esphome.const import (
|
|||||||
CONF_SUPPORTED_SWING_MODES,
|
CONF_SUPPORTED_SWING_MODES,
|
||||||
CONF_TIMEOUT,
|
CONF_TIMEOUT,
|
||||||
CONF_TEMPERATURE,
|
CONF_TEMPERATURE,
|
||||||
|
CONF_USE_FAHRENHEIT,
|
||||||
DEVICE_CLASS_POWER,
|
DEVICE_CLASS_POWER,
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
DEVICE_CLASS_HUMIDITY,
|
DEVICE_CLASS_HUMIDITY,
|
||||||
@ -172,11 +173,10 @@ MIDEA_ACTION_BASE_SCHEMA = cv.Schema(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# FollowMe action
|
# FollowMe action
|
||||||
MIDEA_FOLLOW_ME_MIN = 0
|
|
||||||
MIDEA_FOLLOW_ME_MAX = 37
|
|
||||||
MIDEA_FOLLOW_ME_SCHEMA = cv.Schema(
|
MIDEA_FOLLOW_ME_SCHEMA = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_TEMPERATURE): cv.templatable(cv.temperature),
|
cv.Required(CONF_TEMPERATURE): cv.templatable(cv.temperature),
|
||||||
|
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.templatable(cv.boolean),
|
||||||
cv.Optional(CONF_BEEPER, default=False): cv.templatable(cv.boolean),
|
cv.Optional(CONF_BEEPER, default=False): cv.templatable(cv.boolean),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -186,6 +186,8 @@ MIDEA_FOLLOW_ME_SCHEMA = cv.Schema(
|
|||||||
async def follow_me_to_code(var, config, args):
|
async def follow_me_to_code(var, config, args):
|
||||||
template_ = await cg.templatable(config[CONF_BEEPER], args, cg.bool_)
|
template_ = await cg.templatable(config[CONF_BEEPER], args, cg.bool_)
|
||||||
cg.add(var.set_beeper(template_))
|
cg.add(var.set_beeper(template_))
|
||||||
|
template_ = await cg.templatable(config[CONF_USE_FAHRENHEIT], args, cg.bool_)
|
||||||
|
cg.add(var.set_use_fahrenheit(template_))
|
||||||
template_ = await cg.templatable(config[CONF_TEMPERATURE], args, cg.float_)
|
template_ = await cg.templatable(config[CONF_TEMPERATURE], args, cg.float_)
|
||||||
cg.add(var.set_temperature(template_))
|
cg.add(var.set_temperature(template_))
|
||||||
|
|
||||||
|
@ -16,22 +16,53 @@ class IrFollowMeData : public IrData {
|
|||||||
IrFollowMeData() : IrData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {}
|
IrFollowMeData() : IrData({MIDEA_TYPE_FOLLOW_ME, 0x82, 0x48, 0x7F, 0x1F}) {}
|
||||||
// Copy from Base
|
// Copy from Base
|
||||||
IrFollowMeData(const IrData &data) : IrData(data) {}
|
IrFollowMeData(const IrData &data) : IrData(data) {}
|
||||||
// Direct from temperature and beeper values
|
// Direct from temperature in celsius and beeper values
|
||||||
IrFollowMeData(uint8_t temp, bool beeper = false) : IrFollowMeData() {
|
IrFollowMeData(uint8_t temp, bool beeper = false) : IrFollowMeData() {
|
||||||
this->set_temp(temp);
|
this->set_temp(temp, false);
|
||||||
|
this->set_beeper(beeper);
|
||||||
|
}
|
||||||
|
// Direct from temperature, fahrenheit and beeper values
|
||||||
|
IrFollowMeData(uint8_t temp, bool fahrenheit, bool beeper) : IrFollowMeData() {
|
||||||
|
this->set_temp(temp, fahrenheit);
|
||||||
this->set_beeper(beeper);
|
this->set_beeper(beeper);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TEMPERATURE */
|
/* TEMPERATURE */
|
||||||
uint8_t temp() const { return this->get_value_(4) - 1; }
|
uint8_t temp() const {
|
||||||
void set_temp(uint8_t val) { this->set_value_(4, std::min(MAX_TEMP, val) + 1); }
|
if (this->fahrenheit()) {
|
||||||
|
return this->get_value_(4) + 31;
|
||||||
|
}
|
||||||
|
return this->get_value_(4) - 1;
|
||||||
|
}
|
||||||
|
void set_temp(uint8_t val, bool fahrenheit = false) {
|
||||||
|
this->set_fahrenheit(fahrenheit);
|
||||||
|
if (this->fahrenheit()) {
|
||||||
|
// see https://github.com/esphome/feature-requests/issues/1627#issuecomment-1365639966
|
||||||
|
val = esphome::clamp<uint8_t>(val, MIN_TEMP_F, MAX_TEMP_F) - 31;
|
||||||
|
} else {
|
||||||
|
val = esphome::clamp<uint8_t>(val, MIN_TEMP_C, MAX_TEMP_C) + 1;
|
||||||
|
}
|
||||||
|
this->set_value_(4, val);
|
||||||
|
}
|
||||||
|
|
||||||
/* BEEPER */
|
/* BEEPER */
|
||||||
bool beeper() const { return this->get_value_(3, 128); }
|
bool beeper() const { return this->get_value_(3, 128); }
|
||||||
void set_beeper(bool val) { this->set_mask_(3, val, 128); }
|
void set_beeper(bool val) { this->set_mask_(3, val, 128); }
|
||||||
|
|
||||||
|
/* FAHRENHEIT */
|
||||||
|
bool fahrenheit() const { return this->get_value_(2, 32); }
|
||||||
|
void set_fahrenheit(bool val) { this->set_mask_(2, val, 32); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static const uint8_t MAX_TEMP = 37;
|
static const uint8_t MIN_TEMP_C = 0;
|
||||||
|
static const uint8_t MAX_TEMP_C = 37;
|
||||||
|
|
||||||
|
// see
|
||||||
|
// https://github.com/crankyoldgit/IRremoteESP8266/blob/9bdf8abcb465268c5409db99dc83a26df64c7445/src/ir_Midea.h#L116
|
||||||
|
static const uint8_t MIN_TEMP_F = 32;
|
||||||
|
// see
|
||||||
|
// https://github.com/crankyoldgit/IRremoteESP8266/blob/9bdf8abcb465268c5409db99dc83a26df64c7445/src/ir_Midea.h#L117
|
||||||
|
static const uint8_t MAX_TEMP_F = 99;
|
||||||
};
|
};
|
||||||
|
|
||||||
class IrSpecialData : public IrData {
|
class IrSpecialData : public IrData {
|
||||||
|
@ -119,17 +119,17 @@ async def to_code(config):
|
|||||||
cg.add_library("ESP8266HTTPClient", None)
|
cg.add_library("ESP8266HTTPClient", None)
|
||||||
|
|
||||||
if CONF_TOUCH_SLEEP_TIMEOUT in config:
|
if CONF_TOUCH_SLEEP_TIMEOUT in config:
|
||||||
cg.add(var.set_touch_sleep_timeout_internal(config[CONF_TOUCH_SLEEP_TIMEOUT]))
|
cg.add(var.set_touch_sleep_timeout(config[CONF_TOUCH_SLEEP_TIMEOUT]))
|
||||||
|
|
||||||
if CONF_WAKE_UP_PAGE in config:
|
if CONF_WAKE_UP_PAGE in config:
|
||||||
cg.add(var.set_wake_up_page_internal(config[CONF_WAKE_UP_PAGE]))
|
cg.add(var.set_wake_up_page(config[CONF_WAKE_UP_PAGE]))
|
||||||
|
|
||||||
if CONF_START_UP_PAGE in config:
|
if CONF_START_UP_PAGE in config:
|
||||||
cg.add(var.set_start_up_page_internal(config[CONF_START_UP_PAGE]))
|
cg.add(var.set_start_up_page(config[CONF_START_UP_PAGE]))
|
||||||
|
|
||||||
cg.add(var.set_auto_wake_on_touch_internal(config[CONF_AUTO_WAKE_ON_TOUCH]))
|
cg.add(var.set_auto_wake_on_touch(config[CONF_AUTO_WAKE_ON_TOUCH]))
|
||||||
|
|
||||||
cg.add(var.set_exit_reparse_on_start_internal(config[CONF_EXIT_REPARSE_ON_START]))
|
cg.add(var.set_exit_reparse_on_start(config[CONF_EXIT_REPARSE_ON_START]))
|
||||||
|
|
||||||
cg.add(var.set_skip_connection_handshake(config[CONF_SKIP_CONNECTION_HANDSHAKE]))
|
cg.add(var.set_skip_connection_handshake(config[CONF_SKIP_CONNECTION_HANDSHAKE]))
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ bool Nextion::send_command_(const std::string &command) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Nextion::check_connect_() {
|
bool Nextion::check_connect_() {
|
||||||
if (this->get_is_connected_())
|
if (this->is_connected_)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Check if the handshake should be skipped for the Nextion connection
|
// Check if the handshake should be skipped for the Nextion connection
|
||||||
@ -280,14 +280,6 @@ void Nextion::loop() {
|
|||||||
this->goto_page(this->start_up_page_);
|
this->goto_page(this->start_up_page_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This could probably be removed from the loop area, as those are redundant.
|
|
||||||
this->set_auto_wake_on_touch(this->auto_wake_on_touch_);
|
|
||||||
this->set_exit_reparse_on_start(this->exit_reparse_on_start_);
|
|
||||||
|
|
||||||
if (this->touch_sleep_timeout_ != 0) {
|
|
||||||
this->set_touch_sleep_timeout(this->touch_sleep_timeout_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->wake_up_page_ != -1) {
|
if (this->wake_up_page_ != -1) {
|
||||||
this->set_wake_up_page(this->wake_up_page_);
|
this->set_wake_up_page(this->wake_up_page_);
|
||||||
}
|
}
|
||||||
|
@ -856,76 +856,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
|||||||
*/
|
*/
|
||||||
void set_backlight_brightness(float brightness);
|
void set_backlight_brightness(float brightness);
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the touch sleep timeout of the display.
|
|
||||||
* @param timeout Timeout in seconds.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* ```cpp
|
|
||||||
* it.set_touch_sleep_timeout(30);
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* After 30 seconds the display will go to sleep. Note: the display will only wakeup by a restart or by setting up
|
|
||||||
* `thup`.
|
|
||||||
*/
|
|
||||||
void set_touch_sleep_timeout(uint16_t timeout);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode.
|
|
||||||
* @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
|
|
||||||
* wakes up to current page.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* ```cpp
|
|
||||||
* it.set_wake_up_page(2);
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* The display will wake up to page 2.
|
|
||||||
*/
|
|
||||||
void set_wake_up_page(uint8_t page_id = 255);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets which page Nextion loads when connecting to ESPHome.
|
|
||||||
* @param page_id The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
|
|
||||||
* wakes up to current page.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* ```cpp
|
|
||||||
* it.set_start_up_page(2);
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* The display will go to page 2 when it establishes a connection to ESPHome.
|
|
||||||
*/
|
|
||||||
void set_start_up_page(uint8_t page_id = 255);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets if Nextion should auto-wake from sleep when touch press occurs.
|
|
||||||
* @param auto_wake True or false. When auto_wake is true and Nextion is in sleep mode,
|
|
||||||
* the first touch will only trigger the auto wake mode and not trigger a Touch Event.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* ```cpp
|
|
||||||
* it.set_auto_wake_on_touch(true);
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* The display will wake up by touch.
|
|
||||||
*/
|
|
||||||
void set_auto_wake_on_touch(bool auto_wake);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets if Nextion should exit the active reparse mode before the "connect" command is sent
|
|
||||||
* @param exit_reparse True or false. When exit_reparse is true, the exit reparse command
|
|
||||||
* will be sent before requesting the connection from Nextion.
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
* ```cpp
|
|
||||||
* it.set_exit_reparse_on_start(true);
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* The display will be requested to leave active reparse mode before setup.
|
|
||||||
*/
|
|
||||||
void set_exit_reparse_on_start(bool exit_reparse);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether the Nextion display should skip the connection handshake process.
|
* Sets whether the Nextion display should skip the connection handshake process.
|
||||||
* @param skip_handshake True or false. When skip_connection_handshake is true,
|
* @param skip_handshake True or false. When skip_connection_handshake is true,
|
||||||
@ -1172,15 +1102,75 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
|||||||
|
|
||||||
void update_components_by_prefix(const std::string &prefix);
|
void update_components_by_prefix(const std::string &prefix);
|
||||||
|
|
||||||
void set_touch_sleep_timeout_internal(uint32_t touch_sleep_timeout) {
|
/**
|
||||||
this->touch_sleep_timeout_ = touch_sleep_timeout;
|
* Set the touch sleep timeout of the display.
|
||||||
}
|
* @param timeout Timeout in seconds.
|
||||||
void set_wake_up_page_internal(uint8_t wake_up_page) { this->wake_up_page_ = wake_up_page; }
|
*
|
||||||
void set_start_up_page_internal(uint8_t start_up_page) { this->start_up_page_ = start_up_page; }
|
* Example:
|
||||||
void set_auto_wake_on_touch_internal(bool auto_wake_on_touch) { this->auto_wake_on_touch_ = auto_wake_on_touch; }
|
* ```cpp
|
||||||
void set_exit_reparse_on_start_internal(bool exit_reparse_on_start) {
|
* it.set_touch_sleep_timeout(30);
|
||||||
this->exit_reparse_on_start_ = exit_reparse_on_start;
|
* ```
|
||||||
}
|
*
|
||||||
|
* After 30 seconds the display will go to sleep. Note: the display will only wakeup by a restart or by setting up
|
||||||
|
* `thup`.
|
||||||
|
*/
|
||||||
|
void set_touch_sleep_timeout(uint32_t touch_sleep_timeout);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode.
|
||||||
|
* @param wake_up_page The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
|
||||||
|
* wakes up to current page.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```cpp
|
||||||
|
* it.set_wake_up_page(2);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The display will wake up to page 2.
|
||||||
|
*/
|
||||||
|
void set_wake_up_page(uint8_t wake_up_page = 255);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets which page Nextion loads when connecting to ESPHome.
|
||||||
|
* @param start_up_page The page id, from 0 to the lage page in Nextion. Set 255 (not set to any existing page) to
|
||||||
|
* wakes up to current page.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```cpp
|
||||||
|
* it.set_start_up_page(2);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The display will go to page 2 when it establishes a connection to ESPHome.
|
||||||
|
*/
|
||||||
|
void set_start_up_page(uint8_t start_up_page = 255) { this->start_up_page_ = start_up_page; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets if Nextion should auto-wake from sleep when touch press occurs.
|
||||||
|
* @param auto_wake_on_touch True or false. When auto_wake is true and Nextion is in sleep mode,
|
||||||
|
* the first touch will only trigger the auto wake mode and not trigger a Touch Event.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```cpp
|
||||||
|
* it.set_auto_wake_on_touch(true);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The display will wake up by touch.
|
||||||
|
*/
|
||||||
|
void set_auto_wake_on_touch(bool auto_wake_on_touch);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets if Nextion should exit the active reparse mode before the "connect" command is sent
|
||||||
|
* @param exit_reparse_on_start True or false. When exit_reparse_on_start is true, the exit reparse command
|
||||||
|
* will be sent before requesting the connection from Nextion.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```cpp
|
||||||
|
* it.set_exit_reparse_on_start(true);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The display will be requested to leave active reparse mode before setup.
|
||||||
|
*/
|
||||||
|
void set_exit_reparse_on_start(bool exit_reparse_on_start) { this->exit_reparse_on_start_ = exit_reparse_on_start; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Retrieves the number of commands pending in the Nextion command queue.
|
* @brief Retrieves the number of commands pending in the Nextion command queue.
|
||||||
@ -1217,6 +1207,25 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
|||||||
*/
|
*/
|
||||||
bool is_updating() override;
|
bool is_updating() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if the Nextion display is successfully connected.
|
||||||
|
*
|
||||||
|
* This method returns whether a successful connection has been established with
|
||||||
|
* the Nextion display. A connection is considered established when:
|
||||||
|
*
|
||||||
|
* - The initial handshake with the display is completed successfully, or
|
||||||
|
* - The handshake is skipped via skip_connection_handshake_ flag
|
||||||
|
*
|
||||||
|
* The connection status is particularly useful when:
|
||||||
|
* - Troubleshooting communication issues
|
||||||
|
* - Ensuring the display is ready before sending commands
|
||||||
|
* - Implementing connection-dependent behaviors
|
||||||
|
*
|
||||||
|
* @return true if the Nextion display is connected and ready to receive commands
|
||||||
|
* @return false if the display is not yet connected or connection was lost
|
||||||
|
*/
|
||||||
|
bool is_connected() { return this->is_connected_; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::deque<NextionQueue *> nextion_queue_;
|
std::deque<NextionQueue *> nextion_queue_;
|
||||||
std::deque<NextionQueue *> waveform_queue_;
|
std::deque<NextionQueue *> waveform_queue_;
|
||||||
@ -1315,8 +1324,6 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
|
|||||||
|
|
||||||
#endif // USE_NEXTION_TFT_UPLOAD
|
#endif // USE_NEXTION_TFT_UPLOAD
|
||||||
|
|
||||||
bool get_is_connected_() { return this->is_connected_; }
|
|
||||||
|
|
||||||
bool check_connect_();
|
bool check_connect_();
|
||||||
|
|
||||||
std::vector<NextionComponentBase *> touch_;
|
std::vector<NextionComponentBase *> touch_;
|
||||||
|
@ -10,19 +10,19 @@ static const char *const TAG = "nextion";
|
|||||||
// Sleep safe commands
|
// Sleep safe commands
|
||||||
void Nextion::soft_reset() { this->send_command_("rest"); }
|
void Nextion::soft_reset() { this->send_command_("rest"); }
|
||||||
|
|
||||||
void Nextion::set_wake_up_page(uint8_t page_id) {
|
void Nextion::set_wake_up_page(uint8_t wake_up_page) {
|
||||||
this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", page_id, true);
|
this->wake_up_page_ = wake_up_page;
|
||||||
|
this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", wake_up_page, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nextion::set_start_up_page(uint8_t page_id) { this->start_up_page_ = page_id; }
|
void Nextion::set_touch_sleep_timeout(uint32_t touch_sleep_timeout) {
|
||||||
|
if (touch_sleep_timeout < 3) {
|
||||||
void Nextion::set_touch_sleep_timeout(uint16_t timeout) {
|
|
||||||
if (timeout < 3) {
|
|
||||||
ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535");
|
ESP_LOGD(TAG, "Sleep timeout out of bounds, range 3-65535");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", timeout, true);
|
this->touch_sleep_timeout_ = touch_sleep_timeout;
|
||||||
|
this->add_no_result_to_queue_with_set_internal_("touch_sleep_timeout", "thsp", touch_sleep_timeout, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nextion::sleep(bool sleep) {
|
void Nextion::sleep(bool sleep) {
|
||||||
@ -54,7 +54,6 @@ bool Nextion::set_protocol_reparse_mode(bool active_mode) {
|
|||||||
this->ignore_is_setup_ = false;
|
this->ignore_is_setup_ = false;
|
||||||
return all_commands_sent;
|
return all_commands_sent;
|
||||||
}
|
}
|
||||||
void Nextion::set_exit_reparse_on_start(bool exit_reparse) { this->exit_reparse_on_start_ = exit_reparse; }
|
|
||||||
|
|
||||||
// Set Colors - Background
|
// Set Colors - Background
|
||||||
void Nextion::set_component_background_color(const char *component, uint16_t color) {
|
void Nextion::set_component_background_color(const char *component, uint16_t color) {
|
||||||
@ -191,8 +190,9 @@ void Nextion::set_backlight_brightness(float brightness) {
|
|||||||
this->add_no_result_to_queue_with_printf_("backlight_brightness", "dim=%d", static_cast<int>(brightness * 100));
|
this->add_no_result_to_queue_with_printf_("backlight_brightness", "dim=%d", static_cast<int>(brightness * 100));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nextion::set_auto_wake_on_touch(bool auto_wake) {
|
void Nextion::set_auto_wake_on_touch(bool auto_wake_on_touch) {
|
||||||
this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake ? 1 : 0);
|
this->auto_wake_on_touch_ = auto_wake_on_touch;
|
||||||
|
this->add_no_result_to_queue_with_set("auto_wake_on_touch", "thup", auto_wake_on_touch ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// General Component
|
// General Component
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome import pins
|
from esphome import pins
|
||||||
from esphome.components import sensor
|
from esphome.components import sensor
|
||||||
from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266
|
from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266, CONF_TRIGGER_ID
|
||||||
from . import const, schema, validate, generate
|
from . import const, schema, validate, generate
|
||||||
|
|
||||||
CODEOWNERS = ["@olegtarasov"]
|
CODEOWNERS = ["@olegtarasov"]
|
||||||
@ -20,7 +22,21 @@ CONF_CH2_ACTIVE = "ch2_active"
|
|||||||
CONF_SUMMER_MODE_ACTIVE = "summer_mode_active"
|
CONF_SUMMER_MODE_ACTIVE = "summer_mode_active"
|
||||||
CONF_DHW_BLOCK = "dhw_block"
|
CONF_DHW_BLOCK = "dhw_block"
|
||||||
CONF_SYNC_MODE = "sync_mode"
|
CONF_SYNC_MODE = "sync_mode"
|
||||||
CONF_OPENTHERM_VERSION = "opentherm_version"
|
CONF_OPENTHERM_VERSION = "opentherm_version" # Deprecated, will be removed
|
||||||
|
CONF_BEFORE_SEND = "before_send"
|
||||||
|
CONF_BEFORE_PROCESS_RESPONSE = "before_process_response"
|
||||||
|
|
||||||
|
# Triggers
|
||||||
|
BeforeSendTrigger = generate.opentherm_ns.class_(
|
||||||
|
"BeforeSendTrigger",
|
||||||
|
automation.Trigger.template(generate.OpenthermData.operator("ref")),
|
||||||
|
)
|
||||||
|
BeforeProcessResponseTrigger = generate.opentherm_ns.class_(
|
||||||
|
"BeforeProcessResponseTrigger",
|
||||||
|
automation.Trigger.template(generate.OpenthermData.operator("ref")),
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONFIG_SCHEMA = cv.All(
|
CONFIG_SCHEMA = cv.All(
|
||||||
cv.Schema(
|
cv.Schema(
|
||||||
@ -36,7 +52,19 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
cv.Optional(CONF_SUMMER_MODE_ACTIVE, False): cv.boolean,
|
cv.Optional(CONF_SUMMER_MODE_ACTIVE, False): cv.boolean,
|
||||||
cv.Optional(CONF_DHW_BLOCK, False): cv.boolean,
|
cv.Optional(CONF_DHW_BLOCK, False): cv.boolean,
|
||||||
cv.Optional(CONF_SYNC_MODE, False): cv.boolean,
|
cv.Optional(CONF_SYNC_MODE, False): cv.boolean,
|
||||||
cv.Optional(CONF_OPENTHERM_VERSION): cv.positive_float,
|
cv.Optional(CONF_OPENTHERM_VERSION): cv.positive_float, # Deprecated
|
||||||
|
cv.Optional(CONF_BEFORE_SEND): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(BeforeSendTrigger),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_BEFORE_PROCESS_RESPONSE): automation.validate_automation(
|
||||||
|
{
|
||||||
|
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||||
|
BeforeProcessResponseTrigger
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.extend(
|
.extend(
|
||||||
@ -44,6 +72,11 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
schema.INPUTS, (lambda _: cv.use_id(sensor.Sensor))
|
schema.INPUTS, (lambda _: cv.use_id(sensor.Sensor))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
.extend(
|
||||||
|
validate.create_entities_schema(
|
||||||
|
schema.SETTINGS, (lambda s: s.validation_schema)
|
||||||
|
)
|
||||||
|
)
|
||||||
.extend(cv.COMPONENT_SCHEMA),
|
.extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]),
|
cv.only_on([PLATFORM_ESP32, PLATFORM_ESP8266]),
|
||||||
)
|
)
|
||||||
@ -60,18 +93,33 @@ async def to_code(config: dict[str, Any]) -> None:
|
|||||||
out_pin = await cg.gpio_pin_expression(config[CONF_OUT_PIN])
|
out_pin = await cg.gpio_pin_expression(config[CONF_OUT_PIN])
|
||||||
cg.add(var.set_out_pin(out_pin))
|
cg.add(var.set_out_pin(out_pin))
|
||||||
|
|
||||||
non_sensors = {CONF_ID, CONF_IN_PIN, CONF_OUT_PIN}
|
non_sensors = {
|
||||||
|
CONF_ID,
|
||||||
|
CONF_IN_PIN,
|
||||||
|
CONF_OUT_PIN,
|
||||||
|
CONF_BEFORE_SEND,
|
||||||
|
CONF_BEFORE_PROCESS_RESPONSE,
|
||||||
|
}
|
||||||
input_sensors = []
|
input_sensors = []
|
||||||
|
settings = []
|
||||||
for key, value in config.items():
|
for key, value in config.items():
|
||||||
if key in non_sensors:
|
if key in non_sensors:
|
||||||
continue
|
continue
|
||||||
if key in schema.INPUTS:
|
if key in schema.INPUTS:
|
||||||
input_sensor = await cg.get_variable(value)
|
input_sensor = await cg.get_variable(value)
|
||||||
cg.add(
|
cg.add(getattr(var, f"set_{key}_{const.INPUT_SENSOR}")(input_sensor))
|
||||||
getattr(var, f"set_{key}_{const.INPUT_SENSOR.lower()}")(input_sensor)
|
|
||||||
)
|
|
||||||
input_sensors.append(key)
|
input_sensors.append(key)
|
||||||
|
elif key in schema.SETTINGS:
|
||||||
|
if value == schema.SETTINGS[key].default_value:
|
||||||
|
continue
|
||||||
|
cg.add(getattr(var, f"set_{key}_{const.SETTING}")(value))
|
||||||
|
settings.append(key)
|
||||||
else:
|
else:
|
||||||
|
if key == CONF_OPENTHERM_VERSION:
|
||||||
|
_LOGGER.warning(
|
||||||
|
"opentherm_version is deprecated and will be removed in esphome 2025.2.0\n"
|
||||||
|
"Please change to 'opentherm_version_controller'."
|
||||||
|
)
|
||||||
cg.add(getattr(var, f"set_{key}")(value))
|
cg.add(getattr(var, f"set_{key}")(value))
|
||||||
|
|
||||||
if len(input_sensors) > 0:
|
if len(input_sensors) > 0:
|
||||||
@ -81,3 +129,21 @@ async def to_code(config: dict[str, Any]) -> None:
|
|||||||
)
|
)
|
||||||
generate.define_readers(const.INPUT_SENSOR, input_sensors)
|
generate.define_readers(const.INPUT_SENSOR, input_sensors)
|
||||||
generate.add_messages(var, input_sensors, schema.INPUTS)
|
generate.add_messages(var, input_sensors, schema.INPUTS)
|
||||||
|
|
||||||
|
if len(settings) > 0:
|
||||||
|
generate.define_has_settings(settings, schema.SETTINGS)
|
||||||
|
generate.define_message_handler(const.SETTING, settings, schema.SETTINGS)
|
||||||
|
generate.define_setting_readers(const.SETTING, settings)
|
||||||
|
generate.add_messages(var, settings, schema.SETTINGS)
|
||||||
|
|
||||||
|
for conf in config.get(CONF_BEFORE_SEND, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
await automation.build_automation(
|
||||||
|
trigger, [(generate.OpenthermData.operator("ref"), "x")], conf
|
||||||
|
)
|
||||||
|
|
||||||
|
for conf in config.get(CONF_BEFORE_PROCESS_RESPONSE, []):
|
||||||
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
|
await automation.build_automation(
|
||||||
|
trigger, [(generate.OpenthermData.operator("ref"), "x")], conf
|
||||||
|
)
|
||||||
|
25
esphome/components/opentherm/automation.h
Normal file
25
esphome/components/opentherm/automation.h
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "hub.h"
|
||||||
|
#include "opentherm.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace opentherm {
|
||||||
|
|
||||||
|
class BeforeSendTrigger : public Trigger<OpenthermData &> {
|
||||||
|
public:
|
||||||
|
BeforeSendTrigger(OpenthermHub *hub) {
|
||||||
|
hub->add_on_before_send_callback([this](OpenthermData &x) { this->trigger(x); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class BeforeProcessResponseTrigger : public Trigger<OpenthermData &> {
|
||||||
|
public:
|
||||||
|
BeforeProcessResponseTrigger(OpenthermHub *hub) {
|
||||||
|
hub->add_on_before_process_response_callback([this](OpenthermData &x) { this->trigger(x); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace opentherm
|
||||||
|
} // namespace esphome
|
@ -9,3 +9,4 @@ SWITCH = "switch"
|
|||||||
NUMBER = "number"
|
NUMBER = "number"
|
||||||
OUTPUT = "output"
|
OUTPUT = "output"
|
||||||
INPUT_SENSOR = "input_sensor"
|
INPUT_SENSOR = "input_sensor"
|
||||||
|
SETTING = "setting"
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
from collections.abc import Awaitable
|
from collections.abc import Awaitable
|
||||||
from typing import Any, Callable
|
from typing import Any, Callable, Optional
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.const import CONF_ID
|
from esphome.const import CONF_ID
|
||||||
from . import const
|
from . import const
|
||||||
from .schema import TSchema
|
from .schema import TSchema, SettingSchema
|
||||||
|
|
||||||
opentherm_ns = cg.esphome_ns.namespace("opentherm")
|
opentherm_ns = cg.esphome_ns.namespace("opentherm")
|
||||||
OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component)
|
OpenthermHub = opentherm_ns.class_("OpenthermHub", cg.Component)
|
||||||
|
OpenthermData = opentherm_ns.class_("OpenthermData")
|
||||||
|
|
||||||
|
|
||||||
def define_has_component(component_type: str, keys: list[str]) -> None:
|
def define_has_component(component_type: str, keys: list[str]) -> None:
|
||||||
@ -21,6 +22,24 @@ def define_has_component(component_type: str, keys: list[str]) -> None:
|
|||||||
cg.add_define(f"OPENTHERM_HAS_{component_type.upper()}_{key}")
|
cg.add_define(f"OPENTHERM_HAS_{component_type.upper()}_{key}")
|
||||||
|
|
||||||
|
|
||||||
|
# We need a separate set of macros for settings because there are different backing field types we need to take
|
||||||
|
# into account
|
||||||
|
def define_has_settings(keys: list[str], schemas: dict[str, SettingSchema]) -> None:
|
||||||
|
cg.add_define(
|
||||||
|
"OPENTHERM_SETTING_LIST(F, sep)",
|
||||||
|
cg.RawExpression(
|
||||||
|
" sep ".join(
|
||||||
|
map(
|
||||||
|
lambda key: f"F({schemas[key].backing_type}, {key}_setting, {schemas[key].default_value})",
|
||||||
|
keys,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for key in keys:
|
||||||
|
cg.add_define(f"OPENTHERM_HAS_SETTING_{key}")
|
||||||
|
|
||||||
|
|
||||||
def define_message_handler(
|
def define_message_handler(
|
||||||
component_type: str, keys: list[str], schemas: dict[str, TSchema]
|
component_type: str, keys: list[str], schemas: dict[str, TSchema]
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -74,16 +93,30 @@ def define_readers(component_type: str, keys: list[str]) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_messages(hub: cg.MockObj, keys: list[str], schemas: dict[str, TSchema]):
|
def define_setting_readers(component_type: str, keys: list[str]) -> None:
|
||||||
messages: set[tuple[str, bool]] = set()
|
|
||||||
for key in keys:
|
for key in keys:
|
||||||
messages.add((schemas[key].message, schemas[key].keep_updated))
|
cg.add_define(
|
||||||
for msg, keep_updated in messages:
|
f"OPENTHERM_READ_{key}",
|
||||||
|
cg.RawExpression(f"this->{key}_{component_type.lower()}"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def add_messages(hub: cg.MockObj, keys: list[str], schemas: dict[str, TSchema]):
|
||||||
|
messages: dict[str, tuple[bool, Optional[int]]] = {}
|
||||||
|
for key in keys:
|
||||||
|
messages[schemas[key].message] = (
|
||||||
|
schemas[key].keep_updated,
|
||||||
|
schemas[key].order if hasattr(schemas[key], "order") else None,
|
||||||
|
)
|
||||||
|
for msg, (keep_updated, order) in messages.items():
|
||||||
msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}")
|
msg_expr = cg.RawExpression(f"esphome::opentherm::MessageId::{msg}")
|
||||||
if keep_updated:
|
if keep_updated:
|
||||||
cg.add(hub.add_repeating_message(msg_expr))
|
cg.add(hub.add_repeating_message(msg_expr))
|
||||||
else:
|
else:
|
||||||
cg.add(hub.add_initial_message(msg_expr))
|
if order is not None:
|
||||||
|
cg.add(hub.add_initial_message(msg_expr, order))
|
||||||
|
else:
|
||||||
|
cg.add(hub.add_initial_message(msg_expr))
|
||||||
|
|
||||||
|
|
||||||
def add_property_set(var: cg.MockObj, config_key: str, config: dict[str, Any]) -> None:
|
def add_property_set(var: cg.MockObj, config_key: str, config: dict[str, Any]) -> None:
|
||||||
|
@ -63,7 +63,7 @@ void write_f88(const float value, OpenthermData &data) { data.f88(value); }
|
|||||||
OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
|
OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
|
||||||
OpenthermData data;
|
OpenthermData data;
|
||||||
data.type = 0;
|
data.type = 0;
|
||||||
data.id = 0;
|
data.id = request_id;
|
||||||
data.valueHB = 0;
|
data.valueHB = 0;
|
||||||
data.valueLB = 0;
|
data.valueLB = 0;
|
||||||
|
|
||||||
@ -82,28 +82,13 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
|
|||||||
// NOLINTEND
|
// NOLINTEND
|
||||||
|
|
||||||
data.type = MessageType::READ_DATA;
|
data.type = MessageType::READ_DATA;
|
||||||
data.id = MessageId::STATUS;
|
|
||||||
data.valueHB = ch_enabled | (dhw_enabled << 1) | (cooling_enabled << 2) | (otc_enabled << 3) | (ch2_enabled << 4) |
|
data.valueHB = ch_enabled | (dhw_enabled << 1) | (cooling_enabled << 2) | (otc_enabled << 3) | (ch2_enabled << 4) |
|
||||||
(summer_mode_is_active << 5) | (dhw_blocked << 6);
|
(summer_mode_is_active << 5) | (dhw_blocked << 6);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Another special case is OpenTherm version number which is configured at hub level as a constant
|
// Next, we start with write requests from switches and other inputs,
|
||||||
if (request_id == MessageId::OT_VERSION_CONTROLLER) {
|
|
||||||
data.type = MessageType::WRITE_DATA;
|
|
||||||
data.id = MessageId::OT_VERSION_CONTROLLER;
|
|
||||||
data.f88(this->opentherm_version_);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable incomplete switch statement warnings, because the cases in each
|
|
||||||
// switch are generated based on the configured sensors and inputs.
|
|
||||||
#pragma GCC diagnostic push
|
|
||||||
#pragma GCC diagnostic ignored "-Wswitch"
|
|
||||||
|
|
||||||
// Next, we start with the write requests from switches and other inputs,
|
|
||||||
// because we would want to write that data if it is available, rather than
|
// because we would want to write that data if it is available, rather than
|
||||||
// request a read for that type (in the case that both read and write are
|
// request a read for that type (in the case that both read and write are
|
||||||
// supported).
|
// supported).
|
||||||
@ -116,14 +101,23 @@ OpenthermData OpenthermHub::build_request_(MessageId request_id) const {
|
|||||||
OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
|
OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
|
||||||
OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, ,
|
OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_ENTITY, ,
|
||||||
OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
|
OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
|
||||||
|
OPENTHERM_SETTING_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_WRITE_MESSAGE, OPENTHERM_MESSAGE_WRITE_SETTING, ,
|
||||||
|
OPENTHERM_MESSAGE_WRITE_POSTSCRIPT, )
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, handle the simple read requests, which only change with the message id.
|
// Finally, handle the simple read requests, which only change with the message id.
|
||||||
switch (request_id) { OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , ) }
|
switch (request_id) {
|
||||||
|
OPENTHERM_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , )
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
switch (request_id) {
|
switch (request_id) {
|
||||||
OPENTHERM_BINARY_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , )
|
OPENTHERM_BINARY_SENSOR_MESSAGE_HANDLERS(OPENTHERM_MESSAGE_READ_MESSAGE, OPENTHERM_IGNORE, , , )
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
#pragma GCC diagnostic pop
|
|
||||||
|
|
||||||
// And if we get here, a message was requested which somehow wasn't handled.
|
// And if we get here, a message was requested which somehow wasn't handled.
|
||||||
// This shouldn't happen due to the way the defines are configured, so we
|
// This shouldn't happen due to the way the defines are configured, so we
|
||||||
@ -163,19 +157,37 @@ void OpenthermHub::setup() {
|
|||||||
// communicate at least once every second. Sending the status request is
|
// communicate at least once every second. Sending the status request is
|
||||||
// good practice anyway.
|
// good practice anyway.
|
||||||
this->add_repeating_message(MessageId::STATUS);
|
this->add_repeating_message(MessageId::STATUS);
|
||||||
|
this->write_initial_messages_(this->messages_);
|
||||||
// Also ensure that we start communication with the STATUS message
|
this->message_iterator_ = this->messages_.begin();
|
||||||
this->initial_messages_.insert(this->initial_messages_.begin(), MessageId::STATUS);
|
|
||||||
|
|
||||||
if (this->opentherm_version_ > 0.0f) {
|
|
||||||
this->initial_messages_.insert(this->initial_messages_.begin(), MessageId::OT_VERSION_CONTROLLER);
|
|
||||||
}
|
|
||||||
|
|
||||||
this->current_message_iterator_ = this->initial_messages_.begin();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenthermHub::on_shutdown() { this->opentherm_->stop(); }
|
void OpenthermHub::on_shutdown() { this->opentherm_->stop(); }
|
||||||
|
|
||||||
|
// Disabling clang-tidy for this particular line since it keeps removing the trailing underscore (bug?)
|
||||||
|
void OpenthermHub::write_initial_messages_(std::vector<MessageId> &target) { // NOLINT
|
||||||
|
std::vector<std::pair<MessageId, uint8_t>> sorted;
|
||||||
|
std::copy_if(this->configured_messages_.begin(), this->configured_messages_.end(), std::back_inserter(sorted),
|
||||||
|
[](const std::pair<MessageId, uint8_t> &pair) { return pair.second < REPEATING_MESSAGE_ORDER; });
|
||||||
|
std::sort(sorted.begin(), sorted.end(),
|
||||||
|
[](const std::pair<MessageId, uint8_t> &a, const std::pair<MessageId, uint8_t> &b) {
|
||||||
|
return a.second < b.second;
|
||||||
|
});
|
||||||
|
|
||||||
|
target.clear();
|
||||||
|
std::transform(sorted.begin(), sorted.end(), std::back_inserter(target),
|
||||||
|
[](const std::pair<MessageId, uint8_t> &pair) { return pair.first; });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disabling clang-tidy for this particular line since it keeps removing the trailing underscore (bug?)
|
||||||
|
void OpenthermHub::write_repeating_messages_(std::vector<MessageId> &target) { // NOLINT
|
||||||
|
target.clear();
|
||||||
|
for (auto const &pair : this->configured_messages_) {
|
||||||
|
if (pair.second == REPEATING_MESSAGE_ORDER) {
|
||||||
|
target.push_back(pair.first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void OpenthermHub::loop() {
|
void OpenthermHub::loop() {
|
||||||
if (this->sync_mode_) {
|
if (this->sync_mode_) {
|
||||||
this->sync_loop_();
|
this->sync_loop_();
|
||||||
@ -184,29 +196,18 @@ void OpenthermHub::loop() {
|
|||||||
|
|
||||||
auto cur_time = millis();
|
auto cur_time = millis();
|
||||||
auto const cur_mode = this->opentherm_->get_mode();
|
auto const cur_mode = this->opentherm_->get_mode();
|
||||||
|
|
||||||
|
if (this->handle_error_(cur_mode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (cur_mode) {
|
switch (cur_mode) {
|
||||||
case OperationMode::WRITE:
|
case OperationMode::WRITE:
|
||||||
case OperationMode::READ:
|
case OperationMode::READ:
|
||||||
case OperationMode::LISTEN:
|
case OperationMode::LISTEN:
|
||||||
if (!this->check_timings_(cur_time)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this->last_mode_ = cur_mode;
|
|
||||||
break;
|
|
||||||
case OperationMode::ERROR_PROTOCOL:
|
|
||||||
if (this->last_mode_ == OperationMode::WRITE) {
|
|
||||||
this->handle_protocol_write_error_();
|
|
||||||
} else if (this->last_mode_ == OperationMode::READ) {
|
|
||||||
this->handle_protocol_read_error_();
|
|
||||||
}
|
|
||||||
|
|
||||||
this->stop_opentherm_();
|
|
||||||
break;
|
|
||||||
case OperationMode::ERROR_TIMEOUT:
|
|
||||||
this->handle_timeout_error_();
|
|
||||||
this->stop_opentherm_();
|
|
||||||
break;
|
break;
|
||||||
case OperationMode::IDLE:
|
case OperationMode::IDLE:
|
||||||
|
this->check_timings_(cur_time);
|
||||||
if (this->should_skip_loop_(cur_time)) {
|
if (this->should_skip_loop_(cur_time)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -219,6 +220,28 @@ void OpenthermHub::loop() {
|
|||||||
case OperationMode::RECEIVED:
|
case OperationMode::RECEIVED:
|
||||||
this->read_response_();
|
this->read_response_();
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this->last_mode_ = cur_mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OpenthermHub::handle_error_(OperationMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case OperationMode::ERROR_PROTOCOL:
|
||||||
|
// Protocol error can happen only while reading boiler response.
|
||||||
|
this->handle_protocol_error_();
|
||||||
|
return true;
|
||||||
|
case OperationMode::ERROR_TIMEOUT:
|
||||||
|
// Timeout error might happen while we wait for device to respond.
|
||||||
|
this->handle_timeout_error_();
|
||||||
|
return true;
|
||||||
|
case OperationMode::ERROR_TIMER:
|
||||||
|
// Timer error can happen only on ESP32.
|
||||||
|
this->handle_timer_error_();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,16 +260,20 @@ void OpenthermHub::sync_loop_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this->start_conversation_();
|
this->start_conversation_();
|
||||||
|
// There may be a timer error at this point
|
||||||
|
if (this->handle_error_(this->opentherm_->get_mode())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spin while message is being sent to device
|
||||||
if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) {
|
if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) {
|
||||||
ESP_LOGE(TAG, "Hub timeout triggered during send");
|
ESP_LOGE(TAG, "Hub timeout triggered during send");
|
||||||
this->stop_opentherm_();
|
this->stop_opentherm_();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->opentherm_->is_error()) {
|
// Check for errors and ensure we are in the right state (message sent successfully)
|
||||||
this->handle_protocol_write_error_();
|
if (this->handle_error_(this->opentherm_->get_mode())) {
|
||||||
this->stop_opentherm_();
|
|
||||||
return;
|
return;
|
||||||
} else if (!this->opentherm_->is_sent()) {
|
} else if (!this->opentherm_->is_sent()) {
|
||||||
ESP_LOGW(TAG, "Unexpected state after sending request: %s",
|
ESP_LOGW(TAG, "Unexpected state after sending request: %s",
|
||||||
@ -257,19 +284,20 @@ void OpenthermHub::sync_loop_() {
|
|||||||
|
|
||||||
// Listen for the response
|
// Listen for the response
|
||||||
this->opentherm_->listen();
|
this->opentherm_->listen();
|
||||||
|
// There may be a timer error at this point
|
||||||
|
if (this->handle_error_(this->opentherm_->get_mode())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spin while response is being received
|
||||||
if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) {
|
if (!this->spin_wait_(1150, [&] { return this->opentherm_->is_active(); })) {
|
||||||
ESP_LOGE(TAG, "Hub timeout triggered during receive");
|
ESP_LOGE(TAG, "Hub timeout triggered during receive");
|
||||||
this->stop_opentherm_();
|
this->stop_opentherm_();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->opentherm_->is_timeout()) {
|
// Check for errors and ensure we are in the right state (message received successfully)
|
||||||
this->handle_timeout_error_();
|
if (this->handle_error_(this->opentherm_->get_mode())) {
|
||||||
this->stop_opentherm_();
|
|
||||||
return;
|
|
||||||
} else if (this->opentherm_->is_protocol_error()) {
|
|
||||||
this->handle_protocol_read_error_();
|
|
||||||
this->stop_opentherm_();
|
|
||||||
return;
|
return;
|
||||||
} else if (!this->opentherm_->has_message()) {
|
} else if (!this->opentherm_->has_message()) {
|
||||||
ESP_LOGW(TAG, "Unexpected state after receiving response: %s",
|
ESP_LOGW(TAG, "Unexpected state after receiving response: %s",
|
||||||
@ -281,17 +309,13 @@ void OpenthermHub::sync_loop_() {
|
|||||||
this->read_response_();
|
this->read_response_();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenthermHub::check_timings_(uint32_t cur_time) {
|
void OpenthermHub::check_timings_(uint32_t cur_time) {
|
||||||
if (this->last_conversation_start_ > 0 && (cur_time - this->last_conversation_start_) > 1150) {
|
if (this->last_conversation_start_ > 0 && (cur_time - this->last_conversation_start_) > 1150) {
|
||||||
ESP_LOGW(TAG,
|
ESP_LOGW(TAG,
|
||||||
"%d ms elapsed since the start of the last convo, but 1150 ms are allowed at maximum. Look at other "
|
"%d ms elapsed since the start of the last convo, but 1150 ms are allowed at maximum. Look at other "
|
||||||
"components that might slow the loop down.",
|
"components that might slow the loop down.",
|
||||||
(int) (cur_time - this->last_conversation_start_));
|
(int) (cur_time - this->last_conversation_start_));
|
||||||
this->stop_opentherm_();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const {
|
bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const {
|
||||||
@ -304,14 +328,17 @@ bool OpenthermHub::should_skip_loop_(uint32_t cur_time) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OpenthermHub::start_conversation_() {
|
void OpenthermHub::start_conversation_() {
|
||||||
if (this->sending_initial_ && this->current_message_iterator_ == this->initial_messages_.end()) {
|
if (this->message_iterator_ == this->messages_.end()) {
|
||||||
this->sending_initial_ = false;
|
if (this->sending_initial_) {
|
||||||
this->current_message_iterator_ = this->repeating_messages_.begin();
|
this->sending_initial_ = false;
|
||||||
} else if (this->current_message_iterator_ == this->repeating_messages_.end()) {
|
this->write_repeating_messages_(this->messages_);
|
||||||
this->current_message_iterator_ = this->repeating_messages_.begin();
|
}
|
||||||
|
this->message_iterator_ = this->messages_.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto request = this->build_request_(*this->current_message_iterator_);
|
auto request = this->build_request_(*this->message_iterator_);
|
||||||
|
|
||||||
|
this->before_send_callback_.call(request);
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id,
|
ESP_LOGD(TAG, "Sending request with id %d (%s)", request.id,
|
||||||
this->opentherm_->message_id_to_str((MessageId) request.id));
|
this->opentherm_->message_id_to_str((MessageId) request.id));
|
||||||
@ -331,37 +358,48 @@ void OpenthermHub::read_response_() {
|
|||||||
|
|
||||||
this->stop_opentherm_();
|
this->stop_opentherm_();
|
||||||
|
|
||||||
|
this->before_process_response_callback_.call(response);
|
||||||
this->process_response(response);
|
this->process_response(response);
|
||||||
|
|
||||||
this->current_message_iterator_++;
|
this->message_iterator_++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenthermHub::stop_opentherm_() {
|
void OpenthermHub::stop_opentherm_() {
|
||||||
this->opentherm_->stop();
|
this->opentherm_->stop();
|
||||||
this->last_conversation_end_ = millis();
|
this->last_conversation_end_ = millis();
|
||||||
}
|
}
|
||||||
void OpenthermHub::handle_protocol_write_error_() {
|
|
||||||
ESP_LOGW(TAG, "Error while sending request: %s",
|
void OpenthermHub::handle_protocol_error_() {
|
||||||
this->opentherm_->operation_mode_to_str(this->opentherm_->get_mode()));
|
|
||||||
this->opentherm_->debug_data(this->last_request_);
|
|
||||||
}
|
|
||||||
void OpenthermHub::handle_protocol_read_error_() {
|
|
||||||
OpenThermError error;
|
OpenThermError error;
|
||||||
this->opentherm_->get_protocol_error(error);
|
this->opentherm_->get_protocol_error(error);
|
||||||
ESP_LOGW(TAG, "Protocol error occured while receiving response: %s",
|
ESP_LOGW(TAG, "Protocol error occured while receiving response: %s",
|
||||||
this->opentherm_->protocol_error_to_to_str(error.error_type));
|
this->opentherm_->protocol_error_to_str(error.error_type));
|
||||||
this->opentherm_->debug_error(error);
|
this->opentherm_->debug_error(error);
|
||||||
}
|
|
||||||
void OpenthermHub::handle_timeout_error_() {
|
|
||||||
ESP_LOGW(TAG, "Receive response timed out at a protocol level");
|
|
||||||
this->stop_opentherm_();
|
this->stop_opentherm_();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OpenthermHub::handle_timeout_error_() {
|
||||||
|
ESP_LOGW(TAG, "Timeout while waiting for response from device");
|
||||||
|
this->stop_opentherm_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenthermHub::handle_timer_error_() {
|
||||||
|
this->opentherm_->report_and_reset_timer_error();
|
||||||
|
this->stop_opentherm_();
|
||||||
|
// Timer error is critical, there is no point in retrying.
|
||||||
|
this->mark_failed();
|
||||||
|
}
|
||||||
|
|
||||||
void OpenthermHub::dump_config() {
|
void OpenthermHub::dump_config() {
|
||||||
|
std::vector<MessageId> initial_messages;
|
||||||
|
std::vector<MessageId> repeating_messages;
|
||||||
|
this->write_initial_messages_(initial_messages);
|
||||||
|
this->write_repeating_messages_(repeating_messages);
|
||||||
|
|
||||||
ESP_LOGCONFIG(TAG, "OpenTherm:");
|
ESP_LOGCONFIG(TAG, "OpenTherm:");
|
||||||
LOG_PIN(" In: ", this->in_pin_);
|
LOG_PIN(" In: ", this->in_pin_);
|
||||||
LOG_PIN(" Out: ", this->out_pin_);
|
LOG_PIN(" Out: ", this->out_pin_);
|
||||||
ESP_LOGCONFIG(TAG, " Sync mode: %d", this->sync_mode_);
|
ESP_LOGCONFIG(TAG, " Sync mode: %s", YESNO(this->sync_mode_));
|
||||||
ESP_LOGCONFIG(TAG, " Sensors: %s", SHOW(OPENTHERM_SENSOR_LIST(ID, )));
|
ESP_LOGCONFIG(TAG, " Sensors: %s", SHOW(OPENTHERM_SENSOR_LIST(ID, )));
|
||||||
ESP_LOGCONFIG(TAG, " Binary sensors: %s", SHOW(OPENTHERM_BINARY_SENSOR_LIST(ID, )));
|
ESP_LOGCONFIG(TAG, " Binary sensors: %s", SHOW(OPENTHERM_BINARY_SENSOR_LIST(ID, )));
|
||||||
ESP_LOGCONFIG(TAG, " Switches: %s", SHOW(OPENTHERM_SWITCH_LIST(ID, )));
|
ESP_LOGCONFIG(TAG, " Switches: %s", SHOW(OPENTHERM_SWITCH_LIST(ID, )));
|
||||||
@ -369,12 +407,12 @@ void OpenthermHub::dump_config() {
|
|||||||
ESP_LOGCONFIG(TAG, " Outputs: %s", SHOW(OPENTHERM_OUTPUT_LIST(ID, )));
|
ESP_LOGCONFIG(TAG, " Outputs: %s", SHOW(OPENTHERM_OUTPUT_LIST(ID, )));
|
||||||
ESP_LOGCONFIG(TAG, " Numbers: %s", SHOW(OPENTHERM_NUMBER_LIST(ID, )));
|
ESP_LOGCONFIG(TAG, " Numbers: %s", SHOW(OPENTHERM_NUMBER_LIST(ID, )));
|
||||||
ESP_LOGCONFIG(TAG, " Initial requests:");
|
ESP_LOGCONFIG(TAG, " Initial requests:");
|
||||||
for (auto type : this->initial_messages_) {
|
for (auto type : initial_messages) {
|
||||||
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type)));
|
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type));
|
||||||
}
|
}
|
||||||
ESP_LOGCONFIG(TAG, " Repeating requests:");
|
ESP_LOGCONFIG(TAG, " Repeating requests:");
|
||||||
for (auto type : this->repeating_messages_) {
|
for (auto type : repeating_messages) {
|
||||||
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str((type)));
|
ESP_LOGCONFIG(TAG, " - %d (%s)", type, this->opentherm_->message_id_to_str(type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,9 @@
|
|||||||
namespace esphome {
|
namespace esphome {
|
||||||
namespace opentherm {
|
namespace opentherm {
|
||||||
|
|
||||||
|
static const uint8_t REPEATING_MESSAGE_ORDER = 255;
|
||||||
|
static const uint8_t INITIAL_UNORDERED_MESSAGE_ORDER = 254;
|
||||||
|
|
||||||
// OpenTherm component for ESPHome
|
// OpenTherm component for ESPHome
|
||||||
class OpenthermHub : public Component {
|
class OpenthermHub : public Component {
|
||||||
protected:
|
protected:
|
||||||
@ -58,15 +61,12 @@ class OpenthermHub : public Component {
|
|||||||
|
|
||||||
OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_DECLARE_INPUT_SENSOR, )
|
OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_DECLARE_INPUT_SENSOR, )
|
||||||
|
|
||||||
// The set of initial messages to send on starting communication with the boiler
|
OPENTHERM_SETTING_LIST(OPENTHERM_DECLARE_SETTING, )
|
||||||
std::vector<MessageId> initial_messages_;
|
|
||||||
// and the repeating messages which are sent repeatedly to update various sensors
|
|
||||||
// and boiler parameters (like the setpoint).
|
|
||||||
std::vector<MessageId> repeating_messages_;
|
|
||||||
// Indicates if we are still working on the initial requests or not
|
|
||||||
bool sending_initial_ = true;
|
bool sending_initial_ = true;
|
||||||
// Index for the current request in one of the _requests sets.
|
std::unordered_map<MessageId, uint8_t> configured_messages_;
|
||||||
std::vector<MessageId>::const_iterator current_message_iterator_;
|
std::vector<MessageId> messages_;
|
||||||
|
std::vector<MessageId>::const_iterator message_iterator_;
|
||||||
|
|
||||||
uint32_t last_conversation_start_ = 0;
|
uint32_t last_conversation_start_ = 0;
|
||||||
uint32_t last_conversation_end_ = 0;
|
uint32_t last_conversation_end_ = 0;
|
||||||
@ -78,20 +78,25 @@ class OpenthermHub : public Component {
|
|||||||
// Very likely to happen while using Dallas temperature sensors.
|
// Very likely to happen while using Dallas temperature sensors.
|
||||||
bool sync_mode_ = false;
|
bool sync_mode_ = false;
|
||||||
|
|
||||||
float opentherm_version_ = 0.0f;
|
CallbackManager<void(OpenthermData &)> before_send_callback_;
|
||||||
|
CallbackManager<void(OpenthermData &)> before_process_response_callback_;
|
||||||
|
|
||||||
// Create OpenTherm messages based on the message id
|
// Create OpenTherm messages based on the message id
|
||||||
OpenthermData build_request_(MessageId request_id) const;
|
OpenthermData build_request_(MessageId request_id) const;
|
||||||
void handle_protocol_write_error_();
|
bool handle_error_(OperationMode mode);
|
||||||
void handle_protocol_read_error_();
|
void handle_protocol_error_();
|
||||||
void handle_timeout_error_();
|
void handle_timeout_error_();
|
||||||
|
void handle_timer_error_();
|
||||||
void stop_opentherm_();
|
void stop_opentherm_();
|
||||||
void start_conversation_();
|
void start_conversation_();
|
||||||
void read_response_();
|
void read_response_();
|
||||||
bool check_timings_(uint32_t cur_time);
|
void check_timings_(uint32_t cur_time);
|
||||||
bool should_skip_loop_(uint32_t cur_time) const;
|
bool should_skip_loop_(uint32_t cur_time) const;
|
||||||
void sync_loop_();
|
void sync_loop_();
|
||||||
|
|
||||||
|
void write_initial_messages_(std::vector<MessageId> &target);
|
||||||
|
void write_repeating_messages_(std::vector<MessageId> &target);
|
||||||
|
|
||||||
template<typename F> bool spin_wait_(uint32_t timeout, F func) {
|
template<typename F> bool spin_wait_(uint32_t timeout, F func) {
|
||||||
auto start_time = millis();
|
auto start_time = millis();
|
||||||
while (func()) {
|
while (func()) {
|
||||||
@ -127,13 +132,18 @@ class OpenthermHub : public Component {
|
|||||||
|
|
||||||
OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_SET_INPUT_SENSOR, )
|
OPENTHERM_INPUT_SENSOR_LIST(OPENTHERM_SET_INPUT_SENSOR, )
|
||||||
|
|
||||||
|
OPENTHERM_SETTING_LIST(OPENTHERM_SET_SETTING, )
|
||||||
|
|
||||||
// Add a request to the vector of initial requests
|
// Add a request to the vector of initial requests
|
||||||
void add_initial_message(MessageId message_id) { this->initial_messages_.push_back(message_id); }
|
void add_initial_message(MessageId message_id) {
|
||||||
|
this->configured_messages_[message_id] = INITIAL_UNORDERED_MESSAGE_ORDER;
|
||||||
|
}
|
||||||
|
void add_initial_message(MessageId message_id, uint8_t order) { this->configured_messages_[message_id] = order; }
|
||||||
// Add a request to the set of repeating requests. Note that a large number of repeating
|
// Add a request to the set of repeating requests. Note that a large number of repeating
|
||||||
// requests will slow down communication with the boiler. Each request may take up to 1 second,
|
// requests will slow down communication with the boiler. Each request may take up to 1 second,
|
||||||
// so with all sensors enabled, it may take about half a minute before a change in setpoint
|
// so with all sensors enabled, it may take about half a minute before a change in setpoint
|
||||||
// will be processed.
|
// will be processed.
|
||||||
void add_repeating_message(MessageId message_id) { this->repeating_messages_.push_back(message_id); }
|
void add_repeating_message(MessageId message_id) { this->configured_messages_[message_id] = REPEATING_MESSAGE_ORDER; }
|
||||||
|
|
||||||
// There are seven status variables, which can either be set as a simple variable,
|
// There are seven status variables, which can either be set as a simple variable,
|
||||||
// or using a switch. ch_enable and dhw_enable default to true, the others to false.
|
// or using a switch. ch_enable and dhw_enable default to true, the others to false.
|
||||||
@ -149,7 +159,13 @@ class OpenthermHub : public Component {
|
|||||||
void set_summer_mode_active(bool value) { this->summer_mode_active = value; }
|
void set_summer_mode_active(bool value) { this->summer_mode_active = value; }
|
||||||
void set_dhw_block(bool value) { this->dhw_block = value; }
|
void set_dhw_block(bool value) { this->dhw_block = value; }
|
||||||
void set_sync_mode(bool sync_mode) { this->sync_mode_ = sync_mode; }
|
void set_sync_mode(bool sync_mode) { this->sync_mode_ = sync_mode; }
|
||||||
void set_opentherm_version(float value) { this->opentherm_version_ = value; }
|
|
||||||
|
void add_on_before_send_callback(std::function<void(OpenthermData &)> &&callback) {
|
||||||
|
this->before_send_callback_.add(std::move(callback));
|
||||||
|
}
|
||||||
|
void add_on_before_process_response_callback(std::function<void(OpenthermData &)> &&callback) {
|
||||||
|
this->before_process_response_callback_.add(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||||
|
|
||||||
|
@ -52,7 +52,9 @@ bool OpenTherm::initialize() {
|
|||||||
OpenTherm::instance = this;
|
OpenTherm::instance = this;
|
||||||
#endif
|
#endif
|
||||||
this->in_pin_->pin_mode(gpio::FLAG_INPUT);
|
this->in_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||||
|
this->in_pin_->setup();
|
||||||
this->out_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
this->out_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||||
|
this->out_pin_->setup();
|
||||||
this->out_pin_->digital_write(true);
|
this->out_pin_->digital_write(true);
|
||||||
|
|
||||||
#if defined(ESP32) || defined(USE_ESP_IDF)
|
#if defined(ESP32) || defined(USE_ESP_IDF)
|
||||||
@ -182,7 +184,7 @@ bool IRAM_ATTR OpenTherm::timer_isr(OpenTherm *arg) {
|
|||||||
}
|
}
|
||||||
arg->capture_ = 1; // reset counter
|
arg->capture_ = 1; // reset counter
|
||||||
} else if (arg->capture_ > 0xFF) {
|
} else if (arg->capture_ > 0xFF) {
|
||||||
// no change for too long, invalid mancheter encoding
|
// no change for too long, invalid manchester encoding
|
||||||
arg->mode_ = OperationMode::ERROR_PROTOCOL;
|
arg->mode_ = OperationMode::ERROR_PROTOCOL;
|
||||||
arg->error_type_ = ProtocolErrorType::NO_CHANGE_TOO_LONG;
|
arg->error_type_ = ProtocolErrorType::NO_CHANGE_TOO_LONG;
|
||||||
arg->stop_timer_();
|
arg->stop_timer_();
|
||||||
@ -312,21 +314,31 @@ bool OpenTherm::init_esp32_timer_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) {
|
void IRAM_ATTR OpenTherm::start_esp32_timer_(uint64_t alarm_value) {
|
||||||
esp_err_t result;
|
// We will report timer errors outside of interrupt handler
|
||||||
|
this->timer_error_ = ESP_OK;
|
||||||
|
this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
|
||||||
|
|
||||||
result = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value);
|
this->timer_error_ = timer_set_alarm_value(this->timer_group_, this->timer_idx_, alarm_value);
|
||||||
if (result != ESP_OK) {
|
if (this->timer_error_ != ESP_OK) {
|
||||||
const auto *error = esp_err_to_name(result);
|
this->timer_error_type_ = TimerErrorType::SET_ALARM_VALUE_ERROR;
|
||||||
ESP_LOGE(TAG, "Failed to set alarm value. Error: %s", error);
|
return;
|
||||||
|
}
|
||||||
|
this->timer_error_ = timer_start(this->timer_group_, this->timer_idx_);
|
||||||
|
if (this->timer_error_ != ESP_OK) {
|
||||||
|
this->timer_error_type_ = TimerErrorType::TIMER_START_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpenTherm::report_and_reset_timer_error() {
|
||||||
|
if (this->timer_error_ == ESP_OK) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = timer_start(this->timer_group_, this->timer_idx_);
|
ESP_LOGE(TAG, "Error occured while manipulating timer (%s): %s", this->timer_error_to_str(this->timer_error_type_),
|
||||||
if (result != ESP_OK) {
|
esp_err_to_name(this->timer_error_));
|
||||||
const auto *error = esp_err_to_name(result);
|
|
||||||
ESP_LOGE(TAG, "Failed to start the timer. Error: %s", error);
|
this->timer_error_ = ESP_OK;
|
||||||
return;
|
this->timer_error_type_ = NO_TIMER_ERROR;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5 kHz timer_
|
// 5 kHz timer_
|
||||||
@ -343,21 +355,18 @@ void IRAM_ATTR OpenTherm::start_write_timer_() {
|
|||||||
|
|
||||||
void IRAM_ATTR OpenTherm::stop_timer_() {
|
void IRAM_ATTR OpenTherm::stop_timer_() {
|
||||||
InterruptLock const lock;
|
InterruptLock const lock;
|
||||||
|
// We will report timer errors outside of interrupt handler
|
||||||
|
this->timer_error_ = ESP_OK;
|
||||||
|
this->timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
|
||||||
|
|
||||||
esp_err_t result;
|
this->timer_error_ = timer_pause(this->timer_group_, this->timer_idx_);
|
||||||
|
if (this->timer_error_ != ESP_OK) {
|
||||||
result = timer_pause(this->timer_group_, this->timer_idx_);
|
this->timer_error_type_ = TimerErrorType::TIMER_PAUSE_ERROR;
|
||||||
if (result != ESP_OK) {
|
|
||||||
const auto *error = esp_err_to_name(result);
|
|
||||||
ESP_LOGE(TAG, "Failed to pause the timer. Error: %s", error);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this->timer_error_ = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0);
|
||||||
result = timer_set_counter_value(this->timer_group_, this->timer_idx_, 0);
|
if (this->timer_error_ != ESP_OK) {
|
||||||
if (result != ESP_OK) {
|
this->timer_error_type_ = TimerErrorType::SET_COUNTER_VALUE_ERROR;
|
||||||
const auto *error = esp_err_to_name(result);
|
|
||||||
ESP_LOGE(TAG, "Failed to set timer counter to 0 after pausing. Error: %s", error);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,6 +395,9 @@ void IRAM_ATTR OpenTherm::stop_timer_() {
|
|||||||
timer1_detachInterrupt();
|
timer1_detachInterrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There is nothing to report on ESP8266
|
||||||
|
void OpenTherm::report_and_reset_timer_error() {}
|
||||||
|
|
||||||
#endif // END ESP8266
|
#endif // END ESP8266
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd
|
// https://stackoverflow.com/questions/21617970/how-to-check-if-value-has-even-parity-of-bits-or-odd
|
||||||
@ -412,11 +424,12 @@ const char *OpenTherm::operation_mode_to_str(OperationMode mode) {
|
|||||||
TO_STRING_MEMBER(SENT)
|
TO_STRING_MEMBER(SENT)
|
||||||
TO_STRING_MEMBER(ERROR_PROTOCOL)
|
TO_STRING_MEMBER(ERROR_PROTOCOL)
|
||||||
TO_STRING_MEMBER(ERROR_TIMEOUT)
|
TO_STRING_MEMBER(ERROR_TIMEOUT)
|
||||||
|
TO_STRING_MEMBER(ERROR_TIMER)
|
||||||
default:
|
default:
|
||||||
return "<INVALID>";
|
return "<INVALID>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const char *OpenTherm::protocol_error_to_to_str(ProtocolErrorType error_type) {
|
const char *OpenTherm::protocol_error_to_str(ProtocolErrorType error_type) {
|
||||||
switch (error_type) {
|
switch (error_type) {
|
||||||
TO_STRING_MEMBER(NO_ERROR)
|
TO_STRING_MEMBER(NO_ERROR)
|
||||||
TO_STRING_MEMBER(NO_TRANSITION)
|
TO_STRING_MEMBER(NO_TRANSITION)
|
||||||
@ -427,6 +440,17 @@ const char *OpenTherm::protocol_error_to_to_str(ProtocolErrorType error_type) {
|
|||||||
return "<INVALID>";
|
return "<INVALID>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const char *OpenTherm::timer_error_to_str(TimerErrorType error_type) {
|
||||||
|
switch (error_type) {
|
||||||
|
TO_STRING_MEMBER(NO_TIMER_ERROR)
|
||||||
|
TO_STRING_MEMBER(SET_ALARM_VALUE_ERROR)
|
||||||
|
TO_STRING_MEMBER(TIMER_START_ERROR)
|
||||||
|
TO_STRING_MEMBER(TIMER_PAUSE_ERROR)
|
||||||
|
TO_STRING_MEMBER(SET_COUNTER_VALUE_ERROR)
|
||||||
|
default:
|
||||||
|
return "<INVALID>";
|
||||||
|
}
|
||||||
|
}
|
||||||
const char *OpenTherm::message_type_to_str(MessageType message_type) {
|
const char *OpenTherm::message_type_to_str(MessageType message_type) {
|
||||||
switch (message_type) {
|
switch (message_type) {
|
||||||
TO_STRING_MEMBER(READ_DATA)
|
TO_STRING_MEMBER(READ_DATA)
|
||||||
|
@ -36,11 +36,12 @@ enum OperationMode {
|
|||||||
READ = 2, // reading 32-bit data frame
|
READ = 2, // reading 32-bit data frame
|
||||||
RECEIVED = 3, // data frame received with valid start and stop bit
|
RECEIVED = 3, // data frame received with valid start and stop bit
|
||||||
|
|
||||||
WRITE = 4, // writing data with timer_
|
WRITE = 4, // writing data to output
|
||||||
SENT = 5, // all data written to output
|
SENT = 5, // all data written to output
|
||||||
|
|
||||||
ERROR_PROTOCOL = 8, // manchester protocol data transfer error
|
ERROR_PROTOCOL = 8, // protocol error, can happed only during READ
|
||||||
ERROR_TIMEOUT = 9 // read timeout
|
ERROR_TIMEOUT = 9, // timeout while waiting for response from device, only during LISTEN
|
||||||
|
ERROR_TIMER = 10 // error operating the ESP32 timer
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ProtocolErrorType {
|
enum ProtocolErrorType {
|
||||||
@ -51,6 +52,14 @@ enum ProtocolErrorType {
|
|||||||
NO_CHANGE_TOO_LONG = 4, // No level change for too much timer ticks
|
NO_CHANGE_TOO_LONG = 4, // No level change for too much timer ticks
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum TimerErrorType {
|
||||||
|
NO_TIMER_ERROR = 0, // No error
|
||||||
|
SET_ALARM_VALUE_ERROR = 1, // No transition in the middle of the bit
|
||||||
|
TIMER_START_ERROR = 2, // Stop bit wasn't present when expected
|
||||||
|
TIMER_PAUSE_ERROR = 3, // Parity check didn't pass
|
||||||
|
SET_COUNTER_VALUE_ERROR = 4, // No level change for too much timer ticks
|
||||||
|
};
|
||||||
|
|
||||||
enum MessageType {
|
enum MessageType {
|
||||||
READ_DATA = 0,
|
READ_DATA = 0,
|
||||||
READ_ACK = 4,
|
READ_ACK = 4,
|
||||||
@ -299,7 +308,9 @@ class OpenTherm {
|
|||||||
*
|
*
|
||||||
* @return true if last listen() or send() operation ends up with an error.
|
* @return true if last listen() or send() operation ends up with an error.
|
||||||
*/
|
*/
|
||||||
bool is_error() { return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL; }
|
bool is_error() {
|
||||||
|
return mode_ == OperationMode::ERROR_TIMEOUT || mode_ == OperationMode::ERROR_PROTOCOL || mode_ == ERROR_TIMER;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates whether last listen() or send() operation ends up with a *timeout* error
|
* Indicates whether last listen() or send() operation ends up with a *timeout* error
|
||||||
@ -313,14 +324,22 @@ class OpenTherm {
|
|||||||
*/
|
*/
|
||||||
bool is_protocol_error() { return mode_ == OperationMode::ERROR_PROTOCOL; }
|
bool is_protocol_error() { return mode_ == OperationMode::ERROR_PROTOCOL; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether start_esp32_timer_() or stop_timer_() had an error. Only relevant when used on ESP32.
|
||||||
|
* @return true if there was an error.
|
||||||
|
*/
|
||||||
|
bool is_timer_error() { return mode_ == OperationMode::ERROR_TIMER; }
|
||||||
|
|
||||||
bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; }
|
bool is_active() { return mode_ == LISTEN || mode_ == READ || mode_ == WRITE; }
|
||||||
|
|
||||||
OperationMode get_mode() { return mode_; }
|
OperationMode get_mode() { return mode_; }
|
||||||
|
|
||||||
void debug_data(OpenthermData &data);
|
void debug_data(OpenthermData &data);
|
||||||
void debug_error(OpenThermError &error) const;
|
void debug_error(OpenThermError &error) const;
|
||||||
|
void report_and_reset_timer_error();
|
||||||
|
|
||||||
const char *protocol_error_to_to_str(ProtocolErrorType error_type);
|
const char *protocol_error_to_str(ProtocolErrorType error_type);
|
||||||
|
const char *timer_error_to_str(TimerErrorType error_type);
|
||||||
const char *message_type_to_str(MessageType message_type);
|
const char *message_type_to_str(MessageType message_type);
|
||||||
const char *operation_mode_to_str(OperationMode mode);
|
const char *operation_mode_to_str(OperationMode mode);
|
||||||
const char *message_id_to_str(MessageId id);
|
const char *message_id_to_str(MessageId id);
|
||||||
@ -349,10 +368,12 @@ class OpenTherm {
|
|||||||
uint32_t data_;
|
uint32_t data_;
|
||||||
uint8_t bit_pos_;
|
uint8_t bit_pos_;
|
||||||
int32_t timeout_counter_; // <0 no timeout
|
int32_t timeout_counter_; // <0 no timeout
|
||||||
|
|
||||||
int32_t device_timeout_;
|
int32_t device_timeout_;
|
||||||
|
|
||||||
#if defined(ESP32) || defined(USE_ESP_IDF)
|
#if defined(ESP32) || defined(USE_ESP_IDF)
|
||||||
|
esp_err_t timer_error_ = ESP_OK;
|
||||||
|
TimerErrorType timer_error_type_ = TimerErrorType::NO_TIMER_ERROR;
|
||||||
|
|
||||||
bool init_esp32_timer_();
|
bool init_esp32_timer_();
|
||||||
void start_esp32_timer_(uint64_t alarm_value);
|
void start_esp32_timer_(uint64_t alarm_value);
|
||||||
#endif
|
#endif
|
||||||
|
@ -28,6 +28,9 @@ namespace opentherm {
|
|||||||
#ifndef OPENTHERM_INPUT_SENSOR_LIST
|
#ifndef OPENTHERM_INPUT_SENSOR_LIST
|
||||||
#define OPENTHERM_INPUT_SENSOR_LIST(F, sep)
|
#define OPENTHERM_INPUT_SENSOR_LIST(F, sep)
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef OPENTHERM_SETTING_LIST
|
||||||
|
#define OPENTHERM_SETTING_LIST(F, sep)
|
||||||
|
#endif
|
||||||
|
|
||||||
// Use macros to create fields for every entity specified in the ESPHome configuration
|
// Use macros to create fields for every entity specified in the ESPHome configuration
|
||||||
#define OPENTHERM_DECLARE_SENSOR(entity) sensor::Sensor *entity;
|
#define OPENTHERM_DECLARE_SENSOR(entity) sensor::Sensor *entity;
|
||||||
@ -36,6 +39,7 @@ namespace opentherm {
|
|||||||
#define OPENTHERM_DECLARE_NUMBER(entity) OpenthermNumber *entity;
|
#define OPENTHERM_DECLARE_NUMBER(entity) OpenthermNumber *entity;
|
||||||
#define OPENTHERM_DECLARE_OUTPUT(entity) OpenthermOutput *entity;
|
#define OPENTHERM_DECLARE_OUTPUT(entity) OpenthermOutput *entity;
|
||||||
#define OPENTHERM_DECLARE_INPUT_SENSOR(entity) sensor::Sensor *entity;
|
#define OPENTHERM_DECLARE_INPUT_SENSOR(entity) sensor::Sensor *entity;
|
||||||
|
#define OPENTHERM_DECLARE_SETTING(type, entity, def) type entity = def;
|
||||||
|
|
||||||
// Setter macros
|
// Setter macros
|
||||||
#define OPENTHERM_SET_SENSOR(entity) \
|
#define OPENTHERM_SET_SENSOR(entity) \
|
||||||
@ -56,6 +60,9 @@ namespace opentherm {
|
|||||||
#define OPENTHERM_SET_INPUT_SENSOR(entity) \
|
#define OPENTHERM_SET_INPUT_SENSOR(entity) \
|
||||||
void set_##entity(sensor::Sensor *sensor) { this->entity = sensor; }
|
void set_##entity(sensor::Sensor *sensor) { this->entity = sensor; }
|
||||||
|
|
||||||
|
#define OPENTHERM_SET_SETTING(type, entity, def) \
|
||||||
|
void set_##entity(type value) { this->entity = value; }
|
||||||
|
|
||||||
// ===== hub.cpp macros =====
|
// ===== hub.cpp macros =====
|
||||||
|
|
||||||
// *_MESSAGE_HANDLERS are generated in defines.h and look like this:
|
// *_MESSAGE_HANDLERS are generated in defines.h and look like this:
|
||||||
@ -85,6 +92,9 @@ namespace opentherm {
|
|||||||
#ifndef OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS
|
#ifndef OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS
|
||||||
#define OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep)
|
#define OPENTHERM_INPUT_SENSOR_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep)
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef OPENTHERM_SETTING_MESSAGE_HANDLERS
|
||||||
|
#define OPENTHERM_SETTING_MESSAGE_HANDLERS(MESSAGE, ENTITY, entity_sep, postscript, msg_sep)
|
||||||
|
#endif
|
||||||
|
|
||||||
// Write data request builders
|
// Write data request builders
|
||||||
#define OPENTHERM_MESSAGE_WRITE_MESSAGE(msg) \
|
#define OPENTHERM_MESSAGE_WRITE_MESSAGE(msg) \
|
||||||
@ -92,6 +102,7 @@ namespace opentherm {
|
|||||||
data.type = MessageType::WRITE_DATA; \
|
data.type = MessageType::WRITE_DATA; \
|
||||||
data.id = request_id;
|
data.id = request_id;
|
||||||
#define OPENTHERM_MESSAGE_WRITE_ENTITY(key, msg_data) message_data::write_##msg_data(this->key->state, data);
|
#define OPENTHERM_MESSAGE_WRITE_ENTITY(key, msg_data) message_data::write_##msg_data(this->key->state, data);
|
||||||
|
#define OPENTHERM_MESSAGE_WRITE_SETTING(key, msg_data) message_data::write_##msg_data(this->key, data);
|
||||||
#define OPENTHERM_MESSAGE_WRITE_POSTSCRIPT \
|
#define OPENTHERM_MESSAGE_WRITE_POSTSCRIPT \
|
||||||
return data; \
|
return data; \
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
# inputs of the OpenTherm component.
|
# inputs of the OpenTherm component.
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, TypeVar
|
from typing import Optional, TypeVar, Any
|
||||||
|
|
||||||
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
UNIT_CELSIUS,
|
UNIT_CELSIUS,
|
||||||
UNIT_EMPTY,
|
UNIT_EMPTY,
|
||||||
@ -64,6 +65,7 @@ class SensorSchema(EntitySchema):
|
|||||||
icon: Optional[str] = None
|
icon: Optional[str] = None
|
||||||
device_class: Optional[str] = None
|
device_class: Optional[str] = None
|
||||||
disabled_by_default: bool = False
|
disabled_by_default: bool = False
|
||||||
|
order: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
SENSORS: dict[str, SensorSchema] = {
|
SENSORS: dict[str, SensorSchema] = {
|
||||||
@ -399,6 +401,7 @@ SENSORS: dict[str, SensorSchema] = {
|
|||||||
message="OT_VERSION_DEVICE",
|
message="OT_VERSION_DEVICE",
|
||||||
keep_updated=False,
|
keep_updated=False,
|
||||||
message_data="f88",
|
message_data="f88",
|
||||||
|
order=2,
|
||||||
),
|
),
|
||||||
"device_type": SensorSchema(
|
"device_type": SensorSchema(
|
||||||
description="Device product type",
|
description="Device product type",
|
||||||
@ -409,6 +412,7 @@ SENSORS: dict[str, SensorSchema] = {
|
|||||||
message="VERSION_DEVICE",
|
message="VERSION_DEVICE",
|
||||||
keep_updated=False,
|
keep_updated=False,
|
||||||
message_data="u8_hb",
|
message_data="u8_hb",
|
||||||
|
order=0,
|
||||||
),
|
),
|
||||||
"device_version": SensorSchema(
|
"device_version": SensorSchema(
|
||||||
description="Device product version",
|
description="Device product version",
|
||||||
@ -419,6 +423,7 @@ SENSORS: dict[str, SensorSchema] = {
|
|||||||
message="VERSION_DEVICE",
|
message="VERSION_DEVICE",
|
||||||
keep_updated=False,
|
keep_updated=False,
|
||||||
message_data="u8_lb",
|
message_data="u8_lb",
|
||||||
|
order=0,
|
||||||
),
|
),
|
||||||
"device_id": SensorSchema(
|
"device_id": SensorSchema(
|
||||||
description="Device ID code",
|
description="Device ID code",
|
||||||
@ -429,6 +434,7 @@ SENSORS: dict[str, SensorSchema] = {
|
|||||||
message="DEVICE_CONFIG",
|
message="DEVICE_CONFIG",
|
||||||
keep_updated=False,
|
keep_updated=False,
|
||||||
message_data="u8_lb",
|
message_data="u8_lb",
|
||||||
|
order=4,
|
||||||
),
|
),
|
||||||
"otc_hc_ratio_ub": SensorSchema(
|
"otc_hc_ratio_ub": SensorSchema(
|
||||||
description="OTC heat curve ratio upper bound",
|
description="OTC heat curve ratio upper bound",
|
||||||
@ -457,6 +463,7 @@ SENSORS: dict[str, SensorSchema] = {
|
|||||||
class BinarySensorSchema(EntitySchema):
|
class BinarySensorSchema(EntitySchema):
|
||||||
icon: Optional[str] = None
|
icon: Optional[str] = None
|
||||||
device_class: Optional[str] = None
|
device_class: Optional[str] = None
|
||||||
|
order: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
BINARY_SENSORS: dict[str, BinarySensorSchema] = {
|
BINARY_SENSORS: dict[str, BinarySensorSchema] = {
|
||||||
@ -525,48 +532,56 @@ BINARY_SENSORS: dict[str, BinarySensorSchema] = {
|
|||||||
message="DEVICE_CONFIG",
|
message="DEVICE_CONFIG",
|
||||||
keep_updated=False,
|
keep_updated=False,
|
||||||
message_data="flag8_hb_0",
|
message_data="flag8_hb_0",
|
||||||
|
order=4,
|
||||||
),
|
),
|
||||||
"control_type_on_off": BinarySensorSchema(
|
"control_type_on_off": BinarySensorSchema(
|
||||||
description="Configuration: Control type is on/off",
|
description="Configuration: Control type is on/off",
|
||||||
message="DEVICE_CONFIG",
|
message="DEVICE_CONFIG",
|
||||||
keep_updated=False,
|
keep_updated=False,
|
||||||
message_data="flag8_hb_1",
|
message_data="flag8_hb_1",
|
||||||
|
order=4,
|
||||||
),
|
),
|
||||||
"cooling_supported": BinarySensorSchema(
|
"cooling_supported": BinarySensorSchema(
|
||||||
description="Configuration: Cooling supported",
|
description="Configuration: Cooling supported",
|
||||||
message="DEVICE_CONFIG",
|
message="DEVICE_CONFIG",
|
||||||
keep_updated=False,
|
keep_updated=False,
|
||||||
message_data="flag8_hb_2",
|
message_data="flag8_hb_2",
|
||||||
|
order=4,
|
||||||
),
|
),
|
||||||
"dhw_storage_tank": BinarySensorSchema(
|
"dhw_storage_tank": BinarySensorSchema(
|
||||||
description="Configuration: DHW storage tank",
|
description="Configuration: DHW storage tank",
|
||||||
message="DEVICE_CONFIG",
|
message="DEVICE_CONFIG",
|
||||||
keep_updated=False,
|
keep_updated=False,
|
||||||
message_data="flag8_hb_3",
|
message_data="flag8_hb_3",
|
||||||
|
order=4,
|
||||||
),
|
),
|
||||||
"controller_pump_control_allowed": BinarySensorSchema(
|
"controller_pump_control_allowed": BinarySensorSchema(
|
||||||
description="Configuration: Controller pump control allowed",
|
description="Configuration: Controller pump control allowed",
|
||||||
message="DEVICE_CONFIG",
|
message="DEVICE_CONFIG",
|
||||||
keep_updated=False,
|
keep_updated=False,
|
||||||
message_data="flag8_hb_4",
|
message_data="flag8_hb_4",
|
||||||
|
order=4,
|
||||||
),
|
),
|
||||||
"ch2_present": BinarySensorSchema(
|
"ch2_present": BinarySensorSchema(
|
||||||
description="Configuration: CH2 present",
|
description="Configuration: CH2 present",
|
||||||
message="DEVICE_CONFIG",
|
message="DEVICE_CONFIG",
|
||||||
keep_updated=False,
|
keep_updated=False,
|
||||||
message_data="flag8_hb_5",
|
message_data="flag8_hb_5",
|
||||||
|
order=4,
|
||||||
),
|
),
|
||||||
"water_filling": BinarySensorSchema(
|
"water_filling": BinarySensorSchema(
|
||||||
description="Configuration: Remote water filling",
|
description="Configuration: Remote water filling",
|
||||||
message="DEVICE_CONFIG",
|
message="DEVICE_CONFIG",
|
||||||
keep_updated=False,
|
keep_updated=False,
|
||||||
message_data="flag8_hb_6",
|
message_data="flag8_hb_6",
|
||||||
|
order=4,
|
||||||
),
|
),
|
||||||
"heat_mode": BinarySensorSchema(
|
"heat_mode": BinarySensorSchema(
|
||||||
description="Configuration: Heating or cooling",
|
description="Configuration: Heating or cooling",
|
||||||
message="DEVICE_CONFIG",
|
message="DEVICE_CONFIG",
|
||||||
keep_updated=False,
|
keep_updated=False,
|
||||||
message_data="flag8_hb_7",
|
message_data="flag8_hb_7",
|
||||||
|
order=4,
|
||||||
),
|
),
|
||||||
"dhw_setpoint_transfer_enabled": BinarySensorSchema(
|
"dhw_setpoint_transfer_enabled": BinarySensorSchema(
|
||||||
description="Remote boiler parameters: DHW setpoint transfer enabled",
|
description="Remote boiler parameters: DHW setpoint transfer enabled",
|
||||||
@ -812,3 +827,65 @@ INPUTS: dict[str, InputSchema] = {
|
|||||||
auto_max_value=AutoConfigure(message="OTC_CURVE_BOUNDS", message_data="u8_hb"),
|
auto_max_value=AutoConfigure(message="OTC_CURVE_BOUNDS", message_data="u8_hb"),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SettingSchema(EntitySchema):
|
||||||
|
backing_type: str
|
||||||
|
validation_schema: cv.Schema
|
||||||
|
default_value: Any
|
||||||
|
order: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
SETTINGS: dict[str, SettingSchema] = {
|
||||||
|
"controller_product_type": SettingSchema(
|
||||||
|
description="Controller product type",
|
||||||
|
message="VERSION_CONTROLLER",
|
||||||
|
keep_updated=False,
|
||||||
|
message_data="u8_hb",
|
||||||
|
backing_type="uint8_t",
|
||||||
|
validation_schema=cv.int_range(min=0, max=255),
|
||||||
|
default_value=0,
|
||||||
|
order=1,
|
||||||
|
),
|
||||||
|
"controller_product_version": SettingSchema(
|
||||||
|
description="Controller product version",
|
||||||
|
message="VERSION_CONTROLLER",
|
||||||
|
keep_updated=False,
|
||||||
|
message_data="u8_lb",
|
||||||
|
backing_type="uint8_t",
|
||||||
|
validation_schema=cv.int_range(min=0, max=255),
|
||||||
|
default_value=0,
|
||||||
|
order=1,
|
||||||
|
),
|
||||||
|
"opentherm_version_controller": SettingSchema(
|
||||||
|
description="Version of OpenTherm implemented by controller",
|
||||||
|
message="OT_VERSION_CONTROLLER",
|
||||||
|
keep_updated=False,
|
||||||
|
message_data="f88",
|
||||||
|
backing_type="float",
|
||||||
|
validation_schema=cv.positive_float,
|
||||||
|
default_value=0,
|
||||||
|
order=3,
|
||||||
|
),
|
||||||
|
"controller_configuration": SettingSchema(
|
||||||
|
description="Controller configuration",
|
||||||
|
message="CONTROLLER_CONFIG",
|
||||||
|
keep_updated=False,
|
||||||
|
message_data="u8_hb",
|
||||||
|
backing_type="uint8_t",
|
||||||
|
validation_schema=cv.int_range(min=0, max=255),
|
||||||
|
default_value=0,
|
||||||
|
order=5,
|
||||||
|
),
|
||||||
|
"controller_id": SettingSchema(
|
||||||
|
description="Controller ID code",
|
||||||
|
message="CONTROLLER_CONFIG",
|
||||||
|
keep_updated=False,
|
||||||
|
message_data="u8_lb",
|
||||||
|
backing_type="uint8_t",
|
||||||
|
validation_schema=cv.int_range(min=0, max=255),
|
||||||
|
default_value=0,
|
||||||
|
order=5,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
@ -9,12 +9,17 @@ from .schema import TSchema
|
|||||||
|
|
||||||
|
|
||||||
def create_entities_schema(
|
def create_entities_schema(
|
||||||
entities: dict[str, schema.EntitySchema],
|
entities: dict[str, TSchema],
|
||||||
get_entity_validation_schema: Callable[[TSchema], cv.Schema],
|
get_entity_validation_schema: Callable[[TSchema], cv.Schema],
|
||||||
) -> Schema:
|
) -> Schema:
|
||||||
entity_schema = {}
|
entity_schema = {}
|
||||||
for key, entity in entities.items():
|
for key, entity in entities.items():
|
||||||
entity_schema[cv.Optional(key)] = get_entity_validation_schema(entity)
|
schema_key = (
|
||||||
|
cv.Optional(key, entity.default_value)
|
||||||
|
if hasattr(entity, "default_value")
|
||||||
|
else cv.Optional(key)
|
||||||
|
)
|
||||||
|
entity_schema[schema_key] = get_entity_validation_schema(entity)
|
||||||
return cv.Schema(entity_schema)
|
return cv.Schema(entity_schema)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||||
|
#include "uart_component_esp32_arduino.h"
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "uart_component_esp32_arduino.h"
|
|
||||||
|
|
||||||
#ifdef USE_LOGGER
|
#ifdef USE_LOGGER
|
||||||
#include "esphome/components/logger/logger.h"
|
#include "esphome/components/logger/logger.h"
|
||||||
@ -118,7 +118,7 @@ void ESP32ArduinoUARTComponent::setup() {
|
|||||||
}
|
}
|
||||||
#endif // USE_LOGGER
|
#endif // USE_LOGGER
|
||||||
|
|
||||||
if (next_uart_num >= UART_NUM_MAX) {
|
if (next_uart_num >= SOC_UART_NUM) {
|
||||||
ESP_LOGW(TAG, "Maximum number of UART components created already.");
|
ESP_LOGW(TAG, "Maximum number of UART components created already.");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
#ifdef USE_ESP_IDF
|
#ifdef USE_ESP_IDF
|
||||||
|
|
||||||
#include "uart_component_esp_idf.h"
|
#include "uart_component_esp_idf.h"
|
||||||
|
#include <cinttypes>
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/defines.h"
|
#include "esphome/core/defines.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include <cinttypes>
|
|
||||||
|
|
||||||
#ifdef USE_LOGGER
|
#ifdef USE_LOGGER
|
||||||
#include "esphome/components/logger/logger.h"
|
#include "esphome/components/logger/logger.h"
|
||||||
@ -84,7 +84,7 @@ void IDFUARTComponent::setup() {
|
|||||||
}
|
}
|
||||||
#endif // USE_LOGGER
|
#endif // USE_LOGGER
|
||||||
|
|
||||||
if (next_uart_num >= UART_NUM_MAX) {
|
if (next_uart_num >= SOC_UART_NUM) {
|
||||||
ESP_LOGW(TAG, "Maximum number of UART components created already.");
|
ESP_LOGW(TAG, "Maximum number of UART components created already.");
|
||||||
this->mark_failed();
|
this->mark_failed();
|
||||||
return;
|
return;
|
||||||
|
@ -1660,6 +1660,12 @@ class SplitDefault(Optional):
|
|||||||
esp32_c3=vol.UNDEFINED,
|
esp32_c3=vol.UNDEFINED,
|
||||||
esp32_c3_arduino=vol.UNDEFINED,
|
esp32_c3_arduino=vol.UNDEFINED,
|
||||||
esp32_c3_idf=vol.UNDEFINED,
|
esp32_c3_idf=vol.UNDEFINED,
|
||||||
|
esp32_c6=vol.UNDEFINED,
|
||||||
|
esp32_c6_arduino=vol.UNDEFINED,
|
||||||
|
esp32_c6_idf=vol.UNDEFINED,
|
||||||
|
esp32_h2=vol.UNDEFINED,
|
||||||
|
esp32_h2_arduino=vol.UNDEFINED,
|
||||||
|
esp32_h2_idf=vol.UNDEFINED,
|
||||||
rp2040=vol.UNDEFINED,
|
rp2040=vol.UNDEFINED,
|
||||||
bk72xx=vol.UNDEFINED,
|
bk72xx=vol.UNDEFINED,
|
||||||
rtl87xx=vol.UNDEFINED,
|
rtl87xx=vol.UNDEFINED,
|
||||||
@ -1691,6 +1697,18 @@ class SplitDefault(Optional):
|
|||||||
self._esp32_c3_idf_default = vol.default_factory(
|
self._esp32_c3_idf_default = vol.default_factory(
|
||||||
_get_priority_default(esp32_c3_idf, esp32_c3, esp32_idf, esp32)
|
_get_priority_default(esp32_c3_idf, esp32_c3, esp32_idf, esp32)
|
||||||
)
|
)
|
||||||
|
self._esp32_c6_arduino_default = vol.default_factory(
|
||||||
|
_get_priority_default(esp32_c6_arduino, esp32_c6, esp32_arduino, esp32)
|
||||||
|
)
|
||||||
|
self._esp32_c6_idf_default = vol.default_factory(
|
||||||
|
_get_priority_default(esp32_c6_idf, esp32_c6, esp32_idf, esp32)
|
||||||
|
)
|
||||||
|
self._esp32_h2_arduino_default = vol.default_factory(
|
||||||
|
_get_priority_default(esp32_h2_arduino, esp32_h2, esp32_arduino, esp32)
|
||||||
|
)
|
||||||
|
self._esp32_h2_idf_default = vol.default_factory(
|
||||||
|
_get_priority_default(esp32_h2_idf, esp32_h2, esp32_idf, esp32)
|
||||||
|
)
|
||||||
self._rp2040_default = vol.default_factory(rp2040)
|
self._rp2040_default = vol.default_factory(rp2040)
|
||||||
self._bk72xx_default = vol.default_factory(bk72xx)
|
self._bk72xx_default = vol.default_factory(bk72xx)
|
||||||
self._rtl87xx_default = vol.default_factory(rtl87xx)
|
self._rtl87xx_default = vol.default_factory(rtl87xx)
|
||||||
@ -1704,6 +1722,8 @@ class SplitDefault(Optional):
|
|||||||
from esphome.components.esp32 import get_esp32_variant
|
from esphome.components.esp32 import get_esp32_variant
|
||||||
from esphome.components.esp32.const import (
|
from esphome.components.esp32.const import (
|
||||||
VARIANT_ESP32C3,
|
VARIANT_ESP32C3,
|
||||||
|
VARIANT_ESP32C6,
|
||||||
|
VARIANT_ESP32H2,
|
||||||
VARIANT_ESP32S2,
|
VARIANT_ESP32S2,
|
||||||
VARIANT_ESP32S3,
|
VARIANT_ESP32S3,
|
||||||
)
|
)
|
||||||
@ -1724,6 +1744,16 @@ class SplitDefault(Optional):
|
|||||||
return self._esp32_c3_arduino_default
|
return self._esp32_c3_arduino_default
|
||||||
if CORE.using_esp_idf:
|
if CORE.using_esp_idf:
|
||||||
return self._esp32_c3_idf_default
|
return self._esp32_c3_idf_default
|
||||||
|
elif variant == VARIANT_ESP32C6:
|
||||||
|
if CORE.using_arduino:
|
||||||
|
return self._esp32_c6_arduino_default
|
||||||
|
if CORE.using_esp_idf:
|
||||||
|
return self._esp32_c6_idf_default
|
||||||
|
elif variant == VARIANT_ESP32H2:
|
||||||
|
if CORE.using_arduino:
|
||||||
|
return self._esp32_h2_arduino_default
|
||||||
|
if CORE.using_esp_idf:
|
||||||
|
return self._esp32_h2_idf_default
|
||||||
else:
|
else:
|
||||||
if CORE.using_arduino:
|
if CORE.using_arduino:
|
||||||
return self._esp32_arduino_default
|
return self._esp32_arduino_default
|
||||||
|
@ -108,6 +108,12 @@ def is_authenticated(handler: BaseHandler) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
if settings.using_auth:
|
if settings.using_auth:
|
||||||
|
if auth_header := handler.request.headers.get("Authorization"):
|
||||||
|
assert isinstance(auth_header, str)
|
||||||
|
if auth_header.startswith("Basic "):
|
||||||
|
auth_decoded = base64.b64decode(auth_header[6:]).decode()
|
||||||
|
username, password = auth_decoded.split(":", 1)
|
||||||
|
return settings.check_password(username, password)
|
||||||
return handler.get_secure_cookie(AUTH_COOKIE_NAME) == COOKIE_AUTHENTICATED_YES
|
return handler.get_secure_cookie(AUTH_COOKIE_NAME) == COOKIE_AUTHENTICATED_YES
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -12,7 +12,7 @@ pyserial==3.5
|
|||||||
platformio==6.1.16 # When updating platformio, also update Dockerfile
|
platformio==6.1.16 # When updating platformio, also update Dockerfile
|
||||||
esptool==4.7.0
|
esptool==4.7.0
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
esphome-dashboard==20241120.0
|
esphome-dashboard==20241217.1
|
||||||
aioesphomeapi==24.6.2
|
aioesphomeapi==24.6.2
|
||||||
zeroconf==0.132.2
|
zeroconf==0.132.2
|
||||||
puremagic==1.27
|
puremagic==1.27
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
esphome:
|
esphome:
|
||||||
on_boot:
|
on_boot:
|
||||||
|
- lambda: 'ESP_LOGD("display","is_connected(): %s", YESNO(id(main_lcd).is_connected()));'
|
||||||
|
|
||||||
# Binary sensor publish action tests
|
# Binary sensor publish action tests
|
||||||
- binary_sensor.nextion.publish:
|
- binary_sensor.nextion.publish:
|
||||||
id: r0_sensor
|
id: r0_sensor
|
||||||
|
@ -16,6 +16,19 @@ opentherm:
|
|||||||
summer_mode_active: true
|
summer_mode_active: true
|
||||||
dhw_block: true
|
dhw_block: true
|
||||||
sync_mode: true
|
sync_mode: true
|
||||||
|
controller_product_type: 63
|
||||||
|
controller_product_version: 1
|
||||||
|
opentherm_version_controller: 2.2
|
||||||
|
controller_id: 1
|
||||||
|
controller_configuration: 1
|
||||||
|
before_send:
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
ESP_LOGW("OT", ">> Sending message %d", x.id);
|
||||||
|
before_process_response:
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
ESP_LOGW("OT", "<< Processing response %d", x.id);
|
||||||
|
|
||||||
output:
|
output:
|
||||||
- platform: opentherm
|
- platform: opentherm
|
||||||
|
Loading…
x
Reference in New Issue
Block a user