diff --git a/esphome/components/climate_ir/__init__.py b/esphome/components/climate_ir/__init__.py index e69de29bb2..1163705faa 100644 --- a/esphome/components/climate_ir/__init__.py +++ b/esphome/components/climate_ir/__init__.py @@ -0,0 +1,41 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate, remote_transmitter, remote_receiver, sensor, remote_base +from esphome.components.remote_base import CONF_RECEIVER_ID, CONF_TRANSMITTER_ID +from esphome.const import CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT, CONF_SENSOR +from esphome.core import coroutine + +AUTO_LOAD = ['sensor', 'remote_base'] + +climate_ir_ns = cg.esphome_ns.namespace('climate_ir') +ClimateIR = climate_ir_ns.class_('ClimateIR', climate.Climate, cg.Component, + remote_base.RemoteReceiverListener) + +CLIMATE_IR_SCHEMA = climate.CLIMATE_SCHEMA.extend({ + cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent), + cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean, + cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, + cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), +}).extend(cv.COMPONENT_SCHEMA) + +CLIMATE_IR_WITH_RECEIVER_SCHEMA = CLIMATE_IR_SCHEMA.extend({ + cv.Optional(CONF_RECEIVER_ID): cv.use_id(remote_receiver.RemoteReceiverComponent), +}) + + +@coroutine +def register_climate_ir(var, config): + yield cg.register_component(var, config) + yield climate.register_climate(var, config) + + cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL])) + cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT])) + if CONF_SENSOR in config: + sens = yield cg.get_variable(config[CONF_SENSOR]) + cg.add(var.set_sensor(sens)) + if CONF_RECEIVER_ID in config: + receiver = yield cg.get_variable(config[CONF_RECEIVER_ID]) + cg.add(receiver.register_listener(var)) + + transmitter = yield cg.get_variable(config[CONF_TRANSMITTER_ID]) + cg.add(var.set_transmitter(transmitter)) diff --git a/esphome/components/climate_ir/climate_ir.cpp b/esphome/components/climate_ir/climate_ir.cpp index 995e5a7ba5..4b9a1c0baa 100644 --- a/esphome/components/climate_ir/climate_ir.cpp +++ b/esphome/components/climate_ir/climate_ir.cpp @@ -2,7 +2,7 @@ #include "esphome/core/log.h" namespace esphome { -namespace climate { +namespace climate_ir { static const char *TAG = "climate_ir"; @@ -63,5 +63,5 @@ void ClimateIR::dump_config() { ESP_LOGCONFIG(TAG, " Supports COOL: %s", YESNO(this->supports_cool_)); } -} // namespace climate +} // namespace climate_ir } // namespace esphome diff --git a/esphome/components/climate_ir/climate_ir.h b/esphome/components/climate_ir/climate_ir.h index 51ced6b900..b4c036f3d6 100644 --- a/esphome/components/climate_ir/climate_ir.h +++ b/esphome/components/climate_ir/climate_ir.h @@ -6,7 +6,7 @@ #include "esphome/components/sensor/sensor.h" namespace esphome { -namespace climate { +namespace climate_ir { /* A base for climate which works by sending (and receiving) IR codes @@ -42,7 +42,7 @@ class ClimateIR : public climate::Climate, public Component, public remote_base: climate::ClimateTraits traits() override; /// Transmit via IR the state of this climate controller. - virtual void transmit_state() {} + virtual void transmit_state() = 0; bool supports_cool_{true}; bool supports_heat_{true}; @@ -50,5 +50,6 @@ class ClimateIR : public climate::Climate, public Component, public remote_base: remote_transmitter::RemoteTransmitterComponent *transmitter_; sensor::Sensor *sensor_{nullptr}; }; -} // namespace climate + +} // namespace climate_ir } // namespace esphome diff --git a/esphome/components/coolix/climate.py b/esphome/components/coolix/climate.py index 14868a7be0..81412bb586 100644 --- a/esphome/components/coolix/climate.py +++ b/esphome/components/coolix/climate.py @@ -1,37 +1,18 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import climate, remote_transmitter, remote_receiver, sensor -from esphome.components.remote_base import CONF_TRANSMITTER_ID, CONF_RECEIVER_ID -from esphome.const import CONF_ID, CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT +from esphome.components import climate_ir +from esphome.const import CONF_ID -AUTO_LOAD = ['sensor', 'climate_ir'] +AUTO_LOAD = ['climate_ir'] coolix_ns = cg.esphome_ns.namespace('coolix') -CoolixClimate = coolix_ns.class_('CoolixClimate', climate.Climate, cg.Component) +CoolixClimate = coolix_ns.class_('CoolixClimate', climate_ir.ClimateIR) -CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({ cv.GenerateID(): cv.declare_id(CoolixClimate), - cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent), - cv.Optional(CONF_RECEIVER_ID): cv.use_id(remote_receiver.RemoteReceiverComponent), - cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean, - cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, - cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), -}).extend(cv.COMPONENT_SCHEMA)) +}) def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) - yield climate.register_climate(var, config) - - cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL])) - cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT])) - if CONF_SENSOR in config: - sens = yield cg.get_variable(config[CONF_SENSOR]) - cg.add(var.set_sensor(sens)) - if CONF_RECEIVER_ID in config: - receiver = yield cg.get_variable(config[CONF_RECEIVER_ID]) - cg.add(receiver.register_listener(var)) - - transmitter = yield cg.get_variable(config[CONF_TRANSMITTER_ID]) - cg.add(var.set_transmitter(transmitter)) + yield climate_ir.register_climate_ir(var, config) diff --git a/esphome/components/coolix/coolix.h b/esphome/components/coolix/coolix.h index 125d8ffd37..ed03a2fd1e 100644 --- a/esphome/components/coolix/coolix.h +++ b/esphome/components/coolix/coolix.h @@ -9,9 +9,9 @@ namespace coolix { const uint8_t COOLIX_TEMP_MIN = 17; // Celsius const uint8_t COOLIX_TEMP_MAX = 30; // Celsius -class CoolixClimate : public climate::ClimateIR { +class CoolixClimate : public climate_ir::ClimateIR { public: - CoolixClimate() : climate::ClimateIR(COOLIX_TEMP_MIN, COOLIX_TEMP_MAX) {} + CoolixClimate() : climate_ir::ClimateIR(COOLIX_TEMP_MIN, COOLIX_TEMP_MAX) {} protected: /// Transmit via IR the state of this climate controller. diff --git a/esphome/components/fujitsu_general/__init__.py b/esphome/components/fujitsu_general/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/fujitsu_general/climate.py b/esphome/components/fujitsu_general/climate.py new file mode 100644 index 0000000000..a6774c397a --- /dev/null +++ b/esphome/components/fujitsu_general/climate.py @@ -0,0 +1,18 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import climate_ir +from esphome.const import CONF_ID + +AUTO_LOAD = ['climate_ir'] + +fujitsu_general_ns = cg.esphome_ns.namespace('fujitsu_general') +FujitsuGeneralClimate = fujitsu_general_ns.class_('FujitsuGeneralClimate', climate_ir.ClimateIR) + +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_SCHEMA.extend({ + cv.GenerateID(): cv.declare_id(FujitsuGeneralClimate), +}) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield climate_ir.register_climate_ir(var, config) diff --git a/esphome/components/fujitsu_general/fujitsu_general.cpp b/esphome/components/fujitsu_general/fujitsu_general.cpp new file mode 100644 index 0000000000..261d8be258 --- /dev/null +++ b/esphome/components/fujitsu_general/fujitsu_general.cpp @@ -0,0 +1,212 @@ +#include "fujitsu_general.h" + +namespace esphome { +namespace fujitsu_general { + +static const char *TAG = "fujitsu_general.climate"; + +// Control packet +const uint16_t FUJITSU_GENERAL_STATE_LENGTH = 16; + +const uint8_t FUJITSU_GENERAL_BASE_BYTE0 = 0x14; +const uint8_t FUJITSU_GENERAL_BASE_BYTE1 = 0x63; +const uint8_t FUJITSU_GENERAL_BASE_BYTE2 = 0x00; +const uint8_t FUJITSU_GENERAL_BASE_BYTE3 = 0x10; +const uint8_t FUJITSU_GENERAL_BASE_BYTE4 = 0x10; +const uint8_t FUJITSU_GENERAL_BASE_BYTE5 = 0xFE; +const uint8_t FUJITSU_GENERAL_BASE_BYTE6 = 0x09; +const uint8_t FUJITSU_GENERAL_BASE_BYTE7 = 0x30; + +// Temperature and POWER ON +const uint8_t FUJITSU_GENERAL_POWER_ON_MASK_BYTE8 = 0b00000001; +const uint8_t FUJITSU_GENERAL_BASE_BYTE8 = 0x40; + +// Mode +const uint8_t FUJITSU_GENERAL_MODE_AUTO_BYTE9 = 0x00; +const uint8_t FUJITSU_GENERAL_MODE_HEAT_BYTE9 = 0x04; +const uint8_t FUJITSU_GENERAL_MODE_COOL_BYTE9 = 0x01; +const uint8_t FUJITSU_GENERAL_MODE_DRY_BYTE9 = 0x02; +const uint8_t FUJITSU_GENERAL_MODE_FAN_BYTE9 = 0x03; +const uint8_t FUJITSU_GENERAL_MODE_10C_BYTE9 = 0x0B; +const uint8_t FUJITSU_GENERAL_BASE_BYTE9 = 0x01; + +// Fan speed and swing +const uint8_t FUJITSU_GENERAL_FAN_AUTO_BYTE10 = 0x00; +const uint8_t FUJITSU_GENERAL_FAN_HIGH_BYTE10 = 0x01; +const uint8_t FUJITSU_GENERAL_FAN_MEDIUM_BYTE10 = 0x02; +const uint8_t FUJITSU_GENERAL_FAN_LOW_BYTE10 = 0x03; +const uint8_t FUJITSU_GENERAL_FAN_SILENT_BYTE10 = 0x04; +const uint8_t FUJITSU_GENERAL_SWING_MASK_BYTE10 = 0b00010000; +const uint8_t FUJITSU_GENERAL_BASE_BYTE10 = 0x00; + +const uint8_t FUJITSU_GENERAL_BASE_BYTE11 = 0x00; +const uint8_t FUJITSU_GENERAL_BASE_BYTE12 = 0x00; +const uint8_t FUJITSU_GENERAL_BASE_BYTE13 = 0x00; + +// Outdoor Unit Low Noise +const uint8_t FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14 = 0xA0; +const uint8_t FUJITSU_GENERAL_BASE_BYTE14 = 0x20; + +// CRC +const uint8_t FUJITSU_GENERAL_BASE_BYTE15 = 0x6F; + +// Power off packet is specific +const uint16_t FUJITSU_GENERAL_OFF_LENGTH = 7; + +const uint8_t FUJITSU_GENERAL_OFF_BYTE0 = FUJITSU_GENERAL_BASE_BYTE0; +const uint8_t FUJITSU_GENERAL_OFF_BYTE1 = FUJITSU_GENERAL_BASE_BYTE1; +const uint8_t FUJITSU_GENERAL_OFF_BYTE2 = FUJITSU_GENERAL_BASE_BYTE2; +const uint8_t FUJITSU_GENERAL_OFF_BYTE3 = FUJITSU_GENERAL_BASE_BYTE3; +const uint8_t FUJITSU_GENERAL_OFF_BYTE4 = FUJITSU_GENERAL_BASE_BYTE4; +const uint8_t FUJITSU_GENERAL_OFF_BYTE5 = 0x02; +const uint8_t FUJITSU_GENERAL_OFF_BYTE6 = 0xFD; + +const uint8_t FUJITSU_GENERAL_TEMP_MAX = 30; // Celsius +const uint8_t FUJITSU_GENERAL_TEMP_MIN = 16; // Celsius + +const uint16_t FUJITSU_GENERAL_HEADER_MARK = 3300; +const uint16_t FUJITSU_GENERAL_HEADER_SPACE = 1600; +const uint16_t FUJITSU_GENERAL_BIT_MARK = 420; +const uint16_t FUJITSU_GENERAL_ONE_SPACE = 1200; +const uint16_t FUJITSU_GENERAL_ZERO_SPACE = 420; +const uint16_t FUJITSU_GENERAL_TRL_MARK = 420; +const uint16_t FUJITSU_GENERAL_TRL_SPACE = 8000; + +const uint32_t FUJITSU_GENERAL_CARRIER_FREQUENCY = 38000; + +FujitsuGeneralClimate::FujitsuGeneralClimate() : ClimateIR(FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1) {} + +void FujitsuGeneralClimate::transmit_state() { + if (this->mode == climate::CLIMATE_MODE_OFF) { + this->transmit_off_(); + return; + } + uint8_t remote_state[FUJITSU_GENERAL_STATE_LENGTH] = {0}; + + remote_state[0] = FUJITSU_GENERAL_BASE_BYTE0; + remote_state[1] = FUJITSU_GENERAL_BASE_BYTE1; + remote_state[2] = FUJITSU_GENERAL_BASE_BYTE2; + remote_state[3] = FUJITSU_GENERAL_BASE_BYTE3; + remote_state[4] = FUJITSU_GENERAL_BASE_BYTE4; + remote_state[5] = FUJITSU_GENERAL_BASE_BYTE5; + remote_state[6] = FUJITSU_GENERAL_BASE_BYTE6; + remote_state[7] = FUJITSU_GENERAL_BASE_BYTE7; + remote_state[8] = FUJITSU_GENERAL_BASE_BYTE8; + remote_state[9] = FUJITSU_GENERAL_BASE_BYTE9; + remote_state[10] = FUJITSU_GENERAL_BASE_BYTE10; + remote_state[11] = FUJITSU_GENERAL_BASE_BYTE11; + remote_state[12] = FUJITSU_GENERAL_BASE_BYTE12; + remote_state[13] = FUJITSU_GENERAL_BASE_BYTE13; + remote_state[14] = FUJITSU_GENERAL_BASE_BYTE14; + remote_state[15] = FUJITSU_GENERAL_BASE_BYTE15; + + // Set temperature + uint8_t safecelsius = std::max((uint8_t) this->target_temperature, FUJITSU_GENERAL_TEMP_MIN); + safecelsius = std::min(safecelsius, FUJITSU_GENERAL_TEMP_MAX); + remote_state[8] = (byte) safecelsius - 16; + remote_state[8] = remote_state[8] << 4; + + // If not powered - set power on flag + if (!this->power_) { + remote_state[8] = (byte) remote_state[8] | FUJITSU_GENERAL_POWER_ON_MASK_BYTE8; + } + + // Set mode + switch (this->mode) { + case climate::CLIMATE_MODE_COOL: + remote_state[9] = FUJITSU_GENERAL_MODE_COOL_BYTE9; + break; + case climate::CLIMATE_MODE_HEAT: + remote_state[9] = FUJITSU_GENERAL_MODE_HEAT_BYTE9; + break; + case climate::CLIMATE_MODE_AUTO: + default: + remote_state[9] = FUJITSU_GENERAL_MODE_AUTO_BYTE9; + break; + // TODO: CLIMATE_MODE_FAN_ONLY, CLIMATE_MODE_DRY, CLIMATE_MODE_10C are missing in esphome + } + + // TODO: missing support for fan speed + remote_state[10] = FUJITSU_GENERAL_FAN_AUTO_BYTE10; + + // TODO: missing support for swing + // remote_state[10] = (byte) remote_state[10] | FUJITSU_GENERAL_SWING_MASK_BYTE10; + + // TODO: missing support for outdoor unit low noise + // remote_state[14] = (byte) remote_state[14] | FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14; + + // CRC + remote_state[15] = 0; + for (int i = 7; i < 15; i++) { + remote_state[15] += (byte) remote_state[i]; // Addiction + } + remote_state[15] = 0x100 - remote_state[15]; // mod 256 + + auto transmit = this->transmitter_->transmit(); + auto data = transmit.get_data(); + + data->set_carrier_frequency(FUJITSU_GENERAL_CARRIER_FREQUENCY); + + // Header + data->mark(FUJITSU_GENERAL_HEADER_MARK); + data->space(FUJITSU_GENERAL_HEADER_SPACE); + // Data + for (uint8_t i : remote_state) { + // Send all Bits from Byte Data in Reverse Order + for (uint8_t mask = 00000001; mask > 0; mask <<= 1) { // iterate through bit mask + data->mark(FUJITSU_GENERAL_BIT_MARK); + bool bit = i & mask; + data->space(bit ? FUJITSU_GENERAL_ONE_SPACE : FUJITSU_GENERAL_ZERO_SPACE); + // Next bits + } + } + // Footer + data->mark(FUJITSU_GENERAL_TRL_MARK); + data->space(FUJITSU_GENERAL_TRL_SPACE); + + transmit.perform(); + + this->power_ = true; +} + +void FujitsuGeneralClimate::transmit_off_() { + uint8_t remote_state[FUJITSU_GENERAL_OFF_LENGTH] = {0}; + + remote_state[0] = FUJITSU_GENERAL_OFF_BYTE0; + remote_state[1] = FUJITSU_GENERAL_OFF_BYTE1; + remote_state[2] = FUJITSU_GENERAL_OFF_BYTE2; + remote_state[3] = FUJITSU_GENERAL_OFF_BYTE3; + remote_state[4] = FUJITSU_GENERAL_OFF_BYTE4; + remote_state[5] = FUJITSU_GENERAL_OFF_BYTE5; + remote_state[6] = FUJITSU_GENERAL_OFF_BYTE6; + + auto transmit = this->transmitter_->transmit(); + auto data = transmit.get_data(); + + data->set_carrier_frequency(FUJITSU_GENERAL_CARRIER_FREQUENCY); + + // Header + data->mark(FUJITSU_GENERAL_HEADER_MARK); + data->space(FUJITSU_GENERAL_HEADER_SPACE); + + // Data + for (uint8_t i : remote_state) { + // Send all Bits from Byte Data in Reverse Order + for (uint8_t mask = 00000001; mask > 0; mask <<= 1) { // iterate through bit mask + data->mark(FUJITSU_GENERAL_BIT_MARK); + bool bit = i & mask; + data->space(bit ? FUJITSU_GENERAL_ONE_SPACE : FUJITSU_GENERAL_ZERO_SPACE); + // Next bits + } + } + // Footer + data->mark(FUJITSU_GENERAL_TRL_MARK); + data->space(FUJITSU_GENERAL_TRL_SPACE); + + transmit.perform(); + + this->power_ = false; +} + +} // namespace fujitsu_general +} // namespace esphome diff --git a/esphome/components/fujitsu_general/fujitsu_general.h b/esphome/components/fujitsu_general/fujitsu_general.h new file mode 100644 index 0000000000..80db81a167 --- /dev/null +++ b/esphome/components/fujitsu_general/fujitsu_general.h @@ -0,0 +1,24 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/automation.h" +#include "esphome/components/climate_ir/climate_ir.h" + +namespace esphome { +namespace fujitsu_general { + +class FujitsuGeneralClimate : public climate_ir::ClimateIR { + public: + FujitsuGeneralClimate(); + + protected: + /// Transmit via IR the state of this climate controller. + void transmit_state() override; + /// Transmit via IR power off command. + void transmit_off_(); + + bool power_{false}; +}; + +} // namespace fujitsu_general +} // namespace esphome diff --git a/esphome/components/tcl112/climate.py b/esphome/components/tcl112/climate.py index 66af291a17..3c94f4a243 100644 --- a/esphome/components/tcl112/climate.py +++ b/esphome/components/tcl112/climate.py @@ -1,37 +1,18 @@ import esphome.codegen as cg import esphome.config_validation as cv -from esphome.components import climate, remote_transmitter, remote_receiver, sensor -from esphome.components.remote_base import CONF_TRANSMITTER_ID, CONF_RECEIVER_ID -from esphome.const import CONF_ID, CONF_SENSOR, CONF_SUPPORTS_COOL, CONF_SUPPORTS_HEAT +from esphome.components import climate_ir +from esphome.const import CONF_ID -AUTO_LOAD = ['sensor', 'climate_ir'] +AUTO_LOAD = ['climate_ir'] tcl112_ns = cg.esphome_ns.namespace('tcl112') -Tcl112Climate = tcl112_ns.class_('Tcl112Climate', climate.Climate, cg.Component) +Tcl112Climate = tcl112_ns.class_('Tcl112Climate', climate_ir.ClimateIR) -CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ +CONFIG_SCHEMA = climate_ir.CLIMATE_IR_WITH_RECEIVER_SCHEMA.extend({ cv.GenerateID(): cv.declare_id(Tcl112Climate), - cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(remote_transmitter.RemoteTransmitterComponent), - cv.Optional(CONF_RECEIVER_ID): cv.use_id(remote_receiver.RemoteReceiverComponent), - cv.Optional(CONF_SUPPORTS_COOL, default=True): cv.boolean, - cv.Optional(CONF_SUPPORTS_HEAT, default=True): cv.boolean, - cv.Optional(CONF_SENSOR): cv.use_id(sensor.Sensor), -}).extend(cv.COMPONENT_SCHEMA)) +}) def to_code(config): var = cg.new_Pvariable(config[CONF_ID]) - yield cg.register_component(var, config) - yield climate.register_climate(var, config) - - cg.add(var.set_supports_cool(config[CONF_SUPPORTS_COOL])) - cg.add(var.set_supports_heat(config[CONF_SUPPORTS_HEAT])) - if CONF_SENSOR in config: - sens = yield cg.get_variable(config[CONF_SENSOR]) - cg.add(var.set_sensor(sens)) - if CONF_RECEIVER_ID in config: - receiver = yield cg.get_variable(config[CONF_RECEIVER_ID]) - cg.add(receiver.register_listener(var)) - - transmitter = yield cg.get_variable(config[CONF_TRANSMITTER_ID]) - cg.add(var.set_transmitter(transmitter)) + yield climate_ir.register_climate_ir(var, config) diff --git a/esphome/components/tcl112/tcl112.h b/esphome/components/tcl112/tcl112.h index 7c5257a0f3..273162662d 100644 --- a/esphome/components/tcl112/tcl112.h +++ b/esphome/components/tcl112/tcl112.h @@ -9,9 +9,9 @@ namespace tcl112 { const float TCL112_TEMP_MAX = 31.0; const float TCL112_TEMP_MIN = 16.0; -class Tcl112Climate : public climate::ClimateIR { +class Tcl112Climate : public climate_ir::ClimateIR { public: - Tcl112Climate() : climate::ClimateIR(TCL112_TEMP_MIN, TCL112_TEMP_MAX, .5f) {} + Tcl112Climate() : climate_ir::ClimateIR(TCL112_TEMP_MIN, TCL112_TEMP_MAX, .5f) {} protected: /// Transmit via IR the state of this climate controller. diff --git a/script/ci-custom.py b/script/ci-custom.py index c6d53d0a69..922d94f2aa 100755 --- a/script/ci-custom.py +++ b/script/ci-custom.py @@ -298,6 +298,19 @@ def lint_relative_py_import(fname): ' from . import abc_ns\n\n') +@lint_content_check(include=['esphome/components/*.h', 'esphome/components/*.cpp', + 'esphome/components/*.tcc']) +def lint_namespace(fname, content): + expected_name = re.match(r'^esphome/components/([^/]+)/.*', + fname.replace(os.path.sep, '/')).group(1) + search = 'namespace {}'.format(expected_name) + if search in content: + return None + return 'Invalid namespace found in C++ file. All integration C++ files should put all ' \ + 'functions in a separate namespace that matches the integration\'s name. ' \ + 'Please make sure the file contains {}'.format(highlight(search)) + + @lint_content_find_check('"esphome.h"', include=cpp_include, exclude=['tests/custom.h']) def lint_esphome_h(fname): return ("File contains reference to 'esphome.h' - This file is "