mirror of
https://github.com/esphome/esphome.git
synced 2026-10-19 04:32:50 +00:00
[climate_ir_lg] Fix swing command to not include climate state (#17417)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
co-authored by
J. Nick Koston
parent
5c2b05d0eb
commit
c2c8cb5eb3
@@ -135,13 +135,14 @@ void LgIrClimate::transmit_state() {
|
||||
default:
|
||||
return;
|
||||
}
|
||||
this->transmit_(remote_state);
|
||||
this->publish_state();
|
||||
return;
|
||||
} else { // just toggle swing when advanced_commands_support is not set
|
||||
remote_state |= HEADER_BASIC;
|
||||
remote_state |= BASIC_SWING_TOGGLE;
|
||||
}
|
||||
// The remote's swing button sends a fixed code with no fan speed or temperature bits, so the
|
||||
// frame goes out as is instead of falling through to them
|
||||
this->transmit_(remote_state);
|
||||
return;
|
||||
} else { // Mode commands
|
||||
const bool climate_is_off = (this->mode_before_ == climate::CLIMATE_MODE_OFF);
|
||||
switch (this->mode) {
|
||||
@@ -208,7 +209,6 @@ void LgIrClimate::transmit_state() {
|
||||
}
|
||||
|
||||
this->transmit_(remote_state);
|
||||
this->publish_state();
|
||||
}
|
||||
|
||||
bool LgIrClimate::on_receive(remote_base::RemoteReceiveData data) {
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "../common.h"
|
||||
|
||||
namespace esphome::climate_ir_lg::testing {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t BIT_ONE_LOW = 1600;
|
||||
// A complete LG frame is header (2) + 28 bit pairs (56) + trailing mark (1)
|
||||
constexpr size_t FRAME_SIZE = 2 + 28 * 2 + 1;
|
||||
|
||||
// A climate with timings set, so transmit_() produces a frame decode_lg_frame() can read back
|
||||
void configure(LgIrClimate &sut, MockRemoteTransmitter &transmitter) {
|
||||
sut.set_transmitter(&transmitter);
|
||||
sut.set_header_high(8000);
|
||||
sut.set_header_low(4000);
|
||||
sut.set_bit_high(600);
|
||||
sut.set_bit_one_low(BIT_ONE_LOW);
|
||||
sut.set_bit_zero_low(550);
|
||||
}
|
||||
|
||||
// COOL at 20 °C with the fan on HIGH: a distinctive state the swing tests must not leak into their frame
|
||||
void turn_on(LgIrClimate &sut) {
|
||||
climate::ClimateCall on_call(&sut);
|
||||
on_call.set_mode(climate::CLIMATE_MODE_COOL);
|
||||
on_call.set_target_temperature(20);
|
||||
on_call.set_fan_mode(climate::CLIMATE_FAN_HIGH);
|
||||
sut.control(on_call);
|
||||
}
|
||||
|
||||
uint32_t last_frame(const MockRemoteTransmitter &transmitter) {
|
||||
// Guard against out-of-bounds reads in decode_lg_frame() if the transmission failed
|
||||
if (transmitter.last_data.size() < FRAME_SIZE) {
|
||||
ADD_FAILURE() << "no complete frame was transmitted";
|
||||
return 0;
|
||||
}
|
||||
return decode_lg_frame(transmitter.last_data, BIT_ONE_LOW);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// A mode command carries the mode, the fan speed and the temperature, with the checksum nibble
|
||||
TEST(LgIrClimateTests, ModeCommandCarriesModeFanSpeedAndTemperature) {
|
||||
LgIrClimate sut;
|
||||
MockRemoteTransmitter transmitter;
|
||||
configure(sut, transmitter);
|
||||
|
||||
turn_on(sut);
|
||||
|
||||
// From OFF: COMMAND_ON_COOL, FAN_MAX, 20 °C encoded as (20 - 15) << 8, checksum 9
|
||||
EXPECT_EQ(last_frame(transmitter), 0x8800549u);
|
||||
}
|
||||
|
||||
// The swing command is a distinct, self-contained IR code: the real remote transmits it
|
||||
// on its own, independent of the unit's current mode, target temperature, or fan speed.
|
||||
TEST(LgIrClimateTests, SwingCommandIsIndependentOfClimateState) {
|
||||
LgIrClimate sut;
|
||||
MockRemoteTransmitter transmitter;
|
||||
configure(sut, transmitter);
|
||||
turn_on(sut);
|
||||
|
||||
// Toggle swing -- this must be the fixed swing code below, unaffected by the state set above.
|
||||
climate::ClimateCall swing_call(&sut);
|
||||
swing_call.set_swing_mode(climate::CLIMATE_SWING_VERTICAL);
|
||||
sut.control(swing_call);
|
||||
|
||||
// The swing command is always this exact fixed code, regardless of climate state.
|
||||
EXPECT_EQ(last_frame(transmitter), 0x8810001u);
|
||||
}
|
||||
|
||||
// With advanced commands the swing frames are fixed codes too, one to start and one to stop the swing
|
||||
TEST(LgIrClimateTests, AdvancedSwingCommandsAreIndependentOfClimateState) {
|
||||
LgIrClimate sut;
|
||||
MockRemoteTransmitter transmitter;
|
||||
configure(sut, transmitter);
|
||||
sut.set_advanced_commands_support(true);
|
||||
turn_on(sut);
|
||||
|
||||
climate::ClimateCall swing_on(&sut);
|
||||
swing_on.set_swing_mode(climate::CLIMATE_SWING_VERTICAL);
|
||||
sut.control(swing_on);
|
||||
EXPECT_EQ(last_frame(transmitter), 0x8813149u); // HEADER_ADV_SWING | VERT_SWING_ON
|
||||
|
||||
climate::ClimateCall swing_off(&sut);
|
||||
swing_off.set_swing_mode(climate::CLIMATE_SWING_OFF);
|
||||
sut.control(swing_off);
|
||||
EXPECT_EQ(last_frame(transmitter), 0x881315Au); // HEADER_ADV_SWING | VERT_SWING_OFF
|
||||
}
|
||||
|
||||
} // namespace esphome::climate_ir_lg::testing
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include "esphome/components/climate_ir_lg/climate_ir_lg.h"
|
||||
#include "esphome/components/remote_base/remote_base.h"
|
||||
|
||||
namespace esphome::climate_ir_lg::testing {
|
||||
|
||||
// Records the raw mark/space timings instead of driving real IR hardware.
|
||||
class MockRemoteTransmitter : public remote_base::RemoteTransmitterBase {
|
||||
public:
|
||||
MockRemoteTransmitter() : RemoteTransmitterBase(nullptr) {}
|
||||
|
||||
std::vector<int32_t> last_data;
|
||||
|
||||
protected:
|
||||
void send_internal(uint32_t send_times, uint32_t send_wait) override { this->last_data = this->temp_.get_data(); }
|
||||
};
|
||||
|
||||
// Decodes the raw mark/space timings produced by LgIrClimate::transmit_() back into the
|
||||
// 28-bit value that was transmitted (header + bits, ignoring the trailing mark).
|
||||
inline uint32_t decode_lg_frame(const std::vector<int32_t> &data, uint32_t bit_one_low) {
|
||||
uint32_t value = 0;
|
||||
// data[0]/data[1] are the header mark/space; each bit thereafter is a mark/space pair.
|
||||
for (size_t i = 0; i < 28; i++) {
|
||||
int32_t space = data[2 + 2 * i + 1];
|
||||
value = (value << 1) | (static_cast<uint32_t>(-space) == bit_one_low ? 1 : 0);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace esphome::climate_ir_lg::testing
|
||||
Reference in New Issue
Block a user