diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0cc29416c9..f4e2fa40af 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,11 +41,11 @@ stages: - | if [[ "${IS_HASSIO}" == "YES" ]]; then - BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:1.4.1 + BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:1.4.3 BUILD_TO=esphome/esphome-hassio-${BUILD_ARCH} DOCKERFILE=docker/Dockerfile.hassio else - BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:1.4.1 + BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:1.4.3 if [[ "${BUILD_ARCH}" == "amd64" ]]; then BUILD_TO=esphome/esphome else diff --git a/docker/Dockerfile b/docker/Dockerfile index 2568ef37f3..0416f2496e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,4 @@ -ARG BUILD_FROM=esphome/esphome-base-amd64:1.4.1 +ARG BUILD_FROM=esphome/esphome-base-amd64:1.4.3 FROM ${BUILD_FROM} COPY . . diff --git a/docker/Dockerfile.hassio b/docker/Dockerfile.hassio index 95888739bd..eaa7d9d504 100644 --- a/docker/Dockerfile.hassio +++ b/docker/Dockerfile.hassio @@ -1,4 +1,4 @@ -ARG BUILD_FROM=esphome/esphome-hassio-base-amd64:1.4.1 +ARG BUILD_FROM=esphome/esphome-hassio-base-amd64:1.4.3 FROM ${BUILD_FROM} # Copy root filesystem diff --git a/docker/hooks/build b/docker/hooks/build index 083c5c50a8..60879aaa88 100755 --- a/docker/hooks/build +++ b/docker/hooks/build @@ -16,11 +16,11 @@ echo "PWD: $PWD" if [[ ${IS_HASSIO} = "YES" ]]; then docker build \ - --build-arg "BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:1.4.1" \ + --build-arg "BUILD_FROM=esphome/esphome-hassio-base-${BUILD_ARCH}:1.4.3" \ --build-arg "BUILD_VERSION=${CACHE_TAG}" \ -t "${IMAGE_NAME}" -f ../docker/Dockerfile.hassio .. else docker build \ - --build-arg "BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:1.4.1" \ + --build-arg "BUILD_FROM=esphome/esphome-base-${BUILD_ARCH}:1.4.3" \ -t "${IMAGE_NAME}" -f ../docker/Dockerfile .. fi diff --git a/esphome/components/api.py b/esphome/components/api.py index 86e77bb4c6..544d0e418b 100644 --- a/esphome/components/api.py +++ b/esphome/components/api.py @@ -86,7 +86,7 @@ def lib_deps(config): if CORE.is_esp32: return 'AsyncTCP@1.0.3' if CORE.is_esp8266: - return 'ESPAsyncTCP@1.1.3' + return 'ESPAsyncTCP@1.2.0' raise NotImplementedError diff --git a/esphome/components/binary_sensor/ttp229_lsf.py b/esphome/components/binary_sensor/ttp229_lsf.py new file mode 100644 index 0000000000..76ea5091ed --- /dev/null +++ b/esphome/components/binary_sensor/ttp229_lsf.py @@ -0,0 +1,24 @@ +import voluptuous as vol + +from esphome.components import binary_sensor +from esphome.components.ttp229_lsf import TTP229LSFComponent, CONF_TTP229_ID +import esphome.config_validation as cv +from esphome.const import CONF_CHANNEL, CONF_NAME +from esphome.cpp_generator import get_variable + +DEPENDENCIES = ['ttp229_lsf'] +TTP229Channel = binary_sensor.binary_sensor_ns.class_( + 'TTP229Channel', binary_sensor.BinarySensor) + +PLATFORM_SCHEMA = cv.nameable(binary_sensor.BINARY_SENSOR_PLATFORM_SCHEMA.extend({ + cv.GenerateID(): cv.declare_variable_id(TTP229Channel), + cv.GenerateID(CONF_TTP229_ID): cv.use_variable_id(TTP229LSFComponent), + vol.Required(CONF_CHANNEL): vol.All(vol.Coerce(int), vol.Range(min=0, max=15)) +})) + + +def to_code(config): + for hub in get_variable(config[CONF_TTP229_ID]): + yield + rhs = TTP229Channel.new(config[CONF_NAME], config[CONF_CHANNEL]) + binary_sensor.register_binary_sensor(hub.add_channel(rhs), config) diff --git a/esphome/components/text_sensor/__init__.py b/esphome/components/text_sensor/__init__.py index a4a6caec26..1aff0c1e3b 100644 --- a/esphome/components/text_sensor/__init__.py +++ b/esphome/components/text_sensor/__init__.py @@ -46,7 +46,7 @@ def setup_text_sensor_core_(text_sensor_var, config): trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs) automation.build_automations(trigger, [(std_string, 'x')], conf) - setup_mqtt_component(text_sensor_var.get_mqtt(), config) + setup_mqtt_component(text_sensor_var.Pget_mqtt(), config) def setup_text_sensor(text_sensor_obj, config): diff --git a/esphome/components/ttp229_lsf.py b/esphome/components/ttp229_lsf.py new file mode 100644 index 0000000000..e945066a59 --- /dev/null +++ b/esphome/components/ttp229_lsf.py @@ -0,0 +1,25 @@ +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import CONF_ID +from esphome.cpp_generator import Pvariable +from esphome.cpp_helpers import setup_component +from esphome.cpp_types import App, Component + +DEPENDENCIES = ['i2c'] + +CONF_TTP229_ID = 'ttp229_id' +TTP229LSFComponent = binary_sensor.binary_sensor_ns.class_('TTP229LSFComponent', Component) + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_variable_id(TTP229LSFComponent), +}).extend(cv.COMPONENT_SCHEMA.schema) + + +def to_code(config): + rhs = App.make_ttp229_lsf() + var = Pvariable(config[CONF_ID], rhs) + + setup_component(var, config) + + +BUILD_FLAGS = '-DUSE_TTP229_LSF' diff --git a/esphome/const.py b/esphome/const.py index fb6e33c4f0..90e994b219 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -433,3 +433,4 @@ ARDUINO_VERSION_ESP32_1_0_1 = 'espressif32@1.6.0' ARDUINO_VERSION_ESP8266_DEV = 'https://github.com/platformio/platform-espressif8266.git#feature' \ '/stage' ARDUINO_VERSION_ESP8266_2_5_0 = 'espressif8266@2.0.0' +ARDUINO_VERSION_ESP8266_2_3_0 = 'espressif8266@1.5.0' diff --git a/esphome/dashboard/dashboard.py b/esphome/dashboard/dashboard.py index a70fe2add2..6c1d9f2616 100644 --- a/esphome/dashboard/dashboard.py +++ b/esphome/dashboard/dashboard.py @@ -28,7 +28,7 @@ import tornado.websocket from esphome import const 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 +from esphome.py_compat import IS_PY2, decode_text from esphome.storage_json import EsphomeStorageJSON, StorageJSON, \ esphome_storage_path, ext_storage_path, trash_storage_path from esphome.util import shlex_quote @@ -223,8 +223,8 @@ class WizardRequestHandler(BaseHandler): def post(self): from esphome import wizard - kwargs = {k: ''.join(v) for k, v in self.request.arguments.items()} - destination = os.path.join(CONFIG_DIR, kwargs['name'] + '.yaml') + kwargs = {k: u''.join(decode_text(x) for x in v) for k, v in self.request.arguments.items()} + destination = os.path.join(CONFIG_DIR, kwargs['name'] + u'.yaml') wizard.wizard_write(path=destination, **kwargs) self.redirect('/?begin=True') diff --git a/esphome/espota2.py b/esphome/espota2.py index dbe7e94313..ac0dd33ebc 100755 --- a/esphome/espota2.py +++ b/esphome/espota2.py @@ -195,7 +195,7 @@ def perform_ota(sock, password, file_handle, filename): send_check(sock, cnonce, 'auth cnonce') result_md5 = hashlib.md5() - result_md5.update(password.encode()) + result_md5.update(password.encode('utf-8')) result_md5.update(nonce.encode()) result_md5.update(cnonce.encode()) result = result_md5.hexdigest() diff --git a/esphome/helpers.py b/esphome/helpers.py index 4def9568f4..01b31111b9 100644 --- a/esphome/helpers.py +++ b/esphome/helpers.py @@ -140,16 +140,3 @@ def get_bool_env(var, default=False): def is_hassio(): return get_bool_env('ESPHOME_IS_HASSIO') - - -def symlink(src, dst): - if hasattr(os, 'symlink'): - os.symlink(src, dst) - else: - import ctypes - csl = ctypes.windll.kernel32.CreateSymbolicLinkW - csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32) - csl.restype = ctypes.c_ubyte - flags = 1 if os.path.isdir(src) else 0 - if csl(dst, src, flags) == 0: - raise ctypes.WinError() diff --git a/esphome/py_compat.py b/esphome/py_compat.py index 16a4d27ebf..4b48aa0f88 100644 --- a/esphome/py_compat.py +++ b/esphome/py_compat.py @@ -69,3 +69,13 @@ def indexbytes(buf, i): return buf[i] else: return ord(buf[i]) + + +if IS_PY2: + def decode_text(data, encoding='utf-8', errors='strict'): + # type: (str, str, str) -> unicode + return unicode(data, encoding='utf-8', errors=errors) +else: + def decode_text(data, encoding='utf-8', errors='strict'): + # type: (bytes, str, str) -> str + return data.decode(encoding='utf-8', errors=errors) diff --git a/esphome/symlink_ops.py b/esphome/symlink_ops.py new file mode 100644 index 0000000000..accfbe5ff7 --- /dev/null +++ b/esphome/symlink_ops.py @@ -0,0 +1,164 @@ +import os + +if hasattr(os, 'symlink'): + def symlink(src, dst): + return os.symlink(src, dst) + + def islink(path): + return os.path.islink(path) + + def readlink(path): + return os.readlink(path) + + def unlink(path): + return os.unlink(path) +else: + import ctypes + from ctypes import wintypes + # Code taken from + # https://stackoverflow.com/questions/27972776/having-trouble-implementing-a-readlink-function + + kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) + + FILE_READ_ATTRIBUTES = 0x0080 + OPEN_EXISTING = 3 + FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 + FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 + FILE_ATTRIBUTE_REPARSE_POINT = 0x0400 + + IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003 + IO_REPARSE_TAG_SYMLINK = 0xA000000C + FSCTL_GET_REPARSE_POINT = 0x000900A8 + MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000 + + LPDWORD = ctypes.POINTER(wintypes.DWORD) + LPWIN32_FIND_DATA = ctypes.POINTER(wintypes.WIN32_FIND_DATAW) + INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value + + def IsReparseTagNameSurrogate(tag): + return bool(tag & 0x20000000) + + def _check_invalid_handle(result, func, args): + if result == INVALID_HANDLE_VALUE: + raise ctypes.WinError(ctypes.get_last_error()) + return args + + def _check_bool(result, func, args): + if not result: + raise ctypes.WinError(ctypes.get_last_error()) + return args + + kernel32.FindFirstFileW.errcheck = _check_invalid_handle + kernel32.FindFirstFileW.restype = wintypes.HANDLE + kernel32.FindFirstFileW.argtypes = ( + wintypes.LPCWSTR, # _In_ lpFileName + LPWIN32_FIND_DATA) # _Out_ lpFindFileData + + kernel32.FindClose.argtypes = ( + wintypes.HANDLE,) # _Inout_ hFindFile + + kernel32.CreateFileW.errcheck = _check_invalid_handle + kernel32.CreateFileW.restype = wintypes.HANDLE + kernel32.CreateFileW.argtypes = ( + wintypes.LPCWSTR, # _In_ lpFileName + wintypes.DWORD, # _In_ dwDesiredAccess + wintypes.DWORD, # _In_ dwShareMode + wintypes.LPVOID, # _In_opt_ lpSecurityAttributes + wintypes.DWORD, # _In_ dwCreationDisposition + wintypes.DWORD, # _In_ dwFlagsAndAttributes + wintypes.HANDLE) # _In_opt_ hTemplateFile + + kernel32.CloseHandle.argtypes = ( + wintypes.HANDLE,) # _In_ hObject + + kernel32.DeviceIoControl.errcheck = _check_bool + kernel32.DeviceIoControl.argtypes = ( + wintypes.HANDLE, # _In_ hDevice + wintypes.DWORD, # _In_ dwIoControlCode + wintypes.LPVOID, # _In_opt_ lpInBuffer + wintypes.DWORD, # _In_ nInBufferSize + wintypes.LPVOID, # _Out_opt_ lpOutBuffer + wintypes.DWORD, # _In_ nOutBufferSize + LPDWORD, # _Out_opt_ lpBytesReturned + wintypes.LPVOID) # _Inout_opt_ lpOverlapped + + class REPARSE_DATA_BUFFER(ctypes.Structure): + class ReparseData(ctypes.Union): + class LinkData(ctypes.Structure): + _fields_ = (('SubstituteNameOffset', wintypes.USHORT), + ('SubstituteNameLength', wintypes.USHORT), + ('PrintNameOffset', wintypes.USHORT), + ('PrintNameLength', wintypes.USHORT)) + + @property + def PrintName(self): + dt = wintypes.WCHAR * (self.PrintNameLength // ctypes.sizeof(wintypes.WCHAR)) + name = dt.from_address(ctypes.addressof(self.PathBuffer) + + self.PrintNameOffset).value + if name.startswith(r'\??'): + name = r'\\?' + name[3:] # NT => Windows + return name + + class SymbolicLinkData(LinkData): + _fields_ = (('Flags', wintypes.ULONG), ('PathBuffer', wintypes.BYTE * 0)) + + class MountPointData(LinkData): + _fields_ = (('PathBuffer', wintypes.BYTE * 0),) + + class GenericData(ctypes.Structure): + _fields_ = (('DataBuffer', wintypes.BYTE * 0),) + _fields_ = (('SymbolicLinkReparseBuffer', SymbolicLinkData), + ('MountPointReparseBuffer', MountPointData), + ('GenericReparseBuffer', GenericData)) + _fields_ = (('ReparseTag', wintypes.ULONG), + ('ReparseDataLength', wintypes.USHORT), + ('Reserved', wintypes.USHORT), + ('ReparseData', ReparseData)) + _anonymous_ = ('ReparseData',) + + def symlink(src, dst): + csl = ctypes.windll.kernel32.CreateSymbolicLinkW + csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32) + csl.restype = ctypes.c_ubyte + flags = 1 if os.path.isdir(src) else 0 + if csl(dst, src, flags) == 0: + error = ctypes.WinError() + # pylint: disable=no-member + if error.winerror == 1314 and error.errno == 22: + from esphome.core import EsphomeError + raise EsphomeError("Cannot create symlink from '%s' to '%s'. Try running tool \ +with elevated privileges" % (src, dst)) + raise error + + def islink(path): + if not os.path.isdir(path): + return False + data = wintypes.WIN32_FIND_DATAW() + kernel32.FindClose(kernel32.FindFirstFileW(path, ctypes.byref(data))) + if not data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT: + return False + return IsReparseTagNameSurrogate(data.dwReserved0) + + def readlink(path): + n = wintypes.DWORD() + buf = (wintypes.BYTE * MAXIMUM_REPARSE_DATA_BUFFER_SIZE)() + flags = FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS + handle = kernel32.CreateFileW(path, FILE_READ_ATTRIBUTES, 0, None, + OPEN_EXISTING, flags, None) + try: + kernel32.DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, None, 0, + buf, ctypes.sizeof(buf), ctypes.byref(n), None) + finally: + kernel32.CloseHandle(handle) + rb = REPARSE_DATA_BUFFER.from_buffer(buf) + tag = rb.ReparseTag + if tag == IO_REPARSE_TAG_SYMLINK: + return rb.SymbolicLinkReparseBuffer.PrintName + if tag == IO_REPARSE_TAG_MOUNT_POINT: + return rb.MountPointReparseBuffer.PrintName + if not IsReparseTagNameSurrogate(tag): + raise ValueError("not a link") + raise ValueError("unsupported reparse tag: %d" % tag) + + def unlink(path): + return os.rmdir(path) diff --git a/esphome/wizard.py b/esphome/wizard.py index a2401584a1..abc2270235 100644 --- a/esphome/wizard.py +++ b/esphome/wizard.py @@ -79,7 +79,7 @@ def wizard_write(path, **kwargs): kwargs['platform'] = 'ESP8266' if board in ESP8266_BOARD_PINS else 'ESP32' platform = kwargs['platform'] - with codecs.open(path, 'w') as f_handle: + with codecs.open(path, 'w', 'utf-8') as f_handle: f_handle.write(wizard_file(**kwargs)) storage = StorageJSON.from_wizard(name, name + '.local', platform, board) storage_path = ext_storage_path(os.path.dirname(path), os.path.basename(path)) diff --git a/esphome/writer.py b/esphome/writer.py index 0d62372587..9c2bc8fbd8 100644 --- a/esphome/writer.py +++ b/esphome/writer.py @@ -10,10 +10,12 @@ import shutil from esphome.config import iter_components from esphome.const import ARDUINO_VERSION_ESP32_1_0_0, ARDUINO_VERSION_ESP8266_2_5_0, \ ARDUINO_VERSION_ESP8266_DEV, CONF_BOARD_FLASH_MODE, CONF_BRANCH, CONF_COMMIT, CONF_ESPHOME, \ - CONF_LOCAL, CONF_PLATFORMIO_OPTIONS, CONF_REPOSITORY, CONF_TAG, CONF_USE_CUSTOM_CODE + CONF_LOCAL, CONF_PLATFORMIO_OPTIONS, CONF_REPOSITORY, CONF_TAG, CONF_USE_CUSTOM_CODE, \ + ARDUINO_VERSION_ESP8266_2_3_0 from esphome.core import CORE, EsphomeError from esphome.core_config import GITHUB_ARCHIVE_ZIP, LIBRARY_URI_REPO, VERSION_REGEX -from esphome.helpers import mkdir_p, run_system_command, symlink +from esphome.helpers import mkdir_p, run_system_command +from esphome.symlink_ops import symlink, islink, readlink, unlink from esphome.pins import ESP8266_FLASH_SIZES, ESP8266_LD_SCRIPTS from esphome.py_compat import IS_PY3, string_types from esphome.storage_json import StorageJSON, storage_path @@ -220,10 +222,10 @@ def symlink_esphome_core_version(esphome_core_version): if CORE.is_local_esphome_core_copy: src_path = CORE.relative_path(esphome_core_version[CONF_LOCAL]) do_write = True - if os.path.islink(dst_path): - old_path = os.path.join(os.readlink(dst_path), lib_path) + if islink(dst_path): + old_path = os.path.join(readlink(dst_path), lib_path) if old_path != lib_path: - os.unlink(dst_path) + unlink(dst_path) else: do_write = False if do_write: @@ -231,8 +233,8 @@ def symlink_esphome_core_version(esphome_core_version): symlink(src_path, dst_path) else: # Remove symlink when changing back from local version - if os.path.islink(dst_path): - os.unlink(dst_path) + if islink(dst_path): + unlink(dst_path) def format_ini(data): @@ -288,7 +290,7 @@ def gather_lib_deps(): lib_deps.add('AsyncTCP@1.0.1') lib_deps.add('ESPmDNS') elif CORE.is_esp8266: - lib_deps.add('ESPAsyncTCP@1.1.3') + lib_deps.add('ESPAsyncTCP@1.2.0') lib_deps.add('ESP8266mDNS') # avoid changing build flags order @@ -341,13 +343,14 @@ def gather_build_flags(): '-DUSE_WIFI_SIGNAL_SENSOR', } - if CORE.is_esp8266 and CORE.board in ESP8266_FLASH_SIZES: + if CORE.is_esp8266 and CORE.board in ESP8266_FLASH_SIZES and \ + CORE.arduino_version != ARDUINO_VERSION_ESP8266_2_3_0: flash_size = ESP8266_FLASH_SIZES[CORE.board] ld_scripts = ESP8266_LD_SCRIPTS[flash_size] ld_script = None if CORE.arduino_version in ('espressif8266@1.8.0', 'espressif8266@1.7.3', - 'espressif8266@1.6.0', 'espressif8266@1.5.0'): + 'espressif8266@1.6.0'): ld_script = ld_scripts[0] elif CORE.arduino_version in (ARDUINO_VERSION_ESP8266_DEV, ARDUINO_VERSION_ESP8266_2_5_0): ld_script = ld_scripts[1]