diff --git a/esphome/api/client.py b/esphome/api/client.py index fcea90e3b4..a32c239819 100644 --- a/esphome/api/client.py +++ b/esphome/api/client.py @@ -181,7 +181,7 @@ class APIClient(threading.Thread): self._address) _LOGGER.warning("(If this error persists, please set a static IP address: " "https://esphome.io/components/wifi.html#manual-ips)") - raise APIConnectionError(err) + raise APIConnectionError(err) from err _LOGGER.info("Connecting to %s:%s (%s)", self._address, self._port, ip) self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) @@ -346,12 +346,12 @@ class APIClient(threading.Thread): raise APIConnectionError("No socket!") try: val = self._socket.recv(amount - len(ret)) - except AttributeError: - raise APIConnectionError("Socket was closed") + except AttributeError as err: + raise APIConnectionError("Socket was closed") from err except socket.timeout: continue except OSError as err: - raise APIConnectionError(f"Error while receiving data: {err}") + raise APIConnectionError(f"Error while receiving data: {err}") from err ret += val return ret diff --git a/esphome/automation.py b/esphome/automation.py index 5df884e7c2..4b5e39b0f5 100644 --- a/esphome/automation.py +++ b/esphome/automation.py @@ -84,6 +84,7 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False): return cv.Schema([schema])(value) except cv.Invalid as err2: if 'extra keys not allowed' in str(err2) and len(err2.path) == 2: + # pylint: disable=raise-missing-from raise err if 'Unable to find action' in str(err): raise err2 diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 23cfa51d2b..884ae9867a 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -3,7 +3,8 @@ import esphome.config_validation as cv from esphome import automation from esphome.automation import Condition from esphome.const import CONF_DATA, CONF_DATA_TEMPLATE, CONF_ID, CONF_PASSWORD, CONF_PORT, \ - CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID, CONF_EVENT + CONF_REBOOT_TIMEOUT, CONF_SERVICE, CONF_VARIABLES, CONF_SERVICES, CONF_TRIGGER_ID, CONF_EVENT, \ + CONF_TAG from esphome.core import coroutine_with_priority DEPENDENCIES = ['network'] @@ -137,6 +138,23 @@ def homeassistant_event_to_code(config, action_id, template_arg, args): yield var +HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA = cv.maybe_simple_value({ + cv.GenerateID(): cv.use_id(APIServer), + cv.Required(CONF_TAG): cv.templatable(cv.string_strict), +}, key=CONF_TAG) + + +@automation.register_action('homeassistant.tag_scanned', HomeAssistantServiceCallAction, + HOMEASSISTANT_TAG_SCANNED_ACTION_SCHEMA) +def homeassistant_tag_scanned_to_code(config, action_id, template_arg, args): + serv = yield cg.get_variable(config[CONF_ID]) + var = cg.new_Pvariable(action_id, template_arg, serv, True) + cg.add(var.set_service('esphome.tag_scanned')) + templ = yield cg.templatable(config[CONF_TAG], args, cg.std_string) + cg.add(var.add_data('tag_id', templ)) + yield var + + @automation.register_condition('api.connected', APIConnectedCondition, {}) def api_connected_to_code(config, condition_id, template_arg, args): yield cg.new_Pvariable(condition_id, template_arg) diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 8361ee8004..753010310c 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -104,6 +104,7 @@ def parse_multi_click_timing_str(value): try: state = cv.boolean(parts[0]) except cv.Invalid: + # pylint: disable=raise-missing-from raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0])) if parts[1] != 'for': diff --git a/esphome/components/font/__init__.py b/esphome/components/font/__init__.py index 5b19dc74e0..ee50b10830 100644 --- a/esphome/components/font/__init__.py +++ b/esphome/components/font/__init__.py @@ -42,9 +42,9 @@ def validate_glyphs(value): def validate_pillow_installed(value): try: import PIL - except ImportError: + except ImportError as err: raise cv.Invalid("Please install the pillow python package to use this feature. " - "(pip install pillow)") + "(pip install pillow)") from err if PIL.__version__[0] < '4': raise cv.Invalid("Please update your pillow installation to at least 4.0.x. " diff --git a/esphome/components/http_request/__init__.py b/esphome/components/http_request/__init__.py index b003736d89..f7ec3cdbbf 100644 --- a/esphome/components/http_request/__init__.py +++ b/esphome/components/http_request/__init__.py @@ -43,8 +43,8 @@ def validate_url(value): value = cv.string(value) try: parsed = list(urlparse.urlparse(value)) - except Exception: - raise cv.Invalid('Invalid URL') + except Exception as err: + raise cv.Invalid('Invalid URL') from err if not parsed[0] or not parsed[1]: raise cv.Invalid('URL must have a URL scheme and host') diff --git a/esphome/components/max31855/max31855.cpp b/esphome/components/max31855/max31855.cpp index 88f9e836f9..7a0dc2427c 100644 --- a/esphome/components/max31855/max31855.cpp +++ b/esphome/components/max31855/max31855.cpp @@ -44,7 +44,7 @@ void MAX31855Sensor::read_data_() { const uint32_t mem = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3] << 0; // Verify we got data - if (mem != 0 && mem != 0xFFFFFFFF) { + if (mem != 0xFFFFFFFF) { this->status_clear_error(); } else { ESP_LOGE(TAG, "No data received from MAX31855 (0x%08X). Check wiring!", mem); diff --git a/esphome/components/pn532/binary_sensor.py b/esphome/components/pn532/binary_sensor.py index 1c5e220fa6..46a7ac03c8 100644 --- a/esphome/components/pn532/binary_sensor.py +++ b/esphome/components/pn532/binary_sensor.py @@ -18,8 +18,8 @@ def validate_uid(value): "long.") try: x = int(x, 16) - except ValueError: - raise cv.Invalid("Valid characters for parts of a UID are 0123456789ABCDEF.") + except ValueError as err: + raise cv.Invalid("Valid characters for parts of a UID are 0123456789ABCDEF.") from err if x < 0 or x > 255: raise cv.Invalid("Valid values for UID parts (separated by '-') are 00 to FF") return value diff --git a/esphome/components/remote_receiver/remote_receiver_esp32.cpp b/esphome/components/remote_receiver/remote_receiver_esp32.cpp index 89de4da92a..97806f8a52 100644 --- a/esphome/components/remote_receiver/remote_receiver_esp32.cpp +++ b/esphome/components/remote_receiver/remote_receiver_esp32.cpp @@ -11,6 +11,7 @@ static const char *TAG = "remote_receiver.esp32"; void RemoteReceiverComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up Remote Receiver..."); + this->pin_->setup(); rmt_config_t rmt{}; this->config_rmt(rmt); rmt.gpio_num = gpio_num_t(this->pin_->get_pin()); diff --git a/esphome/components/stepper/__init__.py b/esphome/components/stepper/__init__.py index e8d6acbd1c..c61aaa7fc9 100644 --- a/esphome/components/stepper/__init__.py +++ b/esphome/components/stepper/__init__.py @@ -28,6 +28,7 @@ def validate_acceleration(value): try: value = float(value) except ValueError: + # pylint: disable=raise-missing-from raise cv.Invalid(f"Expected acceleration as floating point number, got {value}") if value <= 0: @@ -48,6 +49,7 @@ def validate_speed(value): try: value = float(value) except ValueError: + # pylint: disable=raise-missing-from raise cv.Invalid(f"Expected speed as floating point number, got {value}") if value <= 0: diff --git a/esphome/components/time/__init__.py b/esphome/components/time/__init__.py index 49ed53c47e..ddbc1d6093 100644 --- a/esphome/components/time/__init__.py +++ b/esphome/components/time/__init__.py @@ -138,6 +138,7 @@ def _parse_cron_int(value, special_mapping, message): try: return int(value) except ValueError: + # pylint: disable=raise-missing-from raise cv.Invalid(message.format(value)) @@ -158,6 +159,7 @@ def _parse_cron_part(part, min_value, max_value, special_mapping): try: repeat_n = int(repeat) except ValueError: + # pylint: disable=raise-missing-from raise cv.Invalid("Repeat for '/' time expression must be an integer, got {}" .format(repeat)) return set(range(offset_n, max_value + 1, repeat_n)) diff --git a/esphome/components/tuya/climate/__init__.py b/esphome/components/tuya/climate/__init__.py index 878c5aefe8..79c6551c14 100644 --- a/esphome/components/tuya/climate/__init__.py +++ b/esphome/components/tuya/climate/__init__.py @@ -7,9 +7,9 @@ from .. import tuya_ns, CONF_TUYA_ID, Tuya DEPENDENCIES = ['tuya'] CODEOWNERS = ['@jesserockz'] -CONF_TARGET_TEMPERATURE_DATAPOINT = "target_temperature_datapoint" -CONF_CURRENT_TEMPERATURE_DATAPOINT = "current_temperature_datapoint" -# CONF_ECO_MODE_DATAPOINT = "eco_mode_datapoint" +CONF_TARGET_TEMPERATURE_DATAPOINT = 'target_temperature_datapoint' +CONF_CURRENT_TEMPERATURE_DATAPOINT = 'current_temperature_datapoint' +CONF_TEMPERATURE_MULTIPLIER = 'temperature_multiplier' TuyaClimate = tuya_ns.class_('TuyaClimate', climate.Climate, cg.Component) @@ -19,7 +19,7 @@ CONFIG_SCHEMA = cv.All(climate.CLIMATE_SCHEMA.extend({ cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, cv.Optional(CONF_TARGET_TEMPERATURE_DATAPOINT): cv.uint8_t, cv.Optional(CONF_CURRENT_TEMPERATURE_DATAPOINT): cv.uint8_t, - # cv.Optional(CONF_ECO_MODE_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_TEMPERATURE_MULTIPLIER, default=1): cv.positive_float, }).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key( CONF_TARGET_TEMPERATURE_DATAPOINT, CONF_SWITCH_DATAPOINT)) @@ -38,5 +38,4 @@ def to_code(config): cg.add(var.set_target_temperature_id(config[CONF_TARGET_TEMPERATURE_DATAPOINT])) if CONF_CURRENT_TEMPERATURE_DATAPOINT in config: cg.add(var.set_current_temperature_id(config[CONF_CURRENT_TEMPERATURE_DATAPOINT])) - # if CONF_ECO_MODE_DATAPOINT in config: - # cg.add(var.set_eco_mode_id(config[CONF_ECO_MODE_DATAPOINT])) + cg.add(var.set_temperature_multiplier(config[CONF_TEMPERATURE_MULTIPLIER])) diff --git a/esphome/components/tuya/climate/tuya_climate.cpp b/esphome/components/tuya/climate/tuya_climate.cpp index cfd5acccbd..0b66a58af6 100644 --- a/esphome/components/tuya/climate/tuya_climate.cpp +++ b/esphome/components/tuya/climate/tuya_climate.cpp @@ -21,28 +21,20 @@ void TuyaClimate::setup() { } if (this->target_temperature_id_.has_value()) { this->parent_->register_listener(*this->target_temperature_id_, [this](TuyaDatapoint datapoint) { - this->target_temperature = datapoint.value_int; + this->target_temperature = datapoint.value_int * this->temperature_multiplier_; this->compute_state_(); this->publish_state(); - ESP_LOGD(TAG, "MCU reported target temperature is: %d", datapoint.value_int); + ESP_LOGD(TAG, "MCU reported target temperature is: %.1f", this->target_temperature); }); } if (this->current_temperature_id_.has_value()) { this->parent_->register_listener(*this->current_temperature_id_, [this](TuyaDatapoint datapoint) { - this->current_temperature = datapoint.value_int; + this->current_temperature = datapoint.value_int * this->temperature_multiplier_; this->compute_state_(); this->publish_state(); - ESP_LOGD(TAG, "MCU reported current temperature is: %d", datapoint.value_int); + ESP_LOGD(TAG, "MCU reported current temperature is: %.1f", this->current_temperature); }); } - // if (this->eco_mode_id_.has_value()) { - // this->parent_->register_listener(*this->eco_mode_id_, [this](TuyaDatapoint datapoint) { - // this->eco_mode = datapoint.value_bool; - // this->compute_state_(); - // this->publish_state(); - // ESP_LOGD(TAG, "MCU reported eco mode of: %s", ONOFF(datapoint.value_bool)); - // }); - // } } void TuyaClimate::control(const climate::ClimateCall &call) { @@ -56,30 +48,17 @@ void TuyaClimate::control(const climate::ClimateCall &call) { this->parent_->set_datapoint_value(datapoint); ESP_LOGD(TAG, "Setting switch: %s", ONOFF(datapoint.value_bool)); } - if (call.get_target_temperature_low().has_value()) - this->target_temperature_low = *call.get_target_temperature_low(); - if (call.get_target_temperature_high().has_value()) - this->target_temperature_high = *call.get_target_temperature_high(); + if (call.get_target_temperature().has_value()) { this->target_temperature = *call.get_target_temperature(); TuyaDatapoint datapoint{}; datapoint.id = *this->target_temperature_id_; datapoint.type = TuyaDatapointType::INTEGER; - datapoint.value_int = (int) this->target_temperature; + datapoint.value_int = (int) (this->target_temperature / this->temperature_multiplier_); this->parent_->set_datapoint_value(datapoint); - ESP_LOGD(TAG, "Setting target temperature: %d", datapoint.value_int); + ESP_LOGD(TAG, "Setting target temperature: %.1f", this->target_temperature); } - // if (call.get_eco_mode().has_value()) { - // this->eco_mode = *call.get_eco_mode(); - - // TuyaDatapoint datapoint{}; - // datapoint.id = *this->eco_mode_id_; - // datapoint.type = TuyaDatapointType::BOOLEAN; - // datapoint.value_bool = this->eco_mode; - // this->parent_->set_datapoint_value(datapoint); - // ESP_LOGD(TAG, "Setting eco mode: %s", ONOFF(datapoint.value_bool)); - // } this->compute_state_(); this->publish_state(); @@ -89,7 +68,6 @@ climate::ClimateTraits TuyaClimate::traits() { auto traits = climate::ClimateTraits(); traits.set_supports_current_temperature(this->current_temperature_id_.has_value()); traits.set_supports_heat_mode(true); - // traits.set_supports_eco_mode(this->eco_mode_id_.has_value()); traits.set_supports_action(true); return traits; } @@ -102,8 +80,6 @@ void TuyaClimate::dump_config() { ESP_LOGCONFIG(TAG, " Target Temperature has datapoint ID %u", *this->target_temperature_id_); if (this->current_temperature_id_.has_value()) ESP_LOGCONFIG(TAG, " Current Temperature has datapoint ID %u", *this->current_temperature_id_); - // if (this->eco_mode_id_.has_value()) - // ESP_LOGCONFIG(TAG, " Eco Mode has datapoint ID %u", *this->mode_id_); } void TuyaClimate::compute_state_() { diff --git a/esphome/components/tuya/climate/tuya_climate.h b/esphome/components/tuya/climate/tuya_climate.h index a073b0996c..e09e110a35 100644 --- a/esphome/components/tuya/climate/tuya_climate.h +++ b/esphome/components/tuya/climate/tuya_climate.h @@ -18,7 +18,9 @@ class TuyaClimate : public climate::Climate, public Component { void set_current_temperature_id(uint8_t current_temperature_id) { this->current_temperature_id_ = current_temperature_id; } - // void set_eco_mode_id(uint8_t eco_mode_id) { this->eco_mode_id_ = eco_mode_id; } + void set_temperature_multiplier(float temperature_multiplier) { + this->temperature_multiplier_ = temperature_multiplier; + } void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } @@ -38,7 +40,7 @@ class TuyaClimate : public climate::Climate, public Component { optional switch_id_{}; optional target_temperature_id_{}; optional current_temperature_id_{}; - // optional eco_mode_id_{}; + float temperature_multiplier_{1.0f}; }; } // namespace tuya diff --git a/esphome/components/wifi/wpa2_eap.py b/esphome/components/wifi/wpa2_eap.py index 8bf50598b0..54195c852b 100644 --- a/esphome/components/wifi/wpa2_eap.py +++ b/esphome/components/wifi/wpa2_eap.py @@ -18,9 +18,9 @@ _LOGGER = logging.getLogger(__name__) def validate_cryptography_installed(): try: import cryptography - except ImportError: + except ImportError as err: raise cv.Invalid("This settings requires the cryptography python package. " - "Please install it with `pip install cryptography`") + "Please install it with `pip install cryptography`") from err if cryptography.__version__[0] < '2': raise cv.Invalid("Please update your python cryptography installation to least 2.x " diff --git a/esphome/components/xiaomi_hhccjcy01/sensor.py b/esphome/components/xiaomi_hhccjcy01/sensor.py index 0b0349d7e4..5e904c7eb6 100644 --- a/esphome/components/xiaomi_hhccjcy01/sensor.py +++ b/esphome/components/xiaomi_hhccjcy01/sensor.py @@ -1,8 +1,8 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import sensor, esp32_ble_tracker -from esphome.const import CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ - UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, CONF_ID, \ +from esphome.const import CONF_BATTERY_LEVEL, CONF_MAC_ADDRESS, CONF_TEMPERATURE, \ + UNIT_CELSIUS, ICON_THERMOMETER, UNIT_PERCENT, ICON_WATER_PERCENT, ICON_BATTERY, CONF_ID, \ CONF_MOISTURE, CONF_ILLUMINANCE, ICON_BRIGHTNESS_5, UNIT_LUX, CONF_CONDUCTIVITY, \ UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER @@ -19,6 +19,7 @@ CONFIG_SCHEMA = cv.Schema({ cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(UNIT_CELSIUS, ICON_THERMOMETER, 1), cv.Optional(CONF_MOISTURE): sensor.sensor_schema(UNIT_PERCENT, ICON_WATER_PERCENT, 0), cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(UNIT_LUX, ICON_BRIGHTNESS_5, 0), + cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 0), cv.Optional(CONF_CONDUCTIVITY): sensor.sensor_schema(UNIT_MICROSIEMENS_PER_CENTIMETER, ICON_FLOWER, 0), }).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA).extend(cv.COMPONENT_SCHEMA) @@ -43,3 +44,6 @@ def to_code(config): if CONF_CONDUCTIVITY in config: sens = yield sensor.new_sensor(config[CONF_CONDUCTIVITY]) cg.add(var.set_conductivity(sens)) + if CONF_BATTERY_LEVEL in config: + sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL]) + cg.add(var.set_battery_level(sens)) diff --git a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp index fd099f7aa5..18e2b2439f 100644 --- a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp +++ b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.cpp @@ -14,6 +14,7 @@ void XiaomiHHCCJCY01::dump_config() { LOG_SENSOR(" ", "Moisture", this->moisture_); LOG_SENSOR(" ", "Conductivity", this->conductivity_); LOG_SENSOR(" ", "Illuminance", this->illuminance_); + LOG_SENSOR(" ", "Battery Level", this->battery_level_); } bool XiaomiHHCCJCY01::parse_device(const esp32_ble_tracker::ESPBTDevice &device) { @@ -50,6 +51,8 @@ bool XiaomiHHCCJCY01::parse_device(const esp32_ble_tracker::ESPBTDevice &device) this->conductivity_->publish_state(*res->conductivity); if (res->illuminance.has_value() && this->illuminance_ != nullptr) this->illuminance_->publish_state(*res->illuminance); + if (res->battery_level.has_value() && this->battery_level_ != nullptr) + this->battery_level_->publish_state(*res->battery_level); success = true; } diff --git a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h index e72bf98161..bd9d742b2d 100644 --- a/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h +++ b/esphome/components/xiaomi_hhccjcy01/xiaomi_hhccjcy01.h @@ -22,6 +22,7 @@ class XiaomiHHCCJCY01 : public Component, public esp32_ble_tracker::ESPBTDeviceL void set_moisture(sensor::Sensor *moisture) { moisture_ = moisture; } void set_conductivity(sensor::Sensor *conductivity) { conductivity_ = conductivity; } void set_illuminance(sensor::Sensor *illuminance) { illuminance_ = illuminance; } + void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; } protected: uint64_t address_; @@ -29,6 +30,7 @@ class XiaomiHHCCJCY01 : public Component, public esp32_ble_tracker::ESPBTDeviceL sensor::Sensor *moisture_{nullptr}; sensor::Sensor *conductivity_{nullptr}; sensor::Sensor *illuminance_{nullptr}; + sensor::Sensor *battery_level_{nullptr}; }; } // namespace xiaomi_hhccjcy01 diff --git a/esphome/config.py b/esphome/config.py index 85f48c64b7..0484414929 100644 --- a/esphome/config.py +++ b/esphome/config.py @@ -675,7 +675,7 @@ def _load_config(command_line_substitutions): try: config = yaml_util.load_yaml(CORE.config_path) except EsphomeError as e: - raise InvalidYAMLError(e) + raise InvalidYAMLError(e) from e CORE.raw_config = config try: @@ -693,7 +693,7 @@ def load_config(command_line_substitutions): try: return _load_config(command_line_substitutions) except vol.Invalid as err: - raise EsphomeError(f"Error while parsing config: {err}") + raise EsphomeError(f"Error while parsing config: {err}") from err def line_info(obj, highlight=True): diff --git a/esphome/config_validation.py b/esphome/config_validation.py index 3fb2b4827c..aaa822c587 100644 --- a/esphome/config_validation.py +++ b/esphome/config_validation.py @@ -224,6 +224,7 @@ def int_(value): try: return int(value, base) except ValueError: + # pylint: disable=raise-missing-from raise Invalid(f"Expected integer, but cannot parse {value} as an integer") @@ -423,6 +424,7 @@ def time_period_str_colon(value): try: parsed = [int(x) for x in value.split(':')] except ValueError: + # pylint: disable=raise-missing-from raise Invalid(TIME_PERIOD_ERROR.format(value)) if len(parsed) == 2: @@ -527,6 +529,7 @@ def time_of_day(value): try: date = datetime.strptime(value, '%H:%M:%S %p') except ValueError: + # pylint: disable=raise-missing-from raise Invalid(f"Invalid time of day: {err}") return { @@ -548,6 +551,7 @@ def mac_address(value): try: parts_int.append(int(part, 16)) except ValueError: + # pylint: disable=raise-missing-from raise Invalid("MAC Address parts must be hexadecimal values from 00 to FF") return core.MACAddress(*parts_int) @@ -565,6 +569,7 @@ def bind_key(value): try: parts_int.append(int(part, 16)) except ValueError: + # pylint: disable=raise-missing-from raise Invalid("Bind key must be hex values from 00 to FF") return ''.join(f'{part:02X}' for part in parts_int) @@ -686,8 +691,8 @@ def domain(value): return value try: return str(ipv4(value)) - except Invalid: - raise Invalid(f"Invalid domain: {value}") + except Invalid as err: + raise Invalid(f"Invalid domain: {value}") from err def domain_name(value): @@ -739,8 +744,8 @@ def _valid_topic(value): value = string(value) try: raw_value = value.encode('utf-8') - except UnicodeError: - raise Invalid("MQTT topic name/filter must be valid UTF-8 string.") + except UnicodeError as err: + raise Invalid("MQTT topic name/filter must be valid UTF-8 string.") from err if not raw_value: raise Invalid("MQTT topic name/filter must not be empty.") if len(raw_value) > 65535: @@ -792,6 +797,7 @@ def mqtt_qos(value): try: value = int(value) except (TypeError, ValueError): + # pylint: disable=raise-missing-from raise Invalid(f"MQTT Quality of Service must be integer, got {value}") return one_of(0, 1, 2)(value) @@ -836,6 +842,7 @@ def possibly_negative_percentage(value): else: value = float(value) except ValueError: + # pylint: disable=raise-missing-from raise Invalid("invalid number") if value > 1: msg = "Percentage must not be higher than 100%." @@ -1006,6 +1013,7 @@ def dimensions(value): try: width, height = int(value[0]), int(value[1]) except ValueError: + # pylint: disable=raise-missing-from raise Invalid("Width and height dimensions must be integers") if width <= 0 or height <= 0: raise Invalid("Width and height must at least be 1") diff --git a/esphome/espota2.py b/esphome/espota2.py index edfa4e63e6..a1408a7d44 100644 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -83,19 +83,19 @@ def receive_exactly(sock, amount, msg, expect, decode=True): try: data += recv_decode(sock, 1, decode=decode) except OSError as err: - raise OTAError(f"Error receiving acknowledge {msg}: {err}") + raise OTAError(f"Error receiving acknowledge {msg}: {err}") from err try: check_error(data, expect) except OTAError as err: sock.close() - raise OTAError(f"Error {msg}: {err}") + raise OTAError(f"Error {msg}: {err}") from err while len(data) < amount: try: data += recv_decode(sock, amount - len(data), decode=decode) except OSError as err: - raise OTAError(f"Error receiving {msg}: {err}") + raise OTAError(f"Error receiving {msg}: {err}") from err return data @@ -151,7 +151,7 @@ def send_check(sock, data, msg): sock.sendall(data) except OSError as err: - raise OTAError(f"Error sending {msg}: {err}") + raise OTAError(f"Error sending {msg}: {err}") from err def perform_ota(sock, password, file_handle, filename): @@ -226,7 +226,7 @@ def perform_ota(sock, password, file_handle, filename): sock.sendall(chunk) except OSError as err: sys.stderr.write('\n') - raise OTAError(f"Error sending data: {err}") + raise OTAError(f"Error sending data: {err}") from err progress.update(offset / float(file_size)) progress.done() @@ -259,7 +259,7 @@ def run_ota_impl_(remote_host, remote_port, password, filename): remote_host) _LOGGER.error("(If this error persists, please set a static IP address: " "https://esphome.io/components/wifi.html#manual-ips)") - raise OTAError(err) + raise OTAError(err) from err _LOGGER.info(" -> %s", ip) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) diff --git a/esphome/helpers.py b/esphome/helpers.py index b30ace89d2..1389804fd9 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -89,7 +89,7 @@ def mkdir_p(path): pass else: from esphome.core import EsphomeError - raise EsphomeError(f"Error creating directories {path}: {err}") + raise EsphomeError(f"Error creating directories {path}: {err}") from err def is_ip_address(host): @@ -110,13 +110,13 @@ def _resolve_with_zeroconf(host): try: zc = Zeroconf() - except Exception: + except Exception as err: raise EsphomeError("Cannot start mDNS sockets, is this a docker container without " - "host network mode?") + "host network mode?") from err try: info = zc.resolve_host(host + '.') except Exception as err: - raise EsphomeError(f"Error resolving mDNS hostname: {err}") + raise EsphomeError(f"Error resolving mDNS hostname: {err}") from err finally: zc.close() if info is None: @@ -142,7 +142,7 @@ def resolve_ip_address(host): except OSError as err: errs.append(str(err)) raise EsphomeError("Error resolving IP address: {}" - "".format(', '.join(errs))) + "".format(', '.join(errs))) from err def get_bool_env(var, default=False): @@ -165,10 +165,10 @@ def read_file(path): return f_handle.read() except OSError as err: from esphome.core import EsphomeError - raise EsphomeError(f"Error reading file {path}: {err}") + raise EsphomeError(f"Error reading file {path}: {err}") from err except UnicodeDecodeError as err: from esphome.core import EsphomeError - raise EsphomeError(f"Error reading file {path}: {err}") + raise EsphomeError(f"Error reading file {path}: {err}") from err def _write_file(path: Union[Path, str], text: Union[str, bytes]): @@ -205,9 +205,9 @@ def _write_file(path: Union[Path, str], text: Union[str, bytes]): def write_file(path: Union[Path, str], text: str): try: _write_file(path, text) - except OSError: + except OSError as err: from esphome.core import EsphomeError - raise EsphomeError(f"Could not write file at {path}") + raise EsphomeError(f"Could not write file at {path}") from err def write_file_if_changed(path: Union[Path, str], text: str): @@ -230,7 +230,7 @@ def copy_file_if_changed(src, dst): shutil.copy(src, dst) except OSError as err: from esphome.core import EsphomeError - raise EsphomeError(f"Error copying file {src} to {dst}: {err}") + raise EsphomeError(f"Error copying file {src} to {dst}: {err}") from err def list_starts_with(list_, sub): diff --git a/esphome/mqtt.py b/esphome/mqtt.py index cbcf067c44..499ccbe7f1 100644 --- a/esphome/mqtt.py +++ b/esphome/mqtt.py @@ -67,7 +67,7 @@ def initialize(config, subscriptions, on_message, username, password, client_id) port = int(config[CONF_MQTT][CONF_PORT]) client.connect(host, port) except OSError as err: - raise EsphomeError(f"Cannot connect to MQTT broker: {err}") + raise EsphomeError(f"Cannot connect to MQTT broker: {err}") from err try: client.loop_forever() diff --git a/esphome/platformio_api.py b/esphome/platformio_api.py index 4866119902..fe86fea1cf 100644 --- a/esphome/platformio_api.py +++ b/esphome/platformio_api.py @@ -22,13 +22,13 @@ def patch_structhash(): from os import makedirs def patched_clean_build_dir(build_dir, *args): - from platformio import util + from platformio import fs from platformio.project.helpers import get_project_dir platformio_ini = join(get_project_dir(), "platformio.ini") # if project's config is modified if isdir(build_dir) and getmtime(platformio_ini) > getmtime(build_dir): - util.rmtree_(build_dir) + fs.rmtree(build_dir) if not isdir(build_dir): makedirs(build_dir) diff --git a/esphome/voluptuous_schema.py b/esphome/voluptuous_schema.py index ce13f0ceb0..d10801fb95 100644 --- a/esphome/voluptuous_schema.py +++ b/esphome/voluptuous_schema.py @@ -32,6 +32,7 @@ class _Schema(vol.Schema): try: res = extra(res) except vol.Invalid as err: + # pylint: disable=raise-missing-from raise ensure_multiple_invalid(err) return res diff --git a/esphome/yaml_util.py b/esphome/yaml_util.py index 053fba6274..857a986538 100644 --- a/esphome/yaml_util.py +++ b/esphome/yaml_util.py @@ -126,6 +126,7 @@ class ESPHomeLoader(yaml.SafeLoader): # pylint: disable=too-many-ancestors try: hash(key) except TypeError: + # pylint: disable=raise-missing-from raise yaml.constructor.ConstructorError( f'Invalid key "{key}" (not hashable)', key_node.start_mark) @@ -297,7 +298,7 @@ def _load_yaml_internal(fname): try: return loader.get_single_data() or OrderedDict() except yaml.YAMLError as exc: - raise EsphomeError(exc) + raise EsphomeError(exc) from exc finally: loader.dispose() diff --git a/requirements.txt b/requirements.txt index 3c6a04e77c..29d987fc6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,11 +3,11 @@ PyYAML==5.3.1 paho-mqtt==1.5.0 colorlog==4.2.1 tornado==6.0.4 -protobuf==3.12.4 +protobuf==3.13.0 tzlocal==2.1 pytz==2020.1 pyserial==3.4 ifaddr==0.1.7 -platformio==4.3.4 +platformio==5.0.1 esptool==2.8 click==7.1.2 diff --git a/requirements_test.txt b/requirements_test.txt index dfa6c7da77..6ea351e75b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,12 +1,12 @@ -pylint==2.5.3 +pylint==2.6.0 flake8==3.8.3 pillow>4.0.0 cryptography>=2.0.0,<4 pexpect==4.8.0 # Unit tests -pytest==6.0.1 +pytest==6.0.2 pytest-cov==2.10.0 -pytest-mock==3.2.0 +pytest-mock==3.3.1 asyncmock==0.4.2 hypothesis==5.21.0 diff --git a/tests/test2.yaml b/tests/test2.yaml index 077ceab886..d19c8ade49 100644 --- a/tests/test2.yaml +++ b/tests/test2.yaml @@ -101,6 +101,8 @@ sensor: name: "Xiaomi HHCCJCY01 Illuminance" conductivity: name: "Xiaomi HHCCJCY01 Soil Conductivity" + battery_level: + name: "Xiaomi HHCCJCY01 Battery Level" - platform: xiaomi_lywsdcgq mac_address: 7A:80:8E:19:36:BA temperature: @@ -280,6 +282,9 @@ text_sensor: service: light.turn_on data: entity_id: light.my_light + - homeassistant.tag_scanned: + tag: 1234-abcd + - homeassistant.tag_scanned: 1234-abcd - platform: template name: "Template Text Sensor" lambda: |- diff --git a/tests/test4.yaml b/tests/test4.yaml index ab78e22fef..51e4c6311f 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -87,6 +87,7 @@ climate: id: tuya_climate switch_datapoint: 1 target_temperature_datapoint: 3 + temperature_multiplier: 0.5 switch: - platform: tuya