mirror of
https://github.com/esphome/esphome.git
synced 2025-02-15 09:28:16 +00:00
Add climate IR Samsung component.
This commit is contained in:
parent
45beea68eb
commit
e3e0f6f5cb
0
esphome/components/climate_ir_samsung/__init__.py
Normal file
0
esphome/components/climate_ir_samsung/__init__.py
Normal file
22
esphome/components/climate_ir_samsung/climate.py
Normal file
22
esphome/components/climate_ir_samsung/climate.py
Normal file
@ -0,0 +1,22 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import climate_ir
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@jorofi"]
|
||||
|
||||
AUTO_LOAD = ["climate_ir"]
|
||||
|
||||
samsung_ns = cg.esphome_ns.namespace("samsung")
|
||||
SamsungClimate = samsung_ns.class_("SamsungClimate", climate_ir.ClimateIR)
|
||||
|
||||
CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SamsungClimate)
|
||||
}
|
||||
)
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
|
||||
await climate_ir.register_climate_ir(var, config)
|
240
esphome/components/climate_ir_samsung/climate_ir_samsung.cpp
Normal file
240
esphome/components/climate_ir_samsung/climate_ir_samsung.cpp
Normal file
@ -0,0 +1,240 @@
|
||||
#include "climate_ir_samsung.h"
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
/// Count the number of bits of a certain type in an array.
|
||||
/// @param[in] start A ptr to the start of the byte array to calculate over.
|
||||
/// @param[in] length How many bytes to use in the calculation.
|
||||
/// @param[in] ones Count the binary nr of `1` bits. False is count the `0`s.
|
||||
/// @param[in] init Starting value of the calculation to use. (Default is 0)
|
||||
/// @return The nr. of bits found of the given type found in the array.
|
||||
uint16_t countBits(const uint8_t * const start, const uint16_t length, const bool ones, const uint16_t init) {
|
||||
uint16_t count = init;
|
||||
|
||||
for (uint16_t offset = 0; offset < length; offset++)
|
||||
for (uint8_t currentbyte = *(start + offset); currentbyte; currentbyte >>= 1)
|
||||
if (currentbyte & 1) count++;
|
||||
|
||||
if (ones || length == 0)
|
||||
return count;
|
||||
else
|
||||
return (length * 8) - count;
|
||||
}
|
||||
|
||||
/// Count the number of bits of a certain type in an Integer.
|
||||
/// @param[in] data The value you want bits counted for. Starting from the LSB.
|
||||
/// @param[in] length How many bits to use in the calculation? Starts at the LSB
|
||||
/// @param[in] ones Count the binary nr of `1` bits. False is count the `0`s.
|
||||
/// @param[in] init Starting value of the calculation to use. (Default is 0)
|
||||
/// @return The nr. of bits found of the given type found in the Integer.
|
||||
uint16_t countBits(const uint64_t data, const uint8_t length, const bool ones, const uint16_t init) {
|
||||
uint16_t count = init;
|
||||
uint8_t bitsSoFar = length;
|
||||
|
||||
for (uint64_t remainder = data; remainder && bitsSoFar; remainder >>= 1, bitsSoFar--)
|
||||
if (remainder & 1) count++;
|
||||
|
||||
if (ones || length == 0)
|
||||
return count;
|
||||
else
|
||||
return length - count;
|
||||
}
|
||||
|
||||
namespace esphome {
|
||||
namespace climate_ir_samsung {
|
||||
|
||||
void SamsungClimateIR::transmit_state() {
|
||||
if(current_climate_mode != climate::ClimateMode::CLIMATE_MODE_OFF && this->mode == climate::ClimateMode::CLIMATE_MODE_OFF) {
|
||||
setAndSendPowerState(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if(current_climate_mode == climate::ClimateMode::CLIMATE_MODE_OFF && this->mode != climate::ClimateMode::CLIMATE_MODE_OFF) {
|
||||
setAndSendPowerState(true);
|
||||
std::this_thread::sleep_for(std::chrono::seconds(2));
|
||||
}
|
||||
|
||||
current_climate_mode = this->mode;
|
||||
|
||||
setMode(this->mode);
|
||||
setTemp(this->target_temperature);
|
||||
setSwing(this->swing_mode);
|
||||
setFan(this->fan_mode.has_value() ? this->fan_mode.value() : climate::CLIMATE_FAN_AUTO);
|
||||
|
||||
send();
|
||||
}
|
||||
|
||||
/// Send the current state of the climate object.
|
||||
void SamsungClimateIR::send() {
|
||||
|
||||
checksum();
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto *data = transmit.get_data();
|
||||
data->set_carrier_frequency(SAMSUNG_IR_FREQUENCY);
|
||||
|
||||
// Header
|
||||
data->mark(SAMSUNG_AIRCON1_HDR_MARK);
|
||||
data->space(SAMSUNG_AIRCON1_HDR_SPACE);
|
||||
|
||||
for (int i = 0; i < 21; i++)
|
||||
{
|
||||
|
||||
if (i == 7 || i == 14)
|
||||
{
|
||||
data->mark(SAMSUNG_AIRCON1_BIT_MARK);
|
||||
data->space(SAMSUNG_AIRCON1_MSG_SPACE);
|
||||
|
||||
data->mark(SAMSUNG_AIRCON1_HDR_MARK);
|
||||
data->space(SAMSUNG_AIRCON1_HDR_SPACE);
|
||||
}
|
||||
|
||||
uint8_t sendByte = protocol.raw[i];
|
||||
|
||||
for (int y = 0; y < 8; y++)
|
||||
{
|
||||
if (sendByte & 0x01)
|
||||
{
|
||||
// ESP_LOGI(TAG, "For Y %d 1", y);
|
||||
data->mark(SAMSUNG_AIRCON1_BIT_MARK);
|
||||
data->space(SAMSUNG_AIRCON1_ONE_SPACE);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ESP_LOGI(TAG, "For Y %d 0", y);
|
||||
data->mark(SAMSUNG_AIRCON1_BIT_MARK);
|
||||
data->space(SAMSUNG_AIRCON1_ZERO_SPACE);
|
||||
}
|
||||
|
||||
sendByte >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
data->mark(SAMSUNG_AIRCON1_BIT_MARK);
|
||||
data->space(0);
|
||||
|
||||
transmit.perform();
|
||||
}
|
||||
|
||||
/// Set the vertical swing setting of the A/C.
|
||||
void SamsungClimateIR::setSwing(const climate::ClimateSwingMode swingMode) {
|
||||
switch (swingMode) {
|
||||
case climate::ClimateSwingMode::CLIMATE_SWING_BOTH:
|
||||
protocol.Swing = kSamsungAcSwingBoth;
|
||||
break;
|
||||
case climate::ClimateSwingMode::CLIMATE_SWING_HORIZONTAL:
|
||||
protocol.Swing = kSamsungAcSwingH;;
|
||||
break;
|
||||
case climate::ClimateSwingMode::CLIMATE_SWING_VERTICAL:
|
||||
protocol.Swing = kSamsungAcSwingV;;
|
||||
break;
|
||||
case climate::ClimateSwingMode::CLIMATE_SWING_OFF:
|
||||
default:
|
||||
protocol.Swing = kSamsungAcSwingOff;;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the operating mode of the A/C.
|
||||
/// @param[in] climateMode The desired operating mode.
|
||||
void SamsungClimateIR::setMode(const climate::ClimateMode climateMode) {
|
||||
switch (climateMode) {
|
||||
case climate::ClimateMode::CLIMATE_MODE_HEAT:
|
||||
protocol.Mode = kSamsungAcHeat;
|
||||
break;
|
||||
case climate::ClimateMode::CLIMATE_MODE_DRY:
|
||||
protocol.Mode = kSamsungAcDry;
|
||||
break;
|
||||
case climate::ClimateMode::CLIMATE_MODE_COOL:
|
||||
protocol.Mode = kSamsungAcCool;
|
||||
break;
|
||||
case climate::ClimateMode::CLIMATE_MODE_FAN_ONLY:
|
||||
protocol.Mode = kSamsungAcFan;
|
||||
break;
|
||||
case climate::ClimateMode::CLIMATE_MODE_HEAT_COOL:
|
||||
case climate::ClimateMode::CLIMATE_MODE_AUTO:
|
||||
default:
|
||||
protocol.Mode = kSamsungAcAuto;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the temperature.
|
||||
/// @param[in] temp The temperature in degrees celsius.
|
||||
void SamsungClimateIR::setTemp(const uint8_t temp) {
|
||||
uint8_t newtemp = std::max(kSamsungAcMinTemp, temp);
|
||||
newtemp = std::min(kSamsungAcMaxTemp, newtemp);
|
||||
protocol.Temp = newtemp - kSamsungAcMinTemp;
|
||||
}
|
||||
|
||||
/// Change the AC power state.
|
||||
/// @param[in] on true, the AC is on. false, the AC is off.
|
||||
void SamsungClimateIR::setAndSendPowerState(const bool on) {
|
||||
|
||||
static const uint8_t kOn[kSamsungAcExtendedStateLength] = {
|
||||
0x02, 0x92, 0x0F, 0x00, 0x00, 0x00, 0xF0,
|
||||
0x01, 0xD2, 0x0F, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0xE2, 0xFE, 0x71, 0x80, 0x11, 0xF0};
|
||||
|
||||
static const uint8_t kOff[kSamsungAcExtendedStateLength] = {
|
||||
0x02, 0xB2, 0x0F, 0x00, 0x00, 0x00, 0xC0,
|
||||
0x01, 0xD2, 0x0F, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x02, 0xFF, 0x71, 0x80, 0x11, 0xC0};
|
||||
|
||||
std::memcpy(protocol.raw, on ? kOn : kOff, kSamsungAcExtendedStateLength);
|
||||
|
||||
send();
|
||||
|
||||
std::memcpy(protocol.raw, kReset, kSamsungAcExtendedStateLength);
|
||||
}
|
||||
|
||||
/// Set the fan speed.
|
||||
void SamsungClimateIR::setFan(const climate::ClimateFanMode fanMode) {
|
||||
switch (fanMode) {
|
||||
case climate::ClimateFanMode::CLIMATE_FAN_LOW:
|
||||
protocol.Fan = kSamsungAcFanAuto;
|
||||
break;
|
||||
case climate::ClimateFanMode::CLIMATE_FAN_MEDIUM:
|
||||
protocol.Fan = kSamsungAcFanMed;
|
||||
break;
|
||||
case climate::ClimateFanMode::CLIMATE_FAN_HIGH:
|
||||
protocol.Fan = kSamsungAcFanHigh;
|
||||
break;
|
||||
case climate::ClimateFanMode::CLIMATE_FAN_AUTO:
|
||||
default:
|
||||
protocol.Fan = kSamsungAcFanAuto;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the checksum for a given state section.
|
||||
/// @param[in] section The array to calc the checksum of.
|
||||
/// @return The calculated checksum value.
|
||||
/// @see https://github.com/crankyoldgit/IRremoteESP8266/issues/1538#issuecomment-894645947
|
||||
uint8_t SamsungClimateIR::calcSectionChecksum(const uint8_t *section) {
|
||||
uint8_t sum = 0;
|
||||
|
||||
sum += countBits(*section, 8); // Include the entire first byte
|
||||
// The lower half of the second byte.
|
||||
sum += countBits(GETBITS8(*(section + 1), kLowNibble, kNibbleSize), 8);
|
||||
// The upper half of the third byte.
|
||||
sum += countBits(GETBITS8(*(section + 2), kHighNibble, kNibbleSize), 8);
|
||||
// The next 4 bytes.
|
||||
sum += countBits(section + 3, 4);
|
||||
// Bitwise invert the result.
|
||||
return sum ^ UINT8_MAX;
|
||||
}
|
||||
|
||||
/// Update the checksum for the internal state.
|
||||
void SamsungClimateIR::checksum(void) {
|
||||
uint8_t sectionsum = calcSectionChecksum(protocol.raw);
|
||||
protocol.Sum1Upper = GETBITS8(sectionsum, kHighNibble, kNibbleSize);
|
||||
protocol.Sum1Lower = GETBITS8(sectionsum, kLowNibble, kNibbleSize);
|
||||
sectionsum = calcSectionChecksum(protocol.raw + kSamsungAcSectionLength);
|
||||
protocol.Sum2Upper = GETBITS8(sectionsum, kHighNibble, kNibbleSize);
|
||||
protocol.Sum2Lower = GETBITS8(sectionsum, kLowNibble, kNibbleSize);
|
||||
sectionsum = calcSectionChecksum(protocol.raw + kSamsungAcSectionLength * 2);
|
||||
protocol.Sum3Upper = GETBITS8(sectionsum, kHighNibble, kNibbleSize);
|
||||
protocol.Sum3Lower = GETBITS8(sectionsum, kLowNibble, kNibbleSize);
|
||||
}
|
||||
}}
|
208
esphome/components/climate_ir_samsung/climate_ir_samsung.h
Normal file
208
esphome/components/climate_ir_samsung/climate_ir_samsung.h
Normal file
@ -0,0 +1,208 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/climate/climate_mode.h"
|
||||
#include "esphome/components/climate_ir/climate_ir.h"
|
||||
|
||||
#define SAMSUNG_AIRCON1_HDR_MARK 3000
|
||||
#define SAMSUNG_AIRCON1_HDR_SPACE 9000
|
||||
#define SAMSUNG_AIRCON1_BIT_MARK 500
|
||||
#define SAMSUNG_AIRCON1_ONE_SPACE 1500
|
||||
#define SAMSUNG_AIRCON1_ZERO_SPACE 500
|
||||
#define SAMSUNG_AIRCON1_MSG_SPACE 2000
|
||||
#define GETBITS8(data, offset, size) \
|
||||
(((data) & (((uint8_t)UINT8_MAX >> (8 - (size))) << (offset))) >> (offset))
|
||||
|
||||
uint16_t countBits(const uint8_t * const start, const uint16_t length, const bool ones = true, const uint16_t init = 0);
|
||||
uint16_t countBits(const uint64_t data, const uint8_t length, const bool ones = true, const uint16_t init = 0);
|
||||
|
||||
namespace esphome {
|
||||
namespace climate_ir_samsung {
|
||||
|
||||
static const char *const TAG = "samsung.climate";
|
||||
static const uint32_t SAMSUNG_IR_FREQUENCY = 38000;
|
||||
const uint16_t kSamsungAcExtendedStateLength = 21;
|
||||
const uint16_t kSamsungAcSectionLength = 7;
|
||||
|
||||
// Temperature
|
||||
const uint8_t kSamsungAcMinTemp = 16; // C Mask 0b11110000
|
||||
const uint8_t kSamsungAcMaxTemp = 30; // C Mask 0b11110000
|
||||
const uint8_t kSamsungAcAutoTemp = 25; // C Mask 0b11110000
|
||||
|
||||
// Mode
|
||||
const uint8_t kSamsungAcAuto = 0;
|
||||
const uint8_t kSamsungAcCool = 1;
|
||||
const uint8_t kSamsungAcDry = 2;
|
||||
const uint8_t kSamsungAcFan = 3;
|
||||
const uint8_t kSamsungAcHeat = 4;
|
||||
|
||||
// Fan
|
||||
const uint8_t kSamsungAcFanAuto = 0;
|
||||
const uint8_t kSamsungAcFanLow = 2;
|
||||
const uint8_t kSamsungAcFanMed = 4;
|
||||
const uint8_t kSamsungAcFanHigh = 5;
|
||||
const uint8_t kSamsungAcFanAuto2 = 6;
|
||||
const uint8_t kSamsungAcFanTurbo = 7;
|
||||
|
||||
// Swing
|
||||
const uint8_t kSamsungAcSwingV = 0b010;
|
||||
const uint8_t kSamsungAcSwingH = 0b011;
|
||||
const uint8_t kSamsungAcSwingBoth = 0b100;
|
||||
const uint8_t kSamsungAcSwingOff = 0b111;
|
||||
|
||||
// Power
|
||||
const uint8_t kNibbleSize = 4;
|
||||
const uint8_t kLowNibble = 0;
|
||||
const uint8_t kHighNibble = 4;
|
||||
const uint8_t kModeBitsSize = 3;
|
||||
|
||||
// static const uint8_t kReset[kSamsungAcExtendedStateLength] = {0x02, 0x92, 0x0F, 0x00, 0x00, 0x00, 0xF0, 0x01, 0x02, 0xAE, 0x71, 0x00, 0x15, 0xF0};
|
||||
static const uint8_t kReset[kSamsungAcExtendedStateLength] = {
|
||||
0x02, 0x92, 0x0F, 0x00, 0x00, 0x00, 0xF0,
|
||||
0x01, 0x02, 0xAE, 0x71, 0x00, 0x15, 0xF0};
|
||||
|
||||
/// Native representation of a Samsung A/C message.
|
||||
union SamsungProtocol {
|
||||
uint8_t raw[kSamsungAcExtendedStateLength]; ///< State in code form.
|
||||
struct { // Standard message map
|
||||
// Byte 0
|
||||
uint8_t :8;
|
||||
// Byte 1
|
||||
uint8_t :4;
|
||||
uint8_t :4; // Sum1Lower
|
||||
// Byte 2
|
||||
uint8_t :4; // Sum1Upper
|
||||
uint8_t :4;
|
||||
// Byte 3
|
||||
uint8_t :8;
|
||||
// Byte 4
|
||||
uint8_t :8;
|
||||
// Byte 5
|
||||
uint8_t :4;
|
||||
uint8_t Sleep5 :1;
|
||||
uint8_t Quiet :1;
|
||||
uint8_t :2;
|
||||
// Byte 6
|
||||
uint8_t :4;
|
||||
uint8_t Power1 :2;
|
||||
uint8_t :2;
|
||||
// Byte 7
|
||||
uint8_t :8;
|
||||
// Byte 8
|
||||
uint8_t :4;
|
||||
uint8_t :4; // Sum2Lower
|
||||
// Byte 9
|
||||
uint8_t :4; // Sum1Upper
|
||||
uint8_t Swing :3;
|
||||
uint8_t :1;
|
||||
// Byte 10
|
||||
uint8_t :1;
|
||||
uint8_t FanSpecial :3; // Powerful, Breeze/WindFree, Econo
|
||||
uint8_t Display :1;
|
||||
uint8_t :2;
|
||||
uint8_t CleanToggle10 :1;
|
||||
// Byte 11
|
||||
uint8_t Ion :1;
|
||||
uint8_t CleanToggle11 :1;
|
||||
uint8_t :2;
|
||||
uint8_t Temp :4;
|
||||
// Byte 12
|
||||
uint8_t :1;
|
||||
uint8_t Fan :3;
|
||||
uint8_t Mode :3;
|
||||
uint8_t :1;
|
||||
// Byte 13
|
||||
uint8_t :2;
|
||||
uint8_t BeepToggle :1;
|
||||
uint8_t :1;
|
||||
uint8_t Power2 :2;
|
||||
uint8_t :2;
|
||||
};
|
||||
struct { // Extended message map
|
||||
// 1st Section
|
||||
// Byte 0
|
||||
uint8_t :8;
|
||||
// Byte 1
|
||||
uint8_t :4;
|
||||
uint8_t Sum1Lower :4;
|
||||
// Byte 2
|
||||
uint8_t Sum1Upper :4;
|
||||
uint8_t :4;
|
||||
// Byte 3
|
||||
uint8_t :8;
|
||||
// Byte 4
|
||||
uint8_t :8;
|
||||
// Byte 5
|
||||
uint8_t :8;
|
||||
// Byte 6
|
||||
uint8_t :8;
|
||||
// 2nd Section
|
||||
// Byte 7
|
||||
uint8_t :8;
|
||||
// Byte 8
|
||||
uint8_t :4;
|
||||
uint8_t Sum2Lower :4;
|
||||
// Byte 9
|
||||
uint8_t Sum2Upper :4;
|
||||
uint8_t OffTimeMins :3; // In units of 10's of mins
|
||||
uint8_t OffTimeHrs1 :1; // LSB of the number of hours.
|
||||
// Byte 10
|
||||
uint8_t OffTimeHrs2 :4; // MSBs of the number of hours.
|
||||
uint8_t OnTimeMins :3; // In units of 10's of mins
|
||||
uint8_t OnTimeHrs1 :1; // LSB of the number of hours.
|
||||
// Byte 11
|
||||
uint8_t OnTimeHrs2 :4; // MSBs of the number of hours.
|
||||
uint8_t :4;
|
||||
// Byte 12
|
||||
uint8_t OffTimeDay :1;
|
||||
uint8_t OnTimerEnable :1;
|
||||
uint8_t OffTimerEnable :1;
|
||||
uint8_t Sleep12 :1;
|
||||
uint8_t OnTimeDay :1;
|
||||
uint8_t :3;
|
||||
// Byte 13
|
||||
uint8_t :8;
|
||||
// 3rd Section
|
||||
// Byte 14
|
||||
uint8_t :8;
|
||||
// Byte 15
|
||||
uint8_t :4;
|
||||
uint8_t Sum3Lower :4;
|
||||
// Byte 16
|
||||
uint8_t Sum3Upper :4;
|
||||
uint8_t :4;
|
||||
// Byte 17
|
||||
uint8_t :8;
|
||||
// Byte 18
|
||||
uint8_t :8;
|
||||
// Byte 19
|
||||
uint8_t :8;
|
||||
// Byte 20
|
||||
uint8_t :8;
|
||||
};
|
||||
};
|
||||
|
||||
class SamsungClimate : public climate_ir::ClimateIR {
|
||||
|
||||
SamsungProtocol protocol;
|
||||
climate::ClimateMode current_climate_mode;
|
||||
|
||||
public: SamsungClimate() :
|
||||
climate_ir::ClimateIR(
|
||||
kSamsungAcMinTemp, kSamsungAcMaxTemp, 1.0f, true, true,
|
||||
{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH},
|
||||
{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) { }
|
||||
|
||||
protected:
|
||||
void transmit_state() override;
|
||||
|
||||
void send();
|
||||
void setSwing(const climate::ClimateSwingMode swingMode);
|
||||
void setMode(const climate::ClimateMode mode);
|
||||
void setTemp(const uint8_t temp);
|
||||
void setAndSendPowerState(const bool on);
|
||||
void setFan(const climate::ClimateFanMode fanMode);
|
||||
|
||||
void checksum(void);
|
||||
static uint8_t calcSectionChecksum(const uint8_t *section);
|
||||
};
|
||||
}}
|
Loading…
x
Reference in New Issue
Block a user