Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions .github/workflows/compile-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Compile tests

# Runs on every PR, and on pushes to main so the badge reflects the branch
# everyone builds from. Paths are filtered so doc-only changes skip the build.
on:
pull_request:
paths:
- 'components/**'
- '.github/workflows/compile-tests.yml'
push:
branches: [main]
paths:
- 'components/**'
- '.github/workflows/compile-tests.yml'
workflow_dispatch:

# A new push to the same PR cancels the previous run.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
compile:
name: ${{ matrix.component }} · ${{ matrix.config }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
component: [levoit, philips]
config:
# every platform and every entity type - maximum codegen coverage
- test-all-entities.yaml
# only a fan: every optional platform absent. Catches sources that
# include a platform header without guarding on USE_<COMPONENT>_*.
- test-fan-only.yaml
# component fan plus template entities on the other platforms. Defines
# the generic USE_SWITCH / USE_SELECT / ... without any entity of that
# kind from this component, which is the mismatch that broke the build.
- test-foreign-platforms.yaml
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: '3.12'

# PlatformIO toolchains and the ESP-IDF sources are the slow part, not the
# compile itself, so cache them across runs.
- name: Cache PlatformIO
uses: actions/cache@v4
with:
path: |
~/.platformio
~/.cache/pip
key: pio-${{ runner.os }}-${{ hashFiles('components/*/tests/*.yaml') }}
restore-keys: pio-${{ runner.os }}-

- name: Install ESPHome
run: pip install esphome

- name: esphome config
working-directory: components/${{ matrix.component }}/tests
run: esphome config ${{ matrix.config }}

- name: esphome compile
working-directory: components/${{ matrix.component }}/tests
run: esphome compile ${{ matrix.config }}
18 changes: 14 additions & 4 deletions components/levoit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -536,10 +536,20 @@ Auto mode options per model:
the purifiers use minutes
* **`dry_level` does not act on its own** — the select records Low/High and
the value is applied when Dry is picked on the fan entity
* Fix a latent build bug: platform includes were guarded on the generic
`USE_SWITCH` / `USE_SENSOR` / … macros, which any component defines, so a
config with (say) a `template` switch but no `levoit` switch failed to
compile. Each platform now defines its own `USE_LEVOIT_*` guard
* **Fix two build failures around optional platforms.** A levoit config with
only a fan did not build at all, and one with an unrelated `template` switch
(or select, number, …) failed the same way: several sources included
`number/levoit_number.h` and friends either unguarded or guarded on the
*generic* `USE_SWITCH` / `USE_NUMBER` macros, which any component defines,
while ESPHome only copies `levoit/<platform>/` when a **levoit** entity of
that platform is configured
* Each platform now defines its own `USE_LEVOIT_*` macro, and the entity
classes are confined to `levoit.cpp` behind new value accessors
(`try_get_number_state()`, `try_get_select_index()`,
`try_get_switch_state()`, `apply_fan_status()`), so the other sources no
longer need those headers at all
* Added compile tests in [`tests/`](./tests) covering every entity type and
both of the above cases, run by GitHub Actions on every PR
* `publish_sensor` widened `uint32_t` → `float` so the humidifier can report
fractional temperature and humidity (it already converted internally)
* ⚠️ Superior 6000S not verified on hardware — see
Expand Down
3 changes: 3 additions & 0 deletions components/levoit/binary_sensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@
)

async def to_code(config):
# Tells the levoit sources this platform is in the build - see the note
# at the top of levoit.cpp on why USE_BINARY_SENSOR is not sufficient.
cg.add_define("USE_LEVOIT_BINARY_SENSOR")
parent = await cg.get_variable(config[CONF_LEVOIT_ID])

var = cg.new_Pvariable(config[CONF_ID])
Expand Down
3 changes: 3 additions & 0 deletions components/levoit/button/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
)

async def to_code(config):
# Tells the levoit sources this platform is in the build - see the note
# at the top of levoit.cpp on why USE_BUTTON is not sufficient.
cg.add_define("USE_LEVOIT_BUTTON")
parent = await cg.get_variable(config[CONF_LEVOIT_ID])

var = cg.new_Pvariable(config[CONF_ID])
Expand Down
13 changes: 6 additions & 7 deletions components/levoit/core_commands.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#include "core_commands.h"
#include "levoit_message.h"
#include "levoit.h"
#include "number/levoit_number.h"
#include "decoder_helpers.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"

namespace esphome
Expand Down Expand Up @@ -120,10 +120,9 @@ namespace esphome
case CommandType::setAutoModeEfficient:
{
msg_type = {0x01, 0xE6, 0xA5};
auto *num = self->get_number(NumberType::EFFICIENCY_ROOM_SIZE);
if (num != nullptr)
float m2;
if (self->try_get_number_state(NumberType::EFFICIENCY_ROOM_SIZE, m2))
{
float m2 = num->state;
uint32_t raw = encode_core_room_size_raw(m2);
uint8_t size_low = raw & 0xFF;
uint8_t size_high = (raw >> 8) & 0xFF;
Expand All @@ -149,10 +148,10 @@ namespace esphome
case CommandType::setTimerMinutes:
{
msg_type = {0x01, 0x64, 0xA2};
auto *num = self->get_number(NumberType::TIMER);
if (num != nullptr)
float mins;
if (self->try_get_number_state(NumberType::TIMER, mins))
{
uint32_t secs = static_cast<uint32_t>(num->state) * 60;
uint32_t secs = static_cast<uint32_t>(mins) * 60;
uint8_t b0 = (secs >> 0) & 0xFF;
uint8_t b1 = (secs >> 8) & 0xFF;
uint8_t b2 = (secs >> 16) & 0xFF;
Expand Down
26 changes: 7 additions & 19 deletions components/levoit/core_status.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#include "core_status.h"
#include "esphome/core/defines.h"
#include "esphome/core/log.h"
#include "tlv.h" // <- TLV extraction
#include "types.h"
#include "fan/levoit_fan.h"
#include "decoder_helpers.h"
#include <vector>
#include <string>
Expand Down Expand Up @@ -106,12 +106,8 @@ namespace esphome
self->publish_switch(SwitchType::CHILD_LOCK, child_lock);
self->publish_select(SelectType::NIGHTLIGHT, nightlight_idx);

auto *fan = self->get_fan();
if (fan != nullptr)
{
ESP_LOGV(TAG_CORE, "Core200S fan: power=%d speed=%d mode=%d", power, fan_speed, (int)fan_mode);
fan->apply_device_status(power, fan_speed, fan_mode);
}
ESP_LOGV(TAG_CORE, "Core200S fan: power=%d speed=%d mode=%d", power, fan_speed, (int)fan_mode);
self->apply_fan_status(power, fan_speed, fan_mode);
return;
}

Expand Down Expand Up @@ -144,12 +140,8 @@ namespace esphome
self->publish_select(SelectType::AUTO_MODE, fan_auto_mode);
self->publish_number(NumberType::EFFICIENCY_ROOM_SIZE, efficency_area_m2);

auto *fan = self->get_fan();
if (fan != nullptr)
{
ESP_LOGV(TAG_CORE, "Core600S fan: power=%d speed=%d mode=%d", power, fan_speed, (int)fan_mode);
fan->apply_device_status(power, fan_speed, fan_mode);
}
ESP_LOGV(TAG_CORE, "Core600S fan: power=%d speed=%d mode=%d", power, fan_speed, (int)fan_mode);
self->apply_fan_status(power, fan_speed, fan_mode);
return;
}

Expand Down Expand Up @@ -189,12 +181,8 @@ namespace esphome
self->publish_select(SelectType::AUTO_MODE, fan_auto_mode);
self->publish_number(NumberType::EFFICIENCY_ROOM_SIZE, efficency_area_m2);

auto *fan = self->get_fan();
if (fan != nullptr)
{
ESP_LOGV(TAG_CORE, "Applying to fan: power=%d speed=%d mode=%d", power, fan_speed, (int)fan_mode);
fan->apply_device_status(power, fan_speed, fan_mode);
}
ESP_LOGV(TAG_CORE, "Applying to fan: power=%d speed=%d mode=%d", power, fan_speed, (int)fan_mode);
self->apply_fan_status(power, fan_speed, fan_mode);
}

} // namespace levoit
Expand Down
3 changes: 3 additions & 0 deletions components/levoit/fan/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@


async def to_code(config):
# Tells the levoit sources this platform is in the build - see the note
# at the top of levoit.cpp on why USE_FAN is not sufficient.
cg.add_define("USE_LEVOIT_FAN")

var = await fan.new_fan(config)
await cg.register_component(var, config)
Expand Down
Loading
Loading