diff --git a/esphomeyaml/__main__.py b/esphomeyaml/__main__.py index d4de99fd8a..4c7782d267 100644 --- a/esphomeyaml/__main__.py +++ b/esphomeyaml/__main__.py @@ -13,23 +13,15 @@ from esphomeyaml.const import CONF_BAUD_RATE, CONF_BUILD_PATH, CONF_DOMAIN, CONF CONF_HOSTNAME, CONF_LOGGER, CONF_MANUAL_IP, CONF_NAME, CONF_STATIC_IP, CONF_USE_CUSTOM_CODE, \ CONF_WIFI, ESP_PLATFORM_ESP8266 from esphomeyaml.core import ESPHomeYAMLError -from esphomeyaml.helpers import AssignmentExpression, Expression, RawStatement, _EXPRESSIONS, add, \ - add_job, color, flush_tasks, indent, quote, statement +from esphomeyaml.helpers import AssignmentExpression, Expression, RawStatement, \ + _EXPRESSIONS, add, \ + add_job, color, flush_tasks, indent, quote, statement, relative_path _LOGGER = logging.getLogger(__name__) PRE_INITIALIZE = ['esphomeyaml', 'logger', 'wifi', 'ota', 'mqtt', 'web_server', 'i2c'] -def get_name(config): - return config[CONF_ESPHOMEYAML][CONF_NAME] - - -def get_base_path(config): - build_path = config[CONF_ESPHOMEYAML].get(CONF_BUILD_PATH, get_name(config)) - return os.path.join(os.path.dirname(core.CONFIG_PATH), build_path) - - def get_serial_ports(): # from https://github.com/pyserial/pyserial/blob/master/serial/tools/list_ports.py from serial.tools.list_ports import comports @@ -148,17 +140,19 @@ def write_cpp(config): exp = exp.rhs all_code.append(unicode(statement(exp))) - writer.write_platformio_project(config, get_base_path(config)) + build_path = relative_path(config[CONF_ESPHOMEYAML][CONF_BUILD_PATH]) + writer.write_platformio_project(config, build_path) code_s = indent('\n'.join(line.rstrip() for line in all_code)) - cpp_path = os.path.join(get_base_path(config), 'src', 'main.cpp') + cpp_path = os.path.join(build_path, 'src', 'main.cpp') writer.write_cpp(code_s, cpp_path) return 0 def compile_program(args, config): _LOGGER.info("Compiling app...") - command = ['platformio', 'run', '-d', get_base_path(config)] + build_path = relative_path(config[CONF_ESPHOMEYAML][CONF_BUILD_PATH]) + command = ['platformio', 'run', '-d', build_path] if args.verbose: command.append('-v') return run_platformio(*command) @@ -177,8 +171,8 @@ def get_upload_host(config): def upload_using_esptool(config, port): import esptool - name = get_name(config) - path = os.path.join(get_base_path(config), '.pioenvs', name, 'firmware.bin') + build_path = relative_path(config[CONF_ESPHOMEYAML][CONF_BUILD_PATH]) + path = os.path.join(build_path, '.pioenvs', core.NAME, 'firmware.bin') # pylint: disable=protected-access return run_platformio('esptool.py', '--before', 'default_reset', '--after', 'hard_reset', '--chip', 'esp8266', '--port', port, 'write_flash', '0x0', @@ -187,13 +181,14 @@ def upload_using_esptool(config, port): def upload_program(config, args, port): _LOGGER.info("Uploading binary...") + build_path = relative_path(config[CONF_ESPHOMEYAML][CONF_BUILD_PATH]) # if upload is to a serial port use platformio, otherwise assume ota serial_port = port.startswith('/') or port.startswith('COM') if port != 'OTA' and serial_port: if core.ESP_PLATFORM == ESP_PLATFORM_ESP8266 and args.use_esptoolpy: return upload_using_esptool(config, port) - command = ['platformio', 'run', '-d', get_base_path(config), + command = ['platformio', 'run', '-d', build_path, '-t', 'upload', '--upload-port', port] if args.verbose: command.append('-v') @@ -213,7 +208,7 @@ def upload_program(config, args, port): from esphomeyaml.components import ota from esphomeyaml import espota - bin_file = os.path.join(get_base_path(config), '.pioenvs', get_name(config), 'firmware.bin') + bin_file = os.path.join(build_path, '.pioenvs', core.NAME, 'firmware.bin') if args.host_port is not None: host_port = args.host_port else: diff --git a/esphomeyaml/core.py b/esphomeyaml/core.py index 60589d6e24..3b3722724a 100644 --- a/esphomeyaml/core.py +++ b/esphomeyaml/core.py @@ -242,3 +242,4 @@ CONFIG_PATH = None ESP_PLATFORM = '' BOARD = '' RAW_CONFIG = None +NAME = '' diff --git a/esphomeyaml/core_config.py b/esphomeyaml/core_config.py index 75e0c0c01d..7700e075c6 100644 --- a/esphomeyaml/core_config.py +++ b/esphomeyaml/core_config.py @@ -1,5 +1,7 @@ +import logging import os import re +import subprocess import voluptuous as vol @@ -15,6 +17,8 @@ from esphomeyaml.core import ESPHomeYAMLError from esphomeyaml.helpers import App, NoArg, Pvariable, add, const_char_p, esphomelib_ns, \ relative_path +_LOGGER = logging.getLogger(__name__) + LIBRARY_URI_REPO = u'https://github.com/OttoWinter/esphomelib.git' BUILD_FLASH_MODES = ['qio', 'qout', 'dio', 'dout'] @@ -42,11 +46,20 @@ def validate_board(value): def validate_simple_esphomelib_version(value): value = cv.string_strict(value) if value.upper() == 'LATEST': - return LIBRARY_URI_REPO + '#v{}'.format(ESPHOMELIB_VERSION) + return { + CONF_REPOSITORY: LIBRARY_URI_REPO, + CONF_TAG: 'v' + ESPHOMELIB_VERSION, + } elif value.upper() == 'DEV': - return LIBRARY_URI_REPO + return { + CONF_REPOSITORY: LIBRARY_URI_REPO, + CONF_BRANCH: 'master' + } elif VERSION_REGEX.match(value) is not None: - return LIBRARY_URI_REPO + '#v{}'.format(value) + return { + CONF_REPOSITORY: LIBRARY_URI_REPO, + CONF_TAG: 'v' + value, + } return value @@ -60,12 +73,11 @@ def validate_local_esphomelib_version(value): return value -def convert_esphomelib_version_schema(value): - if CONF_COMMIT in value: - return value[CONF_REPOSITORY] + '#' + value[CONF_COMMIT] - if CONF_BRANCH in value: - return value[CONF_REPOSITORY] + '#' + value[CONF_BRANCH] - return value[CONF_REPOSITORY] + '#' + value[CONF_TAG] +def validate_commit(value): + value = cv.string(value) + if re.match(r"^[0-9a-f]{7,}$", value) is None: + raise vol.Invalid("Commit option only accepts commit hashes in hex format.") + return value ESPHOMELIB_VERSION_SCHEMA = vol.Any( @@ -76,12 +88,11 @@ ESPHOMELIB_VERSION_SCHEMA = vol.Any( vol.All( vol.Schema({ vol.Optional(CONF_REPOSITORY, default=LIBRARY_URI_REPO): cv.string, - vol.Optional(CONF_COMMIT, 'tag'): cv.string, - vol.Optional(CONF_BRANCH, 'tag'): cv.string, - vol.Optional(CONF_TAG, 'tag'): cv.string, + vol.Optional(CONF_COMMIT): validate_commit, + vol.Optional(CONF_BRANCH): cv.string, + vol.Optional(CONF_TAG): cv.string, }), - cv.has_at_most_one_key(CONF_COMMIT, CONF_BRANCH, CONF_TAG), - convert_esphomelib_version_schema + cv.has_at_most_one_key(CONF_COMMIT, CONF_BRANCH, CONF_TAG) ), ) @@ -138,6 +149,10 @@ def validate_arduino_version(value): raise NotImplementedError +def default_build_path(): + return core.NAME + + CONFIG_SCHEMA = vol.Schema({ vol.Required(CONF_NAME): cv.valid_name, vol.Required(CONF_PLATFORM): vol.All(vol.Upper, cv.one_of('ESP8266', 'ESPRESSIF8266', @@ -146,7 +161,7 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_ESPHOMELIB_VERSION, default='latest'): ESPHOMELIB_VERSION_SCHEMA, vol.Optional(CONF_ARDUINO_VERSION, default='recommended'): validate_arduino_version, vol.Optional(CONF_USE_CUSTOM_CODE, default=False): cv.boolean, - vol.Optional(CONF_BUILD_PATH): cv.string, + vol.Optional(CONF_BUILD_PATH, default=default_build_path): cv.string, vol.Optional(CONF_BOARD_FLASH_MODE): vol.All(vol.Lower, cv.one_of(*BUILD_FLASH_MODES)), vol.Optional(CONF_ON_BOOT): vol.All(cv.ensure_list, [automation.validate_automation({ @@ -173,14 +188,52 @@ def preload_core_config(config): raise ESPHomeYAMLError("esphomeyaml.platform not specified.") if CONF_BOARD not in core_conf: raise ESPHomeYAMLError("esphomeyaml.board not specified.") + if CONF_NAME not in core_conf: + raise ESPHomeYAMLError("esphomeyaml.name not specified.") try: core.ESP_PLATFORM = validate_platform(core_conf[CONF_PLATFORM]) core.BOARD = validate_board(core_conf[CONF_BOARD]) + core.NAME = cv.valid_name(core_conf[CONF_NAME]) except vol.Invalid as e: raise ESPHomeYAMLError(unicode(e)) +def run_command(*args): + p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + rc = p.returncode + return rc, stdout, stderr + + +def update_esphomelib_repo(config): + esphomelib_version = config[CONF_ESPHOMELIB_VERSION] + if CONF_REPOSITORY not in esphomelib_version: + return + + build_path = relative_path(config[CONF_BUILD_PATH]) + esphomelib_path = os.path.join(build_path, '.piolibdeps', 'esphomelib') + is_default_branch = all(x not in esphomelib_version + for x in (CONF_BRANCH, CONF_TAG, CONF_COMMIT)) + if not (CONF_BRANCH in esphomelib_version or is_default_branch): + # Git commit hash or tag cannot be updated + return + + rc, _, _ = run_command('git', '-C', esphomelib_path, '--help') + if rc != 0: + # git not installed or repo not downloaded yet + return + rc, _, _ = run_command('git', '-C', esphomelib_path, 'diff-index', '--quiet', 'HEAD', '--') + if rc != 0: + # local changes, cannot update + _LOGGER.warn("Local changes in esphomelib copy from git. Will not auto-update.") + return + rc, _, _ = run_command('git', '-C', esphomelib_path, 'pull') + if rc != 0: + _LOGGER.warn("Couldn't auto-update local git copy of esphomelib.") + return + + def to_code(config): add(App.set_name(config[CONF_NAME])) @@ -197,3 +250,5 @@ def to_code(config): rhs = App.register_component(LoopTrigger.new()) trigger = Pvariable(conf[CONF_TRIGGER_ID], rhs) automation.build_automation(trigger, NoArg, conf) + + update_esphomelib_repo(config) diff --git a/esphomeyaml/dashboard/dashboard.py b/esphomeyaml/dashboard/dashboard.py index 63550693af..4cec13496a 100644 --- a/esphomeyaml/dashboard/dashboard.py +++ b/esphomeyaml/dashboard/dashboard.py @@ -9,10 +9,11 @@ import os import random import subprocess +from esphomeyaml.const import CONF_ESPHOMEYAML, CONF_BUILD_PATH from esphomeyaml.core import ESPHomeYAMLError from esphomeyaml import const, core, __main__ -from esphomeyaml.__main__ import get_serial_ports, get_base_path, get_name -from esphomeyaml.helpers import quote +from esphomeyaml.__main__ import get_serial_ports +from esphomeyaml.helpers import quote, relative_path try: import tornado @@ -161,10 +162,10 @@ class DownloadBinaryRequestHandler(BaseHandler): config_file = os.path.join(CONFIG_DIR, configuration) core.CONFIG_PATH = config_file config = __main__.read_config(core.CONFIG_PATH) - name = get_name(config) - path = os.path.join(get_base_path(config), '.pioenvs', name, 'firmware.bin') + build_path = relative_path(config[CONF_ESPHOMEYAML][CONF_BUILD_PATH]) + path = os.path.join(build_path, '.pioenvs', core.NAME, 'firmware.bin') self.set_header('Content-Type', 'application/octet-stream') - self.set_header("Content-Disposition", 'attachment; filename="{}.bin"'.format(name)) + self.set_header("Content-Disposition", 'attachment; filename="{}.bin"'.format(core.NAME)) with open(path, 'rb') as f: while 1: data = f.read(16384) # or some other nice-sized chunk diff --git a/esphomeyaml/writer.py b/esphomeyaml/writer.py index af175cb576..d17beff27f 100644 --- a/esphomeyaml/writer.py +++ b/esphomeyaml/writer.py @@ -9,7 +9,7 @@ from esphomeyaml import core from esphomeyaml.config import iter_components from esphomeyaml.const import ARDUINO_VERSION_ESP32_DEV, CONF_ARDUINO_VERSION, CONF_BOARD, \ CONF_BOARD_FLASH_MODE, CONF_ESPHOMELIB_VERSION, CONF_ESPHOMEYAML, CONF_LOCAL, CONF_NAME, \ - CONF_USE_CUSTOM_CODE, ESP_PLATFORM_ESP32 + CONF_USE_CUSTOM_CODE, ESP_PLATFORM_ESP32, CONF_REPOSITORY, CONF_COMMIT, CONF_BRANCH, CONF_TAG from esphomeyaml.core import ESPHomeYAMLError from esphomeyaml.core_config import VERSION_REGEX from esphomeyaml.helpers import relative_path @@ -111,8 +111,13 @@ def get_ini_content(config, path): lib_version = config[CONF_ESPHOMEYAML][CONF_ESPHOMELIB_VERSION] lib_path = os.path.join(path, 'lib') dst_path = os.path.join(lib_path, 'esphomelib') - if isinstance(lib_version, (str, unicode)): - lib_deps.add(lib_version) + if CONF_REPOSITORY in lib_version: + tag = next((lib_version[x] for x in (CONF_COMMIT, CONF_BRANCH, CONF_TAG) + if x in lib_version), None) + if tag is None: + lib_deps.add(lib_version[CONF_REPOSITORY]) + else: + lib_deps.add(lib_version[CONF_REPOSITORY] + '#' + tag) if os.path.islink(dst_path): os.unlink(dst_path) else: