Drop Python 2 Support (#793)

* Remove Python 2 support

* Remove u-strings

* Remove docker symlinks

* Remove from travis

* Update requirements

* Upgrade flake8/pylint

* Fixes

* Manual

* Run pyupgrade

* Lint

* Remove base_int

* Fix

* Update platformio_api.py

* Update component.cpp
This commit is contained in:
Otto Winter
2019-12-07 18:28:55 +01:00
committed by GitHub
parent b5714cd70f
commit 056c72d50d
78 changed files with 815 additions and 1097 deletions
-6
View File
@@ -21,12 +21,6 @@ matrix:
- esphome tests/test1.yaml compile
- esphome tests/test2.yaml compile
- esphome tests/test3.yaml compile
- python: "2.7"
env: TARGET=Test2.7
script:
- esphome tests/test1.yaml compile
- esphome tests/test2.yaml compile
- esphome tests/test3.yaml compile
- env: TARGET=Cpp-Lint
dist: trusty
sudo: required
-2
View File
@@ -14,7 +14,5 @@ RUN \
COPY requirements_test.txt /requirements_test.txt
RUN pip3 install --no-cache-dir wheel && pip3 install --no-cache-dir -r /requirements_test.txt
RUN ln -s /usr/bin/pip3 /usr/bin/pip && ln -f -s /usr/bin/python3 /usr/bin/python
VOLUME ["/esphome"]
WORKDIR /esphome
+24 -37
View File
@@ -1,5 +1,3 @@
from __future__ import print_function
import argparse
import functools
import logging
@@ -14,7 +12,6 @@ from esphome.const import CONF_BAUD_RATE, CONF_BROKER, CONF_LOGGER, CONF_OTA, \
CONF_PASSWORD, CONF_PORT, CONF_ESPHOME, CONF_PLATFORMIO_OPTIONS
from esphome.core import CORE, EsphomeError, coroutine, coroutine_with_priority
from esphome.helpers import color, indent
from esphome.py_compat import IS_PY2, safe_input, IS_PY3
from esphome.util import run_external_command, run_external_process, safe_print, list_yaml_files
_LOGGER = logging.getLogger(__name__)
@@ -42,12 +39,12 @@ def choose_prompt(options):
if len(options) == 1:
return options[0][1]
safe_print(u"Found multiple options, please choose one:")
safe_print("Found multiple options, please choose one:")
for i, (desc, _) in enumerate(options):
safe_print(u" [{}] {}".format(i + 1, desc))
safe_print(f" [{i+1}] {desc}")
while True:
opt = safe_input('(number): ')
opt = input('(number): ')
if opt in options:
opt = options.index(opt)
break
@@ -57,20 +54,20 @@ def choose_prompt(options):
raise ValueError
break
except ValueError:
safe_print(color('red', u"Invalid option: '{}'".format(opt)))
safe_print(color('red', f"Invalid option: '{opt}'"))
return options[opt - 1][1]
def choose_upload_log_host(default, check_default, show_ota, show_mqtt, show_api):
options = []
for res, desc in get_serial_ports():
options.append((u"{} ({})".format(res, desc), res))
options.append((f"{res} ({desc})", res))
if (show_ota and 'ota' in CORE.config) or (show_api and 'api' in CORE.config):
options.append((u"Over The Air ({})".format(CORE.address), CORE.address))
options.append((f"Over The Air ({CORE.address})", CORE.address))
if default == 'OTA':
return CORE.address
if show_mqtt and 'mqtt' in CORE.config:
options.append((u"MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT'))
options.append(("MQTT ({})".format(CORE.config['mqtt'][CONF_BROKER]), 'MQTT'))
if default == 'OTA':
return 'MQTT'
if default is not None:
@@ -108,11 +105,7 @@ def run_miniterm(config, port):
except serial.SerialException:
_LOGGER.error("Serial port closed!")
return
if IS_PY2:
line = raw.replace('\r', '').replace('\n', '')
else:
line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8',
'backslashreplace')
line = raw.replace(b'\r', b'').replace(b'\n', b'').decode('utf8', 'backslashreplace')
time = datetime.now().time().strftime('[%H:%M:%S]')
message = time + line
safe_print(message)
@@ -127,11 +120,9 @@ def wrap_to_code(name, comp):
@functools.wraps(comp.to_code)
@coroutine_with_priority(coro.priority)
def wrapped(conf):
cg.add(cg.LineComment(u"{}:".format(name)))
cg.add(cg.LineComment(f"{name}:"))
if comp.config_schema is not None:
conf_str = yaml_util.dump(conf)
if IS_PY2:
conf_str = conf_str.decode('utf-8')
conf_str = conf_str.replace('//', '')
cg.add(cg.LineComment(indent(conf_str)))
yield coro(conf)
@@ -243,7 +234,7 @@ def setup_log(debug=False, quiet=False):
log_level = logging.INFO
logging.basicConfig(level=log_level)
fmt = "%(levelname)s %(message)s"
colorfmt = "%(log_color)s{}%(reset)s".format(fmt)
colorfmt = f"%(log_color)s{fmt}%(reset)s"
datefmt = '%H:%M:%S'
logging.getLogger('urllib3').setLevel(logging.WARNING)
@@ -292,12 +283,12 @@ def command_compile(args, config):
if exit_code != 0:
return exit_code
if args.only_generate:
_LOGGER.info(u"Successfully generated source code.")
_LOGGER.info("Successfully generated source code.")
return 0
exit_code = compile_program(args, config)
if exit_code != 0:
return exit_code
_LOGGER.info(u"Successfully compiled program.")
_LOGGER.info("Successfully compiled program.")
return 0
@@ -307,7 +298,7 @@ def command_upload(args, config):
exit_code = upload_program(config, args, port)
if exit_code != 0:
return exit_code
_LOGGER.info(u"Successfully uploaded program.")
_LOGGER.info("Successfully uploaded program.")
return 0
@@ -324,13 +315,13 @@ def command_run(args, config):
exit_code = compile_program(args, config)
if exit_code != 0:
return exit_code
_LOGGER.info(u"Successfully compiled program.")
_LOGGER.info("Successfully compiled program.")
port = choose_upload_log_host(default=args.upload_port, check_default=None,
show_ota=True, show_mqtt=False, show_api=True)
exit_code = upload_program(config, args, port)
if exit_code != 0:
return exit_code
_LOGGER.info(u"Successfully uploaded program.")
_LOGGER.info("Successfully uploaded program.")
if args.no_logs:
return 0
port = choose_upload_log_host(default=args.upload_port, check_default=port,
@@ -349,7 +340,7 @@ def command_mqtt_fingerprint(args, config):
def command_version(args):
safe_print(u"Version: {}".format(const.__version__))
safe_print(f"Version: {const.__version__}")
return 0
@@ -377,10 +368,10 @@ def command_update_all(args):
twidth = 60
def print_bar(middle_text):
middle_text = " {} ".format(middle_text)
middle_text = f" {middle_text} "
width = len(click.unstyle(middle_text))
half_line = "=" * ((twidth - width) // 2)
click.echo("%s%s%s" % (half_line, middle_text, half_line))
click.echo(f"{half_line}{middle_text}{half_line}")
for f in files:
print("Updating {}".format(color('cyan', f)))
@@ -431,7 +422,7 @@ POST_CONFIG_ACTIONS = {
def parse_args(argv):
parser = argparse.ArgumentParser(description='ESPHome v{}'.format(const.__version__))
parser = argparse.ArgumentParser(description=f'ESPHome v{const.__version__}')
parser.add_argument('-v', '--verbose', help="Enable verbose esphome logs.",
action='store_true')
parser.add_argument('-q', '--quiet', help="Disable all esphome logs.",
@@ -525,14 +516,10 @@ def run_esphome(argv):
_LOGGER.error("Missing configuration parameter, see esphome --help.")
return 1
if IS_PY2:
_LOGGER.warning("You're using ESPHome with python 2. Support for python 2 is deprecated "
"and will be removed in 1.15.0. Please reinstall ESPHome with python 3.6 "
"or higher.")
elif IS_PY3 and sys.version_info < (3, 6, 0):
_LOGGER.warning("You're using ESPHome with python 3.5. Support for python 3.5 is "
"deprecated and will be removed in 1.15.0. Please reinstall ESPHome with "
"python 3.6 or higher.")
if sys.version_info < (3, 6, 0):
_LOGGER.error("You're running ESPHome with Python <3.6. ESPHome is no longer compatible "
"with this Python version. Please reinstall ESPHome with Python 3.6+")
return 1
if args.command in PRE_CONFIG_ACTIONS:
try:
@@ -551,7 +538,7 @@ def run_esphome(argv):
CORE.config = config
if args.command not in POST_CONFIG_ACTIONS:
safe_print(u"Unknown command {}".format(args.command))
safe_print(f"Unknown command {args.command}")
try:
rc = POST_CONFIG_ACTIONS[args.command](args, config)
+20 -25
View File
@@ -14,7 +14,6 @@ import esphome.api.api_pb2 as pb
from esphome.const import CONF_PASSWORD, CONF_PORT
from esphome.core import EsphomeError
from esphome.helpers import resolve_ip_address, indent, color
from esphome.py_compat import text_type, IS_PY2, byte_to_bytes, char_to_byte
from esphome.util import safe_print
_LOGGER = logging.getLogger(__name__)
@@ -67,16 +66,16 @@ MESSAGE_TYPE_TO_PROTO = {
def _varuint_to_bytes(value):
if value <= 0x7F:
return byte_to_bytes(value)
return bytes([value])
ret = bytes()
while value:
temp = value & 0x7F
value >>= 7
if value:
ret += byte_to_bytes(temp | 0x80)
ret += bytes([temp | 0x80])
else:
ret += byte_to_bytes(temp)
ret += bytes([temp])
return ret
@@ -84,8 +83,7 @@ def _varuint_to_bytes(value):
def _bytes_to_varuint(value):
result = 0
bitpos = 0
for c in value:
val = char_to_byte(c)
for val in value:
result |= (val & 0x7F) << bitpos
bitpos += 7
if (val & 0x80) == 0:
@@ -191,8 +189,8 @@ class APIClient(threading.Thread):
self._socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
try:
self._socket.connect((ip, self._port))
except socket.error as err:
err = APIConnectionError("Error connecting to {}: {}".format(ip, err))
except OSError as err:
err = APIConnectionError(f"Error connecting to {ip}: {err}")
self._fatal_error(err)
raise err
self._socket.settimeout(0.1)
@@ -200,7 +198,7 @@ class APIClient(threading.Thread):
self._socket_open_event.set()
hello = pb.HelloRequest()
hello.client_info = 'ESPHome v{}'.format(const.__version__)
hello.client_info = f'ESPHome v{const.__version__}'
try:
resp = self._send_message_await_response(hello, pb.HelloResponse)
except APIConnectionError as err:
@@ -251,8 +249,8 @@ class APIClient(threading.Thread):
with self._socket_write_lock:
try:
self._socket.sendall(data)
except socket.error as err:
err = APIConnectionError("Error while writing data: {}".format(err))
except OSError as err:
err = APIConnectionError(f"Error while writing data: {err}")
self._fatal_error(err)
raise err
@@ -265,11 +263,8 @@ class APIClient(threading.Thread):
raise ValueError
encoded = msg.SerializeToString()
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(text_type(msg)))
if IS_PY2:
req = chr(0x00)
else:
req = bytes([0])
_LOGGER.debug("Sending %s:\n%s", type(msg), indent(str(msg)))
req = bytes([0])
req += _varuint_to_bytes(len(encoded))
req += _varuint_to_bytes(message_type)
req += encoded
@@ -355,14 +350,14 @@ class APIClient(threading.Thread):
raise APIConnectionError("Socket was closed")
except socket.timeout:
continue
except socket.error as err:
raise APIConnectionError("Error while receiving data: {}".format(err))
except OSError as err:
raise APIConnectionError(f"Error while receiving data: {err}")
ret += val
return ret
def _recv_varint(self):
raw = bytes()
while not raw or char_to_byte(raw[-1]) & 0x80:
while not raw or raw[-1] & 0x80:
raw += self._recv(1)
return _bytes_to_varuint(raw)
@@ -371,7 +366,7 @@ class APIClient(threading.Thread):
return
# Preamble
if char_to_byte(self._recv(1)[0]) != 0x00:
if self._recv(1)[0] != 0x00:
raise APIConnectionError("Invalid preamble")
length = self._recv_varint()
@@ -436,7 +431,7 @@ def run_logs(config, address):
return
if err:
_LOGGER.warning(u"Disconnected from API: %s", err)
_LOGGER.warning("Disconnected from API: %s", err)
while retry_timer:
retry_timer.pop(0).cancel()
@@ -454,18 +449,18 @@ def run_logs(config, address):
wait_time = int(min(1.5**min(tries, 100), 30))
if not has_connects:
_LOGGER.warning(u"Initial connection failed. The ESP might not be connected "
u"to WiFi yet (%s). Re-Trying in %s seconds",
_LOGGER.warning("Initial connection failed. The ESP might not be connected "
"to WiFi yet (%s). Re-Trying in %s seconds",
error, wait_time)
else:
_LOGGER.warning(u"Couldn't connect to API (%s). Trying to reconnect in %s seconds",
_LOGGER.warning("Couldn't connect to API (%s). Trying to reconnect in %s seconds",
error, wait_time)
timer = threading.Timer(wait_time, functools.partial(try_connect, None, tries + 1))
timer.start()
retry_timer.append(timer)
def on_log(msg):
time_ = datetime.now().time().strftime(u'[%H:%M:%S]')
time_ = datetime.now().time().strftime('[%H:%M:%S]')
text = msg.message
if msg.send_failed:
text = color('white', '(Message skipped because it was too big to fit in '
+2 -2
View File
@@ -83,9 +83,9 @@ def validate_automation(extra_schema=None, extra_validators=None, single=False):
try:
return cv.Schema([schema])(value)
except cv.Invalid as err2:
if u'extra keys not allowed' in str(err2) and len(err2.path) == 2:
if 'extra keys not allowed' in str(err2) and len(err2.path) == 2:
raise err
if u'Unable to find action' in str(err):
if 'Unable to find action' in str(err):
raise err2
raise cv.MultipleInvalid([err, err2])
elif isinstance(value, dict):
+1 -1
View File
@@ -36,4 +36,4 @@ def to_code(config):
continue
conf = config[key]
sens = yield sensor.new_sensor(conf)
cg.add(getattr(var, 'set_{}_sensor'.format(key))(sens))
cg.add(getattr(var, f'set_{key}_sensor')(sens))
+3 -4
View File
@@ -2,7 +2,6 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, voltage_sampler
from esphome.const import CONF_GAIN, CONF_MULTIPLEXER, ICON_FLASH, UNIT_VOLT, CONF_ID
from esphome.py_compat import string_types
from . import ads1115_ns, ADS1115Component
DEPENDENCIES = ['ads1115']
@@ -32,9 +31,9 @@ GAIN = {
def validate_gain(value):
if isinstance(value, float):
value = u'{:0.03f}'.format(value)
elif not isinstance(value, string_types):
raise cv.Invalid('invalid gain "{}"'.format(value))
value = f'{value:0.03f}'
elif not isinstance(value, str):
raise cv.Invalid(f'invalid gain "{value}"')
return cv.enum(GAIN)(value)
+1 -1
View File
@@ -102,7 +102,7 @@ def homeassistant_service_to_code(config, action_id, template_arg, args):
def validate_homeassistant_event(value):
value = cv.string(value)
if not value.startswith(u'esphome.'):
if not value.startswith('esphome.'):
raise cv.Invalid("ESPHome can only generate Home Assistant events that begin with "
"esphome. For example 'esphome.xyz'")
return value
+8 -9
View File
@@ -9,7 +9,6 @@ from esphome.const import CONF_DEVICE_CLASS, CONF_FILTERS, \
CONF_ON_DOUBLE_CLICK, CONF_ON_MULTI_CLICK, CONF_ON_PRESS, CONF_ON_RELEASE, CONF_ON_STATE, \
CONF_STATE, CONF_TIMING, CONF_TRIGGER_ID, CONF_FOR, CONF_NAME, CONF_MQTT_ID
from esphome.core import CORE, coroutine, coroutine_with_priority
from esphome.py_compat import string_types
from esphome.util import Registry
DEVICE_CLASSES = [
@@ -94,7 +93,7 @@ MULTI_CLICK_TIMING_SCHEMA = cv.Schema({
def parse_multi_click_timing_str(value):
if not isinstance(value, string_types):
if not isinstance(value, str):
return value
parts = value.lower().split(' ')
@@ -104,10 +103,10 @@ def parse_multi_click_timing_str(value):
try:
state = cv.boolean(parts[0])
except cv.Invalid:
raise cv.Invalid(u"First word must either be ON or OFF, not {}".format(parts[0]))
raise cv.Invalid("First word must either be ON or OFF, not {}".format(parts[0]))
if parts[1] != 'for':
raise cv.Invalid(u"Second word must be 'for', got {}".format(parts[1]))
raise cv.Invalid("Second word must be 'for', got {}".format(parts[1]))
if parts[2] == 'at':
if parts[3] == 'least':
@@ -115,12 +114,12 @@ def parse_multi_click_timing_str(value):
elif parts[3] == 'most':
key = CONF_MAX_LENGTH
else:
raise cv.Invalid(u"Third word after at must either be 'least' or 'most', got {}"
u"".format(parts[3]))
raise cv.Invalid("Third word after at must either be 'least' or 'most', got {}"
"".format(parts[3]))
try:
length = cv.positive_time_period_milliseconds(parts[4])
except cv.Invalid as err:
raise cv.Invalid(u"Multi Click Grammar Parsing length failed: {}".format(err))
raise cv.Invalid(f"Multi Click Grammar Parsing length failed: {err}")
return {
CONF_STATE: state,
key: str(length)
@@ -132,12 +131,12 @@ def parse_multi_click_timing_str(value):
try:
min_length = cv.positive_time_period_milliseconds(parts[2])
except cv.Invalid as err:
raise cv.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err))
raise cv.Invalid(f"Multi Click Grammar Parsing minimum length failed: {err}")
try:
max_length = cv.positive_time_period_milliseconds(parts[4])
except cv.Invalid as err:
raise cv.Invalid(u"Multi Click Grammar Parsing minimum length failed: {}".format(err))
raise cv.Invalid(f"Multi Click Grammar Parsing minimum length failed: {err}")
return {
CONF_STATE: state,
+2 -2
View File
@@ -8,8 +8,8 @@ from esphome.const import CONF_ID, CONF_MODE, CONF_NUMBER, CONF_PINS, CONF_RUN_C
def validate_pin_number(value):
valid_pins = [0, 2, 4, 12, 13, 14, 15, 25, 26, 27, 32, 33, 34, 35, 36, 37, 38, 39]
if value[CONF_NUMBER] not in valid_pins:
raise cv.Invalid(u"Only pins {} support wakeup"
u"".format(', '.join(str(x) for x in valid_pins)))
raise cv.Invalid("Only pins {} support wakeup"
"".format(', '.join(str(x) for x in valid_pins)))
return value
+1 -2
View File
@@ -1,4 +1,3 @@
# coding=utf-8
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import core, automation
@@ -27,7 +26,7 @@ DISPLAY_ROTATIONS = {
def validate_rotation(value):
value = cv.string(value)
if value.endswith(u"°"):
if value.endswith("°"):
value = value[:-1]
return cv.enum(DISPLAY_ROTATIONS, int=True)(value)
@@ -46,33 +46,33 @@ def bt_uuid(value):
pattern = re.compile("^[A-F|0-9]{4,}$")
if not pattern.match(value):
raise cv.Invalid(
u"Invalid hexadecimal value for 16 bit UUID format: '{}'".format(in_value))
f"Invalid hexadecimal value for 16 bit UUID format: '{in_value}'")
return value
if len(value) == len(bt_uuid32_format):
pattern = re.compile("^[A-F|0-9]{8,}$")
if not pattern.match(value):
raise cv.Invalid(
u"Invalid hexadecimal value for 32 bit UUID format: '{}'".format(in_value))
f"Invalid hexadecimal value for 32 bit UUID format: '{in_value}'")
return value
if len(value) == len(bt_uuid128_format):
pattern = re.compile(
"^[A-F|0-9]{8,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{4,}-[A-F|0-9]{12,}$")
if not pattern.match(value):
raise cv.Invalid(
u"Invalid hexadecimal value for 128 UUID format: '{}'".format(in_value))
f"Invalid hexadecimal value for 128 UUID format: '{in_value}'")
return value
raise cv.Invalid(
u"Service UUID must be in 16 bit '{}', 32 bit '{}', or 128 bit '{}' format".format(
"Service UUID must be in 16 bit '{}', 32 bit '{}', or 128 bit '{}' format".format(
bt_uuid16_format, bt_uuid32_format, bt_uuid128_format))
def as_hex(value):
return cg.RawExpression('0x{}ULL'.format(value))
return cg.RawExpression(f'0x{value}ULL')
def as_hex_array(value):
value = value.replace("-", "")
cpp_array = ['0x{}'.format(part) for part in [value[i:i+2] for i in range(0, len(value), 2)]]
cpp_array = [f'0x{part}' for part in [value[i:i+2] for i in range(0, len(value), 2)]]
return cg.RawExpression(
'(uint8_t*)(const uint8_t[16]){{{}}}'.format(','.join(reversed(cpp_array))))
@@ -27,7 +27,7 @@ TOUCH_PADS = {
def validate_touch_pad(value):
value = validate_gpio_pin(value)
if value not in TOUCH_PADS:
raise cv.Invalid("Pin {} does not support touch pads.".format(value))
raise cv.Invalid(f"Pin {value} does not support touch pads.")
return value
+10 -10
View File
@@ -1,11 +1,11 @@
# coding=utf-8
import functools
from esphome import core
from esphome.components import display
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_SIZE
from esphome.core import CORE, HexInt
from esphome.py_compat import sort_by_cmp
DEPENDENCIES = ['display']
MULTI_CONF = True
@@ -33,9 +33,9 @@ def validate_glyphs(value):
return -1
if len(x_) > len(y_):
return 1
raise cv.Invalid(u"Found duplicate glyph {}".format(x))
raise cv.Invalid(f"Found duplicate glyph {x}")
sort_by_cmp(value, comparator)
value.sort(key=functools.cmp_to_key(comparator))
return value
@@ -55,15 +55,15 @@ def validate_pillow_installed(value):
def validate_truetype_file(value):
if value.endswith('.zip'): # for Google Fonts downloads
raise cv.Invalid(u"Please unzip the font archive '{}' first and then use the .ttf files "
u"inside.".format(value))
raise cv.Invalid("Please unzip the font archive '{}' first and then use the .ttf files "
"inside.".format(value))
if not value.endswith('.ttf'):
raise cv.Invalid(u"Only truetype (.ttf) files are supported. Please make sure you're "
u"using the correct format or rename the extension to .ttf")
raise cv.Invalid("Only truetype (.ttf) files are supported. Please make sure you're "
"using the correct format or rename the extension to .ttf")
return cv.file_(value)
DEFAULT_GLYPHS = u' !"%()+,-.:0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
DEFAULT_GLYPHS = ' !"%()+,-.:0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
CONF_RAW_DATA_ID = 'raw_data_id'
FONT_SCHEMA = cv.Schema({
@@ -84,7 +84,7 @@ def to_code(config):
try:
font = ImageFont.truetype(path, config[CONF_SIZE])
except Exception as e:
raise core.EsphomeError(u"Could not load truetype file {}: {}".format(path, e))
raise core.EsphomeError(f"Could not load truetype file {path}: {e}")
ascent, descent = font.getmetrics()
+1 -2
View File
@@ -4,7 +4,6 @@ from esphome import config_validation as cv, automation
from esphome import codegen as cg
from esphome.const import CONF_ID, CONF_INITIAL_VALUE, CONF_RESTORE_VALUE, CONF_TYPE, CONF_VALUE
from esphome.core import coroutine_with_priority
from esphome.py_compat import IS_PY3
globals_ns = cg.esphome_ns.namespace('globals')
GlobalsComponent = globals_ns.class_('GlobalsComponent', cg.Component)
@@ -36,7 +35,7 @@ def to_code(config):
if config[CONF_RESTORE_VALUE]:
value = config[CONF_ID].id
if IS_PY3 and isinstance(value, str):
if isinstance(value, str):
value = value.encode()
hash_ = int(hashlib.md5(value).hexdigest()[:8], 16)
cg.add(glob.set_restore_value(hash_))
+2 -4
View File
@@ -1,11 +1,9 @@
# coding=utf-8
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (CONF_ADDRESS, CONF_ID, CONF_OVERSAMPLING, CONF_RANGE, ICON_MAGNET,
UNIT_MICROTESLA, UNIT_DEGREES, ICON_SCREEN_ROTATION,
CONF_UPDATE_INTERVAL)
from esphome.py_compat import text_type
DEPENDENCIES = ['i2c']
@@ -54,7 +52,7 @@ def validate_enum(enum_values, units=None, int=True):
_units = []
if units is not None:
_units = units if isinstance(units, list) else [units]
_units = [text_type(x) for x in _units]
_units = [str(x) for x in _units]
enum_bound = cv.enum(enum_values, int=int)
def validate_enum_bound(value):
@@ -74,7 +72,7 @@ CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(HMC5883LComponent),
cv.Optional(CONF_ADDRESS): cv.i2c_address,
cv.Optional(CONF_OVERSAMPLING, default='1x'): validate_enum(HMC5883LOversamplings, units="x"),
cv.Optional(CONF_RANGE, default=u'130µT'): validate_enum(HMC5883L_RANGES, units=["uT", u"µT"]),
cv.Optional(CONF_RANGE, default='130µT'): validate_enum(HMC5883L_RANGES, units=["uT", "µT"]),
cv.Optional(CONF_FIELD_STRENGTH_X): field_strength_schema,
cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema,
cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema,
+2 -6
View File
@@ -1,3 +1,5 @@
import urllib.parse as urlparse
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
@@ -5,12 +7,6 @@ from esphome.const import CONF_ID, CONF_TIMEOUT, CONF_ESPHOME, CONF_METHOD, \
CONF_ARDUINO_VERSION, ARDUINO_VERSION_ESP8266_2_5_0
from esphome.core import CORE, Lambda
from esphome.core_config import PLATFORMIO_ESP8266_LUT
from esphome.py_compat import IS_PY3
if IS_PY3:
import urllib.parse as urlparse # pylint: disable=no-name-in-module,import-error
else:
import urlparse # pylint: disable=import-error
DEPENDENCIES = ['network']
AUTO_LOAD = ['json']
+1 -2
View File
@@ -1,4 +1,3 @@
# coding=utf-8
import logging
from esphome import core
@@ -33,7 +32,7 @@ def to_code(config):
try:
image = Image.open(path)
except Exception as e:
raise core.EsphomeError(u"Could not load image file {}: {}".format(path, e))
raise core.EsphomeError(f"Could not load image file {path}: {e}")
if CONF_RESIZE in config:
image.thumbnail(config[CONF_RESIZE])
-1
View File
@@ -1,4 +1,3 @@
# coding=utf-8
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
-1
View File
@@ -1,4 +1,3 @@
# coding=utf-8
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
-1
View File
@@ -1,4 +1,3 @@
# coding=utf-8
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
+2 -2
View File
@@ -253,8 +253,8 @@ def validate_effects(allowed_effects):
name = x[key][CONF_NAME]
if name in names:
errors.append(
cv.Invalid(u"Found the effect name '{}' twice. All effects must have "
u"unique names".format(name), [i])
cv.Invalid("Found the effect name '{}' twice. All effects must have "
"unique names".format(name), [i])
)
continue
names.add(name)
+9 -10
View File
@@ -7,7 +7,6 @@ from esphome.automation import LambdaAction
from esphome.const import CONF_ARGS, CONF_BAUD_RATE, CONF_FORMAT, CONF_HARDWARE_UART, CONF_ID, \
CONF_LEVEL, CONF_LOGS, CONF_ON_MESSAGE, CONF_TAG, CONF_TRIGGER_ID, CONF_TX_BUFFER_SIZE
from esphome.core import CORE, EsphomeError, Lambda, coroutine_with_priority
from esphome.py_compat import text_type
logger_ns = cg.esphome_ns.namespace('logger')
LOG_LEVELS = {
@@ -64,8 +63,8 @@ def validate_local_no_higher_than_global(value):
global_level = value.get(CONF_LEVEL, 'DEBUG')
for tag, level in value.get(CONF_LOGS, {}).items():
if LOG_LEVEL_SEVERITY.index(level) > LOG_LEVEL_SEVERITY.index(global_level):
raise EsphomeError(u"The local log level {} for {} must be less severe than the "
u"global log level {}.".format(level, tag, global_level))
raise EsphomeError("The local log level {} for {} must be less severe than the "
"global log level {}.".format(level, tag, global_level))
return value
@@ -119,7 +118,7 @@ def to_code(config):
if CORE.is_esp8266 and has_serial_logging and is_at_least_verbose:
debug_serial_port = HARDWARE_UART_TO_SERIAL[config.get(CONF_HARDWARE_UART)]
cg.add_build_flag("-DDEBUG_ESP_PORT={}".format(debug_serial_port))
cg.add_build_flag(f"-DDEBUG_ESP_PORT={debug_serial_port}")
cg.add_build_flag("-DLWIP_DEBUG")
DEBUG_COMPONENTS = {
'HTTP_CLIENT',
@@ -134,7 +133,7 @@ def to_code(config):
# 'MDNS_RESPONDER',
}
for comp in DEBUG_COMPONENTS:
cg.add_build_flag("-DDEBUG_ESP_{}".format(comp))
cg.add_build_flag(f"-DDEBUG_ESP_{comp}")
if CORE.is_esp32 and is_at_least_verbose:
cg.add_build_flag('-DCORE_DEBUG_LEVEL=5')
if CORE.is_esp32 and is_at_least_very_verbose:
@@ -165,7 +164,7 @@ def maybe_simple_message(schema):
def validate_printf(value):
# https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python
# pylint: disable=anomalous-backslash-in-string
cfmt = u"""\
cfmt = """\
( # start of capture group 1
% # literal "%"
(?: # first option
@@ -179,8 +178,8 @@ def validate_printf(value):
""" # noqa
matches = re.findall(cfmt, value[CONF_FORMAT], flags=re.X)
if len(matches) != len(value[CONF_ARGS]):
raise cv.Invalid(u"Found {} printf-patterns ({}), but {} args were given!"
u"".format(len(matches), u', '.join(matches), len(value[CONF_ARGS])))
raise cv.Invalid("Found {} printf-patterns ({}), but {} args were given!"
"".format(len(matches), ', '.join(matches), len(value[CONF_ARGS])))
return value
@@ -196,9 +195,9 @@ LOGGER_LOG_ACTION_SCHEMA = cv.All(maybe_simple_message({
@automation.register_action(CONF_LOGGER_LOG, LambdaAction, LOGGER_LOG_ACTION_SCHEMA)
def logger_log_action_to_code(config, action_id, template_arg, args):
esp_log = LOG_LEVEL_TO_ESP_LOG[config[CONF_LEVEL]]
args_ = [cg.RawExpression(text_type(x)) for x in config[CONF_ARGS]]
args_ = [cg.RawExpression(str(x)) for x in config[CONF_ARGS]]
text = text_type(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_)))
text = str(cg.statement(esp_log(config[CONF_TAG], config[CONF_FORMAT], *args_)))
lambda_ = yield cg.process_lambda(Lambda(text), args, return_type=cg.void)
yield cg.new_Pvariable(action_id, template_arg, lambda_)
+4 -4
View File
@@ -39,14 +39,14 @@ def to_code(config):
yield i2c.register_i2c_device(var, config)
for d in ['x', 'y', 'z']:
accel_key = 'accel_{}'.format(d)
accel_key = f'accel_{d}'
if accel_key in config:
sens = yield sensor.new_sensor(config[accel_key])
cg.add(getattr(var, 'set_accel_{}_sensor'.format(d))(sens))
accel_key = 'gyro_{}'.format(d)
cg.add(getattr(var, f'set_accel_{d}_sensor')(sens))
accel_key = f'gyro_{d}'
if accel_key in config:
sens = yield sensor.new_sensor(config[accel_key])
cg.add(getattr(var, 'set_gyro_{}_sensor'.format(d))(sens))
cg.add(getattr(var, f'set_gyro_{d}_sensor')(sens))
if CONF_TEMPERATURE in config:
sens = yield sensor.new_sensor(config[CONF_TEMPERATURE])
+5 -5
View File
@@ -64,28 +64,28 @@ def validate_config(value):
topic_prefix = value[CONF_TOPIC_PREFIX]
if CONF_BIRTH_MESSAGE not in value:
out[CONF_BIRTH_MESSAGE] = {
CONF_TOPIC: '{}/status'.format(topic_prefix),
CONF_TOPIC: f'{topic_prefix}/status',
CONF_PAYLOAD: 'online',
CONF_QOS: 0,
CONF_RETAIN: True,
}
if CONF_WILL_MESSAGE not in value:
out[CONF_WILL_MESSAGE] = {
CONF_TOPIC: '{}/status'.format(topic_prefix),
CONF_TOPIC: f'{topic_prefix}/status',
CONF_PAYLOAD: 'offline',
CONF_QOS: 0,
CONF_RETAIN: True,
}
if CONF_SHUTDOWN_MESSAGE not in value:
out[CONF_SHUTDOWN_MESSAGE] = {
CONF_TOPIC: '{}/status'.format(topic_prefix),
CONF_TOPIC: f'{topic_prefix}/status',
CONF_PAYLOAD: 'offline',
CONF_QOS: 0,
CONF_RETAIN: True,
}
if CONF_LOG_TOPIC not in value:
out[CONF_LOG_TOPIC] = {
CONF_TOPIC: '{}/debug'.format(topic_prefix),
CONF_TOPIC: f'{topic_prefix}/debug',
CONF_QOS: 0,
CONF_RETAIN: True,
}
@@ -95,7 +95,7 @@ def validate_config(value):
def validate_fingerprint(value):
value = cv.string(value)
if re.match(r'^[0-9a-f]{40}$', value) is None:
raise cv.Invalid(u"fingerprint must be valid SHA1 hash")
raise cv.Invalid("fingerprint must be valid SHA1 hash")
return value
+1 -1
View File
@@ -82,7 +82,7 @@ def validate_method_pin(value):
for opt in (CONF_PIN, CONF_CLOCK_PIN, CONF_DATA_PIN):
if opt in value and value[opt] not in pins_:
raise cv.Invalid("Method {} only supports pin(s) {}".format(
method, ', '.join('GPIO{}'.format(x) for x in pins_)
method, ', '.join(f'GPIO{x}' for x in pins_)
), path=[CONF_METHOD])
return value
+1 -2
View File
@@ -1,4 +1,3 @@
# coding=utf-8
from math import log
import esphome.config_validation as cv
@@ -28,7 +27,7 @@ def validate_calibration_parameter(value):
value = cv.string(value)
parts = value.split('->')
if len(parts) != 2:
raise cv.Invalid(u"Calibration parameter must be of form 3000 -> 23°C")
raise cv.Invalid("Calibration parameter must be of form 3000 -> 23°C")
voltage = cv.resistance(parts[0].strip())
temperature = cv.temperature(parts[1].strip())
return validate_calibration_parameter({
+2 -2
View File
@@ -10,8 +10,8 @@ PartitionLightOutput = partitions_ns.class_('PartitionLightOutput', light.Addres
def validate_from_to(value):
if value[CONF_FROM] > value[CONF_TO]:
raise cv.Invalid(u"From ({}) must not be larger than to ({})"
u"".format(value[CONF_FROM], value[CONF_TO]))
raise cv.Invalid("From ({}) must not be larger than to ({})"
"".format(value[CONF_FROM], value[CONF_TO]))
return value
+1 -1
View File
@@ -36,7 +36,7 @@ SENSORS_TO_TYPE = {
def validate_pmsx003_sensors(value):
for key, types in SENSORS_TO_TYPE.items():
if key in value and value[CONF_TYPE] not in types:
raise cv.Invalid(u"{} does not have {} sensor!".format(value[CONF_TYPE], key))
raise cv.Invalid("{} does not have {} sensor!".format(value[CONF_TYPE], key))
return value
+2 -4
View File
@@ -1,11 +1,9 @@
# coding=utf-8
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
from esphome.const import (CONF_ADDRESS, CONF_ID, CONF_OVERSAMPLING, CONF_RANGE, ICON_MAGNET,
UNIT_MICROTESLA, UNIT_DEGREES, ICON_SCREEN_ROTATION,
CONF_UPDATE_INTERVAL)
from esphome.py_compat import text_type
DEPENDENCIES = ['i2c']
@@ -46,7 +44,7 @@ def validate_enum(enum_values, units=None, int=True):
_units = []
if units is not None:
_units = units if isinstance(units, list) else [units]
_units = [text_type(x) for x in _units]
_units = [str(x) for x in _units]
enum_bound = cv.enum(enum_values, int=int)
def validate_enum_bound(value):
@@ -65,7 +63,7 @@ heading_schema = sensor.sensor_schema(UNIT_DEGREES, ICON_SCREEN_ROTATION, 1)
CONFIG_SCHEMA = cv.Schema({
cv.GenerateID(): cv.declare_id(QMC5883LComponent),
cv.Optional(CONF_ADDRESS): cv.i2c_address,
cv.Optional(CONF_RANGE, default=u'200µT'): validate_enum(QMC5883L_RANGES, units=["uT", u"µT"]),
cv.Optional(CONF_RANGE, default='200µT'): validate_enum(QMC5883L_RANGES, units=["uT", "µT"]),
cv.Optional(CONF_OVERSAMPLING, default="512x"): validate_enum(QMC5883LOversamplings, units="x"),
cv.Optional(CONF_FIELD_STRENGTH_X): field_strength_schema,
cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema,
+12 -13
View File
@@ -7,7 +7,6 @@ from esphome.const import CONF_DATA, CONF_TRIGGER_ID, CONF_NBITS, CONF_ADDRESS,
CONF_PROTOCOL, CONF_GROUP, CONF_DEVICE, CONF_STATE, CONF_CHANNEL, CONF_FAMILY, CONF_REPEAT, \
CONF_WAIT_TIME, CONF_TIMES, CONF_TYPE_ID, CONF_CARRIER_FREQUENCY
from esphome.core import coroutine
from esphome.py_compat import string_types, text_type
from esphome.util import Registry, SimpleRegistry
AUTO_LOAD = ['binary_sensor']
@@ -52,7 +51,7 @@ def register_trigger(name, type, data_type):
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(type),
cv.GenerateID(CONF_RECEIVER_ID): cv.use_id(RemoteReceiverBase),
})
registerer = TRIGGER_REGISTRY.register('on_{}'.format(name), validator)
registerer = TRIGGER_REGISTRY.register(f'on_{name}', validator)
def decorator(func):
@coroutine
@@ -98,7 +97,7 @@ def register_action(name, type_, schema):
cv.GenerateID(CONF_TRANSMITTER_ID): cv.use_id(RemoteTransmitterBase),
cv.Optional(CONF_REPEAT): validate_repeat,
})
registerer = automation.register_action('remote_transmitter.transmit_{}'.format(name),
registerer = automation.register_action(f'remote_transmitter.transmit_{name}',
type_, validator)
def decorator(func):
@@ -122,11 +121,11 @@ def register_action(name, type_, schema):
def declare_protocol(name):
data = ns.struct('{}Data'.format(name))
binary_sensor_ = ns.class_('{}BinarySensor'.format(name), RemoteReceiverBinarySensorBase)
trigger = ns.class_('{}Trigger'.format(name), RemoteReceiverTrigger)
action = ns.class_('{}Action'.format(name), RemoteTransmitterActionBase)
dumper = ns.class_('{}Dumper'.format(name), RemoteTransmitterDumper)
data = ns.struct(f'{name}Data')
binary_sensor_ = ns.class_(f'{name}BinarySensor', RemoteReceiverBinarySensorBase)
trigger = ns.class_(f'{name}Trigger', RemoteReceiverTrigger)
action = ns.class_(f'{name}Action', RemoteTransmitterActionBase)
dumper = ns.class_(f'{name}Dumper', RemoteTransmitterDumper)
return data, binary_sensor_, trigger, action, dumper
@@ -141,7 +140,7 @@ DUMPER_REGISTRY = Registry({
def validate_dumpers(value):
if isinstance(value, string_types) and value.lower() == 'all':
if isinstance(value, str) and value.lower() == 'all':
return validate_dumpers(list(DUMPER_REGISTRY.keys()))
return cv.validate_registry('dumper', DUMPER_REGISTRY)(value)
@@ -432,12 +431,12 @@ RC_SWITCH_PROTOCOL_SCHEMA = cv.Any(
def validate_rc_switch_code(value):
if not isinstance(value, (str, text_type)):
if not isinstance(value, (str, str)):
raise cv.Invalid("All RCSwitch codes must be in quotes ('')")
for c in value:
if c not in ('0', '1'):
raise cv.Invalid(u"Invalid RCSwitch code character '{}'. Only '0' and '1' are allowed"
u"".format(c))
raise cv.Invalid("Invalid RCSwitch code character '{}'. Only '0' and '1' are allowed"
"".format(c))
if len(value) > 64:
raise cv.Invalid("Maximum length for RCSwitch codes is 64, code '{}' has length {}"
"".format(value, len(value)))
@@ -447,7 +446,7 @@ def validate_rc_switch_code(value):
def validate_rc_switch_raw_code(value):
if not isinstance(value, (str, text_type)):
if not isinstance(value, (str, str)):
raise cv.Invalid("All RCSwitch raw codes must be in quotes ('')")
for c in value:
if c not in ('0', '1', 'x'):
@@ -19,12 +19,12 @@ def show_new(value):
if 'name' in value:
args.append(('name', value['name']))
args.append(('turn_on_action', {
'remote_transmitter.transmit_{}'.format(key): val
f'remote_transmitter.transmit_{key}': val
}))
text = yaml_util.dump([OrderedDict(args)])
raise cv.Invalid(u"This platform has been removed in 1.13, please change to:\n\n{}\n\n."
u"".format(text))
raise cv.Invalid("This platform has been removed in 1.13, please change to:\n\n{}\n\n."
"".format(text))
CONFIG_SCHEMA = show_new
+2 -2
View File
@@ -28,7 +28,7 @@ def validate_acceleration(value):
try:
value = float(value)
except ValueError:
raise cv.Invalid("Expected acceleration as floating point number, got {}".format(value))
raise cv.Invalid(f"Expected acceleration as floating point number, got {value}")
if value <= 0:
raise cv.Invalid("Acceleration must be larger than 0 steps/s^2!")
@@ -48,7 +48,7 @@ def validate_speed(value):
try:
value = float(value)
except ValueError:
raise cv.Invalid("Expected speed as floating point number, got {}".format(value))
raise cv.Invalid(f"Expected speed as floating point number, got {value}")
if value <= 0:
raise cv.Invalid("Speed must be larger than 0 steps/s!")
+9 -10
View File
@@ -3,7 +3,6 @@ import re
import esphome.config_validation as cv
from esphome import core
from esphome.py_compat import string_types
_LOGGER = logging.getLogger(__name__)
@@ -24,8 +23,8 @@ def validate_substitution_key(value):
for char in value:
if char not in VALID_SUBSTITUTIONS_CHARACTERS:
raise cv.Invalid(
u"Substitution must only consist of upper/lowercase characters, the underscore "
u"and numbers. The character '{}' cannot be used".format(char))
"Substitution must only consist of upper/lowercase characters, the underscore "
"and numbers. The character '{}' cannot be used".format(char))
return value
@@ -42,7 +41,7 @@ VARIABLE_PROG = re.compile('\\$([{0}]+|\\{{[{0}]*\\}})'.format(VALID_SUBSTITUTIO
def _expand_substitutions(substitutions, value, path):
if u'$' not in value:
if '$' not in value:
return value
orig_value = value
@@ -56,11 +55,11 @@ def _expand_substitutions(substitutions, value, path):
i, j = m.span(0)
name = m.group(1)
if name.startswith(u'{') and name.endswith(u'}'):
if name.startswith('{') and name.endswith('}'):
name = name[1:-1]
if name not in substitutions:
_LOGGER.warning(u"Found '%s' (see %s) which looks like a substitution, but '%s' was "
u"not declared", orig_value, u'->'.join(str(x) for x in path), name)
_LOGGER.warning("Found '%s' (see %s) which looks like a substitution, but '%s' was "
"not declared", orig_value, '->'.join(str(x) for x in path), name)
i = j
continue
@@ -91,7 +90,7 @@ def _substitute_item(substitutions, item, path):
for old, new in replace_keys:
item[new] = item[old]
del item[old]
elif isinstance(item, string_types):
elif isinstance(item, str):
sub = _expand_substitutions(substitutions, item, path)
if sub != item:
return sub
@@ -109,8 +108,8 @@ def do_substitution_pass(config):
substitutions = config[CONF_SUBSTITUTIONS]
with cv.prepend_path('substitutions'):
if not isinstance(substitutions, dict):
raise cv.Invalid(u"Substitutions must be a key to value mapping, got {}"
u"".format(type(substitutions)))
raise cv.Invalid("Substitutions must be a key to value mapping, got {}"
"".format(type(substitutions)))
replace_keys = []
for key, value in substitutions.items():
+1 -2
View File
@@ -3,7 +3,6 @@ import esphome.config_validation as cv
from esphome import automation
from esphome.components import time
from esphome.const import CONF_TIME_ID, CONF_ID, CONF_TRIGGER_ID
from esphome.py_compat import string_types
sun_ns = cg.esphome_ns.namespace('sun')
@@ -32,7 +31,7 @@ ELEVATION_MAP = {
def elevation(value):
if isinstance(value, string_types):
if isinstance(value, str):
try:
value = ELEVATION_MAP[cv.one_of(*ELEVATION_MAP, lower=True, space='_')(value)]
except cv.Invalid:
-1
View File
@@ -1,4 +1,3 @@
# coding=utf-8
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor
+20 -22
View File
@@ -14,7 +14,6 @@ from esphome.const import CONF_CRON, CONF_DAYS_OF_MONTH, CONF_DAYS_OF_WEEK, CONF
CONF_MINUTES, CONF_MONTHS, CONF_ON_TIME, CONF_SECONDS, CONF_TIMEZONE, CONF_TRIGGER_ID, \
CONF_AT, CONF_SECOND, CONF_HOUR, CONF_MINUTE
from esphome.core import coroutine, coroutine_with_priority
from esphome.py_compat import string_types
_LOGGER = logging.getLogger(__name__)
@@ -33,10 +32,10 @@ def _tz_timedelta(td):
if offset_hour == 0 and offset_minute == 0 and offset_second == 0:
return '0'
if offset_minute == 0 and offset_second == 0:
return '{}'.format(offset_hour)
return f'{offset_hour}'
if offset_second == 0:
return '{}:{}'.format(offset_hour, offset_minute)
return '{}:{}:{}'.format(offset_hour, offset_minute, offset_second)
return f'{offset_hour}:{offset_minute}'
return f'{offset_hour}:{offset_minute}:{offset_second}'
# https://stackoverflow.com/a/16804556/8924614
@@ -133,7 +132,7 @@ def detect_tz():
def _parse_cron_int(value, special_mapping, message):
special_mapping = special_mapping or {}
if isinstance(value, string_types) and value in special_mapping:
if isinstance(value, str) and value in special_mapping:
return special_mapping[value]
try:
return int(value)
@@ -143,41 +142,40 @@ def _parse_cron_int(value, special_mapping, message):
def _parse_cron_part(part, min_value, max_value, special_mapping):
if part in ('*', '?'):
return set(x for x in range(min_value, max_value + 1))
return set(range(min_value, max_value + 1))
if '/' in part:
data = part.split('/')
if len(data) > 2:
raise cv.Invalid(u"Can't have more than two '/' in one time expression, got {}"
raise cv.Invalid("Can't have more than two '/' in one time expression, got {}"
.format(part))
offset, repeat = data
offset_n = 0
if offset:
offset_n = _parse_cron_int(offset, special_mapping,
u"Offset for '/' time expression must be an integer, got {}")
"Offset for '/' time expression must be an integer, got {}")
try:
repeat_n = int(repeat)
except ValueError:
raise cv.Invalid(u"Repeat for '/' time expression must be an integer, got {}"
raise cv.Invalid("Repeat for '/' time expression must be an integer, got {}"
.format(repeat))
return set(x for x in range(offset_n, max_value + 1, repeat_n))
return set(range(offset_n, max_value + 1, repeat_n))
if '-' in part:
data = part.split('-')
if len(data) > 2:
raise cv.Invalid(u"Can't have more than two '-' in range time expression '{}'"
raise cv.Invalid("Can't have more than two '-' in range time expression '{}'"
.format(part))
begin, end = data
begin_n = _parse_cron_int(begin, special_mapping, u"Number for time range must be integer, "
u"got {}")
end_n = _parse_cron_int(end, special_mapping, u"Number for time range must be integer, "
u"got {}")
begin_n = _parse_cron_int(begin, special_mapping, "Number for time range must be integer, "
"got {}")
end_n = _parse_cron_int(end, special_mapping, "Number for time range must be integer, "
"got {}")
if end_n < begin_n:
return set(x for x in range(end_n, max_value + 1)) | \
set(x for x in range(min_value, begin_n + 1))
return set(x for x in range(begin_n, end_n + 1))
return set(range(end_n, max_value + 1)) | set(range(min_value, begin_n + 1))
return set(range(begin_n, end_n + 1))
return {_parse_cron_int(part, special_mapping, u"Number for time expression must be an "
u"integer, got {}")}
return {_parse_cron_int(part, special_mapping, "Number for time expression must be an "
"integer, got {}")}
def cron_expression_validator(name, min_value, max_value, special_mapping=None):
@@ -249,7 +247,7 @@ def validate_cron_keys(value):
if CONF_CRON in value:
for key in value.keys():
if key in CRON_KEYS:
raise cv.Invalid("Cannot use option {} when cron: is specified.".format(key))
raise cv.Invalid(f"Cannot use option {key} when cron: is specified.")
if CONF_AT in value:
raise cv.Invalid("Cannot use option at with cron!")
cron_ = value[CONF_CRON]
@@ -259,7 +257,7 @@ def validate_cron_keys(value):
if CONF_AT in value:
for key in value.keys():
if key in CRON_KEYS:
raise cv.Invalid("Cannot use option {} when at: is specified.".format(key))
raise cv.Invalid(f"Cannot use option {key} when at: is specified.")
at_ = value[CONF_AT]
value = {x: value[x] for x in value if x != CONF_AT}
value.update(at_)
+3 -4
View File
@@ -3,7 +3,6 @@ import esphome.config_validation as cv
from esphome import pins, automation
from esphome.const import CONF_BAUD_RATE, CONF_ID, CONF_RX_PIN, CONF_TX_PIN, CONF_UART_ID, CONF_DATA
from esphome.core import CORE, coroutine
from esphome.py_compat import text_type, binary_type, char_to_byte
uart_ns = cg.esphome_ns.namespace('uart')
UARTComponent = uart_ns.class_('UARTComponent', cg.Component)
@@ -13,7 +12,7 @@ MULTI_CONF = True
def validate_raw_data(value):
if isinstance(value, text_type):
if isinstance(value, str):
return value.encode('utf-8')
if isinstance(value, str):
return value
@@ -77,8 +76,8 @@ def uart_write_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
yield cg.register_parented(var, config[CONF_ID])
data = config[CONF_DATA]
if isinstance(data, binary_type):
data = [char_to_byte(x) for x in data]
if isinstance(data, bytes):
data = list(data)
if cg.is_template(data):
templ = yield cg.templatable(data, args, cg.std_vector.template(cg.uint8))
+2 -3
View File
@@ -3,7 +3,6 @@ import esphome.config_validation as cv
from esphome.components import switch, uart
from esphome.const import CONF_DATA, CONF_ID, CONF_INVERTED
from esphome.core import HexInt
from esphome.py_compat import binary_type, char_to_byte
from .. import uart_ns, validate_raw_data
DEPENDENCIES = ['uart']
@@ -25,6 +24,6 @@ def to_code(config):
yield uart.register_uart_device(var, config)
data = config[CONF_DATA]
if isinstance(data, binary_type):
data = [HexInt(char_to_byte(x)) for x in data]
if isinstance(data, bytes):
data = [HexInt(x) for x in data]
cg.add(var.set_data(data))
+2 -2
View File
@@ -30,9 +30,9 @@ def validate_password(value):
if not value:
return value
if len(value) < 8:
raise cv.Invalid(u"WPA password must be at least 8 characters long")
raise cv.Invalid("WPA password must be at least 8 characters long")
if len(value) > 64:
raise cv.Invalid(u"WPA password must be at most 64 characters long")
raise cv.Invalid("WPA password must be at most 64 characters long")
return value
+80 -92
View File
@@ -1,5 +1,3 @@
from __future__ import print_function
import collections
import importlib
import logging
@@ -18,7 +16,6 @@ from esphome.components.substitutions import CONF_SUBSTITUTIONS
from esphome.const import CONF_ESPHOME, CONF_PLATFORM, ESP_PLATFORMS
from esphome.core import CORE, EsphomeError # noqa
from esphome.helpers import color, indent
from esphome.py_compat import text_type, IS_PY2, decode_text, string_types
from esphome.util import safe_print, OrderedDict
from typing import List, Optional, Tuple, Union # noqa
@@ -31,7 +28,7 @@ _LOGGER = logging.getLogger(__name__)
_COMPONENT_CACHE = {}
class ComponentManifest(object):
class ComponentManifest:
def __init__(self, module, base_components_path, is_core=False, is_platform=False):
self.module = module
self._is_core = is_core
@@ -89,7 +86,7 @@ class ComponentManifest(object):
source_files = core.find_source_files(os.path.join(core_p, 'dummy'))
ret = {}
for f in source_files:
ret['esphome/core/{}'.format(f)] = os.path.join(core_p, f)
ret[f'esphome/core/{f}'] = os.path.join(core_p, f)
return ret
source_files = core.find_source_files(self.module.__file__)
@@ -101,7 +98,7 @@ class ComponentManifest(object):
rel = os.path.relpath(full_file, self.base_components_path)
# Always use / for C++ include names
rel = rel.replace(os.sep, '/')
target_file = 'esphome/components/{}'.format(rel)
target_file = f'esphome/components/{rel}'
ret[target_file] = full_file
return ret
@@ -119,12 +116,6 @@ def _mount_config_dir():
if not os.path.isdir(custom_path):
CUSTOM_COMPONENTS_PATH = None
return
init_path = os.path.join(custom_path, '__init__.py')
if IS_PY2 and not os.path.isfile(init_path):
_LOGGER.warning("Found 'custom_components' folder, but file __init__.py was not found. "
"ESPHome will automatically create it now....")
with open(init_path, 'w') as f:
f.write('\n')
if CORE.config_dir not in sys.path:
sys.path.insert(0, CORE.config_dir)
CUSTOM_COMPONENTS_PATH = custom_path
@@ -137,7 +128,7 @@ def _lookup_module(domain, is_platform):
_mount_config_dir()
# First look for custom_components
try:
module = importlib.import_module('custom_components.{}'.format(domain))
module = importlib.import_module(f'custom_components.{domain}')
except ImportError as e:
# ImportError when no such module
if 'No module named' not in str(e):
@@ -153,7 +144,7 @@ def _lookup_module(domain, is_platform):
return manif
try:
module = importlib.import_module('esphome.components.{}'.format(domain))
module = importlib.import_module(f'esphome.components.{domain}')
except ImportError as e:
if 'No module named' not in str(e):
_LOGGER.error("Unable to import component %s:", domain, exc_info=True)
@@ -173,7 +164,7 @@ def get_component(domain):
def get_platform(domain, platform):
full = '{}.{}'.format(platform, domain)
full = f'{platform}.{domain}'
return _lookup_module(full, True)
@@ -192,7 +183,7 @@ def iter_components(config):
yield domain, component, conf
if component.is_platform_component:
for p_config in conf:
p_name = u"{}.{}".format(domain, p_config[CONF_PLATFORM])
p_name = "{}.{}".format(domain, p_config[CONF_PLATFORM])
platform = get_platform(domain, p_config[CONF_PLATFORM])
yield p_name, platform, p_config
@@ -208,13 +199,13 @@ def _path_begins_with(path, other): # type: (ConfigPath, ConfigPath) -> bool
class Config(OrderedDict):
def __init__(self):
super(Config, self).__init__()
super().__init__()
# A list of voluptuous errors
self.errors = [] # type: List[vol.Invalid]
# A list of paths that should be fully outputted
# The values will be the paths to all "domain", for example (['logger'], 'logger')
# or (['sensor', 'ultrasonic'], 'sensor.ultrasonic')
self.output_paths = [] # type: List[Tuple[ConfigPath, unicode]]
self.output_paths = [] # type: List[Tuple[ConfigPath, str]]
def add_error(self, error):
# type: (vol.Invalid) -> None
@@ -234,15 +225,15 @@ class Config(OrderedDict):
self.add_error(e)
def add_str_error(self, message, path):
# type: (basestring, ConfigPath) -> None
# type: (str, ConfigPath) -> None
self.add_error(vol.Invalid(message, path))
def add_output_path(self, path, domain):
# type: (ConfigPath, unicode) -> None
# type: (ConfigPath, str) -> None
self.output_paths.append((path, domain))
def remove_output_path(self, path, domain):
# type: (ConfigPath, unicode) -> None
# type: (ConfigPath, str) -> None
self.output_paths.remove((path, domain))
def is_in_error_path(self, path):
@@ -312,12 +303,10 @@ def iter_ids(config, path=None):
yield id, path
elif isinstance(config, list):
for i, item in enumerate(config):
for result in iter_ids(item, path + [i]):
yield result
yield from iter_ids(item, path + [i])
elif isinstance(config, dict):
for key, value in config.items():
for result in iter_ids(value, path + [key]):
yield result
yield from iter_ids(value, path + [key])
def do_id_pass(result): # type: (Config) -> None
@@ -332,8 +321,8 @@ def do_id_pass(result): # type: (Config) -> None
# Look for duplicate definitions
match = next((v for v in declare_ids if v[0].id == id.id), None)
if match is not None:
opath = u'->'.join(text_type(v) for v in match[1])
result.add_str_error(u"ID {} redefined! Check {}".format(id.id, opath), path)
opath = '->'.join(str(v) for v in match[1])
result.add_str_error(f"ID {id.id} redefined! Check {opath}", path)
continue
declare_ids.append((id, path))
else:
@@ -357,8 +346,8 @@ def do_id_pass(result): # type: (Config) -> None
# Find candidates
matches = difflib.get_close_matches(id.id, [v[0].id for v in declare_ids])
if matches:
matches_s = ', '.join('"{}"'.format(x) for x in matches)
error += " These IDs look similar: {}.".format(matches_s)
matches_s = ', '.join(f'"{x}"' for x in matches)
error += f" These IDs look similar: {matches_s}."
result.add_str_error(error, path)
continue
if not isinstance(match.type, MockObjClass) or not isinstance(id.type, MockObjClass):
@@ -377,7 +366,7 @@ def do_id_pass(result): # type: (Config) -> None
id.id = v[0].id
break
else:
result.add_str_error("Couldn't resolve ID for type '{}'".format(id.type), path)
result.add_str_error(f"Couldn't resolve ID for type '{id.type}'", path)
def recursive_check_replaceme(value):
@@ -389,7 +378,7 @@ def recursive_check_replaceme(value):
return cv.Schema({cv.valid: recursive_check_replaceme})(value)
if isinstance(value, ESPForceValue):
pass
if isinstance(value, string_types) and value == 'REPLACEME':
if isinstance(value, str) and value == 'REPLACEME':
raise cv.Invalid("Found 'REPLACEME' in configuration, this is most likely an error. "
"Please make sure you have replaced all fields from the sample "
"configuration.\n"
@@ -455,8 +444,8 @@ def validate_config(config):
while load_queue:
domain, conf = load_queue.popleft()
domain = text_type(domain)
if domain.startswith(u'.'):
domain = str(domain)
if domain.startswith('.'):
# Ignore top-level keys starting with a dot
continue
result.add_output_path([domain], domain)
@@ -464,7 +453,7 @@ def validate_config(config):
component = get_component(domain)
path = [domain]
if component is None:
result.add_str_error(u"Component not found: {}".format(domain), path)
result.add_str_error(f"Component not found: {domain}", path)
continue
CORE.loaded_integrations.add(domain)
@@ -492,24 +481,24 @@ def validate_config(config):
for i, p_config in enumerate(conf):
path = [domain, i]
# Construct temporary unknown output path
p_domain = u'{}.unknown'.format(domain)
p_domain = f'{domain}.unknown'
result.add_output_path(path, p_domain)
result[domain][i] = p_config
if not isinstance(p_config, dict):
result.add_str_error(u"Platform schemas must be key-value pairs.", path)
result.add_str_error("Platform schemas must be key-value pairs.", path)
continue
p_name = p_config.get('platform')
if p_name is None:
result.add_str_error(u"No platform specified! See 'platform' key.", path)
result.add_str_error("No platform specified! See 'platform' key.", path)
continue
# Remove temp output path and construct new one
result.remove_output_path(path, p_domain)
p_domain = u'{}.{}'.format(domain, p_name)
p_domain = f'{domain}.{p_name}'
result.add_output_path(path, p_domain)
# Try Load platform
platform = get_platform(domain, p_name)
if platform is None:
result.add_str_error(u"Platform not found: '{}'".format(p_domain), path)
result.add_str_error(f"Platform not found: '{p_domain}'", path)
continue
CORE.loaded_integrations.add(p_name)
@@ -537,8 +526,8 @@ def validate_config(config):
success = True
for dependency in comp.dependencies:
if dependency not in config:
result.add_str_error(u"Component {} requires component {}"
u"".format(domain, dependency), path)
result.add_str_error("Component {} requires component {}"
"".format(domain, dependency), path)
success = False
if not success:
continue
@@ -546,22 +535,22 @@ def validate_config(config):
success = True
for conflict in comp.conflicts_with:
if conflict in config:
result.add_str_error(u"Component {} cannot be used together with component {}"
u"".format(domain, conflict), path)
result.add_str_error("Component {} cannot be used together with component {}"
"".format(domain, conflict), path)
success = False
if not success:
continue
if CORE.esp_platform not in comp.esp_platforms:
result.add_str_error(u"Component {} doesn't support {}.".format(domain,
CORE.esp_platform),
result.add_str_error("Component {} doesn't support {}.".format(domain,
CORE.esp_platform),
path)
continue
if not comp.is_platform_component and comp.config_schema is None and \
not isinstance(conf, core.AutoLoad):
result.add_str_error(u"Component {} cannot be loaded via YAML "
u"(no CONFIG_SCHEMA).".format(domain), path)
result.add_str_error("Component {} cannot be loaded via YAML "
"(no CONFIG_SCHEMA).".format(domain), path)
continue
if comp.is_multi_conf:
@@ -611,13 +600,13 @@ def _nested_getitem(data, path):
def humanize_error(config, validation_error):
validation_error = text_type(validation_error)
validation_error = str(validation_error)
m = re.match(r'^(.*?)\s*(?:for dictionary value )?@ data\[.*$', validation_error, re.DOTALL)
if m is not None:
validation_error = m.group(1)
validation_error = validation_error.strip()
if not validation_error.endswith(u'.'):
validation_error += u'.'
if not validation_error.endswith('.'):
validation_error += '.'
return validation_error
@@ -634,22 +623,22 @@ def _get_parent_name(path, config):
def _format_vol_invalid(ex, config):
# type: (vol.Invalid, Config) -> unicode
message = u''
# type: (vol.Invalid, Config) -> str
message = ''
paren = _get_parent_name(ex.path[:-1], config)
if isinstance(ex, ExtraKeysInvalid):
if ex.candidates:
message += u'[{}] is an invalid option for [{}]. Did you mean {}?'.format(
ex.path[-1], paren, u', '.join(u'[{}]'.format(x) for x in ex.candidates))
message += '[{}] is an invalid option for [{}]. Did you mean {}?'.format(
ex.path[-1], paren, ', '.join(f'[{x}]' for x in ex.candidates))
else:
message += u'[{}] is an invalid option for [{}]. Please check the indentation.'.format(
message += '[{}] is an invalid option for [{}]. Please check the indentation.'.format(
ex.path[-1], paren)
elif u'extra keys not allowed' in text_type(ex):
message += u'[{}] is an invalid option for [{}].'.format(ex.path[-1], paren)
elif u'required key not provided' in text_type(ex):
message += u"'{}' is a required option for [{}].".format(ex.path[-1], paren)
elif 'extra keys not allowed' in str(ex):
message += '[{}] is an invalid option for [{}].'.format(ex.path[-1], paren)
elif 'required key not provided' in str(ex):
message += "'{}' is a required option for [{}].".format(ex.path[-1], paren)
else:
message += humanize_error(config, ex)
@@ -662,9 +651,8 @@ class InvalidYAMLError(EsphomeError):
base = str(base_exc)
except UnicodeDecodeError:
base = repr(base_exc)
base = decode_text(base)
message = u"Invalid YAML syntax:\n\n{}".format(base)
super(InvalidYAMLError, self).__init__(message)
message = f"Invalid YAML syntax:\n\n{base}"
super().__init__(message)
self.base_exc = base_exc
@@ -680,7 +668,7 @@ def _load_config():
except EsphomeError:
raise
except Exception:
_LOGGER.error(u"Unexpected exception while reading configuration:")
_LOGGER.error("Unexpected exception while reading configuration:")
raise
return result
@@ -690,7 +678,7 @@ def load_config():
try:
return _load_config()
except vol.Invalid as err:
raise EsphomeError("Error while parsing config: {}".format(err))
raise EsphomeError(f"Error while parsing config: {err}")
def line_info(obj, highlight=True):
@@ -699,7 +687,7 @@ def line_info(obj, highlight=True):
return None
if isinstance(obj, ESPHomeDataBase) and obj.esp_range is not None:
mark = obj.esp_range.start_mark
source = u"[source {}:{}]".format(mark.document, mark.line + 1)
source = "[source {}:{}]".format(mark.document, mark.line + 1)
return color('cyan', source)
return None
@@ -715,82 +703,82 @@ def _print_on_next_line(obj):
def dump_dict(config, path, at_root=True):
# type: (Config, ConfigPath, bool) -> Tuple[unicode, bool]
# type: (Config, ConfigPath, bool) -> Tuple[str, bool]
conf = config.get_nested_item(path)
ret = u''
ret = ''
multiline = False
if at_root:
error = config.get_error_for_path(path)
if error is not None:
ret += u'\n' + color('bold_red', _format_vol_invalid(error, config)) + u'\n'
ret += '\n' + color('bold_red', _format_vol_invalid(error, config)) + '\n'
if isinstance(conf, (list, tuple)):
multiline = True
if not conf:
ret += u'[]'
ret += '[]'
multiline = False
for i in range(len(conf)):
path_ = path + [i]
error = config.get_error_for_path(path_)
if error is not None:
ret += u'\n' + color('bold_red', _format_vol_invalid(error, config)) + u'\n'
ret += '\n' + color('bold_red', _format_vol_invalid(error, config)) + '\n'
sep = u'- '
sep = '- '
if config.is_in_error_path(path_):
sep = color('red', sep)
msg, _ = dump_dict(config, path_, at_root=False)
msg = indent(msg)
inf = line_info(config.get_nested_item(path_), highlight=config.is_in_error_path(path_))
if inf is not None:
msg = inf + u'\n' + msg
msg = inf + '\n' + msg
elif msg:
msg = msg[2:]
ret += sep + msg + u'\n'
ret += sep + msg + '\n'
elif isinstance(conf, dict):
multiline = True
if not conf:
ret += u'{}'
ret += '{}'
multiline = False
for k in conf.keys():
path_ = path + [k]
error = config.get_error_for_path(path_)
if error is not None:
ret += u'\n' + color('bold_red', _format_vol_invalid(error, config)) + u'\n'
ret += '\n' + color('bold_red', _format_vol_invalid(error, config)) + '\n'
st = u'{}: '.format(k)
st = f'{k}: '
if config.is_in_error_path(path_):
st = color('red', st)
msg, m = dump_dict(config, path_, at_root=False)
inf = line_info(config.get_nested_item(path_), highlight=config.is_in_error_path(path_))
if m:
msg = u'\n' + indent(msg)
msg = '\n' + indent(msg)
if inf is not None:
if m:
msg = u' ' + inf + msg
msg = ' ' + inf + msg
else:
msg = msg + u' ' + inf
ret += st + msg + u'\n'
msg = msg + ' ' + inf
ret += st + msg + '\n'
elif isinstance(conf, str):
if is_secret(conf):
conf = u'!secret {}'.format(is_secret(conf))
conf = '!secret {}'.format(is_secret(conf))
if not conf:
conf += u"''"
conf += "''"
if len(conf) > 80:
conf = u'|-\n' + indent(conf)
conf = '|-\n' + indent(conf)
error = config.get_error_for_path(path)
col = 'bold_red' if error else 'white'
ret += color(col, text_type(conf))
ret += color(col, str(conf))
elif isinstance(conf, core.Lambda):
if is_secret(conf):
conf = u'!secret {}'.format(is_secret(conf))
conf = '!secret {}'.format(is_secret(conf))
conf = u'!lambda |-\n' + indent(text_type(conf.value))
conf = '!lambda |-\n' + indent(str(conf.value))
error = config.get_error_for_path(path)
col = 'bold_red' if error else 'white'
ret += color(col, conf)
@@ -799,8 +787,8 @@ def dump_dict(config, path, at_root=True):
else:
error = config.get_error_for_path(path)
col = 'bold_red' if error else 'white'
ret += color(col, text_type(conf))
multiline = u'\n' in ret
ret += color(col, str(conf))
multiline = '\n' in ret
return ret, multiline
@@ -830,20 +818,20 @@ def read_config():
try:
res = load_config()
except EsphomeError as err:
_LOGGER.error(u"Error while reading config: %s", err)
_LOGGER.error("Error while reading config: %s", err)
return None
if res.errors:
if not CORE.verbose:
res = strip_default_ids(res)
safe_print(color('bold_red', u"Failed config"))
safe_print(color('bold_red', "Failed config"))
safe_print('')
for path, domain in res.output_paths:
if not res.is_in_error_path(path):
continue
safe_print(color('bold_red', u'{}:'.format(domain)) + u' ' +
(line_info(res.get_nested_item(path)) or u''))
safe_print(color('bold_red', f'{domain}:') + ' ' +
(line_info(res.get_nested_item(path)) or ''))
safe_print(indent(dump_dict(res, path)[0]))
return None
return OrderedDict(res)
+2 -5
View File
@@ -1,22 +1,19 @@
from __future__ import print_function
import json
import os
from esphome.core import CORE
from esphome.helpers import read_file
from esphome.py_compat import safe_input
def read_config_file(path):
# type: (basestring) -> unicode
# type: (str) -> str
if CORE.vscode and (not CORE.ace or
os.path.abspath(path) == os.path.abspath(CORE.config_path)):
print(json.dumps({
'type': 'read_file',
'path': path,
}))
data = json.loads(safe_input())
data = json.loads(input())
assert data['type'] == 'file_response'
return data['content']
File diff suppressed because it is too large Load Diff
+12 -13
View File
@@ -1,17 +1,16 @@
# coding=utf-8
"""Constants used by esphome."""
MAJOR_VERSION = 1
MINOR_VERSION = 15
PATCH_VERSION = '0-dev'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
__short_version__ = f'{MAJOR_VERSION}.{MINOR_VERSION}'
__version__ = f'{__short_version__}.{PATCH_VERSION}'
ESP_PLATFORM_ESP32 = 'ESP32'
ESP_PLATFORM_ESP8266 = 'ESP8266'
ESP_PLATFORMS = [ESP_PLATFORM_ESP32, ESP_PLATFORM_ESP8266]
ALLOWED_NAME_CHARS = u'abcdefghijklmnopqrstuvwxyz0123456789_'
ALLOWED_NAME_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_'
ARDUINO_VERSION_ESP32_DEV = 'https://github.com/platformio/platform-espressif32.git#feature/stage'
ARDUINO_VERSION_ESP32_1_0_0 = 'espressif32@1.5.0'
ARDUINO_VERSION_ESP32_1_0_1 = 'espressif32@1.6.0'
@@ -544,12 +543,12 @@ ICON_WEATHER_WINDY = 'mdi:weather-windy'
ICON_WIFI = 'mdi:wifi'
UNIT_AMPERE = 'A'
UNIT_CELSIUS = u'°C'
UNIT_COUNTS_PER_CUBIC_METER = u'#/m³'
UNIT_CELSIUS = '°C'
UNIT_COUNTS_PER_CUBIC_METER = '#/m³'
UNIT_DECIBEL = 'dB'
UNIT_DECIBEL_MILLIWATT = 'dBm'
UNIT_DEGREE_PER_SECOND = u'°/s'
UNIT_DEGREES = u'°'
UNIT_DEGREE_PER_SECOND = '°/s'
UNIT_DEGREES = '°'
UNIT_EMPTY = ''
UNIT_G = 'G'
UNIT_HECTOPASCAL = 'hPa'
@@ -559,12 +558,12 @@ UNIT_KILOMETER = 'km'
UNIT_KILOMETER_PER_HOUR = 'km/h'
UNIT_LUX = 'lx'
UNIT_METER = 'm'
UNIT_METER_PER_SECOND_SQUARED = u'm/s²'
UNIT_MICROGRAMS_PER_CUBIC_METER = u'µg/m³'
UNIT_METER_PER_SECOND_SQUARED = 'm/s²'
UNIT_MICROGRAMS_PER_CUBIC_METER = 'µg/m³'
UNIT_MICROMETER = 'µm'
UNIT_MICROSIEMENS_PER_CENTIMETER = u'µS/cm'
UNIT_MICROTESLA = u'µT'
UNIT_OHM = u'Ω'
UNIT_MICROSIEMENS_PER_CENTIMETER = 'µS/cm'
UNIT_MICROTESLA = 'µT'
UNIT_OHM = 'Ω'
UNIT_PARTS_PER_BILLION = 'ppb'
UNIT_PARTS_PER_MILLION = 'ppm'
UNIT_PERCENT = '%'
+61 -68
View File
@@ -13,7 +13,6 @@ from typing import Any, Dict, List, Optional, Set # noqa
from esphome.const import CONF_ARDUINO_VERSION, SOURCE_FILE_EXTENSIONS, \
CONF_COMMENT, CONF_ESPHOME, CONF_USE_ADDRESS, CONF_WIFI
from esphome.helpers import ensure_unique_string, is_hassio
from esphome.py_compat import IS_PY2, integer_types, text_type, string_types
from esphome.util import OrderedDict
_LOGGER = logging.getLogger(__name__)
@@ -23,53 +22,47 @@ class EsphomeError(Exception):
"""General ESPHome exception occurred."""
if IS_PY2:
base_int = long
else:
base_int = int
class HexInt(base_int):
class HexInt(int):
def __str__(self):
if 0 <= self <= 255:
return "0x{:02X}".format(self)
return "0x{:X}".format(self)
return f"0x{self:02X}"
return f"0x{self:X}"
class IPAddress(object):
class IPAddress:
def __init__(self, *args):
if len(args) != 4:
raise ValueError(u"IPAddress must consist up 4 items")
raise ValueError("IPAddress must consist up 4 items")
self.args = args
def __str__(self):
return '.'.join(str(x) for x in self.args)
class MACAddress(object):
class MACAddress:
def __init__(self, *parts):
if len(parts) != 6:
raise ValueError(u"MAC Address must consist of 6 items")
raise ValueError("MAC Address must consist of 6 items")
self.parts = parts
def __str__(self):
return ':'.join('{:02X}'.format(part) for part in self.parts)
return ':'.join(f'{part:02X}' for part in self.parts)
@property
def as_hex(self):
from esphome.cpp_generator import RawExpression
num = ''.join('{:02X}'.format(part) for part in self.parts)
return RawExpression('0x{}ULL'.format(num))
num = ''.join(f'{part:02X}' for part in self.parts)
return RawExpression(f'0x{num}ULL')
def is_approximately_integer(value):
if isinstance(value, integer_types):
if isinstance(value, int):
return True
return abs(value - round(value)) < 0.001
class TimePeriod(object):
class TimePeriod:
def __init__(self, microseconds=None, milliseconds=None, seconds=None,
minutes=None, hours=None, days=None):
if days is not None:
@@ -137,17 +130,17 @@ class TimePeriod(object):
def __str__(self):
if self.microseconds is not None:
return '{}us'.format(self.total_microseconds)
return f'{self.total_microseconds}us'
if self.milliseconds is not None:
return '{}ms'.format(self.total_milliseconds)
return f'{self.total_milliseconds}ms'
if self.seconds is not None:
return '{}s'.format(self.total_seconds)
return f'{self.total_seconds}s'
if self.minutes is not None:
return '{}min'.format(self.total_minutes)
return f'{self.total_minutes}min'
if self.hours is not None:
return '{}h'.format(self.total_hours)
return f'{self.total_hours}h'
if self.days is not None:
return '{}d'.format(self.total_days)
return f'{self.total_days}d'
return '0s'
@property
@@ -224,7 +217,7 @@ class TimePeriodMinutes(TimePeriod):
LAMBDA_PROG = re.compile(r'id\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)')
class Lambda(object):
class Lambda:
def __init__(self, value):
# pylint: disable=protected-access
if isinstance(value, Lambda):
@@ -260,10 +253,10 @@ class Lambda(object):
return self.value
def __repr__(self):
return u'Lambda<{}>'.format(self.value)
return f'Lambda<{self.value}>'
class ID(object):
class ID:
def __init__(self, id, is_declaration=False, type=None, is_manual=None):
self.id = id
if is_manual is None:
@@ -289,7 +282,7 @@ class ID(object):
return self.id
def __repr__(self):
return u'ID<{} declaration={}, type={}, manual={}>'.format(
return 'ID<{} declaration={}, type={}, manual={}>'.format(
self.id, self.is_declaration, self.type, self.is_manual)
def __eq__(self, other):
@@ -305,10 +298,10 @@ class ID(object):
is_manual=self.is_manual)
class DocumentLocation(object):
class DocumentLocation:
def __init__(self, document, line, column):
# type: (basestring, int, int) -> None
self.document = document # type: basestring
# type: (str, int, int) -> None
self.document = document # type: str
self.line = line # type: int
self.column = column # type: int
@@ -321,10 +314,10 @@ class DocumentLocation(object):
)
def __str__(self):
return u'{} {}:{}'.format(self.document, self.line, self.column)
return f'{self.document} {self.line}:{self.column}'
class DocumentRange(object):
class DocumentRange:
def __init__(self, start_mark, end_mark):
# type: (DocumentLocation, DocumentLocation) -> None
self.start_mark = start_mark # type: DocumentLocation
@@ -338,10 +331,10 @@ class DocumentRange(object):
)
def __str__(self):
return u'[{} - {}]'.format(self.start_mark, self.end_mark)
return f'[{self.start_mark} - {self.end_mark}]'
class Define(object):
class Define:
def __init__(self, name, value=None):
self.name = name
self.value = value
@@ -349,14 +342,14 @@ class Define(object):
@property
def as_build_flag(self):
if self.value is None:
return u'-D{}'.format(self.name)
return u'-D{}={}'.format(self.name, self.value)
return f'-D{self.name}'
return f'-D{self.name}={self.value}'
@property
def as_macro(self):
if self.value is None:
return u'#define {}'.format(self.name)
return u'#define {} {}'.format(self.name, self.value)
return f'#define {self.name}'
return f'#define {self.name} {self.value}'
@property
def as_tuple(self):
@@ -369,7 +362,7 @@ class Define(object):
return isinstance(self, type(other)) and self.as_tuple == other.as_tuple
class Library(object):
class Library:
def __init__(self, name, version):
self.name = name
self.version = version
@@ -378,7 +371,7 @@ class Library(object):
def as_lib_dep(self):
if self.version is None:
return self.name
return u'{}@{}'.format(self.name, self.version)
return f'{self.name}@{self.version}'
@property
def as_tuple(self):
@@ -461,7 +454,7 @@ def find_source_files(file):
# pylint: disable=too-many-instance-attributes,too-many-public-methods
class EsphomeCore(object):
class EsphomeCore:
def __init__(self):
# True if command is run from dashboard
self.dashboard = False
@@ -499,7 +492,7 @@ class EsphomeCore(object):
# A set of build flags to set in the platformio project
self.build_flags = set() # type: Set[str]
# A set of defines to set for the compile process in esphome/core/defines.h
self.defines = set() # type: Set[Define]
self.defines = set() # type: Set['Define']
# A dictionary of started coroutines, used to warn when a coroutine was not
# awaited.
self.active_coroutines = {} # type: Dict[int, Any]
@@ -634,15 +627,15 @@ class EsphomeCore(object):
# Print not-awaited coroutines
for obj in self.active_coroutines.values():
_LOGGER.warning(u"Coroutine '%s' %s was never awaited with 'yield'.", obj.__name__, obj)
_LOGGER.warning(u"Please file a bug report with your configuration.")
_LOGGER.warning("Coroutine '%s' %s was never awaited with 'yield'.", obj.__name__, obj)
_LOGGER.warning("Please file a bug report with your configuration.")
if self.active_coroutines:
raise EsphomeError()
if self.component_ids:
comps = u', '.join(u"'{}'".format(x) for x in self.component_ids)
_LOGGER.warning(u"Components %s were never registered. Please create a bug report",
comps = ', '.join(f"'{x}'" for x in self.component_ids)
_LOGGER.warning("Components %s were never registered. Please create a bug report",
comps)
_LOGGER.warning(u"with your configuration.")
_LOGGER.warning("with your configuration.")
raise EsphomeError()
self.active_coroutines.clear()
@@ -652,8 +645,8 @@ class EsphomeCore(object):
if isinstance(expression, Expression):
expression = statement(expression)
if not isinstance(expression, Statement):
raise ValueError(u"Add '{}' must be expression or statement, not {}"
u"".format(expression, type(expression)))
raise ValueError("Add '{}' must be expression or statement, not {}"
"".format(expression, type(expression)))
self.main_statements.append(expression)
_LOGGER.debug("Adding: %s", expression)
@@ -665,16 +658,16 @@ class EsphomeCore(object):
if isinstance(expression, Expression):
expression = statement(expression)
if not isinstance(expression, Statement):
raise ValueError(u"Add '{}' must be expression or statement, not {}"
u"".format(expression, type(expression)))
raise ValueError("Add '{}' must be expression or statement, not {}"
"".format(expression, type(expression)))
self.global_statements.append(expression)
_LOGGER.debug("Adding global: %s", expression)
return expression
def add_library(self, library):
if not isinstance(library, Library):
raise ValueError(u"Library {} must be instance of Library, not {}"
u"".format(library, type(library)))
raise ValueError("Library {} must be instance of Library, not {}"
"".format(library, type(library)))
_LOGGER.debug("Adding library: %s", library)
for other in self.libraries[:]:
if other.name != library.name:
@@ -689,9 +682,9 @@ class EsphomeCore(object):
if other.version == library.version:
break
raise ValueError(u"Version pinning failed! Libraries {} and {} "
u"requested with conflicting versions!"
u"".format(library, other))
raise ValueError("Version pinning failed! Libraries {} and {} "
"requested with conflicting versions!"
"".format(library, other))
else:
self.libraries.append(library)
return library
@@ -702,20 +695,20 @@ class EsphomeCore(object):
return build_flag
def add_define(self, define):
if isinstance(define, string_types):
if isinstance(define, str):
define = Define(define)
elif isinstance(define, Define):
pass
else:
raise ValueError(u"Define {} must be string or Define, not {}"
u"".format(define, type(define)))
raise ValueError("Define {} must be string or Define, not {}"
"".format(define, type(define)))
self.defines.add(define)
_LOGGER.debug("Adding define: %s", define)
return define
def get_variable(self, id):
if not isinstance(id, ID):
raise ValueError("ID {!r} must be of type ID!".format(id))
raise ValueError(f"ID {id!r} must be of type ID!")
while True:
if id in self.variables:
yield self.variables[id]
@@ -735,7 +728,7 @@ class EsphomeCore(object):
def register_variable(self, id, obj):
if id in self.variables:
raise EsphomeError("ID {} is already registered".format(id))
raise EsphomeError(f"ID {id} is already registered")
_LOGGER.debug("Registered variable %s of type %s", id.id, id.type)
self.variables[id] = obj
@@ -748,10 +741,10 @@ class EsphomeCore(object):
main_code = []
for exp in self.main_statements:
text = text_type(statement(exp))
text = str(statement(exp))
text = text.rstrip()
main_code.append(text)
return u'\n'.join(main_code) + u'\n\n'
return '\n'.join(main_code) + '\n\n'
@property
def cpp_global_section(self):
@@ -759,17 +752,17 @@ class EsphomeCore(object):
global_code = []
for exp in self.global_statements:
text = text_type(statement(exp))
text = str(statement(exp))
text = text.rstrip()
global_code.append(text)
return u'\n'.join(global_code) + u'\n'
return '\n'.join(global_code) + '\n'
class AutoLoad(OrderedDict):
pass
class EnumValue(object):
class EnumValue:
"""Special type used by ESPHome to mark enum values for cv.enum."""
@property
def enum_value(self):
+4
View File
@@ -138,6 +138,9 @@ float Component::get_actual_setup_priority() const {
return this->setup_priority_override_;
}
void Component::set_setup_priority(float priority) { this->setup_priority_override_ = priority; }
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpmf-conversions"
bool Component::has_overridden_loop() const {
#ifdef CLANG_TIDY
bool loop_overridden = true;
@@ -148,6 +151,7 @@ bool Component::has_overridden_loop() const {
#endif
return loop_overridden || call_loop_overridden;
}
#pragma GCC diagnostic pop
PollingComponent::PollingComponent(uint32_t update_interval) : Component(), update_interval_(update_interval) {}
+6 -6
View File
@@ -37,8 +37,8 @@ def validate_board(value):
raise NotImplementedError
if value not in board_pins:
raise cv.Invalid(u"Could not find board '{}'. Valid boards are {}".format(
value, u', '.join(sorted(board_pins.keys()))))
raise cv.Invalid("Could not find board '{}'. Valid boards are {}".format(
value, ', '.join(sorted(board_pins.keys()))))
return value
@@ -108,8 +108,8 @@ def valid_include(value):
value = cv.file_(value)
_, ext = os.path.splitext(value)
if ext not in VALID_INCLUDE_EXTS:
raise cv.Invalid(u"Include has invalid file extension {} - valid extensions are {}"
u"".format(ext, ', '.join(VALID_INCLUDE_EXTS)))
raise cv.Invalid("Include has invalid file extension {} - valid extensions are {}"
"".format(ext, ', '.join(VALID_INCLUDE_EXTS)))
return value
@@ -184,7 +184,7 @@ def include_file(path, basename):
_, ext = os.path.splitext(path)
if ext in ['.h', '.hpp', '.tcc']:
# Header, add include statement
cg.add_global(cg.RawStatement(u'#include "{}"'.format(basename)))
cg.add_global(cg.RawStatement(f'#include "{basename}"'))
@coroutine_with_priority(-1000.0)
@@ -238,7 +238,7 @@ def to_code(config):
ld_script = ld_scripts[1]
if ld_script is not None:
cg.add_build_flag('-Wl,-T{}'.format(ld_script))
cg.add_build_flag(f'-Wl,-T{ld_script}')
cg.add_build_flag('-fno-exceptions')
+101 -102
View File
File diff suppressed because it is too large Load Diff
+8 -7
View File
@@ -1,9 +1,10 @@
from esphome.const import CONF_INVERTED, CONF_MODE, CONF_NUMBER, CONF_SETUP_PRIORITY, \
CONF_UPDATE_INTERVAL, CONF_TYPE_ID
from esphome.core import coroutine, ID, CORE
# pylint: disable=unused-import
from esphome.core import coroutine, ID, CORE, ConfigType
from esphome.cpp_generator import RawExpression, add, get_variable
from esphome.cpp_types import App, GPIOPin
from esphome.py_compat import text_type
from esphome.util import Registry, RegistryEntry
@coroutine
@@ -35,11 +36,11 @@ def register_component(var, config):
:param var: The variable representing the component.
:param config: The configuration for the component.
"""
id_ = text_type(var.base)
id_ = str(var.base)
if id_ not in CORE.component_ids:
raise ValueError(u"Component ID {} was not declared to inherit from Component, "
u"or was registered twice. Please create a bug report with your "
u"configuration.".format(id_))
raise ValueError("Component ID {} was not declared to inherit from Component, "
"or was registered twice. Please create a bug report with your "
"configuration.".format(id_))
CORE.component_ids.remove(id_)
if CONF_SETUP_PRIORITY in config:
add(var.set_setup_priority(config[CONF_SETUP_PRIORITY]))
@@ -59,7 +60,7 @@ def register_parented(var, value):
def extract_registry_entry_config(registry, full_config):
# type: ('Registry', 'ConfigType') -> 'RegistryEntry'
# type: (Registry, ConfigType) -> RegistryEntry
key, config = next((k, v) for k, v in full_config.items() if k in registry)
return registry[key], config
+19 -30
View File
@@ -1,5 +1,4 @@
# pylint: disable=wrong-import-position
from __future__ import print_function
import codecs
import collections
@@ -29,7 +28,6 @@ import tornado.websocket
from esphome import const, util
from esphome.__main__ import get_serial_ports
from esphome.helpers import mkdir_p, get_bool_env, run_system_command
from esphome.py_compat import IS_PY2, decode_text, encode_text
from esphome.storage_json import EsphomeStorageJSON, StorageJSON, \
esphome_storage_path, ext_storage_path, trash_storage_path
from esphome.util import shlex_quote
@@ -42,7 +40,7 @@ from esphome.zeroconf import DashboardStatus, Zeroconf
_LOGGER = logging.getLogger(__name__)
class DashboardSettings(object):
class DashboardSettings:
def __init__(self):
self.config_dir = ''
self.password_digest = ''
@@ -58,10 +56,7 @@ class DashboardSettings(object):
self.username = args.username or os.getenv('USERNAME', '')
self.using_password = bool(password)
if self.using_password:
if IS_PY2:
self.password_digest = hmac.new(password).digest()
else:
self.password_digest = hmac.new(password.encode()).digest()
self.password_digest = hmac.new(password.encode()).digest()
self.config_dir = args.configuration[0]
@property
@@ -88,8 +83,8 @@ class DashboardSettings(object):
if username != self.username:
return False
password_digest = hmac.new(encode_text(password)).digest()
return hmac.compare_digest(self.password_digest, password_digest)
password = hmac.new(password.encode()).digest()
return username == self.username and hmac.compare_digest(self.password_digest, password)
def rel_path(self, *args):
return os.path.join(self.config_dir, *args)
@@ -100,10 +95,7 @@ class DashboardSettings(object):
settings = DashboardSettings()
if IS_PY2:
cookie_authenticated_yes = 'yes'
else:
cookie_authenticated_yes = b'yes'
cookie_authenticated_yes = b'yes'
def template_args():
@@ -181,7 +173,7 @@ def websocket_method(name):
@websocket_class
class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
def __init__(self, application, request, **kwargs):
super(EsphomeCommandWebSocket, self).__init__(application, request, **kwargs)
super().__init__(application, request, **kwargs)
self._proc = None
self._is_closed = False
@@ -204,7 +196,7 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
# spawn can only be called once
return
command = self.build_command(json_message)
_LOGGER.info(u"Running command '%s'", ' '.join(shlex_quote(x) for x in command))
_LOGGER.info("Running command '%s'", ' '.join(shlex_quote(x) for x in command))
self._proc = tornado.process.Subprocess(command,
stdout=tornado.process.Subprocess.STREAM,
stderr=subprocess.STDOUT,
@@ -227,10 +219,7 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
@tornado.gen.coroutine
def _redirect_stdout(self):
if IS_PY2:
reg = '[\n\r]'
else:
reg = b'[\n\r]'
reg = b'[\n\r]'
while True:
try:
@@ -336,8 +325,8 @@ class WizardRequestHandler(BaseHandler):
def post(self):
from esphome import wizard
kwargs = {k: u''.join(decode_text(x) for x in v) for k, v in self.request.arguments.items()}
destination = settings.rel_path(kwargs['name'] + u'.yaml')
kwargs = {k: ''.join(str(x) for x in v) for k, v in self.request.arguments.items()}
destination = settings.rel_path(kwargs['name'] + '.yaml')
wizard.wizard_write(path=destination, **kwargs)
self.redirect('./?begin=True')
@@ -355,8 +344,8 @@ class DownloadBinaryRequestHandler(BaseHandler):
path = storage_json.firmware_bin_path
self.set_header('Content-Type', 'application/octet-stream')
filename = '{}.bin'.format(storage_json.name)
self.set_header("Content-Disposition", 'attachment; filename="{}"'.format(filename))
filename = f'{storage_json.name}.bin'
self.set_header("Content-Disposition", f'attachment; filename="{filename}"')
with open(path, 'rb') as f:
while True:
data = f.read(16384)
@@ -371,7 +360,7 @@ def _list_dashboard_entries():
return [DashboardEntry(file) for file in files]
class DashboardEntry(object):
class DashboardEntry:
def __init__(self, path):
self.path = path
self._storage = None
@@ -609,8 +598,8 @@ class LoginHandler(BaseHandler):
'X-HASSIO-KEY': os.getenv('HASSIO_TOKEN'),
}
data = {
'username': decode_text(self.get_argument('username', '')),
'password': decode_text(self.get_argument('password', ''))
'username': self.get_argument('username', ''),
'password': self.get_argument('password', '')
}
try:
req = requests.post('http://hassio/auth', headers=headers, data=data)
@@ -627,8 +616,8 @@ class LoginHandler(BaseHandler):
self.render_login_page(error="Invalid username or password")
def post_native_login(self):
username = decode_text(self.get_argument("username", ''))
password = decode_text(self.get_argument("password", ''))
username = self.get_argument("username", '')
password = self.get_argument("password", '')
if settings.check_password(username, password):
self.set_secure_cookie("authenticated", cookie_authenticated_yes)
self.redirect("/")
@@ -663,7 +652,7 @@ def get_static_file_url(name):
with open(path, 'rb') as f_handle:
hash_ = hashlib.md5(f_handle.read()).hexdigest()[:8]
_STATIC_FILE_HASHES[name] = hash_
return u'./static/{}?hash={}'.format(name, hash_)
return f'./static/{name}?hash={hash_}'
def make_app(debug=False):
@@ -754,7 +743,7 @@ def start_web_server(args):
if args.open_ui:
import webbrowser
webbrowser.open('localhost:{}'.format(args.port))
webbrowser.open(f'localhost:{args.port}')
if settings.status_use_ping:
status_thread = PingStatusThread()

Some files were not shown because too many files have changed in this diff Show More